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