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