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