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