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