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+
26+package javax.swing.plaf.basic;
27+
28+import java.awt.*;
29+import java.awt.event.*;
30+import javax.swing.*;
31+import javax.accessibility.*;
32+import javax.swing.plaf.*;
33+import javax.swing.text.*;
34+import javax.swing.event.*;
35+import java.beans.PropertyChangeListener;
36+import java.beans.PropertyChangeEvent;
37+import sun.awt.AppContext;
38+import sun.swing.DefaultLookup;
39+import 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+ */
64+public 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+}