1 /*
   2  * Copyright (c) 1998, 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 javax.swing.plaf.basic;
  27 
  28 import javax.accessibility.AccessibleContext;
  29 import javax.swing.*;
  30 import javax.swing.border.Border;
  31 import javax.swing.border.LineBorder;
  32 import javax.swing.event.*;
  33 import java.awt.*;
  34 import java.awt.event.*;
  35 import java.beans.PropertyChangeListener;
  36 import java.beans.PropertyChangeEvent;
  37 import java.io.Serializable;
  38 
  39 
  40 /**
  41  * This is a basic implementation of the <code>ComboPopup</code> interface.
  42  *
  43  * This class represents the ui for the popup portion of the combo box.
  44  * <p>
  45  * All event handling is handled by listener classes created with the
  46  * <code>createxxxListener()</code> methods and internal classes.
  47  * You can change the behavior of this class by overriding the
  48  * <code>createxxxListener()</code> methods and supplying your own
  49  * event listeners or subclassing from the ones supplied in this class.
  50  * <p>
  51  * <strong>Warning:</strong>
  52  * Serialized objects of this class will not be compatible with
  53  * future Swing releases. The current serialization support is
  54  * appropriate for short term storage or RMI between applications running
  55  * the same version of Swing.  As of 1.4, support for long term storage
  56  * of all JavaBeans&trade;
  57  * has been added to the <code>java.beans</code> package.
  58  * Please see {@link java.beans.XMLEncoder}.
  59  *
  60  * @author Tom Santos
  61  * @author Mark Davidson
  62  */
  63 @SuppressWarnings("serial") // Same-version serialization only
  64 public class BasicComboPopup extends JPopupMenu implements ComboPopup {
  65     // An empty ListMode, this is used when the UI changes to allow
  66     // the JList to be gc'ed.
  67     private static class EmptyListModelClass implements ListModel<Object>, Serializable {
  68         public int getSize() { return 0; }
  69         public Object getElementAt(int index) { return null; }
  70         public void addListDataListener(ListDataListener l) {}
  71         public void removeListDataListener(ListDataListener l) {}
  72     };
  73 
  74     static final ListModel<Object> EmptyListModel = new EmptyListModelClass();
  75 
  76     private static Border LIST_BORDER = new LineBorder(Color.BLACK, 1);
  77 
  78     /**
  79      * The instance of {@code JComboBox}.
  80      */
  81     protected JComboBox<Object>             comboBox;
  82     /**
  83      * This protected field is implementation specific. Do not access directly
  84      * or override. Use the accessor methods instead.
  85      *
  86      * @see #getList
  87      * @see #createList
  88      */
  89     protected JList<Object>                 list;
  90     /**
  91      * This protected field is implementation specific. Do not access directly
  92      * or override. Use the create method instead
  93      *
  94      * @see #createScroller
  95      */
  96     protected JScrollPane              scroller;
  97 
  98     /**
  99      * As of Java 2 platform v1.4 this previously undocumented field is no
 100      * longer used.
 101      */
 102     protected boolean                  valueIsAdjusting = false;
 103 
 104     // Listeners that are required by the ComboPopup interface
 105 
 106     /**
 107      * Implementation of all the listener classes.
 108      */
 109     private Handler handler;
 110 
 111     /**
 112      * This protected field is implementation specific. Do not access directly
 113      * or override. Use the accessor or create methods instead.
 114      *
 115      * @see #getMouseMotionListener
 116      * @see #createMouseMotionListener
 117      */
 118     protected MouseMotionListener      mouseMotionListener;
 119     /**
 120      * This protected field is implementation specific. Do not access directly
 121      * or override. Use the accessor or create methods instead.
 122      *
 123      * @see #getMouseListener
 124      * @see #createMouseListener
 125      */
 126     protected MouseListener            mouseListener;
 127 
 128     /**
 129      * This protected field is implementation specific. Do not access directly
 130      * or override. Use the accessor or create methods instead.
 131      *
 132      * @see #getKeyListener
 133      * @see #createKeyListener
 134      */
 135     protected KeyListener              keyListener;
 136 
 137     /**
 138      * This protected field is implementation specific. Do not access directly
 139      * or override. Use the create method instead.
 140      *
 141      * @see #createListSelectionListener
 142      */
 143     protected ListSelectionListener    listSelectionListener;
 144 
 145     // Listeners that are attached to the list
 146     /**
 147      * This protected field is implementation specific. Do not access directly
 148      * or override. Use the create method instead.
 149      *
 150      * @see #createListMouseListener
 151      */
 152     protected MouseListener            listMouseListener;
 153     /**
 154      * This protected field is implementation specific. Do not access directly
 155      * or override. Use the create method instead
 156      *
 157      * @see #createListMouseMotionListener
 158      */
 159     protected MouseMotionListener      listMouseMotionListener;
 160 
 161     // Added to the combo box for bound properties
 162     /**
 163      * This protected field is implementation specific. Do not access directly
 164      * or override. Use the create method instead
 165      *
 166      * @see #createPropertyChangeListener
 167      */
 168     protected PropertyChangeListener   propertyChangeListener;
 169 
 170     // Added to the combo box model
 171     /**
 172      * This protected field is implementation specific. Do not access directly
 173      * or override. Use the create method instead
 174      *
 175      * @see #createListDataListener
 176      */
 177     protected ListDataListener         listDataListener;
 178 
 179     /**
 180      * This protected field is implementation specific. Do not access directly
 181      * or override. Use the create method instead
 182      *
 183      * @see #createItemListener
 184      */
 185     protected ItemListener             itemListener;
 186 
 187     /**
 188      * This protected field is implementation specific. Do not access directly
 189      * or override.
 190      */
 191     protected Timer                    autoscrollTimer;
 192 
 193     /**
 194      * {@code true} if the mouse cursor is in the popup.
 195      */
 196     protected boolean                  hasEntered = false;
 197 
 198     /**
 199      * If {@code true} the auto-scrolling is enabled.
 200      */
 201     protected boolean                  isAutoScrolling = false;
 202 
 203     /**
 204      * The direction of scrolling.
 205      */
 206     protected int                      scrollDirection = SCROLL_UP;
 207 
 208     /**
 209      * The direction of scrolling up.
 210      */
 211     protected static final int         SCROLL_UP = 0;
 212 
 213     /**
 214      * The direction of scrolling down.
 215      */
 216     protected static final int         SCROLL_DOWN = 1;
 217 
 218 
 219     //========================================
 220     // begin ComboPopup method implementations
 221     //
 222 
 223     /**
 224      * Implementation of ComboPopup.show().
 225      */
 226     public void show() {
 227         comboBox.firePopupMenuWillBecomeVisible();
 228         setListSelection(comboBox.getSelectedIndex());
 229         Point location = getPopupLocation();
 230         show( comboBox, location.x, location.y );
 231     }
 232 
 233 
 234     /**
 235      * Implementation of ComboPopup.hide().
 236      */
 237     public void hide() {
 238         MenuSelectionManager manager = MenuSelectionManager.defaultManager();
 239         MenuElement [] selection = manager.getSelectedPath();
 240         for ( int i = 0 ; i < selection.length ; i++ ) {
 241             if ( selection[i] == this ) {
 242                 manager.clearSelectedPath();
 243                 break;
 244             }
 245         }
 246         if (selection.length > 0) {
 247             comboBox.repaint();
 248         }
 249     }
 250 
 251     /**
 252      * Implementation of ComboPopup.getList().
 253      */
 254     public JList<Object> getList() {
 255         return list;
 256     }
 257 
 258     /**
 259      * Implementation of ComboPopup.getMouseListener().
 260      *
 261      * @return a <code>MouseListener</code> or null
 262      * @see ComboPopup#getMouseListener
 263      */
 264     public MouseListener getMouseListener() {
 265         if (mouseListener == null) {
 266             mouseListener = createMouseListener();
 267         }
 268         return mouseListener;
 269     }
 270 
 271     /**
 272      * Implementation of ComboPopup.getMouseMotionListener().
 273      *
 274      * @return a <code>MouseMotionListener</code> or null
 275      * @see ComboPopup#getMouseMotionListener
 276      */
 277     public MouseMotionListener getMouseMotionListener() {
 278         if (mouseMotionListener == null) {
 279             mouseMotionListener = createMouseMotionListener();
 280         }
 281         return mouseMotionListener;
 282     }
 283 
 284     /**
 285      * Implementation of ComboPopup.getKeyListener().
 286      *
 287      * @return a <code>KeyListener</code> or null
 288      * @see ComboPopup#getKeyListener
 289      */
 290     public KeyListener getKeyListener() {
 291         if (keyListener == null) {
 292             keyListener = createKeyListener();
 293         }
 294         return keyListener;
 295     }
 296 
 297     /**
 298      * Called when the UI is uninstalling.  Since this popup isn't in the component
 299      * tree, it won't get it's uninstallUI() called.  It removes the listeners that
 300      * were added in addComboBoxListeners().
 301      */
 302     public void uninstallingUI() {
 303         if (propertyChangeListener != null) {
 304             comboBox.removePropertyChangeListener( propertyChangeListener );
 305         }
 306         if (itemListener != null) {
 307             comboBox.removeItemListener( itemListener );
 308         }
 309         uninstallComboBoxModelListeners(comboBox.getModel());
 310         uninstallKeyboardActions();
 311         uninstallListListeners();
 312         // We do this, otherwise the listener the ui installs on
 313         // the model (the combobox model in this case) will keep a
 314         // reference to the list, causing the list (and us) to never get gced.
 315         list.setModel(EmptyListModel);
 316     }
 317 
 318     //
 319     // end ComboPopup method implementations
 320     //======================================
 321 
 322     /**
 323      * Removes the listeners from the combo box model
 324      *
 325      * @param model The combo box model to install listeners
 326      * @see #installComboBoxModelListeners
 327      */
 328     protected void uninstallComboBoxModelListeners( ComboBoxModel<?> model ) {
 329         if (model != null && listDataListener != null) {
 330             model.removeListDataListener(listDataListener);
 331         }
 332     }
 333 
 334     /**
 335      * Unregisters keyboard actions.
 336      */
 337     protected void uninstallKeyboardActions() {
 338         // XXX - shouldn't call this method
 339 //        comboBox.unregisterKeyboardAction( KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ) );
 340     }
 341 
 342 
 343 
 344     //===================================================================
 345     // begin Initialization routines
 346     //
 347 
 348     /**
 349      * Constructs a new instance of {@code BasicComboPopup}.
 350      *
 351      * @param combo an instance of {@code JComboBox}
 352      */
 353     public BasicComboPopup( JComboBox<Object> combo ) {
 354         super();
 355         setName("ComboPopup.popup");
 356         comboBox = combo;
 357 
 358         setLightWeightPopupEnabled( comboBox.isLightWeightPopupEnabled() );
 359 
 360         // UI construction of the popup.
 361         list = createList();
 362         list.setName("ComboBox.list");
 363         configureList();
 364         scroller = createScroller();
 365         scroller.setName("ComboBox.scrollPane");
 366         configureScroller();
 367         configurePopup();
 368 
 369         installComboBoxListeners();
 370         installKeyboardActions();
 371     }
 372 
 373     // Overriden PopupMenuListener notification methods to inform combo box
 374     // PopupMenuListeners.
 375 
 376     protected void firePopupMenuWillBecomeVisible() {
 377         super.firePopupMenuWillBecomeVisible();
 378         // comboBox.firePopupMenuWillBecomeVisible() is called from BasicComboPopup.show() method
 379         // to let the user change the popup menu from the PopupMenuListener.popupMenuWillBecomeVisible()
 380     }
 381 
 382     protected void firePopupMenuWillBecomeInvisible() {
 383         super.firePopupMenuWillBecomeInvisible();
 384         comboBox.firePopupMenuWillBecomeInvisible();
 385     }
 386 
 387     protected void firePopupMenuCanceled() {
 388         super.firePopupMenuCanceled();
 389         comboBox.firePopupMenuCanceled();
 390     }
 391 
 392     /**
 393      * Creates a listener
 394      * that will watch for mouse-press and release events on the combo box.
 395      *
 396      * <strong>Warning:</strong>
 397      * When overriding this method, make sure to maintain the existing
 398      * behavior.
 399      *
 400      * @return a <code>MouseListener</code> which will be added to
 401      * the combo box or null
 402      */
 403     protected MouseListener createMouseListener() {
 404         return getHandler();
 405     }
 406 
 407     /**
 408      * Creates the mouse motion listener which will be added to the combo
 409      * box.
 410      *
 411      * <strong>Warning:</strong>
 412      * When overriding this method, make sure to maintain the existing
 413      * behavior.
 414      *
 415      * @return a <code>MouseMotionListener</code> which will be added to
 416      *         the combo box or null
 417      */
 418     protected MouseMotionListener createMouseMotionListener() {
 419         return getHandler();
 420     }
 421 
 422     /**
 423      * Creates the key listener that will be added to the combo box. If
 424      * this method returns null then it will not be added to the combo box.
 425      *
 426      * @return a <code>KeyListener</code> or null
 427      */
 428     protected KeyListener createKeyListener() {
 429         return null;
 430     }
 431 
 432     /**
 433      * Creates a list selection listener that watches for selection changes in
 434      * the popup's list.  If this method returns null then it will not
 435      * be added to the popup list.
 436      *
 437      * @return an instance of a <code>ListSelectionListener</code> or null
 438      */
 439     protected ListSelectionListener createListSelectionListener() {
 440         return null;
 441     }
 442 
 443     /**
 444      * Creates a list data listener which will be added to the
 445      * <code>ComboBoxModel</code>. If this method returns null then
 446      * it will not be added to the combo box model.
 447      *
 448      * @return an instance of a <code>ListDataListener</code> or null
 449      */
 450     protected ListDataListener createListDataListener() {
 451         return null;
 452     }
 453 
 454     /**
 455      * Creates a mouse listener that watches for mouse events in
 456      * the popup's list. If this method returns null then it will
 457      * not be added to the combo box.
 458      *
 459      * @return an instance of a <code>MouseListener</code> or null
 460      */
 461     protected MouseListener createListMouseListener() {
 462         return getHandler();
 463     }
 464 
 465     /**
 466      * Creates a mouse motion listener that watches for mouse motion
 467      * events in the popup's list. If this method returns null then it will
 468      * not be added to the combo box.
 469      *
 470      * @return an instance of a <code>MouseMotionListener</code> or null
 471      */
 472     protected MouseMotionListener createListMouseMotionListener() {
 473         return getHandler();
 474     }
 475 
 476     /**
 477      * Creates a <code>PropertyChangeListener</code> which will be added to
 478      * the combo box. If this method returns null then it will not
 479      * be added to the combo box.
 480      *
 481      * @return an instance of a <code>PropertyChangeListener</code> or null
 482      */
 483     protected PropertyChangeListener createPropertyChangeListener() {
 484         return getHandler();
 485     }
 486 
 487     /**
 488      * Creates an <code>ItemListener</code> which will be added to the
 489      * combo box. If this method returns null then it will not
 490      * be added to the combo box.
 491      * <p>
 492      * Subclasses may override this method to return instances of their own
 493      * ItemEvent handlers.
 494      *
 495      * @return an instance of an <code>ItemListener</code> or null
 496      */
 497     protected ItemListener createItemListener() {
 498         return getHandler();
 499     }
 500 
 501     private Handler getHandler() {
 502         if (handler == null) {
 503             handler = new Handler();
 504         }
 505         return handler;
 506     }
 507 
 508     /**
 509      * Creates the JList used in the popup to display
 510      * the items in the combo box model. This method is called when the UI class
 511      * is created.
 512      *
 513      * @return a <code>JList</code> used to display the combo box items
 514      */
 515     protected JList<Object> createList() {
 516         return new JList<Object>( comboBox.getModel() ) {
 517             public void processMouseEvent(MouseEvent e)  {
 518                 if (BasicGraphicsUtils.isMenuShortcutKeyDown(e))  {
 519                     // Fix for 4234053. Filter out the Control Key from the list.
 520                     // ie., don't allow CTRL key deselection.
 521                     Toolkit toolkit = Toolkit.getDefaultToolkit();
 522                     e = new MouseEvent((Component)e.getSource(), e.getID(), e.getWhen(),
 523                                        e.getModifiers() ^ toolkit.getMenuShortcutKeyMask(),
 524                                        e.getX(), e.getY(),
 525                                        e.getXOnScreen(), e.getYOnScreen(),
 526                                        e.getClickCount(),
 527                                        e.isPopupTrigger(),
 528                                        MouseEvent.NOBUTTON);
 529                 }
 530                 super.processMouseEvent(e);
 531             }
 532         };
 533     }
 534 
 535     /**
 536      * Configures the list which is used to hold the combo box items in the
 537      * popup. This method is called when the UI class
 538      * is created.
 539      *
 540      * @see #createList
 541      */
 542     protected void configureList() {
 543         list.setFont( comboBox.getFont() );
 544         list.setForeground( comboBox.getForeground() );
 545         list.setBackground( comboBox.getBackground() );
 546         list.setSelectionForeground( UIManager.getColor( "ComboBox.selectionForeground" ) );
 547         list.setSelectionBackground( UIManager.getColor( "ComboBox.selectionBackground" ) );
 548         list.setBorder( null );
 549         list.setCellRenderer( comboBox.getRenderer() );
 550         list.setFocusable( false );
 551         list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
 552         setListSelection( comboBox.getSelectedIndex() );
 553         installListListeners();
 554     }
 555 
 556     /**
 557      * Adds the listeners to the list control.
 558      */
 559     protected void installListListeners() {
 560         if ((listMouseListener = createListMouseListener()) != null) {
 561             list.addMouseListener( listMouseListener );
 562         }
 563         if ((listMouseMotionListener = createListMouseMotionListener()) != null) {
 564             list.addMouseMotionListener( listMouseMotionListener );
 565         }
 566         if ((listSelectionListener = createListSelectionListener()) != null) {
 567             list.addListSelectionListener( listSelectionListener );
 568         }
 569     }
 570 
 571     void uninstallListListeners() {
 572         if (listMouseListener != null) {
 573             list.removeMouseListener(listMouseListener);
 574             listMouseListener = null;
 575         }
 576         if (listMouseMotionListener != null) {
 577             list.removeMouseMotionListener(listMouseMotionListener);
 578             listMouseMotionListener = null;
 579         }
 580         if (listSelectionListener != null) {
 581             list.removeListSelectionListener(listSelectionListener);
 582             listSelectionListener = null;
 583         }
 584         handler = null;
 585     }
 586 
 587     /**
 588      * Creates the scroll pane which houses the scrollable list.
 589      *
 590      * @return the scroll pane which houses the scrollable list
 591      */
 592     protected JScrollPane createScroller() {
 593         JScrollPane sp = new JScrollPane( list,
 594                                 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
 595                                 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
 596         sp.setHorizontalScrollBar(null);
 597         return sp;
 598     }
 599 
 600     /**
 601      * Configures the scrollable portion which holds the list within
 602      * the combo box popup. This method is called when the UI class
 603      * is created.
 604      */
 605     protected void configureScroller() {
 606         scroller.setFocusable( false );
 607         scroller.getVerticalScrollBar().setFocusable( false );
 608         scroller.setBorder( null );
 609     }
 610 
 611     /**
 612      * Configures the popup portion of the combo box. This method is called
 613      * when the UI class is created.
 614      */
 615     protected void configurePopup() {
 616         setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) );
 617         setBorderPainted( true );
 618         setBorder(LIST_BORDER);
 619         setOpaque( false );
 620         add( scroller );
 621         setDoubleBuffered( true );
 622         setFocusable( false );
 623     }
 624 
 625     /**
 626      * This method adds the necessary listeners to the JComboBox.
 627      */
 628     protected void installComboBoxListeners() {
 629         if ((propertyChangeListener = createPropertyChangeListener()) != null) {
 630             comboBox.addPropertyChangeListener(propertyChangeListener);
 631         }
 632         if ((itemListener = createItemListener()) != null) {
 633             comboBox.addItemListener(itemListener);
 634         }
 635         installComboBoxModelListeners(comboBox.getModel());
 636     }
 637 
 638     /**
 639      * Installs the listeners on the combo box model. Any listeners installed
 640      * on the combo box model should be removed in
 641      * <code>uninstallComboBoxModelListeners</code>.
 642      *
 643      * @param model The combo box model to install listeners
 644      * @see #uninstallComboBoxModelListeners
 645      */
 646     protected void installComboBoxModelListeners( ComboBoxModel<?> model ) {
 647         if (model != null && (listDataListener = createListDataListener()) != null) {
 648             model.addListDataListener(listDataListener);
 649         }
 650     }
 651 
 652     /**
 653      * Registers keyboard actions.
 654      */
 655     protected void installKeyboardActions() {
 656 
 657         /* XXX - shouldn't call this method. take it out for testing.
 658         ActionListener action = new ActionListener() {
 659             public void actionPerformed(ActionEvent e){
 660             }
 661         };
 662 
 663         comboBox.registerKeyboardAction( action,
 664                                          KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ),
 665                                          JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); */
 666 
 667     }
 668 
 669     //
 670     // end Initialization routines
 671     //=================================================================
 672 
 673 
 674     //===================================================================
 675     // begin Event Listenters
 676     //
 677 
 678     /**
 679      * A listener to be registered upon the combo box
 680      * (<em>not</em> its popup menu)
 681      * to handle mouse events
 682      * that affect the state of the popup menu.
 683      * The main purpose of this listener is to make the popup menu
 684      * appear and disappear.
 685      * This listener also helps
 686      * with click-and-drag scenarios by setting the selection if the mouse was
 687      * released over the list during a drag.
 688      *
 689      * <p>
 690      * <strong>Warning:</strong>
 691      * We recommend that you <em>not</em>
 692      * create subclasses of this class.
 693      * If you absolutely must create a subclass,
 694      * be sure to invoke the superclass
 695      * version of each method.
 696      *
 697      * @see BasicComboPopup#createMouseListener
 698      */
 699     protected class InvocationMouseHandler extends MouseAdapter {
 700         /**
 701          * Responds to mouse-pressed events on the combo box.
 702          *
 703          * @param e the mouse-press event to be handled
 704          */
 705         public void mousePressed( MouseEvent e ) {
 706             getHandler().mousePressed(e);
 707         }
 708 
 709         /**
 710          * Responds to the user terminating
 711          * a click or drag that began on the combo box.
 712          *
 713          * @param e the mouse-release event to be handled
 714          */
 715         public void mouseReleased( MouseEvent e ) {
 716             getHandler().mouseReleased(e);
 717         }
 718     }
 719 
 720     /**
 721      * This listener watches for dragging and updates the current selection in the
 722      * list if it is dragging over the list.
 723      */
 724     protected class InvocationMouseMotionHandler extends MouseMotionAdapter {
 725         public void mouseDragged( MouseEvent e ) {
 726             getHandler().mouseDragged(e);
 727         }
 728     }
 729 
 730     /**
 731      * As of Java 2 platform v 1.4, this class is now obsolete and is only included for
 732      * backwards API compatibility. Do not instantiate or subclass.
 733      * <p>
 734      * All the functionality of this class has been included in
 735      * BasicComboBoxUI ActionMap/InputMap methods.
 736      */
 737     public class InvocationKeyHandler extends KeyAdapter {
 738         public void keyReleased( KeyEvent e ) {}
 739     }
 740 
 741     /**
 742      * As of Java 2 platform v 1.4, this class is now obsolete, doesn't do anything, and
 743      * is only included for backwards API compatibility. Do not call or
 744      * override.
 745      */
 746     protected class ListSelectionHandler implements ListSelectionListener {
 747         public void valueChanged( ListSelectionEvent e ) {}
 748     }
 749 
 750     /**
 751      * As of 1.4, this class is now obsolete, doesn't do anything, and
 752      * is only included for backwards API compatibility. Do not call or
 753      * override.
 754      * <p>
 755      * The functionality has been migrated into <code>ItemHandler</code>.
 756      *
 757      * @see #createItemListener
 758      */
 759     public class ListDataHandler implements ListDataListener {
 760         public void contentsChanged( ListDataEvent e ) {}
 761 
 762         public void intervalAdded( ListDataEvent e ) {
 763         }
 764 
 765         public void intervalRemoved( ListDataEvent e ) {
 766         }
 767     }
 768 
 769     /**
 770      * This listener hides the popup when the mouse is released in the list.
 771      */
 772     protected class ListMouseHandler extends MouseAdapter {
 773         public void mousePressed( MouseEvent e ) {
 774         }
 775         public void mouseReleased(MouseEvent anEvent) {
 776             getHandler().mouseReleased(anEvent);
 777         }
 778     }
 779 
 780     /**
 781      * This listener changes the selected item as you move the mouse over the list.
 782      * The selection change is not committed to the model, this is for user feedback only.
 783      */
 784     protected class ListMouseMotionHandler extends MouseMotionAdapter {
 785         public void mouseMoved( MouseEvent anEvent ) {
 786             getHandler().mouseMoved(anEvent);
 787         }
 788     }
 789 
 790     /**
 791      * This listener watches for changes to the selection in the
 792      * combo box.
 793      */
 794     protected class ItemHandler implements ItemListener {
 795         public void itemStateChanged( ItemEvent e ) {
 796             getHandler().itemStateChanged(e);
 797         }
 798     }
 799 
 800     /**
 801      * This listener watches for bound properties that have changed in the
 802      * combo box.
 803      * <p>
 804      * Subclasses which wish to listen to combo box property changes should
 805      * call the superclass methods to ensure that the combo popup correctly
 806      * handles property changes.
 807      *
 808      * @see #createPropertyChangeListener
 809      */
 810     protected class PropertyChangeHandler implements PropertyChangeListener {
 811         public void propertyChange( PropertyChangeEvent e ) {
 812             getHandler().propertyChange(e);
 813         }
 814     }
 815 
 816 
 817     private class AutoScrollActionHandler implements ActionListener {
 818         private int direction;
 819 
 820         AutoScrollActionHandler(int direction) {
 821             this.direction = direction;
 822         }
 823 
 824         public void actionPerformed(ActionEvent e) {
 825             if (direction == SCROLL_UP) {
 826                 autoScrollUp();
 827             }
 828             else {
 829                 autoScrollDown();
 830             }
 831         }
 832     }
 833 
 834 
 835     private class Handler implements ItemListener, MouseListener,
 836                           MouseMotionListener, PropertyChangeListener,
 837                           Serializable {
 838         //
 839         // MouseListener
 840         // NOTE: this is added to both the JList and JComboBox
 841         //
 842         public void mouseClicked(MouseEvent e) {
 843         }
 844 
 845         public void mousePressed(MouseEvent e) {
 846             if (e.getSource() == list) {
 847                 return;
 848             }
 849             if (!SwingUtilities.isLeftMouseButton(e) || !comboBox.isEnabled())
 850                 return;
 851 
 852             if ( comboBox.isEditable() ) {
 853                 Component comp = comboBox.getEditor().getEditorComponent();
 854                 if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) {
 855                     comp.requestFocus();
 856                 }
 857             }
 858             else if (comboBox.isRequestFocusEnabled()) {
 859                 comboBox.requestFocus();
 860             }
 861             togglePopup();
 862         }
 863 
 864         public void mouseReleased(MouseEvent e) {
 865             if (e.getSource() == list) {
 866                 if (list.getModel().getSize() > 0) {
 867                     // JList mouse listener
 868                     if (comboBox.getSelectedIndex() == list.getSelectedIndex()) {
 869                         comboBox.getEditor().setItem(list.getSelectedValue());
 870                     }
 871                     comboBox.setSelectedIndex(list.getSelectedIndex());
 872                 }
 873                 comboBox.setPopupVisible(false);
 874                 // workaround for cancelling an edited item (bug 4530953)
 875                 if (comboBox.isEditable() && comboBox.getEditor() != null) {
 876                     comboBox.configureEditor(comboBox.getEditor(),
 877                                              comboBox.getSelectedItem());
 878                 }
 879                 return;
 880             }
 881             // JComboBox mouse listener
 882             Component source = (Component)e.getSource();
 883             Dimension size = source.getSize();
 884             Rectangle bounds = new Rectangle( 0, 0, size.width - 1, size.height - 1 );
 885             if ( !bounds.contains( e.getPoint() ) ) {
 886                 MouseEvent newEvent = convertMouseEvent( e );
 887                 Point location = newEvent.getPoint();
 888                 Rectangle r = new Rectangle();
 889                 list.computeVisibleRect( r );
 890                 if ( r.contains( location ) ) {
 891                     if (comboBox.getSelectedIndex() == list.getSelectedIndex()) {
 892                         comboBox.getEditor().setItem(list.getSelectedValue());
 893                     }
 894                     comboBox.setSelectedIndex(list.getSelectedIndex());
 895                 }
 896                 comboBox.setPopupVisible(false);
 897             }
 898             hasEntered = false;
 899             stopAutoScrolling();
 900         }
 901 
 902         public void mouseEntered(MouseEvent e) {
 903         }
 904 
 905         public void mouseExited(MouseEvent e) {
 906         }
 907 
 908         //
 909         // MouseMotionListener:
 910         // NOTE: this is added to both the List and ComboBox
 911         //
 912         public void mouseMoved(MouseEvent anEvent) {
 913             if (anEvent.getSource() == list) {
 914                 Point location = anEvent.getPoint();
 915                 Rectangle r = new Rectangle();
 916                 list.computeVisibleRect( r );
 917                 if ( r.contains( location ) ) {
 918                     updateListBoxSelectionForEvent( anEvent, false );
 919                 }
 920             }
 921         }
 922 
 923         public void mouseDragged( MouseEvent e ) {
 924             if (e.getSource() == list) {
 925                 return;
 926             }
 927             if ( isVisible() ) {
 928                 MouseEvent newEvent = convertMouseEvent( e );
 929                 Rectangle r = new Rectangle();
 930                 list.computeVisibleRect( r );
 931 
 932                 if ( newEvent.getPoint().y >= r.y && newEvent.getPoint().y <= r.y + r.height - 1 ) {
 933                     hasEntered = true;
 934                     if ( isAutoScrolling ) {
 935                         stopAutoScrolling();
 936                     }
 937                     Point location = newEvent.getPoint();
 938                     if ( r.contains( location ) ) {
 939                         updateListBoxSelectionForEvent( newEvent, false );
 940                     }
 941                 }
 942                 else {
 943                     if ( hasEntered ) {
 944                         int directionToScroll = newEvent.getPoint().y < r.y ? SCROLL_UP : SCROLL_DOWN;
 945                         if ( isAutoScrolling && scrollDirection != directionToScroll ) {
 946                             stopAutoScrolling();
 947                             startAutoScrolling( directionToScroll );
 948                         }
 949                         else if ( !isAutoScrolling ) {
 950                             startAutoScrolling( directionToScroll );
 951                         }
 952                     }
 953                     else {
 954                         if ( e.getPoint().y < 0 ) {
 955                             hasEntered = true;
 956                             startAutoScrolling( SCROLL_UP );
 957                         }
 958                     }
 959                 }
 960             }
 961         }
 962 
 963         //
 964         // PropertyChangeListener
 965         //
 966         public void propertyChange(PropertyChangeEvent e) {
 967             @SuppressWarnings("unchecked")
 968             JComboBox<Object> comboBox = (JComboBox)e.getSource();
 969             String propertyName = e.getPropertyName();
 970 
 971             if ( propertyName == "model" ) {
 972                 @SuppressWarnings("unchecked")
 973                 ComboBoxModel<Object> oldModel = (ComboBoxModel)e.getOldValue();
 974                 @SuppressWarnings("unchecked")
 975                 ComboBoxModel<Object> newModel = (ComboBoxModel)e.getNewValue();
 976                 uninstallComboBoxModelListeners(oldModel);
 977                 installComboBoxModelListeners(newModel);
 978 
 979                 list.setModel(newModel);
 980 
 981                 if ( isVisible() ) {
 982                     hide();
 983                 }
 984             }
 985             else if ( propertyName == "renderer" ) {
 986                 list.setCellRenderer( comboBox.getRenderer() );
 987                 if ( isVisible() ) {
 988                     hide();
 989                 }
 990             }
 991             else if (propertyName == "componentOrientation") {
 992                 // Pass along the new component orientation
 993                 // to the list and the scroller
 994 
 995                 ComponentOrientation o =(ComponentOrientation)e.getNewValue();
 996 
 997                 JList<?> list = getList();
 998                 if (list!=null && list.getComponentOrientation()!=o) {
 999                     list.setComponentOrientation(o);
1000                 }
1001 
1002                 if (scroller!=null && scroller.getComponentOrientation()!=o) {
1003                     scroller.setComponentOrientation(o);
1004                 }
1005 
1006                 if (o!=getComponentOrientation()) {
1007                     setComponentOrientation(o);
1008                 }
1009             }
1010             else if (propertyName == "lightWeightPopupEnabled") {
1011                 setLightWeightPopupEnabled(comboBox.isLightWeightPopupEnabled());
1012             }
1013         }
1014 
1015         //
1016         // ItemListener
1017         //
1018         public void itemStateChanged( ItemEvent e ) {
1019             if (e.getStateChange() == ItemEvent.SELECTED) {
1020                 @SuppressWarnings("unchecked")
1021                 JComboBox<Object> comboBox = (JComboBox)e.getSource();
1022                 setListSelection(comboBox.getSelectedIndex());
1023             }
1024         }
1025     }
1026 
1027     //
1028     // end Event Listeners
1029     //=================================================================
1030 
1031 
1032     /**
1033      * Overridden to unconditionally return false.
1034      */
1035     public boolean isFocusTraversable() {
1036         return false;
1037     }
1038 
1039     //===================================================================
1040     // begin Autoscroll methods
1041     //
1042 
1043     /**
1044      * This protected method is implementation specific and should be private.
1045      * do not call or override.
1046      *
1047      * @param direction the direction of scrolling
1048      */
1049     protected void startAutoScrolling( int direction ) {
1050         // XXX - should be a private method within InvocationMouseMotionHandler
1051         // if possible.
1052         if ( isAutoScrolling ) {
1053             autoscrollTimer.stop();
1054         }
1055 
1056         isAutoScrolling = true;
1057 
1058         if ( direction == SCROLL_UP ) {
1059             scrollDirection = SCROLL_UP;
1060             Point convertedPoint = SwingUtilities.convertPoint( scroller, new Point( 1, 1 ), list );
1061             int top = list.locationToIndex( convertedPoint );
1062             list.setSelectedIndex( top );
1063 
1064             autoscrollTimer = new Timer( 100, new AutoScrollActionHandler(
1065                                              SCROLL_UP) );
1066         }
1067         else if ( direction == SCROLL_DOWN ) {
1068             scrollDirection = SCROLL_DOWN;
1069             Dimension size = scroller.getSize();
1070             Point convertedPoint = SwingUtilities.convertPoint( scroller,
1071                                                                 new Point( 1, (size.height - 1) - 2 ),
1072                                                                 list );
1073             int bottom = list.locationToIndex( convertedPoint );
1074             list.setSelectedIndex( bottom );
1075 
1076             autoscrollTimer = new Timer(100, new AutoScrollActionHandler(
1077                                             SCROLL_DOWN));
1078         }
1079         autoscrollTimer.start();
1080     }
1081 
1082     /**
1083      * This protected method is implementation specific and should be private.
1084      * do not call or override.
1085      */
1086     protected void stopAutoScrolling() {
1087         isAutoScrolling = false;
1088 
1089         if ( autoscrollTimer != null ) {
1090             autoscrollTimer.stop();
1091             autoscrollTimer = null;
1092         }
1093     }
1094 
1095     /**
1096      * This protected method is implementation specific and should be private.
1097      * do not call or override.
1098      */
1099     protected void autoScrollUp() {
1100         int index = list.getSelectedIndex();
1101         if ( index > 0 ) {
1102             list.setSelectedIndex( index - 1 );
1103             list.ensureIndexIsVisible( index - 1 );
1104         }
1105     }
1106 
1107     /**
1108      * This protected method is implementation specific and should be private.
1109      * do not call or override.
1110      */
1111     protected void autoScrollDown() {
1112         int index = list.getSelectedIndex();
1113         int lastItem = list.getModel().getSize() - 1;
1114         if ( index < lastItem ) {
1115             list.setSelectedIndex( index + 1 );
1116             list.ensureIndexIsVisible( index + 1 );
1117         }
1118     }
1119 
1120     //
1121     // end Autoscroll methods
1122     //=================================================================
1123 
1124 
1125     //===================================================================
1126     // begin Utility methods
1127     //
1128 
1129     /**
1130      * Gets the AccessibleContext associated with this BasicComboPopup.
1131      * The AccessibleContext will have its parent set to the ComboBox.
1132      *
1133      * @return an AccessibleContext for the BasicComboPopup
1134      * @since 1.5
1135      */
1136     public AccessibleContext getAccessibleContext() {
1137         AccessibleContext context = super.getAccessibleContext();
1138         context.setAccessibleParent(comboBox);
1139         return context;
1140     }
1141 
1142 
1143     /**
1144      * This is is a utility method that helps event handlers figure out where to
1145      * send the focus when the popup is brought up.  The standard implementation
1146      * delegates the focus to the editor (if the combo box is editable) or to
1147      * the JComboBox if it is not editable.
1148      *
1149      * @param e a mouse event
1150      */
1151     protected void delegateFocus( MouseEvent e ) {
1152         if ( comboBox.isEditable() ) {
1153             Component comp = comboBox.getEditor().getEditorComponent();
1154             if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) {
1155                 comp.requestFocus();
1156             }
1157         }
1158         else if (comboBox.isRequestFocusEnabled()) {
1159             comboBox.requestFocus();
1160         }
1161     }
1162 
1163     /**
1164      * Makes the popup visible if it is hidden and makes it hidden if it is
1165      * visible.
1166      */
1167     protected void togglePopup() {
1168         if ( isVisible() ) {
1169             hide();
1170         }
1171         else {
1172             show();
1173         }
1174     }
1175 
1176     /**
1177      * Sets the list selection index to the selectedIndex. This
1178      * method is used to synchronize the list selection with the
1179      * combo box selection.
1180      *
1181      * @param selectedIndex the index to set the list
1182      */
1183     private void setListSelection(int selectedIndex) {
1184         if ( selectedIndex == -1 ) {
1185             list.clearSelection();
1186         }
1187         else {
1188             list.setSelectedIndex( selectedIndex );
1189             list.ensureIndexIsVisible( selectedIndex );
1190         }
1191     }
1192 
1193     /**
1194      * Converts mouse event.
1195      *
1196      * @param e a mouse event
1197      * @return converted mouse event
1198      */
1199     protected MouseEvent convertMouseEvent( MouseEvent e ) {
1200         Point convertedPoint = SwingUtilities.convertPoint( (Component)e.getSource(),
1201                                                             e.getPoint(), list );
1202         MouseEvent newEvent = new MouseEvent( (Component)e.getSource(),
1203                                               e.getID(),
1204                                               e.getWhen(),
1205                                               e.getModifiers(),
1206                                               convertedPoint.x,
1207                                               convertedPoint.y,
1208                                               e.getXOnScreen(),
1209                                               e.getYOnScreen(),
1210                                               e.getClickCount(),
1211                                               e.isPopupTrigger(),
1212                                               MouseEvent.NOBUTTON );
1213         return newEvent;
1214     }
1215 
1216 
1217     /**
1218      * Retrieves the height of the popup based on the current
1219      * ListCellRenderer and the maximum row count.
1220      *
1221      * @param maxRowCount the row count
1222      * @return the height of the popup
1223      */
1224     protected int getPopupHeightForRowCount(int maxRowCount) {
1225         // Set the cached value of the minimum row count
1226         int minRowCount = Math.min( maxRowCount, comboBox.getItemCount() );
1227         int height = 0;
1228         ListCellRenderer<Object> renderer = list.getCellRenderer();
1229         Object value = null;
1230 
1231         for ( int i = 0; i < minRowCount; ++i ) {
1232             value = list.getModel().getElementAt( i );
1233             Component c = renderer.getListCellRendererComponent( list, value, i, false, false );
1234             height += c.getPreferredSize().height;
1235         }
1236 
1237         if (height == 0) {
1238             height = comboBox.getHeight();
1239         }
1240 
1241         Border border = scroller.getViewportBorder();
1242         if (border != null) {
1243             Insets insets = border.getBorderInsets(null);
1244             height += insets.top + insets.bottom;
1245         }
1246 
1247         border = scroller.getBorder();
1248         if (border != null) {
1249             Insets insets = border.getBorderInsets(null);
1250             height += insets.top + insets.bottom;
1251         }
1252 
1253         return height;
1254     }
1255 
1256     /**
1257      * Calculate the placement and size of the popup portion of the combo box based
1258      * on the combo box location and the enclosing screen bounds. If
1259      * no transformations are required, then the returned rectangle will
1260      * have the same values as the parameters.
1261      *
1262      * @param px starting x location
1263      * @param py starting y location
1264      * @param pw starting width
1265      * @param ph starting height
1266      * @return a rectangle which represents the placement and size of the popup
1267      */
1268     protected Rectangle computePopupBounds(int px,int py,int pw,int ph) {
1269         Toolkit toolkit = Toolkit.getDefaultToolkit();
1270         Rectangle screenBounds;
1271 
1272         // Calculate the desktop dimensions relative to the combo box.
1273         GraphicsConfiguration gc = comboBox.getGraphicsConfiguration();
1274         Point p = new Point();
1275         SwingUtilities.convertPointFromScreen(p, comboBox);
1276         if (gc != null) {
1277             Insets screenInsets = toolkit.getScreenInsets(gc);
1278             screenBounds = gc.getBounds();
1279             screenBounds.width -= (screenInsets.left + screenInsets.right);
1280             screenBounds.height -= (screenInsets.top + screenInsets.bottom);
1281             screenBounds.x += (p.x + screenInsets.left);
1282             screenBounds.y += (p.y + screenInsets.top);
1283         }
1284         else {
1285             screenBounds = new Rectangle(p, toolkit.getScreenSize());
1286         }
1287 
1288         Rectangle rect = new Rectangle(px,py,pw,ph);
1289         if (py+ph > screenBounds.y+screenBounds.height
1290             && ph < screenBounds.height) {
1291             rect.y = -rect.height;
1292         }
1293         return rect;
1294     }
1295 
1296     /**
1297      * Calculates the upper left location of the Popup.
1298      */
1299     private Point getPopupLocation() {
1300         Dimension popupSize = comboBox.getSize();
1301         Insets insets = getInsets();
1302 
1303         // reduce the width of the scrollpane by the insets so that the popup
1304         // is the same width as the combo box.
1305         popupSize.setSize(popupSize.width - (insets.right + insets.left),
1306                           getPopupHeightForRowCount( comboBox.getMaximumRowCount()));
1307         Rectangle popupBounds = computePopupBounds( 0, comboBox.getBounds().height,
1308                                                     popupSize.width, popupSize.height);
1309         Dimension scrollSize = popupBounds.getSize();
1310         Point popupLocation = popupBounds.getLocation();
1311 
1312         scroller.setMaximumSize( scrollSize );
1313         scroller.setPreferredSize( scrollSize );
1314         scroller.setMinimumSize( scrollSize );
1315 
1316         list.revalidate();
1317 
1318         return popupLocation;
1319     }
1320 
1321     /**
1322      * A utility method used by the event listeners.  Given a mouse event, it changes
1323      * the list selection to the list item below the mouse.
1324      *
1325      * @param anEvent a mouse event
1326      * @param shouldScroll if {@code true} list should be scrolled.
1327      */
1328     protected void updateListBoxSelectionForEvent(MouseEvent anEvent,boolean shouldScroll) {
1329         // XXX - only seems to be called from this class. shouldScroll flag is
1330         // never true
1331         Point location = anEvent.getPoint();
1332         if ( list == null )
1333             return;
1334         int index = list.locationToIndex(location);
1335         if ( index == -1 ) {
1336             if ( location.y < 0 )
1337                 index = 0;
1338             else
1339                 index = comboBox.getModel().getSize() - 1;
1340         }
1341         if ( list.getSelectedIndex() != index ) {
1342             list.setSelectedIndex(index);
1343             if ( shouldScroll )
1344                 list.ensureIndexIsVisible(index);
1345         }
1346     }
1347 
1348     //
1349     // end Utility methods
1350     //=================================================================
1351 }