1 /*
   2  * Copyright (c) 2003, 2018, 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     public void setFont(Font font) {
 682         super.setFont(font);
 683         helper.setFont(this.font);
 684     }
 685 
 686     public void setForeground(Color c) {
 687         super.setForeground(c);
 688         helper.updateColors(getGUIcolors());
 689     }
 690 
 691     public void setBackground(Color c) {
 692         super.setBackground(c);
 693         unfurledChoice.setBackground(c);
 694         helper.updateColors(getGUIcolors());
 695         updateMotifColors(c);
 696     }
 697 
 698     public void setDrawSelectedItem(boolean value) {
 699         drawSelectedItem = value;
 700     }
 701 
 702     public void setAlignUnder(Component comp) {
 703         alignUnder = comp;
 704     }
 705 
 706     // see 6240074 for more information
 707     public void addXChoicePeerListener(XChoicePeerListener l){
 708         choiceListener = l;
 709     }
 710 
 711     // see 6240074 for more information
 712     public void removeXChoicePeerListener(){
 713         choiceListener = null;
 714     }
 715 
 716     public boolean isUnfurled(){
 717         return unfurled;
 718     }
 719 
 720     /* fix for 6261352. We should detect if current parent Window (containing a Choice) become iconified and hide pop-down menu with grab release.
 721      * In this case we should hide pop-down menu.
 722      */
 723     //calls from XWindowPeer. Could accept X-styled state events
 724     public void stateChangedICCCM(int oldState, int newState) {
 725         if (unfurled && oldState != newState){
 726                 hidePopdownMenu();
 727         }
 728     }
 729 
 730     //calls from XFramePeer. Could accept Frame's states.
 731     public void stateChangedJava(int oldState, int newState) {
 732         if (unfurled && oldState != newState){
 733             hidePopdownMenu();
 734         }
 735     }
 736 
 737     @Override
 738     protected void initGraphicsConfiguration() {
 739         super.initGraphicsConfiguration();
 740         // The popup have the same graphic config, so update it at the same time
 741         if (unfurledChoice != null) {
 742             unfurledChoice.initGraphicsConfiguration();
 743             unfurledChoice.doValidateSurface();
 744         }
 745     }
 746 
 747     /**************************************************************************/
 748     /* Common functionality between List & Choice
 749        /**************************************************************************/
 750 
 751     /**
 752      * Inner class for the unfurled Choice list
 753      * Much, much more docs
 754      */
 755     final class UnfurledChoice extends XWindow /*implements XScrollbarClient*/ {
 756 
 757         // First try - use Choice as the target
 758 
 759         public UnfurledChoice(Component target) {
 760             super(target);
 761         }
 762 
 763         // Override so we can do our own create()
 764         public void preInit(XCreateWindowParams params) {
 765             // A parent of this window is the target, at this point: wrong.
 766             // Remove parent window; in the following preInit() call we'll calculate as a default
 767             // a correct root window which is the proper parent for override redirect.
 768             params.delete(PARENT_WINDOW);
 769             super.preInit(params);
 770             // Reset bounds(we'll set them later), set overrideRedirect
 771             params.remove(BOUNDS);
 772             params.add(OVERRIDE_REDIRECT, Boolean.TRUE);
 773         }
 774 
 775         // Generally, bounds should be:
 776         //  x = target.x
 777         //  y = target.y + target.height
 778         //  w = Max(target.width, getLongestItemWidth) + possible vertScrollbar
 779         //  h = Min(MAX_UNFURLED_ITEMS, target.getItemCount()) * itemHeight
 780         Rectangle placeOnScreen() {
 781             int numItemsDisplayed;
 782             // Motif paints an empty Choice the same size as a single item
 783             if (helper.isEmpty()) {
 784                 numItemsDisplayed = 1;
 785             }
 786             else {
 787                 int numItems = helper.getItemCount();
 788                 numItemsDisplayed = Math.min(MAX_UNFURLED_ITEMS, numItems);
 789             }
 790             Point global = XChoicePeer.this.toGlobal(0,0);
 791             Rectangle screenBounds = graphicsConfig.getBounds();
 792 
 793             if (alignUnder != null) {
 794                 Rectangle choiceRec = XChoicePeer.this.getBounds();
 795                 choiceRec.setLocation(0, 0);
 796                 choiceRec = XChoicePeer.this.toGlobal(choiceRec);
 797                 Rectangle alignUnderRec = new Rectangle(alignUnder.getLocationOnScreen(), alignUnder.getSize()); // TODO: Security?
 798                 Rectangle result = choiceRec.union(alignUnderRec);
 799                 // we've got the left and width, calculate top and height
 800                 width = result.width;
 801                 x = result.x;
 802                 y = result.y + result.height;
 803                 height = 2*BORDER_WIDTH +
 804                     numItemsDisplayed*(helper.getItemHeight()+2*ITEM_MARGIN);
 805             } else {
 806                 x = global.x;
 807                 y = global.y + XChoicePeer.this.height;
 808                 width = Math.max(XChoicePeer.this.width,
 809                                  helper.getMaxItemWidth() + 2 * (BORDER_WIDTH + ITEM_MARGIN + TEXT_SPACE) + (helper.isVSBVisible() ? SCROLLBAR_WIDTH : 0));
 810                 height = 2*BORDER_WIDTH +
 811                     numItemsDisplayed*(helper.getItemHeight()+2*ITEM_MARGIN);
 812             }
 813             // Don't run off the edge of the screenBounds
 814             if (x < screenBounds.x) {
 815                 x = screenBounds.x;
 816             }
 817             else if (x + width > screenBounds.x + screenBounds.width) {
 818                 x = screenBounds.x + screenBounds.width - width;
 819             }
 820 
 821             if (y + height > screenBounds.y + screenBounds.height) {
 822                 y = global.y - height;
 823             }
 824             if (y < screenBounds.y) {
 825                 y = screenBounds.y;
 826             }
 827             return new Rectangle(x, y, width, height);
 828         }
 829 
 830         public void toFront() {
 831             // see 6240074 for more information
 832             if (choiceListener != null)
 833                 choiceListener.unfurledChoiceOpening(helper);
 834 
 835             Rectangle r = placeOnScreen();
 836             reshape(r.x, r.y, r.width, r.height);
 837             super.toFront();
 838             setVisible(true);
 839         }
 840 
 841         /*
 842          * Track a MouseEvent (either a drag or a press) and paint a new
 843          * selected item, if necessary.
 844          */
 845         // FIXME: first unfurl after move is not at edge of the screen  onto second monitor doesn't
 846         // track mouse correctly.  Problem is w/ UnfurledChoice coords
 847         public void trackMouse(MouseEvent e) {
 848             // Event coords are relative to the button, so translate a bit
 849             Point local = toLocalCoords(e);
 850 
 851             // If x,y is over unfurled Choice,
 852             // highlight item under cursor
 853 
 854             switch (e.getID()) {
 855               case MouseEvent.MOUSE_PRESSED:
 856                   // FIXME: If the Choice is unfurled and the mouse is pressed
 857                   // outside of the Choice, the mouse should ungrab on the
 858                   // the press, not the release
 859                   if (helper.isInVertSB(getBounds(), local.x, local.y)) {
 860                       mouseInSB = true;
 861                       helper.handleVSBEvent(e, getBounds(), local.x, local.y);
 862                   }
 863                   else {
 864                       trackSelection(local.x, local.y);
 865                   }
 866                   break;
 867               case MouseEvent.MOUSE_RELEASED:
 868                   if (mouseInSB) {
 869                       mouseInSB = false;
 870                       helper.handleVSBEvent(e, getBounds(), local.x, local.y);
 871                   }else{
 872                       // See 6243382 for more information
 873                       helper.trackMouseReleasedScroll();
 874                   }
 875                   /*
 876                     else {
 877                     trackSelection(local.x, local.y);
 878                     }
 879                   */
 880                   break;
 881               case MouseEvent.MOUSE_DRAGGED:
 882                   if (mouseInSB) {
 883                       helper.handleVSBEvent(e, getBounds(), local.x, local.y);
 884                   }
 885                   else {
 886                       // See 6243382 for more information
 887                       helper.trackMouseDraggedScroll(local.x, local.y, width, height);
 888                       trackSelection(local.x, local.y);
 889                   }
 890                   break;
 891             }
 892         }
 893 
 894         private void trackSelection(int transX, int transY) {
 895             if (!helper.isEmpty()) {
 896                 if (transX > 0 && transX < width &&
 897                     transY > 0 && transY < height) {
 898                     int newIdx = helper.y2index(transY);
 899                     if (log.isLoggable(PlatformLogger.Level.FINE)) {
 900                         log.fine("transX=" + transX + ", transY=" + transY
 901                                  + ",width=" + width + ", height=" + height
 902                                  + ", newIdx=" + newIdx + " on " + target);
 903                     }
 904                     if ((newIdx >=0) && (newIdx < helper.getItemCount())
 905                         && (newIdx != helper.getSelectedIndex()))
 906                     {
 907                         helper.select(newIdx);
 908                         unfurledChoice.repaint();
 909                     }
 910                 }
 911             }
 912             // FIXME: If dragged off top or bottom, scroll if there's a vsb
 913             // (ICK - we'll need a timer or our own event or something)
 914         }
 915 
 916         /*
 917          * fillRect with current Background color on the whole dropdown list.
 918          */
 919         public void paintBackground() {
 920             final Graphics g = getGraphics();
 921             if (g != null) {
 922                 try {
 923                     g.setColor(getPeerBackground());
 924                     g.fillRect(0, 0, width, height);
 925                 } finally {
 926                     g.dispose();
 927                 }
 928             }
 929         }
 930         /*
 931          * 6405689. In some cases we should erase background to eliminate painting
 932          * artefacts.
 933          */
 934         @Override
 935         public void repaint() {
 936             if (!isVisible()) {
 937                 return;
 938             }
 939             if (helper.checkVsbVisibilityChangedAndReset()){
 940                 paintBackground();
 941             }
 942             super.repaint();
 943         }
 944         @Override
 945         public void paintPeer(Graphics g) {
 946             //System.out.println("UC.paint()");
 947             Choice choice = (Choice)target;
 948             Color[] colors = XChoicePeer.this.getGUIcolors();
 949             draw3DRect(g, getSystemColors(), 0, 0, width - 1, height - 1, true);
 950             draw3DRect(g, getSystemColors(), 1, 1, width - 3, height - 3, true);
 951 
 952             helper.paintAllItems(g,
 953                                  colors,
 954                                  getBounds());
 955         }
 956 
 957         public void setVisible(boolean vis) {
 958             xSetVisible(vis);
 959 
 960             if (!vis && alignUnder != null) {
 961                 alignUnder.requestFocusInWindow();
 962             }
 963         }
 964 
 965         /**
 966          * Return a MouseEvent's Point in coordinates relative to the
 967          * UnfurledChoice.
 968          */
 969         private Point toLocalCoords(MouseEvent e) {
 970             // Event coords are relative to the button, so translate a bit
 971             Point global = e.getLocationOnScreen();
 972 
 973             global.x -= x;
 974             global.y -= y;
 975             return global;
 976         }
 977 
 978         /* Returns true if the MouseEvent coords (which are based on the Choice)
 979          * are inside of the UnfurledChoice.
 980          */
 981         private boolean isMouseEventInside(MouseEvent e) {
 982             Point local = toLocalCoords(e);
 983             if (local.x > 0 && local.x < width &&
 984                 local.y > 0 && local.y < height) {
 985                 return true;
 986             }
 987             return false;
 988         }
 989 
 990         /**
 991          * Tests if the mouse cursor is in the Unfurled Choice, yet not
 992          * in the vertical scrollbar
 993          */
 994         private boolean isMouseInListArea(MouseEvent e) {
 995             if (isMouseEventInside(e)) {
 996                 Point local = toLocalCoords(e);
 997                 Rectangle bounds = getBounds();
 998                 if (!helper.isInVertSB(bounds, local.x, local.y)) {
 999                     return true;
1000                 }
1001             }
1002             return false;
1003         }
1004 
1005         /*
1006          * Overridden from XWindow() because we don't want to send
1007          * ComponentEvents
1008          */
1009         public void handleConfigureNotifyEvent(XEvent xev) {}
1010         public void handleMapNotifyEvent(XEvent xev) {}
1011         public void handleUnmapNotifyEvent(XEvent xev) {}
1012     } //UnfurledChoice
1013 
1014     public void dispose() {
1015         if (unfurledChoice != null) {
1016             unfurledChoice.destroy();
1017         }
1018         super.dispose();
1019     }
1020 
1021     /*
1022      * fix for 6239938 : Choice drop-down does not disappear when it loses
1023      * focus, on XToolkit
1024      * We are able to handle all _Key_ events received by Choice when
1025      * it is in opened state without sending it to EventQueue.
1026      * If Choice is in closed state we should behave like before: send
1027      * all events to EventQueue.
1028      * To be compatible with Motif we should handle all KeyEvents in
1029      * Choice if it is opened. KeyEvents should be sent into Java if Choice is not opened.
1030      */
1031     boolean prePostEvent(final AWTEvent e) {
1032         if (unfurled){
1033             // fix for 6253211: PIT: MouseWheel events not triggered for Choice drop down in XAWT
1034             if (e instanceof MouseWheelEvent){
1035                 return super.prePostEvent(e);
1036             }
1037             //fix 6252982: PIT: Keyboard FocusTraversal not working when choice's drop-down is visible, on XToolkit
1038             if (e instanceof KeyEvent){
1039                 // notify XWindow that this event had been already handled and no need to post it again
1040                 InvocationEvent ev = new InvocationEvent(target, new Runnable() {
1041                     public void run() {
1042                         if(target.isFocusable() &&
1043                                 getParentTopLevel().isFocusableWindow() )
1044                         {
1045                             handleJavaKeyEvent((KeyEvent)e);
1046                         }
1047                     }
1048                 });
1049                 postEvent(ev);
1050 
1051                 return true;
1052             } else {
1053                 if (e instanceof MouseEvent){
1054                     // Fix for 6240046 : REG:Choice's Drop-down does not disappear when clicking somewhere, after popup menu is disposed
1055                     // if user presses Right Mouse Button on opened (unfurled)
1056                     // Choice then we mustn't open a popup menu. We could filter
1057                     // Mouse Events and handle them in XChoicePeer if Choice
1058                     // currently in opened state.
1059                     MouseEvent me = (MouseEvent)e;
1060                     int eventId = e.getID();
1061                     // fix 6251983: PIT: MouseDragged events not triggered
1062                     // fix 6251988: PIT: Choice consumes MouseReleased, MouseClicked events when clicking it with left button,
1063                     if ((unfurledChoice.isMouseEventInside(me) ||
1064                          (!firstPress && eventId == MouseEvent.MOUSE_DRAGGED)))
1065                     {
1066                         return handleMouseEventByChoice(me);
1067                     }
1068                     // MouseMoved events should be fired in Choice's comp if it's not opened
1069                     // Shouldn't generate Moved Events. CR : 6251995
1070                     if (eventId == MouseEvent.MOUSE_MOVED){
1071                         return handleMouseEventByChoice(me);
1072                     }
1073                     //fix for 6272965: PIT: Choice triggers MousePressed when pressing mouse outside comp while drop-down is active, XTkt
1074                     if (  !firstPress && !( isMouseEventInChoice(me) ||
1075                              unfurledChoice.isMouseEventInside(me)) &&
1076                              ( eventId == MouseEvent.MOUSE_PRESSED ||
1077                                eventId == MouseEvent.MOUSE_RELEASED ||
1078                                eventId == MouseEvent.MOUSE_CLICKED )
1079                           )
1080                     {
1081                         return handleMouseEventByChoice(me);
1082                     }
1083                 }
1084             }//else KeyEvent
1085         }//if unfurled
1086         return super.prePostEvent(e);
1087     }
1088 
1089     //convenient method
1090     //do not generate this kind of Events
1091     public boolean handleMouseEventByChoice(final MouseEvent me){
1092         InvocationEvent ev = new InvocationEvent(target, new Runnable() {
1093             public void run() {
1094                 handleJavaMouseEvent(me);
1095             }
1096         });
1097         postEvent(ev);
1098 
1099         return true;
1100     }
1101 
1102     /* Returns true if the MouseEvent coords
1103      * are inside of the Choice itself (it doesnt's depends on
1104      * if this choice opened or not).
1105      */
1106     private boolean isMouseEventInChoice(MouseEvent e) {
1107         int x = e.getX();
1108         int y = e.getY();
1109         Rectangle choiceRect = getBounds();
1110 
1111         if (x < 0 || x > choiceRect.width ||
1112             y < 0 || y > choiceRect.height)
1113         {
1114             return false;
1115         }
1116         return true;
1117     }
1118 }