blob: f073eeda816fbc3b7ffad44c4918c6c8cd5933f2 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-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
26package javax.swing.plaf.basic;
27
28import java.awt.*;
29import java.awt.event.*;
30import javax.swing.*;
31import javax.accessibility.*;
32import javax.swing.FocusManager;
33import javax.swing.plaf.*;
34import javax.swing.border.*;
35import javax.swing.text.*;
36import javax.swing.event.*;
37import java.beans.PropertyChangeListener;
38import java.beans.PropertyChangeEvent;
39import sun.awt.AppContext;
40import sun.swing.DefaultLookup;
41import sun.swing.UIAction;
42
43/**
44 * Basic UI implementation for JComboBox.
45 * <p>
46 * The combo box is a compound component which means that it is an agregate of
47 * many simpler components. This class creates and manages the listeners
48 * on the combo box and the combo box model. These listeners update the user
49 * interface in response to changes in the properties and state of the combo box.
50 * <p>
51 * All event handling is handled by listener classes created with the
52 * <code>createxxxListener()</code> methods and internal classes.
53 * You can change the behavior of this class by overriding the
54 * <code>createxxxListener()</code> methods and supplying your own
55 * event listeners or subclassing from the ones supplied in this class.
56 * <p>
57 * For adding specific actions,
58 * overide <code>installKeyboardActions</code> to add actions in response to
59 * KeyStroke bindings. See the article <a href="http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html">Keyboard Bindings in Swing</a>
60 * at <a href="http://java.sun.com/products/jfc/tsc"><em>The Swing Connection</em></a>.
61 *
62 * @author Arnaud Weber
63 * @author Tom Santos
64 * @author Mark Davidson
65 */
66public class BasicComboBoxUI extends ComboBoxUI {
67 protected JComboBox comboBox;
68 /**
69 * This protected field is implementation specific. Do not access directly
70 * or override.
71 */
72 protected boolean hasFocus = false;
73
74 // Control the selection behavior of the JComboBox when it is used
75 // in the JTable DefaultCellEditor.
76 private boolean isTableCellEditor = false;
77 private static final String IS_TABLE_CELL_EDITOR = "JComboBox.isTableCellEditor";
78
79 // This list is for drawing the current item in the combo box.
80 protected JList listBox;
81
82 // Used to render the currently selected item in the combo box.
83 // It doesn't have anything to do with the popup's rendering.
84 protected CellRendererPane currentValuePane = new CellRendererPane();
85
86 // The implementation of ComboPopup that is used to show the popup.
87 protected ComboPopup popup;
88
89 // The Component that the ComboBoxEditor uses for editing
90 protected Component editor;
91
92 // The arrow button that invokes the popup.
93 protected JButton arrowButton;
94
95 // Listeners that are attached to the JComboBox
96 /**
97 * This protected field is implementation specific. Do not access directly
98 * or override. Override the listener construction method instead.
99 *
100 * @see #createKeyListener
101 */
102 protected KeyListener keyListener;
103 /**
104 * This protected field is implementation specific. Do not access directly
105 * or override. Override the listener construction method instead.
106 *
107 * @see #createFocusListener
108 */
109 protected FocusListener focusListener;
110 /**
111 * This protected field is implementation specific. Do not access directly
112 * or override. Override the listener construction method instead.
113 *
114 * @see #createPropertyChangeListener
115 */
116 protected PropertyChangeListener propertyChangeListener;
117
118 /**
119 * This protected field is implementation specific. Do not access directly
120 * or override. Override the listener construction method instead.
121 *
122 * @see #createItemListener
123 */
124 protected ItemListener itemListener;
125
126 // Listeners that the ComboPopup produces.
127 protected MouseListener popupMouseListener;
128 protected MouseMotionListener popupMouseMotionListener;
129 protected KeyListener popupKeyListener;
130
131 // This is used for knowing when to cache the minimum preferred size.
132 // If the data in the list changes, the cached value get marked for recalc.
133 // Added to the current JComboBox model
134 /**
135 * This protected field is implementation specific. Do not access directly
136 * or override. Override the listener construction method instead.
137 *
138 * @see #createListDataListener
139 */
140 protected ListDataListener listDataListener;
141
142 /**
143 * Implements all the Listeners needed by this class, all existing
144 * listeners redirect to it.
145 */
146 private Handler handler;
147
148 /**
149 * The time factor to treate the series of typed alphanumeric key
150 * as prefix for first letter navigation.
151 */
152 private long timeFactor = 1000L;
153
154 /**
155 * This is tricky, this variables is needed for DefaultKeySelectionManager
156 * to take into account time factor.
157 */
158 private long lastTime = 0L;
159 private long time = 0L;
160
161 /**
162 * The default key selection manager
163 */
164 JComboBox.KeySelectionManager keySelectionManager;
165
166 // Flag for recalculating the minimum preferred size.
167 protected boolean isMinimumSizeDirty = true;
168
169 // Cached minimum preferred size.
170 protected Dimension cachedMinimumSize = new Dimension( 0, 0 );
171
172 // Flag for calculating the display size
173 private boolean isDisplaySizeDirty = true;
174
175 // Cached the size that the display needs to render the largest item
176 private Dimension cachedDisplaySize = new Dimension( 0, 0 );
177
178 // Key used for lookup of the DefaultListCellRenderer in the AppContext.
179 private static final Object COMBO_UI_LIST_CELL_RENDERER_KEY =
180 new StringBuffer("DefaultListCellRendererKey");
181
182 static final StringBuffer HIDE_POPUP_KEY
183 = new StringBuffer("HidePopupKey");
184
185 /**
186 * Whether or not all cells have the same baseline.
187 */
188 private boolean sameBaseline;
189
190 // Used for calculating the default size.
191 private static ListCellRenderer getDefaultListCellRenderer() {
192 ListCellRenderer renderer = (ListCellRenderer)AppContext.
193 getAppContext().get(COMBO_UI_LIST_CELL_RENDERER_KEY);
194
195 if (renderer == null) {
196 renderer = new DefaultListCellRenderer();
197 AppContext.getAppContext().put(COMBO_UI_LIST_CELL_RENDERER_KEY,
198 new DefaultListCellRenderer());
199 }
200 return renderer;
201 }
202
203 /**
204 * Populates ComboBox's actions.
205 */
206 static void loadActionMap(LazyActionMap map) {
207 map.put(new Actions(Actions.HIDE));
208 map.put(new Actions(Actions.PAGE_DOWN));
209 map.put(new Actions(Actions.PAGE_UP));
210 map.put(new Actions(Actions.HOME));
211 map.put(new Actions(Actions.END));
212 map.put(new Actions(Actions.DOWN));
213 map.put(new Actions(Actions.DOWN_2));
214 map.put(new Actions(Actions.TOGGLE));
215 map.put(new Actions(Actions.TOGGLE_2));
216 map.put(new Actions(Actions.UP));
217 map.put(new Actions(Actions.UP_2));
218 map.put(new Actions(Actions.ENTER));
219 }
220
221 //========================
222 // begin UI Initialization
223 //
224
225 public static ComponentUI createUI(JComponent c) {
226 return new BasicComboBoxUI();
227 }
228
229 public void installUI( JComponent c ) {
230 isMinimumSizeDirty = true;
231
232 comboBox = (JComboBox)c;
233 installDefaults();
234 popup = createPopup();
235 listBox = popup.getList();
236
237 // Is this combo box a cell editor?
238 Boolean inTable = (Boolean)c.getClientProperty(IS_TABLE_CELL_EDITOR );
239 if (inTable != null) {
240 isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false;
241 }
242
243 if ( comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource ) {
244 comboBox.setRenderer( createRenderer() );
245 }
246
247 if ( comboBox.getEditor() == null || comboBox.getEditor() instanceof UIResource ) {
248 comboBox.setEditor( createEditor() );
249 }
250
251 installListeners();
252 installComponents();
253
254 comboBox.setLayout( createLayoutManager() );
255
256 comboBox.setRequestFocusEnabled( true );
257
258 installKeyboardActions();
259
260 comboBox.putClientProperty("doNotCancelPopup", HIDE_POPUP_KEY);
261
262 if (keySelectionManager == null || keySelectionManager instanceof UIResource) {
263 keySelectionManager = new DefaultKeySelectionManager();
264 }
265 comboBox.setKeySelectionManager(keySelectionManager);
266 }
267
268 public void uninstallUI( JComponent c ) {
269 setPopupVisible( comboBox, false);
270 popup.uninstallingUI();
271
272 uninstallKeyboardActions();
273
274 comboBox.setLayout( null );
275
276 uninstallComponents();
277 uninstallListeners();
278 uninstallDefaults();
279
280 if ( comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource ) {
281 comboBox.setRenderer( null );
282 }
283
284 ComboBoxEditor comboBoxEditor = comboBox.getEditor();
285 if (comboBoxEditor instanceof UIResource ) {
286 if (comboBoxEditor.getEditorComponent().hasFocus()) {
287 // Leave focus in JComboBox.
288 comboBox.requestFocusInWindow();
289 }
290 comboBox.setEditor( null );
291 }
292
293 if (keySelectionManager instanceof UIResource) {
294 comboBox.setKeySelectionManager(null);
295 }
296
297 handler = null;
298 keyListener = null;
299 focusListener = null;
300 listDataListener = null;
301 propertyChangeListener = null;
302 popup = null;
303 listBox = null;
304 comboBox = null;
305 }
306
307 /**
308 * Installs the default colors, default font, default renderer, and default
309 * editor into the JComboBox.
310 */
311 protected void installDefaults() {
312 LookAndFeel.installColorsAndFont( comboBox,
313 "ComboBox.background",
314 "ComboBox.foreground",
315 "ComboBox.font" );
316 LookAndFeel.installBorder( comboBox, "ComboBox.border" );
317 LookAndFeel.installProperty( comboBox, "opaque", Boolean.TRUE);
318
319 Long l = (Long)UIManager.get("ComboBox.timeFactor");
320 timeFactor = (l!=null) ? l.longValue() : 1000L;
321 }
322
323 /**
324 * Create and install the listeners for the combo box and its model.
325 * This method is called when the UI is installed.
326 */
327 protected void installListeners() {
328 if ( (itemListener = createItemListener()) != null) {
329 comboBox.addItemListener( itemListener );
330 }
331 if ( (propertyChangeListener = createPropertyChangeListener()) != null ) {
332 comboBox.addPropertyChangeListener( propertyChangeListener );
333 }
334 if ( (keyListener = createKeyListener()) != null ) {
335 comboBox.addKeyListener( keyListener );
336 }
337 if ( (focusListener = createFocusListener()) != null ) {
338 comboBox.addFocusListener( focusListener );
339 }
340 if ((popupMouseListener = popup.getMouseListener()) != null) {
341 comboBox.addMouseListener( popupMouseListener );
342 }
343 if ((popupMouseMotionListener = popup.getMouseMotionListener()) != null) {
344 comboBox.addMouseMotionListener( popupMouseMotionListener );
345 }
346 if ((popupKeyListener = popup.getKeyListener()) != null) {
347 comboBox.addKeyListener(popupKeyListener);
348 }
349
350 if ( comboBox.getModel() != null ) {
351 if ( (listDataListener = createListDataListener()) != null ) {
352 comboBox.getModel().addListDataListener( listDataListener );
353 }
354 }
355 }
356
357 /**
358 * Uninstalls the default colors, default font, default renderer, and default
359 * editor into the JComboBox.
360 */
361 protected void uninstallDefaults() {
362 LookAndFeel.installColorsAndFont( comboBox,
363 "ComboBox.background",
364 "ComboBox.foreground",
365 "ComboBox.font" );
366 LookAndFeel.uninstallBorder( comboBox );
367 }
368
369 /**
370 * Remove the installed listeners from the combo box and its model.
371 * The number and types of listeners removed and in this method should be
372 * the same that was added in <code>installListeners</code>
373 */
374 protected void uninstallListeners() {
375 if ( keyListener != null ) {
376 comboBox.removeKeyListener( keyListener );
377 }
378 if ( itemListener != null) {
379 comboBox.removeItemListener( itemListener );
380 }
381 if ( propertyChangeListener != null ) {
382 comboBox.removePropertyChangeListener( propertyChangeListener );
383 }
384 if ( focusListener != null) {
385 comboBox.removeFocusListener( focusListener );
386 }
387 if ( popupMouseListener != null) {
388 comboBox.removeMouseListener( popupMouseListener );
389 }
390 if ( popupMouseMotionListener != null) {
391 comboBox.removeMouseMotionListener( popupMouseMotionListener );
392 }
393 if (popupKeyListener != null) {
394 comboBox.removeKeyListener(popupKeyListener);
395 }
396 if ( comboBox.getModel() != null ) {
397 if ( listDataListener != null ) {
398 comboBox.getModel().removeListDataListener( listDataListener );
399 }
400 }
401 }
402
403 /**
404 * Creates the popup portion of the combo box.
405 *
406 * @return an instance of <code>ComboPopup</code>
407 * @see ComboPopup
408 */
409 protected ComboPopup createPopup() {
410 return new BasicComboPopup( comboBox );
411 }
412
413 /**
414 * Creates a <code>KeyListener</code> which will be added to the
415 * combo box. If this method returns null then it will not be added
416 * to the combo box.
417 *
418 * @return an instance <code>KeyListener</code> or null
419 */
420 protected KeyListener createKeyListener() {
421 return getHandler();
422 }
423
424 /**
425 * Creates a <code>FocusListener</code> which will be added to the combo box.
426 * If this method returns null then it will not be added to the combo box.
427 *
428 * @return an instance of a <code>FocusListener</code> or null
429 */
430 protected FocusListener createFocusListener() {
431 return getHandler();
432 }
433
434 /**
435 * Creates a list data listener which will be added to the
436 * <code>ComboBoxModel</code>. If this method returns null then
437 * it will not be added to the combo box model.
438 *
439 * @return an instance of a <code>ListDataListener</code> or null
440 */
441 protected ListDataListener createListDataListener() {
442 return getHandler();
443 }
444
445 /**
446 * Creates an <code>ItemListener</code> which will be added to the
447 * combo box. If this method returns null then it will not
448 * be added to the combo box.
449 * <p>
450 * Subclasses may override this method to return instances of their own
451 * ItemEvent handlers.
452 *
453 * @return an instance of an <code>ItemListener</code> or null
454 */
455 protected ItemListener createItemListener() {
456 return null;
457 }
458
459 /**
460 * Creates a <code>PropertyChangeListener</code> which will be added to
461 * the combo box. If this method returns null then it will not
462 * be added to the combo box.
463 *
464 * @return an instance of a <code>PropertyChangeListener</code> or null
465 */
466 protected PropertyChangeListener createPropertyChangeListener() {
467 return getHandler();
468 }
469
470 /**
471 * Creates a layout manager for managing the components which make up the
472 * combo box.
473 *
474 * @return an instance of a layout manager
475 */
476 protected LayoutManager createLayoutManager() {
477 return getHandler();
478 }
479
480 /**
481 * Creates the default renderer that will be used in a non-editiable combo
482 * box. A default renderer will used only if a renderer has not been
483 * explicitly set with <code>setRenderer</code>.
484 *
485 * @return a <code>ListCellRender</code> used for the combo box
486 * @see javax.swing.JComboBox#setRenderer
487 */
488 protected ListCellRenderer createRenderer() {
489 return new BasicComboBoxRenderer.UIResource();
490 }
491
492 /**
493 * Creates the default editor that will be used in editable combo boxes.
494 * A default editor will be used only if an editor has not been
495 * explicitly set with <code>setEditor</code>.
496 *
497 * @return a <code>ComboBoxEditor</code> used for the combo box
498 * @see javax.swing.JComboBox#setEditor
499 */
500 protected ComboBoxEditor createEditor() {
501 return new BasicComboBoxEditor.UIResource();
502 }
503
504 /**
505 * Returns the shared listener.
506 */
507 private Handler getHandler() {
508 if (handler == null) {
509 handler = new Handler();
510 }
511 return handler;
512 }
513
514 //
515 // end UI Initialization
516 //======================
517
518
519 //======================
520 // begin Inner classes
521 //
522
523 /**
524 * This listener checks to see if the key event isn't a navigation key. If
525 * it finds a key event that wasn't a navigation key it dispatches it to
526 * JComboBox.selectWithKeyChar() so that it can do type-ahead.
527 *
528 * This public inner class should be treated as protected.
529 * Instantiate it only within subclasses of
530 * <code>BasicComboBoxUI</code>.
531 */
532 public class KeyHandler extends KeyAdapter {
533 public void keyPressed( KeyEvent e ) {
534 getHandler().keyPressed(e);
535 }
536 }
537
538 /**
539 * This listener hides the popup when the focus is lost. It also repaints
540 * when focus is gained or lost.
541 *
542 * This public inner class should be treated as protected.
543 * Instantiate it only within subclasses of
544 * <code>BasicComboBoxUI</code>.
545 */
546 public class FocusHandler implements FocusListener {
547 public void focusGained( FocusEvent e ) {
548 getHandler().focusGained(e);
549 }
550
551 public void focusLost( FocusEvent e ) {
552 getHandler().focusLost(e);
553 }
554 }
555
556 /**
557 * This listener watches for changes in the
558 * <code>ComboBoxModel</code>.
559 * <p>
560 * This public inner class should be treated as protected.
561 * Instantiate it only within subclasses of
562 * <code>BasicComboBoxUI</code>.
563 *
564 * @see #createListDataListener
565 */
566 public class ListDataHandler implements ListDataListener {
567 public void contentsChanged( ListDataEvent e ) {
568 getHandler().contentsChanged(e);
569 }
570
571 public void intervalAdded( ListDataEvent e ) {
572 getHandler().intervalAdded(e);
573 }
574
575 public void intervalRemoved( ListDataEvent e ) {
576 getHandler().intervalRemoved(e);
577 }
578 }
579
580 /**
581 * This listener watches for changes to the selection in the
582 * combo box.
583 * <p>
584 * This public inner class should be treated as protected.
585 * Instantiate it only within subclasses of
586 * <code>BasicComboBoxUI</code>.
587 *
588 * @see #createItemListener
589 */
590 public class ItemHandler implements ItemListener {
591 // This class used to implement behavior which is now redundant.
592 public void itemStateChanged(ItemEvent e) {}
593 }
594
595 /**
596 * This listener watches for bound properties that have changed in the
597 * combo box.
598 * <p>
599 * Subclasses which wish to listen to combo box property changes should
600 * call the superclass methods to ensure that the combo box ui correctly
601 * handles property changes.
602 * <p>
603 * This public inner class should be treated as protected.
604 * Instantiate it only within subclasses of
605 * <code>BasicComboBoxUI</code>.
606 *
607 * @see #createPropertyChangeListener
608 */
609 public class PropertyChangeHandler implements PropertyChangeListener {
610 public void propertyChange(PropertyChangeEvent e) {
611 getHandler().propertyChange(e);
612 }
613 }
614
615
616 // Syncronizes the ToolTip text for the components within the combo box to be the
617 // same value as the combo box ToolTip text.
618 private void updateToolTipTextForChildren() {
619 Component[] children = comboBox.getComponents();
620 for ( int i = 0; i < children.length; ++i ) {
621 if ( children[i] instanceof JComponent ) {
622 ((JComponent)children[i]).setToolTipText( comboBox.getToolTipText() );
623 }
624 }
625 }
626
627 /**
628 * This layout manager handles the 'standard' layout of combo boxes. It puts
629 * the arrow button to the right and the editor to the left. If there is no
630 * editor it still keeps the arrow button to the right.
631 *
632 * This public inner class should be treated as protected.
633 * Instantiate it only within subclasses of
634 * <code>BasicComboBoxUI</code>.
635 */
636 public class ComboBoxLayoutManager implements LayoutManager {
637 public void addLayoutComponent(String name, Component comp) {}
638
639 public void removeLayoutComponent(Component comp) {}
640
641 public Dimension preferredLayoutSize(Container parent) {
642 return getHandler().preferredLayoutSize(parent);
643 }
644
645 public Dimension minimumLayoutSize(Container parent) {
646 return getHandler().minimumLayoutSize(parent);
647 }
648
649 public void layoutContainer(Container parent) {
650 getHandler().layoutContainer(parent);
651 }
652 }
653
654 //
655 // end Inner classes
656 //====================
657
658
659 //===============================
660 // begin Sub-Component Management
661 //
662
663 /**
664 * Creates and initializes the components which make up the
665 * aggregate combo box. This method is called as part of the UI
666 * installation process.
667 */
668 protected void installComponents() {
669 arrowButton = createArrowButton();
670 comboBox.add( arrowButton );
671
672 if (arrowButton != null) {
673 configureArrowButton();
674 }
675
676 if ( comboBox.isEditable() ) {
677 addEditor();
678 }
679
680 comboBox.add( currentValuePane );
681 }
682
683 /**
684 * The aggregate components which compise the combo box are
685 * unregistered and uninitialized. This method is called as part of the
686 * UI uninstallation process.
687 */
688 protected void uninstallComponents() {
689 if ( arrowButton != null ) {
690 unconfigureArrowButton();
691 }
692 if ( editor != null ) {
693 unconfigureEditor();
694 }
695 comboBox.removeAll(); // Just to be safe.
696 arrowButton = null;
697 }
698
699 /**
700 * This public method is implementation specific and should be private.
701 * do not call or override. To implement a specific editor create a
702 * custom <code>ComboBoxEditor</code>
703 *
704 * @see #createEditor
705 * @see javax.swing.JComboBox#setEditor
706 * @see javax.swing.ComboBoxEditor
707 */
708 public void addEditor() {
709 removeEditor();
710 editor = comboBox.getEditor().getEditorComponent();
711 if ( editor != null ) {
712 configureEditor();
713 comboBox.add(editor);
714 if(comboBox.isFocusOwner()) {
715 // Switch focus to the editor component
716 editor.requestFocusInWindow();
717 }
718 }
719 }
720
721 /**
722 * This public method is implementation specific and should be private.
723 * do not call or override.
724 *
725 * @see #addEditor
726 */
727 public void removeEditor() {
728 if ( editor != null ) {
729 unconfigureEditor();
730 comboBox.remove( editor );
731 editor = null;
732 }
733 }
734
735 /**
736 * This protected method is implementation specific and should be private.
737 * do not call or override.
738 *
739 * @see #addEditor
740 */
741 protected void configureEditor() {
742 // Should be in the same state as the combobox
743 editor.setEnabled(comboBox.isEnabled());
744
745 editor.setFocusable(comboBox.isFocusable());
746
747 editor.setFont( comboBox.getFont() );
748
749 if (focusListener != null) {
750 editor.addFocusListener(focusListener);
751 }
752
753 editor.addFocusListener( getHandler() );
754
755 comboBox.getEditor().addActionListener(getHandler());
756
757 if(editor instanceof JComponent) {
758 ((JComponent)editor).putClientProperty("doNotCancelPopup",
759 HIDE_POPUP_KEY);
760 ((JComponent)editor).setInheritsPopupMenu(true);
761 }
762
763 comboBox.configureEditor(comboBox.getEditor(),comboBox.getSelectedItem());
764 }
765
766 /**
767 * This protected method is implementation specific and should be private.
768 * Do not call or override.
769 *
770 * @see #addEditor
771 */
772 protected void unconfigureEditor() {
773 if (focusListener != null) {
774 editor.removeFocusListener(focusListener);
775 }
776
777 editor.removeFocusListener(getHandler());
778 comboBox.getEditor().removeActionListener(getHandler());
779 }
780
781 /**
782 * This public method is implementation specific and should be private. Do
783 * not call or override.
784 *
785 * @see #createArrowButton
786 */
787 public void configureArrowButton() {
788 if ( arrowButton != null ) {
789 arrowButton.setEnabled( comboBox.isEnabled() );
790 arrowButton.setFocusable(comboBox.isFocusable());
791 arrowButton.setRequestFocusEnabled(false);
792 arrowButton.addMouseListener( popup.getMouseListener() );
793 arrowButton.addMouseMotionListener( popup.getMouseMotionListener() );
794 arrowButton.resetKeyboardActions();
795 arrowButton.putClientProperty("doNotCancelPopup", HIDE_POPUP_KEY);
796 arrowButton.setInheritsPopupMenu(true);
797 }
798 }
799
800 /**
801 * This public method is implementation specific and should be private. Do
802 * not call or override.
803 *
804 * @see #createArrowButton
805 */
806 public void unconfigureArrowButton() {
807 if ( arrowButton != null ) {
808 arrowButton.removeMouseListener( popup.getMouseListener() );
809 arrowButton.removeMouseMotionListener( popup.getMouseMotionListener() );
810 }
811 }
812
813 /**
814 * Creates an button which will be used as the control to show or hide
815 * the popup portion of the combo box.
816 *
817 * @return a button which represents the popup control
818 */
819 protected JButton createArrowButton() {
820 JButton button = new BasicArrowButton(BasicArrowButton.SOUTH,
821 UIManager.getColor("ComboBox.buttonBackground"),
822 UIManager.getColor("ComboBox.buttonShadow"),
823 UIManager.getColor("ComboBox.buttonDarkShadow"),
824 UIManager.getColor("ComboBox.buttonHighlight"));
825 button.setName("ComboBox.arrowButton");
826 return button;
827 }
828
829 //
830 // end Sub-Component Management
831 //===============================
832
833
834 //================================
835 // begin ComboBoxUI Implementation
836 //
837
838 /**
839 * Tells if the popup is visible or not.
840 */
841 public boolean isPopupVisible( JComboBox c ) {
842 return popup.isVisible();
843 }
844
845 /**
846 * Hides the popup.
847 */
848 public void setPopupVisible( JComboBox c, boolean v ) {
849 if ( v ) {
850 popup.show();
851 } else {
852 popup.hide();
853 }
854 }
855
856 /**
857 * Determines if the JComboBox is focus traversable. If the JComboBox is editable
858 * this returns false, otherwise it returns true.
859 */
860 public boolean isFocusTraversable( JComboBox c ) {
861 return !comboBox.isEditable();
862 }
863
864 //
865 // end ComboBoxUI Implementation
866 //==============================
867
868
869 //=================================
870 // begin ComponentUI Implementation
871
872 public void paint( Graphics g, JComponent c ) {
873 hasFocus = comboBox.hasFocus();
874 if ( !comboBox.isEditable() ) {
875 Rectangle r = rectangleForCurrentValue();
876 paintCurrentValueBackground(g,r,hasFocus);
877 paintCurrentValue(g,r,hasFocus);
878 }
879 }
880
881 public Dimension getPreferredSize( JComponent c ) {
882 return getMinimumSize(c);
883 }
884
885 /**
886 * The minumum size is the size of the display area plus insets plus the button.
887 */
888 public Dimension getMinimumSize( JComponent c ) {
889 if ( !isMinimumSizeDirty ) {
890 return new Dimension(cachedMinimumSize);
891 }
892 Dimension size = getDisplaySize();
893 Insets insets = getInsets();
894 size.height += insets.top + insets.bottom;
895 int buttonSize = size.height - (insets.top + insets.bottom);
896 size.width += insets.left + insets.right + buttonSize;
897
898 cachedMinimumSize.setSize( size.width, size.height );
899 isMinimumSizeDirty = false;
900
901 return new Dimension(size);
902 }
903
904 public Dimension getMaximumSize( JComponent c ) {
905 return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
906 }
907
908 /**
909 * Returns the baseline.
910 *
911 * @throws NullPointerException {@inheritDoc}
912 * @throws IllegalArgumentException {@inheritDoc}
913 * @see javax.swing.JComponent#getBaseline(int, int)
914 * @since 1.6
915 */
916 public int getBaseline(JComponent c, int width, int height) {
917 super.getBaseline(c, width, height);
918 int baseline = -1;
919 // force sameBaseline to be updated.
920 getDisplaySize();
921 if (sameBaseline) {
922 Insets insets = c.getInsets();
923 height = height - insets.top - insets.bottom;
924 if (!comboBox.isEditable()) {
925 ListCellRenderer renderer = comboBox.getRenderer();
926 if (renderer == null) {
927 renderer = new DefaultListCellRenderer();
928 }
929 Object value = null;
930 Object prototypeValue = comboBox.getPrototypeDisplayValue();
931 if (prototypeValue != null) {
932 value = prototypeValue;
933 }
934 else if (comboBox.getModel().getSize() > 0) {
935 // Note, we're assuming the baseline is the same for all
936 // cells, if not, this needs to loop through all.
937 value = comboBox.getModel().getElementAt(0);
938 }
939 if (value == null) {
940 value = " ";
941 } else if (value instanceof String && "".equals(value)) {
942 value = " ";
943 }
944 Component component = renderer.
945 getListCellRendererComponent(listBox, value, -1,
946 false, false);
947 if (component instanceof JComponent) {
948 component.setFont(comboBox.getFont());
949 }
950 baseline = component.getBaseline(width, height);
951 }
952 else {
953 baseline = editor.getBaseline(width, height);
954 }
955 if (baseline > 0) {
956 baseline += insets.top;
957 }
958 }
959 return baseline;
960 }
961
962 /**
963 * Returns an enum indicating how the baseline of the component
964 * changes as the size changes.
965 *
966 * @throws NullPointerException {@inheritDoc}
967 * @see javax.swing.JComponent#getBaseline(int, int)
968 * @since 1.6
969 */
970 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
971 JComponent c) {
972 super.getBaselineResizeBehavior(c);
973 // Force sameBaseline to be updated.
974 getDisplaySize();
975 if (comboBox.isEditable()) {
976 return editor.getBaselineResizeBehavior();
977 }
978 else if (sameBaseline) {
979 ListCellRenderer renderer = comboBox.getRenderer();
980 if (renderer == null) {
981 renderer = new DefaultListCellRenderer();
982 }
983 Object value = null;
984 Object prototypeValue = comboBox.getPrototypeDisplayValue();
985 if (prototypeValue != null) {
986 value = prototypeValue;
987 }
988 else if (comboBox.getModel().getSize() > 0) {
989 // Note, we're assuming the baseline is the same for all
990 // cells, if not, this needs to loop through all.
991 value = comboBox.getModel().getElementAt(0);
992 }
993 if (value != null) {
994 Component component = renderer.
995 getListCellRendererComponent(listBox, value, -1,
996 false, false);
997 return component.getBaselineResizeBehavior();
998 }
999 }
1000 return Component.BaselineResizeBehavior.OTHER;
1001 }
1002
1003 // This is currently hacky...
1004 public int getAccessibleChildrenCount(JComponent c) {
1005 if ( comboBox.isEditable() ) {
1006 return 2;
1007 }
1008 else {
1009 return 1;
1010 }
1011 }
1012
1013 // This is currently hacky...
1014 public Accessible getAccessibleChild(JComponent c, int i) {
1015 // 0 = the popup
1016 // 1 = the editor
1017 switch ( i ) {
1018 case 0:
1019 if ( popup instanceof Accessible ) {
1020 AccessibleContext ac = ((Accessible) popup).getAccessibleContext();
1021 ac.setAccessibleParent(comboBox);
1022 return(Accessible) popup;
1023 }
1024 break;
1025 case 1:
1026 if ( comboBox.isEditable()
1027 && (editor instanceof Accessible) ) {
1028 AccessibleContext ac = ((Accessible) editor).getAccessibleContext();
1029 ac.setAccessibleParent(comboBox);
1030 return(Accessible) editor;
1031 }
1032 break;
1033 }
1034 return null;
1035 }
1036
1037 //
1038 // end ComponentUI Implementation
1039 //===============================
1040
1041
1042 //======================
1043 // begin Utility Methods
1044 //
1045
1046 /**
1047 * Returns whether or not the supplied keyCode maps to a key that is used for
1048 * navigation. This is used for optimizing key input by only passing non-
1049 * navigation keys to the type-ahead mechanism. Subclasses should override this
1050 * if they change the navigation keys.
1051 */
1052 protected boolean isNavigationKey( int keyCode ) {
1053 return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN ||
1054 keyCode == KeyEvent.VK_KP_UP || keyCode == KeyEvent.VK_KP_DOWN;
1055 }
1056
1057 private boolean isNavigationKey(int keyCode, int modifiers) {
1058 InputMap inputMap = comboBox.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1059 KeyStroke key = KeyStroke.getKeyStroke(keyCode, modifiers);
1060
1061 if (inputMap != null && inputMap.get(key) != null) {
1062 return true;
1063 }
1064 return false;
1065 }
1066
1067 /**
1068 * Selects the next item in the list. It won't change the selection if the
1069 * currently selected item is already the last item.
1070 */
1071 protected void selectNextPossibleValue() {
1072 int si;
1073
1074 if ( comboBox.isPopupVisible() ) {
1075 si = listBox.getSelectedIndex();
1076 }
1077 else {
1078 si = comboBox.getSelectedIndex();
1079 }
1080
1081 if ( si < comboBox.getModel().getSize() - 1 ) {
1082 listBox.setSelectedIndex( si + 1 );
1083 listBox.ensureIndexIsVisible( si + 1 );
1084 if ( !isTableCellEditor ) {
1085 comboBox.setSelectedIndex(si+1);
1086 }
1087 comboBox.repaint();
1088 }
1089 }
1090
1091 /**
1092 * Selects the previous item in the list. It won't change the selection if the
1093 * currently selected item is already the first item.
1094 */
1095 protected void selectPreviousPossibleValue() {
1096 int si;
1097
1098 if ( comboBox.isPopupVisible() ) {
1099 si = listBox.getSelectedIndex();
1100 }
1101 else {
1102 si = comboBox.getSelectedIndex();
1103 }
1104
1105 if ( si > 0 ) {
1106 listBox.setSelectedIndex( si - 1 );
1107 listBox.ensureIndexIsVisible( si - 1 );
1108 if ( !isTableCellEditor ) {
1109 comboBox.setSelectedIndex(si-1);
1110 }
1111 comboBox.repaint();
1112 }
1113 }
1114
1115 /**
1116 * Hides the popup if it is showing and shows the popup if it is hidden.
1117 */
1118 protected void toggleOpenClose() {
1119 setPopupVisible(comboBox, !isPopupVisible(comboBox));
1120 }
1121
1122 /**
1123 * Returns the area that is reserved for drawing the currently selected item.
1124 */
1125 protected Rectangle rectangleForCurrentValue() {
1126 int width = comboBox.getWidth();
1127 int height = comboBox.getHeight();
1128 Insets insets = getInsets();
1129 int buttonSize = height - (insets.top + insets.bottom);
1130 if ( arrowButton != null ) {
1131 buttonSize = arrowButton.getWidth();
1132 }
1133 if(BasicGraphicsUtils.isLeftToRight(comboBox)) {
1134 return new Rectangle(insets.left, insets.top,
1135 width - (insets.left + insets.right + buttonSize),
1136 height - (insets.top + insets.bottom));
1137 }
1138 else {
1139 return new Rectangle(insets.left + buttonSize, insets.top,
1140 width - (insets.left + insets.right + buttonSize),
1141 height - (insets.top + insets.bottom));
1142 }
1143 }
1144
1145 /**
1146 * Gets the insets from the JComboBox.
1147 */
1148 protected Insets getInsets() {
1149 return comboBox.getInsets();
1150 }
1151
1152 //
1153 // end Utility Methods
1154 //====================
1155
1156
1157 //===============================
1158 // begin Painting Utility Methods
1159 //
1160
1161 /**
1162 * Paints the currently selected item.
1163 */
1164 public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) {
1165 ListCellRenderer renderer = comboBox.getRenderer();
1166 Component c;
1167
1168 if ( hasFocus && !isPopupVisible(comboBox) ) {
1169 c = renderer.getListCellRendererComponent( listBox,
1170 comboBox.getSelectedItem(),
1171 -1,
1172 true,
1173 false );
1174 }
1175 else {
1176 c = renderer.getListCellRendererComponent( listBox,
1177 comboBox.getSelectedItem(),
1178 -1,
1179 false,
1180 false );
1181 c.setBackground(UIManager.getColor("ComboBox.background"));
1182 }
1183 c.setFont(comboBox.getFont());
1184 if ( hasFocus && !isPopupVisible(comboBox) ) {
1185 c.setForeground(listBox.getSelectionForeground());
1186 c.setBackground(listBox.getSelectionBackground());
1187 }
1188 else {
1189 if ( comboBox.isEnabled() ) {
1190 c.setForeground(comboBox.getForeground());
1191 c.setBackground(comboBox.getBackground());
1192 }
1193 else {
1194 c.setForeground(DefaultLookup.getColor(
1195 comboBox, this, "ComboBox.disabledForeground", null));
1196 c.setBackground(DefaultLookup.getColor(
1197 comboBox, this, "ComboBox.disabledBackground", null));
1198 }
1199 }
1200
1201 // Fix for 4238829: should lay out the JPanel.
1202 boolean shouldValidate = false;
1203 if (c instanceof JPanel) {
1204 shouldValidate = true;
1205 }
1206
1207 currentValuePane.paintComponent(g,c,comboBox,bounds.x,bounds.y,
1208 bounds.width,bounds.height, shouldValidate);
1209 }
1210
1211 /**
1212 * Paints the background of the currently selected item.
1213 */
1214 public void paintCurrentValueBackground(Graphics g,Rectangle bounds,boolean hasFocus) {
1215 Color t = g.getColor();
1216 if ( comboBox.isEnabled() )
1217 g.setColor(DefaultLookup.getColor(comboBox, this,
1218 "ComboBox.background", null));
1219 else
1220 g.setColor(DefaultLookup.getColor(comboBox, this,
1221 "ComboBox.disabledBackground", null));
1222 g.fillRect(bounds.x,bounds.y,bounds.width,bounds.height);
1223 g.setColor(t);
1224 }
1225
1226 /**
1227 * Repaint the currently selected item.
1228 */
1229 void repaintCurrentValue() {
1230 Rectangle r = rectangleForCurrentValue();
1231 comboBox.repaint(r.x,r.y,r.width,r.height);
1232 }
1233
1234 //
1235 // end Painting Utility Methods
1236 //=============================
1237
1238
1239 //===============================
1240 // begin Size Utility Methods
1241 //
1242
1243 /**
1244 * Return the default size of an empty display area of the combo box using
1245 * the current renderer and font.
1246 *
1247 * @return the size of an empty display area
1248 * @see #getDisplaySize
1249 */
1250 protected Dimension getDefaultSize() {
1251 // Calculates the height and width using the default text renderer
1252 Dimension d = getSizeForComponent(getDefaultListCellRenderer().getListCellRendererComponent(listBox, " ", -1, false, false));
1253
1254 return new Dimension(d.width, d.height);
1255 }
1256
1257 /**
1258 * Returns the calculated size of the display area. The display area is the
1259 * portion of the combo box in which the selected item is displayed. This
1260 * method will use the prototype display value if it has been set.
1261 * <p>
1262 * For combo boxes with a non trivial number of items, it is recommended to
1263 * use a prototype display value to significantly speed up the display
1264 * size calculation.
1265 *
1266 * @return the size of the display area calculated from the combo box items
1267 * @see javax.swing.JComboBox#setPrototypeDisplayValue
1268 */
1269 protected Dimension getDisplaySize() {
1270 if (!isDisplaySizeDirty) {
1271 return new Dimension(cachedDisplaySize);
1272 }
1273 Dimension result = new Dimension();
1274
1275 ListCellRenderer renderer = comboBox.getRenderer();
1276 if (renderer == null) {
1277 renderer = new DefaultListCellRenderer();
1278 }
1279
1280 sameBaseline = true;
1281
1282 Object prototypeValue = comboBox.getPrototypeDisplayValue();
1283 if (prototypeValue != null) {
1284 // Calculates the dimension based on the prototype value
1285 result = getSizeForComponent(renderer.getListCellRendererComponent(listBox,
1286 prototypeValue,
1287 -1, false, false));
1288 } else {
1289 // Calculate the dimension by iterating over all the elements in the combo
1290 // box list.
1291 ComboBoxModel model = comboBox.getModel();
1292 int modelSize = model.getSize();
1293 int baseline = -1;
1294 Dimension d;
1295
1296 Component cpn;
1297
1298 if (modelSize > 0 ) {
1299 for (int i = 0; i < modelSize ; i++ ) {
1300 // Calculates the maximum height and width based on the largest
1301 // element
1302 Object value = model.getElementAt(i);
1303 Component c = renderer.getListCellRendererComponent(
1304 listBox, value, -1, false, false);
1305 d = getSizeForComponent(c);
1306 if (sameBaseline && value != null &&
1307 (!(value instanceof String) || !"".equals(value))) {
1308 int newBaseline = c.getBaseline(d.width, d.height);
1309 if (newBaseline == -1) {
1310 sameBaseline = false;
1311 }
1312 else if (baseline == -1) {
1313 baseline = newBaseline;
1314 }
1315 else if (baseline != newBaseline) {
1316 sameBaseline = false;
1317 }
1318 }
1319 result.width = Math.max(result.width,d.width);
1320 result.height = Math.max(result.height,d.height);
1321 }
1322 } else {
1323 result = getDefaultSize();
1324 if (comboBox.isEditable()) {
1325 result.width = 100;
1326 }
1327 }
1328 }
1329
1330 if ( comboBox.isEditable() ) {
1331 Dimension d = editor.getPreferredSize();
1332 result.width = Math.max(result.width,d.width);
1333 result.height = Math.max(result.height,d.height);
1334 }
1335
1336 // Set the cached value
1337 cachedDisplaySize.setSize(result.width, result.height);
1338 isDisplaySizeDirty = false;
1339
1340 return result;
1341 }
1342
1343 /**
1344 * This has been refactored out in hopes that it may be investigated and
1345 * simplified for the next major release. adding/removing
1346 * the component to the currentValuePane and changing the font may be
1347 * redundant operations.
1348 */
1349 private Dimension getSizeForComponent(Component comp) {
1350 currentValuePane.add(comp);
1351 comp.setFont(comboBox.getFont());
1352 Dimension d = comp.getPreferredSize();
1353 currentValuePane.remove(comp);
1354 return d;
1355 }
1356
1357
1358 //
1359 // end Size Utility Methods
1360 //=============================
1361
1362
1363 //=================================
1364 // begin Keyboard Action Management
1365 //
1366
1367 /**
1368 * Adds keyboard actions to the JComboBox. Actions on enter and esc are already
1369 * supplied. Add more actions as you need them.
1370 */
1371 protected void installKeyboardActions() {
1372 InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1373 SwingUtilities.replaceUIInputMap(comboBox, JComponent.
1374 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km);
1375
1376
1377 LazyActionMap.installLazyActionMap(comboBox, BasicComboBoxUI.class,
1378 "ComboBox.actionMap");
1379 }
1380
1381 InputMap getInputMap(int condition) {
1382 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
1383 return (InputMap)DefaultLookup.get(comboBox, this,
1384 "ComboBox.ancestorInputMap");
1385 }
1386 return null;
1387 }
1388
1389 boolean isTableCellEditor() {
1390 return isTableCellEditor;
1391 }
1392
1393 /**
1394 * Removes the focus InputMap and ActionMap.
1395 */
1396 protected void uninstallKeyboardActions() {
1397 SwingUtilities.replaceUIInputMap(comboBox, JComponent.
1398 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1399 SwingUtilities.replaceUIActionMap(comboBox, null);
1400 }
1401
1402
1403 //
1404 // Actions
1405 //
1406 private static class Actions extends UIAction {
1407 private static final String HIDE = "hidePopup";
1408 private static final String DOWN = "selectNext";
1409 private static final String DOWN_2 = "selectNext2";
1410 private static final String TOGGLE = "togglePopup";
1411 private static final String TOGGLE_2 = "spacePopup";
1412 private static final String UP = "selectPrevious";
1413 private static final String UP_2 = "selectPrevious2";
1414 private static final String ENTER = "enterPressed";
1415 private static final String PAGE_DOWN = "pageDownPassThrough";
1416 private static final String PAGE_UP = "pageUpPassThrough";
1417 private static final String HOME = "homePassThrough";
1418 private static final String END = "endPassThrough";
1419
1420 Actions(String name) {
1421 super(name);
1422 }
1423
1424 public void actionPerformed( ActionEvent e ) {
1425 String key = getName();
1426 JComboBox comboBox = (JComboBox)e.getSource();
1427 BasicComboBoxUI ui = (BasicComboBoxUI)BasicLookAndFeel.getUIOfType(
1428 comboBox.getUI(), BasicComboBoxUI.class);
1429 if (key == HIDE) {
1430 comboBox.firePopupMenuCanceled();
1431 comboBox.setPopupVisible(false);
1432 }
1433 else if (key == PAGE_DOWN || key == PAGE_UP ||
1434 key == HOME || key == END) {
1435 int index = getNextIndex(comboBox, key);
1436 if (index >= 0 && index < comboBox.getItemCount()) {
1437 comboBox.setSelectedIndex(index);
1438 }
1439 }
1440 else if (key == DOWN) {
1441 if (comboBox.isShowing() ) {
1442 if ( comboBox.isPopupVisible() ) {
1443 if (ui != null) {
1444 ui.selectNextPossibleValue();
1445 }
1446 } else {
1447 comboBox.setPopupVisible(true);
1448 }
1449 }
1450 }
1451 else if (key == DOWN_2) {
1452 // Special case in which pressing the arrow keys will not
1453 // make the popup appear - except for editable combo boxes
1454 // and combo boxes inside a table.
1455 if (comboBox.isShowing() ) {
1456 if ( (comboBox.isEditable() ||
1457 (ui != null && ui.isTableCellEditor()))
1458 && !comboBox.isPopupVisible() ) {
1459 comboBox.setPopupVisible(true);
1460 } else {
1461 if (ui != null) {
1462 ui.selectNextPossibleValue();
1463 }
1464 }
1465 }
1466 }
1467 else if (key == TOGGLE || key == TOGGLE_2) {
1468 if (ui != null && (key == TOGGLE || !comboBox.isEditable())) {
1469 if ( ui.isTableCellEditor() ) {
1470 // Forces the selection of the list item if the
1471 // combo box is in a JTable.
1472 comboBox.setSelectedIndex(ui.popup.getList().
1473 getSelectedIndex());
1474 }
1475 else {
1476 comboBox.setPopupVisible(!comboBox.isPopupVisible());
1477 }
1478 }
1479 }
1480 else if (key == UP) {
1481 if (ui != null) {
1482 if (ui.isPopupVisible(comboBox)) {
1483 ui.selectPreviousPossibleValue();
1484 }
1485 else if (DefaultLookup.getBoolean(comboBox, ui,
1486 "ComboBox.showPopupOnNavigation", false)) {
1487 ui.setPopupVisible(comboBox, true);
1488 }
1489 }
1490 }
1491 else if (key == UP_2) {
1492 // Special case in which pressing the arrow keys will not
1493 // make the popup appear - except for editable combo boxes.
1494 if (comboBox.isShowing() && ui != null) {
1495 if ( comboBox.isEditable() && !comboBox.isPopupVisible()) {
1496 comboBox.setPopupVisible(true);
1497 } else {
1498 ui.selectPreviousPossibleValue();
1499 }
1500 }
1501 }
1502
1503 else if (key == ENTER) {
1504 if (comboBox.isPopupVisible()) {
1505 // Forces the selection of the list item
1506 boolean isEnterSelectablePopup =
1507 UIManager.getBoolean("ComboBox.isEnterSelectablePopup");
1508 if (!comboBox.isEditable() || isEnterSelectablePopup
1509 || ui.isTableCellEditor) {
1510 Object listItem = ui.popup.getList().getSelectedValue();
1511 if (listItem != null) {
1512 comboBox.getModel().setSelectedItem(listItem);
1513 // Ensure that JComboBox.actionPerformed()
1514 // doesn't set editor value as selected item
1515 comboBox.getEditor().setItem(listItem);
1516 }
1517 }
1518 comboBox.setPopupVisible(false);
1519 }
1520 else {
1521 // Call the default button binding.
1522 // This is a pretty messy way of passing an event through
1523 // to the root pane.
1524 JRootPane root = SwingUtilities.getRootPane(comboBox);
1525 if (root != null) {
1526 InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1527 ActionMap am = root.getActionMap();
1528 if (im != null && am != null) {
1529 Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0));
1530 if (obj != null) {
1531 Action action = am.get(obj);
1532 if (action != null) {
1533 action.actionPerformed(new ActionEvent(
1534 root, e.getID(), e.getActionCommand(),
1535 e.getWhen(), e.getModifiers()));
1536 }
1537 }
1538 }
1539 }
1540 }
1541 }
1542 }
1543
1544 private int getNextIndex(JComboBox comboBox, String key) {
1545 if (key == PAGE_UP) {
1546 int listHeight = comboBox.getMaximumRowCount();
1547 int index = comboBox.getSelectedIndex() - listHeight;
1548 return (index < 0 ? 0: index);
1549 }
1550 else if (key == PAGE_DOWN) {
1551 int listHeight = comboBox.getMaximumRowCount();
1552 int index = comboBox.getSelectedIndex() + listHeight;
1553 int max = comboBox.getItemCount();
1554 return (index < max ? index: max-1);
1555 }
1556 else if (key == HOME) {
1557 return 0;
1558 }
1559 else if (key == END) {
1560 return comboBox.getItemCount() - 1;
1561 }
1562 return comboBox.getSelectedIndex();
1563 }
1564
1565 public boolean isEnabled(Object c) {
1566 if (getName() == HIDE) {
1567 return (c != null && ((JComboBox)c).isPopupVisible());
1568 }
1569 return true;
1570 }
1571 }
1572 //
1573 // end Keyboard Action Management
1574 //===============================
1575
1576
1577 //
1578 // Shared Handler, implements all listeners
1579 //
1580 private class Handler implements ActionListener, FocusListener,
1581 KeyListener, LayoutManager,
1582 ListDataListener, PropertyChangeListener {
1583 //
1584 // PropertyChangeListener
1585 //
1586 public void propertyChange(PropertyChangeEvent e) {
1587 String propertyName = e.getPropertyName();
1588 JComboBox comboBox = (JComboBox)e.getSource();
1589
1590 if ( propertyName == "model" ) {
1591 ComboBoxModel newModel = (ComboBoxModel)e.getNewValue();
1592 ComboBoxModel oldModel = (ComboBoxModel)e.getOldValue();
1593
1594 if ( oldModel != null && listDataListener != null ) {
1595 oldModel.removeListDataListener( listDataListener );
1596 }
1597
1598 if ( newModel != null && listDataListener != null ) {
1599 newModel.addListDataListener( listDataListener );
1600 }
1601
1602 if ( editor != null ) {
1603 comboBox.configureEditor( comboBox.getEditor(), comboBox.getSelectedItem() );
1604 }
1605 isMinimumSizeDirty = true;
1606 isDisplaySizeDirty = true;
1607 comboBox.revalidate();
1608 comboBox.repaint();
1609 }
1610 else if ( propertyName == "editor" && comboBox.isEditable() ) {
1611 addEditor();
1612 comboBox.revalidate();
1613 }
1614 else if ( propertyName == "editable" ) {
1615 if ( comboBox.isEditable() ) {
1616 comboBox.setRequestFocusEnabled( false );
1617 addEditor();
1618 } else {
1619 comboBox.setRequestFocusEnabled( true );
1620 removeEditor();
1621 }
1622
1623 updateToolTipTextForChildren();
1624
1625 comboBox.revalidate();
1626 }
1627 else if ( propertyName == "enabled" ) {
1628 boolean enabled = comboBox.isEnabled();
1629 if ( editor != null )
1630 editor.setEnabled(enabled);
1631 if ( arrowButton != null )
1632 arrowButton.setEnabled(enabled);
1633 comboBox.repaint();
1634 }
1635 else if ( propertyName == "focusable" ) {
1636 boolean focusable = comboBox.isFocusable();
1637 if ( editor != null )
1638 editor.setFocusable(focusable);
1639 if ( arrowButton != null )
1640 arrowButton.setFocusable(focusable);
1641 comboBox.repaint();
1642 }
1643 else if ( propertyName == "maximumRowCount" ) {
1644 if ( isPopupVisible( comboBox ) ) {
1645 setPopupVisible(comboBox, false);
1646 setPopupVisible(comboBox, true);
1647 }
1648 }
1649 else if ( propertyName == "font" ) {
1650 listBox.setFont( comboBox.getFont() );
1651 if ( editor != null ) {
1652 editor.setFont( comboBox.getFont() );
1653 }
1654 isMinimumSizeDirty = true;
1655 comboBox.validate();
1656 }
1657 else if ( propertyName == JComponent.TOOL_TIP_TEXT_KEY ) {
1658 updateToolTipTextForChildren();
1659 }
1660 else if ( propertyName == BasicComboBoxUI.IS_TABLE_CELL_EDITOR ) {
1661 Boolean inTable = (Boolean)e.getNewValue();
1662 isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false;
1663 }
1664 else if (propertyName == "prototypeDisplayValue") {
1665 isMinimumSizeDirty = true;
1666 isDisplaySizeDirty = true;
1667 comboBox.revalidate();
1668 }
1669 else if (propertyName == "renderer") {
1670 isMinimumSizeDirty = true;
1671 isDisplaySizeDirty = true;
1672 comboBox.revalidate();
1673 }
1674 }
1675
1676
1677 //
1678 // KeyListener
1679 //
1680
1681 // This listener checks to see if the key event isn't a navigation
1682 // key. If it finds a key event that wasn't a navigation key it
1683 // dispatches it to JComboBox.selectWithKeyChar() so that it can do
1684 // type-ahead.
1685 public void keyPressed( KeyEvent e ) {
1686 if ( isNavigationKey(e.getKeyCode(), e.getModifiers()) ) {
1687 lastTime = 0L;
1688 } else if ( comboBox.isEnabled() && comboBox.getModel().getSize()!=0 &&
1689 isTypeAheadKey( e ) && e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
1690 time = e.getWhen();
1691 if ( comboBox.selectWithKeyChar(e.getKeyChar()) ) {
1692 e.consume();
1693 }
1694 }
1695 }
1696
1697 public void keyTyped(KeyEvent e) {
1698 }
1699
1700 public void keyReleased(KeyEvent e) {
1701 }
1702
1703 private boolean isTypeAheadKey( KeyEvent e ) {
1704 return !e.isAltDown() && !e.isControlDown() && !e.isMetaDown();
1705 }
1706
1707 //
1708 // FocusListener
1709 //
1710 // NOTE: The class is added to both the Editor and ComboBox.
1711 // The combo box listener hides the popup when the focus is lost.
1712 // It also repaints when focus is gained or lost.
1713
1714 public void focusGained( FocusEvent e ) {
1715 ComboBoxEditor comboBoxEditor = comboBox.getEditor();
1716
1717 if ( (comboBoxEditor != null) &&
1718 (e.getSource() == comboBoxEditor.getEditorComponent()) ) {
1719 return;
1720 }
1721 hasFocus = true;
1722 comboBox.repaint();
1723
1724 if (comboBox.isEditable() && editor != null) {
1725 editor.requestFocus();
1726 }
1727 }
1728
1729 public void focusLost( FocusEvent e ) {
1730 ComboBoxEditor editor = comboBox.getEditor();
1731 if ( (editor != null) &&
1732 (e.getSource() == editor.getEditorComponent()) ) {
1733 Object item = editor.getItem();
1734
1735 Object selectedItem = comboBox.getSelectedItem();
1736 if (!e.isTemporary() && item != null &&
1737 !item.equals((selectedItem == null) ? "" : selectedItem )) {
1738 comboBox.actionPerformed
1739 (new ActionEvent(editor, 0, "",
1740 EventQueue.getMostRecentEventTime(), 0));
1741 }
1742 }
1743
1744 hasFocus = false;
1745 if (!e.isTemporary()) {
1746 setPopupVisible(comboBox, false);
1747 }
1748 comboBox.repaint();
1749 }
1750
1751 //
1752 // ListDataListener
1753 //
1754
1755 // This listener watches for changes in the ComboBoxModel
1756 public void contentsChanged( ListDataEvent e ) {
1757 if ( !(e.getIndex0() == -1 && e.getIndex1() == -1) ) {
1758 isMinimumSizeDirty = true;
1759 comboBox.revalidate();
1760 }
1761
1762 // set the editor with the selected item since this
1763 // is the event handler for a selected item change.
1764 if (comboBox.isEditable() && editor != null) {
1765 comboBox.configureEditor( comboBox.getEditor(),
1766 comboBox.getSelectedItem() );
1767 }
1768
1769 isDisplaySizeDirty = true;
1770 comboBox.repaint();
1771 }
1772
1773 public void intervalAdded( ListDataEvent e ) {
1774 contentsChanged( e );
1775 }
1776
1777 public void intervalRemoved( ListDataEvent e ) {
1778 contentsChanged( e );
1779 }
1780
1781 //
1782 // LayoutManager
1783 //
1784
1785 // This layout manager handles the 'standard' layout of combo boxes.
1786 // It puts the arrow button to the right and the editor to the left.
1787 // If there is no editor it still keeps the arrow button to the right.
1788 public void addLayoutComponent(String name, Component comp) {}
1789
1790 public void removeLayoutComponent(Component comp) {}
1791
1792 public Dimension preferredLayoutSize(Container parent) {
1793 return parent.getPreferredSize();
1794 }
1795
1796 public Dimension minimumLayoutSize(Container parent) {
1797 return parent.getMinimumSize();
1798 }
1799
1800 public void layoutContainer(Container parent) {
1801 JComboBox cb = (JComboBox)parent;
1802 int width = cb.getWidth();
1803 int height = cb.getHeight();
1804
1805 Insets insets = getInsets();
1806 int buttonSize = height - (insets.top + insets.bottom);
1807 Rectangle cvb;
1808
1809 if ( arrowButton != null ) {
1810 if(BasicGraphicsUtils.isLeftToRight(cb)) {
1811 arrowButton.setBounds( width - (insets.right + buttonSize),
1812 insets.top,
1813 buttonSize, buttonSize);
1814 }
1815 else {
1816 arrowButton.setBounds( insets.left, insets.top,
1817 buttonSize, buttonSize);
1818 }
1819 }
1820 if ( editor != null ) {
1821 cvb = rectangleForCurrentValue();
1822 editor.setBounds(cvb);
1823 }
1824 }
1825
1826
1827 //
1828 // ActionListener
1829 //
1830 // Fix for 4515752: Forward the Enter pressed on the
1831 // editable combo box to the default button
1832
1833 // Note: This could depend on event ordering. The first ActionEvent
1834 // from the editor may be handled by the JComboBox in which case, the
1835 // enterPressed action will always be invoked.
1836 public void actionPerformed(ActionEvent evt) {
1837 Object item = comboBox.getEditor().getItem();
1838 if (item != null) {
1839 if(!comboBox.isPopupVisible() && !item.equals(comboBox.getSelectedItem())) {
1840 comboBox.setSelectedItem(comboBox.getEditor().getItem());
1841 }
1842 ActionMap am = comboBox.getActionMap();
1843 if (am != null) {
1844 Action action = am.get("enterPressed");
1845 if (action != null) {
1846 action.actionPerformed(new ActionEvent(comboBox, evt.getID(),
1847 evt.getActionCommand(),
1848 evt.getModifiers()));
1849 }
1850 }
1851 }
1852 }
1853 }
1854
1855 class DefaultKeySelectionManager implements JComboBox.KeySelectionManager, UIResource {
1856 private String prefix = "";
1857 private String typedString = "";
1858
1859 public int selectionForKey(char aKey,ComboBoxModel aModel) {
1860 if (lastTime == 0L) {
1861 prefix = "";
1862 typedString = "";
1863 }
1864 boolean startingFromSelection = true;
1865
1866 int startIndex = comboBox.getSelectedIndex();
1867 if (time - lastTime < timeFactor) {
1868 typedString += aKey;
1869 if((prefix.length() == 1) && (aKey == prefix.charAt(0))) {
1870 // Subsequent same key presses move the keyboard focus to the next
1871 // object that starts with the same letter.
1872 startIndex++;
1873 } else {
1874 prefix = typedString;
1875 }
1876 } else {
1877 startIndex++;
1878 typedString = "" + aKey;
1879 prefix = typedString;
1880 }
1881 lastTime = time;
1882
1883 if (startIndex < 0 || startIndex >= aModel.getSize()) {
1884 startingFromSelection = false;
1885 startIndex = 0;
1886 }
1887 int index = listBox.getNextMatch(prefix, startIndex,
1888 Position.Bias.Forward);
1889 if (index < 0 && startingFromSelection) { // wrap
1890 index = listBox.getNextMatch(prefix, 0,
1891 Position.Bias.Forward);
1892 }
1893 return index;
1894 }
1895 }
1896
1897}