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