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