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