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