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