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