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