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