View Javadoc
1   /*
2    * Copyright (c) 1998, 2013, 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 }