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