History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/plaf/basic/BasicComboBoxUI.java (1 of 18352 files). Show all

Unified · Split

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