1 /*
   2  * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.X11;
  27 
  28 import java.awt.*;
  29 import java.awt.peer.*;
  30 import java.awt.event.*;
  31 import sun.util.logging.PlatformLogger;
  32 
  33 // FIXME: tab traversal should be disabled when mouse is captured (4816336)
  34 
  35 // FIXME: key and mouse events should not be delivered to listeners when the Choice is unfurled.  Must override handleNativeKey/MouseEvent (4816336)
  36 
  37 // FIXME: test programmatic add/remove/clear/etc
  38 
  39 // FIXME: account for unfurling at the edge of the screen
  40 // Note: can't set x,y on layout(), 'cause moving the top-level to the
  41 // edge of the screen won't call layout().  Just do it on paint, I guess
  42 
  43 // TODO: make painting more efficient (i.e. when down arrow is pressed, only two items should need to be repainted.
  44 
  45 public final class XChoicePeer extends XComponentPeer implements ChoicePeer, ToplevelStateListener {
  46     private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XChoicePeer");
  47 
  48     private static final int MAX_UNFURLED_ITEMS = 10;  // Maximum number of
  49     // items to be displayed
  50     // at a time in an
  51     // unfurled Choice
  52     // Description of these constants in ListHelper
  53     public static final int TEXT_SPACE = 1;
  54     public static final int BORDER_WIDTH = 1;
  55     public static final int ITEM_MARGIN = 1;
  56     public static final int SCROLLBAR_WIDTH = 15;
  57 
  58 
  59     // SHARE THESE!
  60     private static final Insets focusInsets = new Insets(0,0,0,0);
  61 
  62 
  63     static final int WIDGET_OFFSET = 18;
  64 
  65     // Stolen from Tiny
  66     static final int            TEXT_XPAD = 8;
  67     static final int            TEXT_YPAD = 6;
  68 
  69     // FIXME: Motif uses a different focus color for the item within
  70     // the unfurled Choice list and for when the Choice itself is focused and
  71     // popped up.
  72     static final Color focusColor = Color.black;
  73 
  74     // TODO: there is a time value that the mouse is held down.  If short
  75     // enough,  the Choice stays popped down.  If long enough, Choice
  76     // is furled when the mouse is released
  77 
  78     private boolean unfurled = false;        // Choice list is popped down
  79 
  80     private boolean dragging = false;        // Mouse was pressed and is being
  81                                              // dragged over the (unfurled)
  82                                              // Choice
  83 
  84     private boolean mouseInSB = false;       // Mouse is interacting with the
  85                                              // scrollbar
  86 
  87     private boolean firstPress = false;      // mouse was pressed on
  88                                              // furled Choice so we
  89                                              // not need to furl the
  90                                              // Choice when MOUSE_RELEASED occurred
  91 
  92     // 6425067. Mouse was pressed on furled choice and dropdown list appeared over Choice itself
  93     // and then there were no mouse movements until MOUSE_RELEASE.
  94     // This scenario leads to ItemStateChanged as the choice logic uses
  95     // MouseReleased event to send ItemStateChanged. To prevent it we should
  96     // use a combination of firstPress and wasDragged variables.
  97     // The only difference in dragging and wasDragged is: last one will not
  98     // set to false on mouse ungrab. It become false after MouseRelased() finishes.
  99     private boolean wasDragged = false;
 100     private ListHelper helper;
 101     private UnfurledChoice unfurledChoice;
 102 
 103     // TODO: Choice remembers where it was scrolled to when unfurled - it's not
 104     // always to the currently selected item.
 105 
 106     // Indicates whether or not to paint selected item in the choice.
 107     // Default is to paint
 108     private boolean drawSelectedItem = true;
 109 
 110     // If set, indicates components under which choice popup should be showed.
 111     // The choice's popup width and location should be adjust to appear
 112     // under both choice and alignUnder component.
 113     private Component alignUnder;
 114 
 115     // If cursor is outside of an unfurled Choice when the mouse is
 116     // released, Choice item should NOT be updated.  Remember the proper index.
 117     private int dragStartIdx = -1;
 118 
 119     // Holds the listener (XFileDialogPeer) which the processing events from the choice
 120     // See 6240074 for more information
 121     private XChoicePeerListener choiceListener;
 122 
 123     XChoicePeer(Choice target) {
 124         super(target);
 125     }
 126 
 127     void preInit(XCreateWindowParams params) {
 128         super.preInit(params);
 129         Choice target = (Choice)this.target;
 130         int numItems = target.getItemCount();
 131         unfurledChoice = new UnfurledChoice(target);
 132         getToplevelXWindow().addToplevelStateListener(this);
 133         helper = new ListHelper(unfurledChoice,
 134                                 getGUIcolors(),
 135                                 numItems,
 136                                 false,
 137                                 true,
 138                                 false,
 139                                 target.getFont(),
 140                                 MAX_UNFURLED_ITEMS,
 141                                 TEXT_SPACE,
 142                                 ITEM_MARGIN,
 143                                 BORDER_WIDTH,
 144                                 SCROLLBAR_WIDTH);
 145     }
 146 
 147     void postInit(XCreateWindowParams params) {
 148         super.postInit(params);
 149         Choice target = (Choice)this.target;
 150         int numItems = target.getItemCount();
 151 
 152         // Add all items
 153         for (int i = 0; i < numItems; i++) {
 154             helper.add(target.getItem(i));
 155         }
 156         if (!helper.isEmpty()) {
 157             helper.select(target.getSelectedIndex());
 158             helper.setFocusedIndex(target.getSelectedIndex());
 159         }
 160         helper.updateColors(getGUIcolors());
 161         updateMotifColors(getPeerBackground());
 162     }
 163 
 164     public boolean isFocusable() { return true; }
 165 
 166     // 6399679. check if super.setBounds() actually changes the size of the
 167     // component and then compare current Choice size with a new one. If
 168     // they differs then hide dropdown menu
 169     public void setBounds(int x, int y, int width, int height, int op) {
 170         int oldX = this.x;
 171         int oldY = this.y;
 172         int oldWidth = this.width;
 173         int oldHeight = this.height;
 174         super.setBounds(x, y, width, height, op);
 175         if (unfurled && (oldX != this.x || oldY != this.y || oldWidth != this.width || oldHeight != this.height) ) {
 176             hidePopdownMenu();
 177         }
 178     }
 179 
 180     public void focusGained(FocusEvent e) {
 181         // TODO: only need to paint the focus bit
 182         super.focusGained(e);
 183         repaint();
 184     }
 185 
 186     /*
 187      * Fix for 6246503 : Disabling a choice after selection locks keyboard, mouse and makes the system unusable, Xtoolkit
 188      * if setEnabled(false) invoked we should close opened choice in
 189      * order to prevent keyboard/mouse lock.
 190      */
 191     public void setEnabled(boolean value) {
 192         super.setEnabled(value);
 193         helper.updateColors(getGUIcolors());
 194         if (!value && unfurled){
 195             hidePopdownMenu();
 196         }
 197     }
 198 
 199     public void focusLost(FocusEvent e) {
 200         // TODO: only need to paint the focus bit?
 201         super.focusLost(e);
 202         repaint();
 203     }
 204 
 205     void ungrabInputImpl() {
 206         if (unfurled) {
 207             unfurled = false;
 208             dragging = false;
 209             mouseInSB = false;
 210             unfurledChoice.setVisible(false);
 211         }
 212 
 213         super.ungrabInputImpl();
 214     }
 215 
 216     void handleJavaKeyEvent(KeyEvent e) {
 217         if (e.getID() == KeyEvent.KEY_PRESSED) {
 218             keyPressed(e);
 219         }
 220     }
 221 
 222     public void keyPressed(KeyEvent e) {
 223         switch(e.getKeyCode()) {
 224             // UP & DOWN are same if furled or unfurled
 225           case KeyEvent.VK_DOWN:
 226           case KeyEvent.VK_KP_DOWN: {
 227               if (helper.getItemCount() > 1) {
 228                   helper.down();
 229                   int newIdx = helper.getSelectedIndex();
 230 
 231                   ((Choice)target).select(newIdx);
 232                   postEvent(new ItemEvent((Choice)target,
 233                                           ItemEvent.ITEM_STATE_CHANGED,
 234                                           ((Choice)target).getItem(newIdx),
 235                                           ItemEvent.SELECTED));
 236                   repaint();
 237               }
 238               break;
 239           }
 240           case KeyEvent.VK_UP:
 241           case KeyEvent.VK_KP_UP: {
 242               if (helper.getItemCount() > 1) {
 243                   helper.up();
 244                   int newIdx = helper.getSelectedIndex();
 245 
 246                   ((Choice)target).select(newIdx);
 247                   postEvent(new ItemEvent((Choice)target,
 248                                           ItemEvent.ITEM_STATE_CHANGED,
 249                                           ((Choice)target).getItem(newIdx),
 250                                           ItemEvent.SELECTED));
 251                   repaint();
 252               }
 253               break;
 254           }
 255           case KeyEvent.VK_PAGE_DOWN:
 256               if (unfurled && !dragging) {
 257                   int oldIdx = helper.getSelectedIndex();
 258                   helper.pageDown();
 259                   int newIdx = helper.getSelectedIndex();
 260                   if (oldIdx != newIdx) {
 261                       ((Choice)target).select(newIdx);
 262                       postEvent(new ItemEvent((Choice)target,
 263                                               ItemEvent.ITEM_STATE_CHANGED,
 264                                               ((Choice)target).getItem(newIdx),
 265                                               ItemEvent.SELECTED));
 266                       repaint();
 267                   }
 268               }
 269               break;
 270           case KeyEvent.VK_PAGE_UP:
 271               if (unfurled && !dragging) {
 272                   int oldIdx = helper.getSelectedIndex();
 273                   helper.pageUp();
 274                   int newIdx = helper.getSelectedIndex();
 275                   if (oldIdx != newIdx) {
 276                       ((Choice)target).select(newIdx);
 277                       postEvent(new ItemEvent((Choice)target,
 278                                               ItemEvent.ITEM_STATE_CHANGED,
 279                                               ((Choice)target).getItem(newIdx),
 280                                               ItemEvent.SELECTED));
 281                       repaint();
 282                   }
 283               }
 284               break;
 285           case KeyEvent.VK_ESCAPE:
 286           case KeyEvent.VK_ENTER:
 287               if (unfurled) {
 288                   if (dragging){
 289                       if (e.getKeyCode() == KeyEvent.VK_ESCAPE){
 290                           //This also happens on
 291                           // - MouseButton2,3, etc. press
 292                           // - ENTER press
 293                           helper.select(dragStartIdx);
 294                       } else { //KeyEvent.VK_ENTER:
 295                           int newIdx = helper.getSelectedIndex();
 296                           ((Choice)target).select(newIdx);
 297                           postEvent(new ItemEvent((Choice)target,
 298                                                   ItemEvent.ITEM_STATE_CHANGED,
 299                                                   ((Choice)target).getItem(newIdx),
 300                                                   ItemEvent.SELECTED));
 301                       }
 302                   }
 303                   hidePopdownMenu();
 304                   dragging = false;
 305                   wasDragged = false;
 306                   mouseInSB = false;
 307 
 308                   // See 6240074 for more information
 309                   if (choiceListener != null){
 310                       choiceListener.unfurledChoiceClosing();
 311                   }
 312               }
 313               break;
 314           default:
 315               if (unfurled) {
 316                   Toolkit.getDefaultToolkit().beep();
 317               }
 318               break;
 319         }
 320     }
 321 
 322     public boolean handlesWheelScrolling() { return true; }
 323 
 324     void handleJavaMouseWheelEvent(MouseWheelEvent e) {
 325         if (unfurled && helper.isVSBVisible()) {
 326             if (ListHelper.doWheelScroll(helper.getVSB(), null, e)) {
 327                 repaint();
 328             }
 329         }
 330     }
 331 
 332     void handleJavaMouseEvent(MouseEvent e) {
 333         super.handleJavaMouseEvent(e);
 334         int i = e.getID();
 335         switch (i) {
 336           case MouseEvent.MOUSE_PRESSED:
 337               mousePressed(e);
 338               break;
 339           case MouseEvent.MOUSE_RELEASED:
 340               mouseReleased(e);
 341               break;
 342           case MouseEvent.MOUSE_DRAGGED:
 343               mouseDragged(e);
 344               break;
 345         }
 346     }
 347 
 348     public void mousePressed(MouseEvent e) {
 349         /*
 350          * fix for 5003166: a Choice on XAWT shouldn't react to any
 351          * mouse button presses except left. This involves presses on
 352          * Choice but not on opened part of choice.
 353          */
 354         if (e.getButton() == MouseEvent.BUTTON1){
 355             dragStartIdx = helper.getSelectedIndex();
 356             if (unfurled) {
 357                 //fix 6259328: PIT: Choice scrolls when dragging the parent frame while drop-down is active, XToolkit
 358                 if (! (isMouseEventInChoice(e) ||
 359                        unfurledChoice.isMouseEventInside(e)))
 360                 {
 361                     hidePopdownMenu();
 362                 }
 363                 // Press on unfurled Choice.  Highlight the item under the cursor,
 364                 // but don't send item event or set the text on the button yet
 365                 unfurledChoice.trackMouse(e);
 366             }
 367             else {
 368                 // Choice is up - unfurl it
 369                 grabInput();
 370                 unfurledChoice.toFront();
 371                 firstPress = true;
 372                 wasDragged = false;
 373                 unfurled = true;
 374             }
 375         }
 376     }
 377 
 378     /*
 379      * helper method for mouseReleased routine
 380      */
 381     void hidePopdownMenu(){
 382         ungrabInput();
 383         unfurledChoice.setVisible(false);
 384         unfurled = false;
 385     }
 386 
 387     public void mouseReleased(MouseEvent e) {
 388         if (unfurled) {
 389             if (mouseInSB) {
 390                 unfurledChoice.trackMouse(e);
 391             }
 392             else {
 393                 // We pressed and dragged onto the Choice, or, this is the
 394                 // second release after clicking to make the Choice "stick"
 395                 // unfurled.
 396                 // This release should ungrab/furl, and set the new item if
 397                 // release was over the unfurled Choice.
 398 
 399                 // Fix for 6239944 : Choice shouldn't close its
 400                 // pop-down menu if user presses Mouse on Choice's Scrollbar
 401                 // some additional cases like releasing mouse outside
 402                 // of Choice are considered too
 403                 boolean isMouseEventInside = unfurledChoice.isMouseEventInside( e );
 404                 boolean isMouseInListArea = unfurledChoice.isMouseInListArea( e );
 405 
 406                 // Fixed 6318746: REG: File Selection is failing
 407                 // We shouldn't restore the selected item
 408                 // if the mouse was dragged outside the drop-down choice area
 409                 if (!helper.isEmpty() && !isMouseInListArea && dragging) {
 410                     // Set the selected item back how it was.
 411                     ((Choice)target).select(dragStartIdx);
 412                 }
 413 
 414                 // Choice must be closed if user releases mouse on
 415                 // pop-down menu on the second click
 416                 if ( !firstPress && isMouseInListArea) {
 417                     hidePopdownMenu();
 418                 }
 419                 // Choice must be closed if user releases mouse
 420                 // outside of Choice's pop-down menu  on the second click
 421                 if ( !firstPress && !isMouseEventInside) {
 422                     hidePopdownMenu();
 423                 }
 424                 //if user drags Mouse on pop-down menu, Scrollbar or
 425                 // outside the Choice
 426                 if ( firstPress && dragging) {
 427                     hidePopdownMenu();
 428                 }
 429                 /* this could happen when user has opened a Choice and
 430                  * released mouse button. Then he drags mouse on the
 431                  * Scrollbar and releases mouse again.
 432                  */
 433                 if ( !firstPress && !isMouseInListArea &&
 434                      isMouseEventInside && dragging)
 435                 {
 436                     hidePopdownMenu();
 437                 }
 438 
 439                 if (!helper.isEmpty()) {
 440                     // Only update the Choice if the mouse button is released
 441                     // over the list of items.
 442                     if (unfurledChoice.isMouseInListArea(e)) {
 443                         int newIdx = helper.getSelectedIndex();
 444                         if (newIdx >= 0) {
 445                             // Update the selected item in the target now that
 446                             // the mouse selection is complete.
 447                             if (newIdx != dragStartIdx) {
 448                                 ((Choice)target).select(newIdx);
 449                                 // NOTE: We get a repaint when Choice.select()
 450                                 // calls our peer.select().
 451                             }
 452                             if (wasDragged && e.getButton() != MouseEvent.BUTTON1){
 453                                 ((Choice)target).select(dragStartIdx);
 454                             }
 455 
 456                             /*fix for 6239941 : Choice triggers ItemEvent when selecting an item with right mouse button, Xtoolkit
 457                             * We should generate ItemEvent if only
 458                             * LeftMouseButton used */
 459                             if (e.getButton() == MouseEvent.BUTTON1 &&
 460                                 (!firstPress || wasDragged ))
 461                             {
 462                                 postEvent(new ItemEvent((Choice)target,
 463                                                         ItemEvent.ITEM_STATE_CHANGED,
 464                                                         ((Choice)target).getItem(newIdx),
 465                                                         ItemEvent.SELECTED));
 466                             }
 467 
 468                             // see 6240074 for more information
 469                             if (choiceListener != null) {
 470                                 choiceListener.unfurledChoiceClosing();
 471                             }
 472                         }
 473                     }
 474                 }
 475                 // See 6243382 for more information
 476                 unfurledChoice.trackMouse(e);
 477             }
 478         }
 479 
 480         dragging = false;
 481         wasDragged = false;
 482         firstPress = false;
 483         dragStartIdx = -1;
 484     }
 485     @SuppressWarnings("deprecation")
 486     public void mouseDragged(MouseEvent e) {
 487         /*
 488          * fix for 5003166. On Motif user are unable to drag
 489          * mouse inside opened Choice if he drags the mouse with
 490          * different from LEFT mouse button ( e.g. RIGHT or MIDDLE).
 491          * This fix make impossible to drag mouse inside opened choice
 492          * with other mouse buttons rather then LEFT one.
 493          */
 494         if ( e.getModifiers() == MouseEvent.BUTTON1_MASK ){
 495             dragging = true;
 496             wasDragged = true;
 497             unfurledChoice.trackMouse(e);
 498         }
 499     }
 500 
 501     // Stolen from TinyChoicePeer
 502     @SuppressWarnings("deprecation")
 503     public Dimension getMinimumSize() {
 504         // TODO: move this impl into ListHelper?
 505         FontMetrics fm = getFontMetrics(target.getFont());
 506         Choice c = (Choice)target;
 507         int w = 0;
 508         for (int i = c.countItems() ; i-- > 0 ;) {
 509             w = Math.max(fm.stringWidth(c.getItem(i)), w);
 510         }
 511         return new Dimension(w + TEXT_XPAD + WIDGET_OFFSET,
 512                              fm.getMaxAscent() + fm.getMaxDescent() + TEXT_YPAD);
 513     }
 514 
 515     /*
 516      * Layout the...
 517      */
 518     public void layout() {
 519         /*
 520           Dimension size = target.getSize();
 521           Font f = target.getFont();
 522           FontMetrics fm = target.getFontMetrics(f);
 523           String text = ((Choice)target).getLabel();
 524 
 525           textRect.height = fm.getHeight();
 526 
 527           checkBoxSize = getChoiceSize(fm);
 528 
 529           // Note - Motif appears to use an left inset that is slightly
 530           // scaled to the checkbox/font size.
 531           cbX = borderInsets.left + checkBoxInsetFromText;
 532           cbY = size.height / 2 - checkBoxSize / 2;
 533           int minTextX = borderInsets.left + 2 * checkBoxInsetFromText + checkBoxSize;
 534           // FIXME: will need to account for alignment?
 535           // FIXME: call layout() on alignment changes
 536           //textRect.width = fm.stringWidth(text);
 537           textRect.width = fm.stringWidth(text == null ? "" : text);
 538           textRect.x = Math.max(minTextX, size.width / 2 - textRect.width / 2);
 539           textRect.y = size.height / 2 - textRect.height / 2 + borderInsets.top;
 540 
 541           focusRect.x = focusInsets.left;
 542           focusRect.y = focusInsets.top;
 543           focusRect.width = size.width-(focusInsets.left+focusInsets.right)-1;
 544           focusRect.height = size.height-(focusInsets.top+focusInsets.bottom)-1;
 545 
 546           myCheckMark = AffineTransform.getScaleInstance((double)target.getFont().getSize() / MASTER_SIZE, (double)target.getFont().getSize() / MASTER_SIZE).createTransformedShape(MASTER_CHECKMARK);
 547         */
 548 
 549     }
 550 
 551     /**
 552      * Paint the choice
 553      */
 554     @Override
 555     void paintPeer(final Graphics g) {
 556         flush();
 557         Dimension size = getPeerSize();
 558         // TODO: when mouse is down over button, widget should be drawn depressed
 559         g.setColor(getPeerBackground());
 560         g.fillRect(0, 0, width, height);
 561 
 562         drawMotif3DRect(g, 1, 1, width-2, height-2, false);
 563         drawMotif3DRect(g, width - WIDGET_OFFSET, (height / 2) - 3, 12, 6, false);
 564 
 565         if (!helper.isEmpty() && helper.getSelectedIndex() != -1) {
 566             g.setFont(getPeerFont());
 567             FontMetrics fm = g.getFontMetrics();
 568             String lbl = helper.getItem(helper.getSelectedIndex());
 569             if (lbl != null && drawSelectedItem) {
 570                 g.setClip(1, 1, width - WIDGET_OFFSET - 2, height);
 571                 if (isEnabled()) {
 572                     g.setColor(getPeerForeground());
 573                     g.drawString(lbl, 5, (height + fm.getMaxAscent()-fm.getMaxDescent())/2);
 574                 }
 575                 else {
 576                     g.setColor(getPeerBackground().brighter());
 577                     g.drawString(lbl, 5, (height + fm.getMaxAscent()-fm.getMaxDescent())/2);
 578                     g.setColor(getPeerBackground().darker());
 579                     g.drawString(lbl, 4, ((height + fm.getMaxAscent()-fm.getMaxDescent())/2)-1);
 580                 }
 581                 g.setClip(0, 0, width, height);
 582             }
 583         }
 584         if (hasFocus()) {
 585             paintFocus(g,focusInsets.left,focusInsets.top,size.width-(focusInsets.left+focusInsets.right)-1,size.height-(focusInsets.top+focusInsets.bottom)-1);
 586         }
 587         if (unfurled) {
 588             unfurledChoice.repaint();
 589         }
 590         flush();
 591     }
 592 
 593     protected void paintFocus(Graphics g,
 594                               int x, int y, int w, int h) {
 595         g.setColor(focusColor);
 596         g.drawRect(x,y,w,h);
 597     }
 598 
 599 
 600 
 601     /*
 602      * ChoicePeer methods stolen from TinyChoicePeer
 603      */
 604 
 605     public void select(int index) {
 606         helper.select(index);
 607         helper.setFocusedIndex(index);
 608         repaint();
 609     }
 610 
 611     public void add(String item, int index) {
 612         helper.add(item, index);
 613         repaint();
 614     }
 615 
 616     public void remove(int index) {
 617         boolean selected = (index == helper.getSelectedIndex());
 618         boolean visibled = (index >= helper.firstDisplayedIndex() && index <= helper.lastDisplayedIndex());
 619         helper.remove(index);
 620         if (selected) {
 621             if (helper.isEmpty()) {
 622                 helper.select(-1);
 623             }
 624             else {
 625                 helper.select(0);
 626             }
 627         }
 628         /*
 629          * Fix for 6248016
 630          * After removing the item of the choice we need to reshape unfurled choice
 631          * in order to keep actual bounds of the choice
 632          */
 633 
 634         /*
 635          * condition added only for performance
 636          */
 637         if (!unfurled) {
 638             // Fix 6292186: PIT: Choice is not refreshed properly when the last item gets removed, XToolkit
 639             // We should take into account that there is no 'select' invoking (hence 'repaint')
 640             // if the choice is empty (see Choice.java method removeNoInvalidate())
 641             // The condition isn't 'visibled' since it would be cause of the twice repainting
 642             if (helper.isEmpty()) {
 643                 repaint();
 644             }
 645             return;
 646         }
 647 
 648         /*
 649          * condition added only for performance
 650          * the count of the visible items changed
 651          */
 652         if (visibled){
 653             Rectangle r = unfurledChoice.placeOnScreen();
 654             unfurledChoice.reshape(r.x, r.y, r.width, r.height);
 655             return;
 656         }
 657 
 658         /*
 659          * condition added only for performance
 660          * the structure of visible items changed
 661          * if removable item is non visible and non selected then there is no repaint
 662          */
 663         if (visibled || selected){
 664             repaint();
 665         }
 666     }
 667 
 668     public void removeAll() {
 669         helper.removeAll();
 670         helper.select(-1);
 671         /*
 672          * Fix for 6248016
 673          * After removing the item of the choice we need to reshape unfurled choice
 674          * in order to keep actual bounds of the choice
 675          */
 676         Rectangle r = unfurledChoice.placeOnScreen();
 677         unfurledChoice.reshape(r.x, r.y, r.width, r.height);
 678         repaint();
 679     }
 680 
 681     /**
 682      * DEPRECATED: Replaced by add(String, int).
 683      */
 684     public void addItem(String item, int index) {
 685         add(item, index);
 686     }
 687 
 688     public void setFont(Font font) {
 689         super.setFont(font);
 690         helper.setFont(this.font);
 691     }
 692 
 693     public void setForeground(Color c) {
 694         super.setForeground(c);
 695         helper.updateColors(getGUIcolors());
 696     }
 697 
 698     public void setBackground(Color c) {
 699         super.setBackground(c);
 700         unfurledChoice.setBackground(c);
 701         helper.updateColors(getGUIcolors());
 702         updateMotifColors(c);
 703     }
 704 
 705     public void setDrawSelectedItem(boolean value) {
 706         drawSelectedItem = value;
 707     }
 708 
 709     public void setAlignUnder(Component comp) {
 710         alignUnder = comp;
 711     }
 712 
 713     // see 6240074 for more information
 714     public void addXChoicePeerListener(XChoicePeerListener l){
 715         choiceListener = l;
 716     }
 717 
 718     // see 6240074 for more information
 719     public void removeXChoicePeerListener(){
 720         choiceListener = null;
 721     }
 722 
 723     public boolean isUnfurled(){
 724         return unfurled;
 725     }
 726 
 727     /* fix for 6261352. We should detect if current parent Window (containing a Choice) become iconified and hide pop-down menu with grab release.
 728      * In this case we should hide pop-down menu.
 729      */
 730     //calls from XWindowPeer. Could accept X-styled state events
 731     public void stateChangedICCCM(int oldState, int newState) {
 732         if (unfurled && oldState != newState){
 733                 hidePopdownMenu();
 734         }
 735     }
 736 
 737     //calls from XFramePeer. Could accept Frame's states.
 738     public void stateChangedJava(int oldState, int newState) {
 739         if (unfurled && oldState != newState){
 740             hidePopdownMenu();
 741         }
 742     }
 743 
 744     @Override
 745     protected void initGraphicsConfiguration() {
 746         super.initGraphicsConfiguration();
 747         // The popup have the same graphic config, so update it at the same time
 748         if (unfurledChoice != null) {
 749             unfurledChoice.initGraphicsConfiguration();
 750             unfurledChoice.doValidateSurface();
 751         }
 752     }
 753 
 754     /**************************************************************************/
 755     /* Common functionality between List & Choice
 756        /**************************************************************************/
 757 
 758     /**
 759      * Inner class for the unfurled Choice list
 760      * Much, much more docs
 761      */
 762     final class UnfurledChoice extends XWindow /*implements XScrollbarClient*/ {
 763 
 764         // First try - use Choice as the target
 765 
 766         public UnfurledChoice(Component target) {
 767             super(target);
 768         }
 769 
 770         // Override so we can do our own create()
 771         public void preInit(XCreateWindowParams params) {
 772             // A parent of this window is the target, at this point: wrong.
 773             // Remove parent window; in the following preInit() call we'll calculate as a default
 774             // a correct root window which is the proper parent for override redirect.
 775             params.delete(PARENT_WINDOW);
 776             super.preInit(params);
 777             // Reset bounds(we'll set them later), set overrideRedirect
 778             params.remove(BOUNDS);
 779             params.add(OVERRIDE_REDIRECT, Boolean.TRUE);
 780         }
 781 
 782         // Generally, bounds should be:
 783         //  x = target.x
 784         //  y = target.y + target.height
 785         //  w = Max(target.width, getLongestItemWidth) + possible vertScrollbar
 786         //  h = Min(MAX_UNFURLED_ITEMS, target.getItemCount()) * itemHeight
 787         Rectangle placeOnScreen() {
 788             int numItemsDisplayed;
 789             // Motif paints an empty Choice the same size as a single item
 790             if (helper.isEmpty()) {
 791                 numItemsDisplayed = 1;
 792             }
 793             else {
 794                 int numItems = helper.getItemCount();
 795                 numItemsDisplayed = Math.min(MAX_UNFURLED_ITEMS, numItems);
 796             }
 797             Point global = XChoicePeer.this.toGlobal(0,0);
 798             Rectangle screenBounds = graphicsConfig.getBounds();
 799 
 800             if (alignUnder != null) {
 801                 Rectangle choiceRec = XChoicePeer.this.getBounds();
 802                 choiceRec.setLocation(0, 0);
 803                 choiceRec = XChoicePeer.this.toGlobal(choiceRec);
 804                 Rectangle alignUnderRec = new Rectangle(alignUnder.getLocationOnScreen(), alignUnder.getSize()); // TODO: Security?
 805                 Rectangle result = choiceRec.union(alignUnderRec);
 806                 // we've got the left and width, calculate top and height
 807                 width = result.width;
 808                 x = result.x;
 809                 y = result.y + result.height;
 810                 height = 2*BORDER_WIDTH +
 811                     numItemsDisplayed*(helper.getItemHeight()+2*ITEM_MARGIN);
 812             } else {
 813                 x = global.x;
 814                 y = global.y + XChoicePeer.this.height;
 815                 width = Math.max(XChoicePeer.this.width,
 816                                  helper.getMaxItemWidth() + 2 * (BORDER_WIDTH + ITEM_MARGIN + TEXT_SPACE) + (helper.isVSBVisible() ? SCROLLBAR_WIDTH : 0));
 817                 height = 2*BORDER_WIDTH +
 818                     numItemsDisplayed*(helper.getItemHeight()+2*ITEM_MARGIN);
 819             }
 820             // Don't run off the edge of the screenBounds
 821             if (x < screenBounds.x) {
 822                 x = screenBounds.x;
 823             }
 824             else if (x + width > screenBounds.x + screenBounds.width) {
 825                 x = screenBounds.x + screenBounds.width - width;
 826             }
 827 
 828             if (y + height > screenBounds.y + screenBounds.height) {
 829                 y = global.y - height;
 830             }
 831             if (y < screenBounds.y) {
 832                 y = screenBounds.y;
 833             }
 834             return new Rectangle(x, y, width, height);
 835         }
 836 
 837         public void toFront() {
 838             // see 6240074 for more information
 839             if (choiceListener != null)
 840                 choiceListener.unfurledChoiceOpening(helper);
 841 
 842             Rectangle r = placeOnScreen();
 843             reshape(r.x, r.y, r.width, r.height);
 844             super.toFront();
 845             setVisible(true);
 846         }
 847 
 848         /*
 849          * Track a MouseEvent (either a drag or a press) and paint a new
 850          * selected item, if necessary.
 851          */
 852         // FIXME: first unfurl after move is not at edge of the screen  onto second monitor doesn't
 853         // track mouse correctly.  Problem is w/ UnfurledChoice coords
 854         public void trackMouse(MouseEvent e) {
 855             // Event coords are relative to the button, so translate a bit
 856             Point local = toLocalCoords(e);
 857 
 858             // If x,y is over unfurled Choice,
 859             // highlight item under cursor
 860 
 861             switch (e.getID()) {
 862               case MouseEvent.MOUSE_PRESSED:
 863                   // FIXME: If the Choice is unfurled and the mouse is pressed
 864                   // outside of the Choice, the mouse should ungrab on the
 865                   // the press, not the release
 866                   if (helper.isInVertSB(getBounds(), local.x, local.y)) {
 867                       mouseInSB = true;
 868                       helper.handleVSBEvent(e, getBounds(), local.x, local.y);
 869                   }
 870                   else {
 871                       trackSelection(local.x, local.y);
 872                   }
 873                   break;
 874               case MouseEvent.MOUSE_RELEASED:
 875                   if (mouseInSB) {
 876                       mouseInSB = false;
 877                       helper.handleVSBEvent(e, getBounds(), local.x, local.y);
 878                   }else{
 879                       // See 6243382 for more information
 880                       helper.trackMouseReleasedScroll();
 881                   }
 882                   /*
 883                     else {
 884                     trackSelection(local.x, local.y);
 885                     }
 886                   */
 887                   break;
 888               case MouseEvent.MOUSE_DRAGGED:
 889                   if (mouseInSB) {
 890                       helper.handleVSBEvent(e, getBounds(), local.x, local.y);
 891                   }
 892                   else {
 893                       // See 6243382 for more information
 894                       helper.trackMouseDraggedScroll(local.x, local.y, width, height);
 895                       trackSelection(local.x, local.y);
 896                   }
 897                   break;
 898             }
 899         }
 900 
 901         private void trackSelection(int transX, int transY) {
 902             if (!helper.isEmpty()) {
 903                 if (transX > 0 && transX < width &&
 904                     transY > 0 && transY < height) {
 905                     int newIdx = helper.y2index(transY);
 906                     if (log.isLoggable(PlatformLogger.Level.FINE)) {
 907                         log.fine("transX=" + transX + ", transY=" + transY
 908                                  + ",width=" + width + ", height=" + height
 909                                  + ", newIdx=" + newIdx + " on " + target);
 910                     }
 911                     if ((newIdx >=0) && (newIdx < helper.getItemCount())
 912                         && (newIdx != helper.getSelectedIndex()))
 913                     {
 914                         helper.select(newIdx);
 915                         unfurledChoice.repaint();
 916                     }
 917                 }
 918             }
 919             // FIXME: If dragged off top or bottom, scroll if there's a vsb
 920             // (ICK - we'll need a timer or our own event or something)
 921         }
 922 
 923         /*
 924          * fillRect with current Background color on the whole dropdown list.
 925          */
 926         public void paintBackground() {
 927             final Graphics g = getGraphics();
 928             if (g != null) {
 929                 try {
 930                     g.setColor(getPeerBackground());
 931                     g.fillRect(0, 0, width, height);
 932                 } finally {
 933                     g.dispose();
 934                 }
 935             }
 936         }
 937         /*
 938          * 6405689. In some cases we should erase background to eliminate painting
 939          * artefacts.
 940          */
 941         @Override
 942         public void repaint() {
 943             if (!isVisible()) {
 944                 return;
 945             }
 946             if (helper.checkVsbVisibilityChangedAndReset()){
 947                 paintBackground();
 948             }
 949             super.repaint();
 950         }
 951         @Override
 952         public void paintPeer(Graphics g) {
 953             //System.out.println("UC.paint()");
 954             Choice choice = (Choice)target;
 955             Color colors[] = XChoicePeer.this.getGUIcolors();
 956             draw3DRect(g, getSystemColors(), 0, 0, width - 1, height - 1, true);
 957             draw3DRect(g, getSystemColors(), 1, 1, width - 3, height - 3, true);
 958 
 959             helper.paintAllItems(g,
 960                                  colors,
 961                                  getBounds());
 962         }
 963 
 964         public void setVisible(boolean vis) {
 965             xSetVisible(vis);
 966 
 967             if (!vis && alignUnder != null) {
 968                 alignUnder.requestFocusInWindow();
 969             }
 970         }
 971 
 972         /**
 973          * Return a MouseEvent's Point in coordinates relative to the
 974          * UnfurledChoice.
 975          */
 976         private Point toLocalCoords(MouseEvent e) {
 977             // Event coords are relative to the button, so translate a bit
 978             Point global = e.getLocationOnScreen();
 979 
 980             global.x -= x;
 981             global.y -= y;
 982             return global;
 983         }
 984 
 985         /* Returns true if the MouseEvent coords (which are based on the Choice)
 986          * are inside of the UnfurledChoice.
 987          */
 988         private boolean isMouseEventInside(MouseEvent e) {
 989             Point local = toLocalCoords(e);
 990             if (local.x > 0 && local.x < width &&
 991                 local.y > 0 && local.y < height) {
 992                 return true;
 993             }
 994             return false;
 995         }
 996 
 997         /**
 998          * Tests if the mouse cursor is in the Unfurled Choice, yet not
 999          * in the vertical scrollbar
1000          */
1001         private boolean isMouseInListArea(MouseEvent e) {
1002             if (isMouseEventInside(e)) {
1003                 Point local = toLocalCoords(e);
1004                 Rectangle bounds = getBounds();
1005                 if (!helper.isInVertSB(bounds, local.x, local.y)) {
1006                     return true;
1007                 }
1008             }
1009             return false;
1010         }
1011 
1012         /*
1013          * Overridden from XWindow() because we don't want to send
1014          * ComponentEvents
1015          */
1016         public void handleConfigureNotifyEvent(XEvent xev) {}
1017         public void handleMapNotifyEvent(XEvent xev) {}
1018         public void handleUnmapNotifyEvent(XEvent xev) {}
1019     } //UnfurledChoice
1020 
1021     public void dispose() {
1022         if (unfurledChoice != null) {
1023             unfurledChoice.destroy();
1024         }
1025         super.dispose();
1026     }
1027 
1028     /*
1029      * fix for 6239938 : Choice drop-down does not disappear when it loses
1030      * focus, on XToolkit
1031      * We are able to handle all _Key_ events received by Choice when
1032      * it is in opened state without sending it to EventQueue.
1033      * If Choice is in closed state we should behave like before: send
1034      * all events to EventQueue.
1035      * To be compatible with Motif we should handle all KeyEvents in
1036      * Choice if it is opened. KeyEvents should be sent into Java if Choice is not opened.
1037      */
1038     boolean prePostEvent(final AWTEvent e) {
1039         if (unfurled){
1040             // fix for 6253211: PIT: MouseWheel events not triggered for Choice drop down in XAWT
1041             if (e instanceof MouseWheelEvent){
1042                 return super.prePostEvent(e);
1043             }
1044             //fix 6252982: PIT: Keyboard FocusTraversal not working when choice's drop-down is visible, on XToolkit
1045             if (e instanceof KeyEvent){
1046                 // notify XWindow that this event had been already handled and no need to post it again
1047                 InvocationEvent ev = new InvocationEvent(target, new Runnable() {
1048                     public void run() {
1049                         if(target.isFocusable() &&
1050                                 getParentTopLevel().isFocusableWindow() )
1051                         {
1052                             handleJavaKeyEvent((KeyEvent)e);
1053                         }
1054                     }
1055                 });
1056                 postEvent(ev);
1057 
1058                 return true;
1059             } else {
1060                 if (e instanceof MouseEvent){
1061                     // Fix for 6240046 : REG:Choice's Drop-down does not disappear when clicking somewhere, after popup menu is disposed
1062                     // if user presses Right Mouse Button on opened (unfurled)
1063                     // Choice then we mustn't open a popup menu. We could filter
1064                     // Mouse Events and handle them in XChoicePeer if Choice
1065                     // currently in opened state.
1066                     MouseEvent me = (MouseEvent)e;
1067                     int eventId = e.getID();
1068                     // fix 6251983: PIT: MouseDragged events not triggered
1069                     // fix 6251988: PIT: Choice consumes MouseReleased, MouseClicked events when clicking it with left button,
1070                     if ((unfurledChoice.isMouseEventInside(me) ||
1071                          (!firstPress && eventId == MouseEvent.MOUSE_DRAGGED)))
1072                     {
1073                         return handleMouseEventByChoice(me);
1074                     }
1075                     // MouseMoved events should be fired in Choice's comp if it's not opened
1076                     // Shouldn't generate Moved Events. CR : 6251995
1077                     if (eventId == MouseEvent.MOUSE_MOVED){
1078                         return handleMouseEventByChoice(me);
1079                     }
1080                     //fix for 6272965: PIT: Choice triggers MousePressed when pressing mouse outside comp while drop-down is active, XTkt
1081                     if (  !firstPress && !( isMouseEventInChoice(me) ||
1082                              unfurledChoice.isMouseEventInside(me)) &&
1083                              ( eventId == MouseEvent.MOUSE_PRESSED ||
1084                                eventId == MouseEvent.MOUSE_RELEASED ||
1085                                eventId == MouseEvent.MOUSE_CLICKED )
1086                           )
1087                     {
1088                         return handleMouseEventByChoice(me);
1089                     }
1090                 }
1091             }//else KeyEvent
1092         }//if unfurled
1093         return super.prePostEvent(e);
1094     }
1095 
1096     //convenient method
1097     //do not generate this kind of Events
1098     public boolean handleMouseEventByChoice(final MouseEvent me){
1099         InvocationEvent ev = new InvocationEvent(target, new Runnable() {
1100             public void run() {
1101                 handleJavaMouseEvent(me);
1102             }
1103         });
1104         postEvent(ev);
1105 
1106         return true;
1107     }
1108 
1109     /* Returns true if the MouseEvent coords
1110      * are inside of the Choice itself (it doesnt's depends on
1111      * if this choice opened or not).
1112      */
1113     private boolean isMouseEventInChoice(MouseEvent e) {
1114         int x = e.getX();
1115         int y = e.getY();
1116         Rectangle choiceRect = getBounds();
1117 
1118         if (x < 0 || x > choiceRect.width ||
1119             y < 0 || y > choiceRect.height)
1120         {
1121             return false;
1122         }
1123         return true;
1124     }
1125 }