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