1 /*
   2  * Copyright (c) 1997, 2008, 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         comboBox.add( arrowButton );
 696 
 697         if (arrowButton != null)  {
 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                 if (value == null) {
 975                     value = " ";
 976                 } else if (value instanceof String && "".equals(value)) {
 977                     value = " ";
 978                 }
 979                 Component component = renderer.
 980                         getListCellRendererComponent(listBox, value, -1,
 981                                                      false, false);
 982                 if (component instanceof JComponent) {
 983                     component.setFont(comboBox.getFont());
 984                 }
 985                 baseline = component.getBaseline(width, height);
 986             }
 987             else {
 988                 baseline = editor.getBaseline(width, height);
 989             }
 990             if (baseline > 0) {
 991                 baseline += insets.top;
 992             }
 993         }
 994         return baseline;
 995     }
 996 
 997     /**
 998      * Returns an enum indicating how the baseline of the component
 999      * changes as the size changes.
1000      *
1001      * @throws NullPointerException {@inheritDoc}
1002      * @see javax.swing.JComponent#getBaseline(int, int)
1003      * @since 1.6
1004      */
1005     @Override
1006     public Component.BaselineResizeBehavior getBaselineResizeBehavior(
1007             JComponent c) {
1008         super.getBaselineResizeBehavior(c);
1009         // Force sameBaseline to be updated.
1010         getDisplaySize();
1011         if (comboBox.isEditable()) {
1012             return editor.getBaselineResizeBehavior();
1013         }
1014         else if (sameBaseline) {
1015             ListCellRenderer renderer = comboBox.getRenderer();
1016             if (renderer == null)  {
1017                 renderer = new DefaultListCellRenderer();
1018             }
1019             Object value = null;
1020             Object prototypeValue = comboBox.getPrototypeDisplayValue();
1021             if (prototypeValue != null)  {
1022                 value = prototypeValue;
1023             }
1024             else if (comboBox.getModel().getSize() > 0) {
1025                 // Note, we're assuming the baseline is the same for all
1026                 // cells, if not, this needs to loop through all.
1027                 value = comboBox.getModel().getElementAt(0);
1028             }
1029             if (value != null) {
1030                 Component component = renderer.
1031                         getListCellRendererComponent(listBox, value, -1,
1032                                                      false, false);
1033                 return component.getBaselineResizeBehavior();
1034             }
1035         }
1036         return Component.BaselineResizeBehavior.OTHER;
1037     }
1038 
1039     // This is currently hacky...
1040     @Override
1041     public int getAccessibleChildrenCount(JComponent c) {
1042         if ( comboBox.isEditable() ) {
1043             return 2;
1044         }
1045         else {
1046             return 1;
1047         }
1048     }
1049 
1050     // This is currently hacky...
1051     @Override
1052     public Accessible getAccessibleChild(JComponent c, int i) {
1053         // 0 = the popup
1054         // 1 = the editor
1055         switch ( i ) {
1056         case 0:
1057             if ( popup instanceof Accessible ) {
1058                 AccessibleContext ac = ((Accessible) popup).getAccessibleContext();
1059                 ac.setAccessibleParent(comboBox);
1060                 return(Accessible) popup;
1061             }
1062             break;
1063         case 1:
1064             if ( comboBox.isEditable()
1065                  && (editor instanceof Accessible) ) {
1066                 AccessibleContext ac = ((Accessible) editor).getAccessibleContext();
1067                 ac.setAccessibleParent(comboBox);
1068                 return(Accessible) editor;
1069             }
1070             break;
1071         }
1072         return null;
1073     }
1074 
1075     //
1076     // end ComponentUI Implementation
1077     //===============================
1078 
1079 
1080     //======================
1081     // begin Utility Methods
1082     //
1083 
1084     /**
1085      * Returns whether or not the supplied keyCode maps to a key that is used for
1086      * navigation.  This is used for optimizing key input by only passing non-
1087      * navigation keys to the type-ahead mechanism.  Subclasses should override this
1088      * if they change the navigation keys.
1089      */
1090     protected boolean isNavigationKey( int keyCode ) {
1091         return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN ||
1092                keyCode == KeyEvent.VK_KP_UP || keyCode == KeyEvent.VK_KP_DOWN;
1093     }
1094 
1095     private boolean isNavigationKey(int keyCode, int modifiers) {
1096         InputMap inputMap = comboBox.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1097         KeyStroke key = KeyStroke.getKeyStroke(keyCode, modifiers);
1098 
1099         if (inputMap != null && inputMap.get(key) != null) {
1100             return true;
1101         }
1102         return false;
1103     }
1104 
1105     /**
1106      * Selects the next item in the list.  It won't change the selection if the
1107      * currently selected item is already the last item.
1108      */
1109     protected void selectNextPossibleValue() {
1110         int si;
1111 
1112         if ( comboBox.isPopupVisible() ) {
1113             si = listBox.getSelectedIndex();
1114         }
1115         else {
1116             si = comboBox.getSelectedIndex();
1117         }
1118 
1119         if ( si < comboBox.getModel().getSize() - 1 ) {
1120             listBox.setSelectedIndex( si + 1 );
1121             listBox.ensureIndexIsVisible( si + 1 );
1122             if ( !isTableCellEditor ) {
1123                 comboBox.setSelectedIndex(si+1);
1124             }
1125             comboBox.repaint();
1126         }
1127     }
1128 
1129     /**
1130      * Selects the previous item in the list.  It won't change the selection if the
1131      * currently selected item is already the first item.
1132      */
1133     protected void selectPreviousPossibleValue() {
1134         int si;
1135 
1136         if ( comboBox.isPopupVisible() ) {
1137             si = listBox.getSelectedIndex();
1138         }
1139         else {
1140             si = comboBox.getSelectedIndex();
1141         }
1142 
1143         if ( si > 0 ) {
1144             listBox.setSelectedIndex( si - 1 );
1145             listBox.ensureIndexIsVisible( si - 1 );
1146             if ( !isTableCellEditor ) {
1147                 comboBox.setSelectedIndex(si-1);
1148             }
1149             comboBox.repaint();
1150         }
1151     }
1152 
1153     /**
1154      * Hides the popup if it is showing and shows the popup if it is hidden.
1155      */
1156     protected void toggleOpenClose() {
1157         setPopupVisible(comboBox, !isPopupVisible(comboBox));
1158     }
1159 
1160     /**
1161      * Returns the area that is reserved for drawing the currently selected item.
1162      */
1163     protected Rectangle rectangleForCurrentValue() {
1164         int width = comboBox.getWidth();
1165         int height = comboBox.getHeight();
1166         Insets insets = getInsets();
1167         int buttonSize = height - (insets.top + insets.bottom);
1168         if ( arrowButton != null ) {
1169             buttonSize = arrowButton.getWidth();
1170         }
1171         if(BasicGraphicsUtils.isLeftToRight(comboBox)) {
1172             return new Rectangle(insets.left, insets.top,
1173                              width - (insets.left + insets.right + buttonSize),
1174                              height - (insets.top + insets.bottom));
1175         }
1176         else {
1177             return new Rectangle(insets.left + buttonSize, insets.top,
1178                              width - (insets.left + insets.right + buttonSize),
1179                              height - (insets.top + insets.bottom));
1180         }
1181     }
1182 
1183     /**
1184      * Gets the insets from the JComboBox.
1185      */
1186     protected Insets getInsets() {
1187         return comboBox.getInsets();
1188     }
1189 
1190     //
1191     // end Utility Methods
1192     //====================
1193 
1194 
1195     //===============================
1196     // begin Painting Utility Methods
1197     //
1198 
1199     /**
1200      * Paints the currently selected item.
1201      */
1202     public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) {
1203         ListCellRenderer renderer = comboBox.getRenderer();
1204         Component c;
1205 
1206         if ( hasFocus && !isPopupVisible(comboBox) ) {
1207             c = renderer.getListCellRendererComponent( listBox,
1208                                                        comboBox.getSelectedItem(),
1209                                                        -1,
1210                                                        true,
1211                                                        false );
1212         }
1213         else {
1214             c = renderer.getListCellRendererComponent( listBox,
1215                                                        comboBox.getSelectedItem(),
1216                                                        -1,
1217                                                        false,
1218                                                        false );
1219             c.setBackground(UIManager.getColor("ComboBox.background"));
1220         }
1221         c.setFont(comboBox.getFont());
1222         if ( hasFocus && !isPopupVisible(comboBox) ) {
1223             c.setForeground(listBox.getSelectionForeground());
1224             c.setBackground(listBox.getSelectionBackground());
1225         }
1226         else {
1227             if ( comboBox.isEnabled() ) {
1228                 c.setForeground(comboBox.getForeground());
1229                 c.setBackground(comboBox.getBackground());
1230             }
1231             else {
1232                 c.setForeground(DefaultLookup.getColor(
1233                          comboBox, this, "ComboBox.disabledForeground", null));
1234                 c.setBackground(DefaultLookup.getColor(
1235                          comboBox, this, "ComboBox.disabledBackground", null));
1236             }
1237         }
1238 
1239         // Fix for 4238829: should lay out the JPanel.
1240         boolean shouldValidate = false;
1241         if (c instanceof JPanel)  {
1242             shouldValidate = true;
1243         }
1244 
1245         int x = bounds.x, y = bounds.y, w = bounds.width, h = bounds.height;
1246         if (padding != null) {
1247             x = bounds.x + padding.left;
1248             y = bounds.y + padding.top;
1249             w = bounds.width - (padding.left + padding.right);
1250             h = bounds.height - (padding.top + padding.bottom);
1251         }
1252 
1253         currentValuePane.paintComponent(g,c,comboBox,x,y,w,h,shouldValidate);
1254     }
1255 
1256     /**
1257      * Paints the background of the currently selected item.
1258      */
1259     public void paintCurrentValueBackground(Graphics g,Rectangle bounds,boolean hasFocus) {
1260         Color t = g.getColor();
1261         if ( comboBox.isEnabled() )
1262             g.setColor(DefaultLookup.getColor(comboBox, this,
1263                                               "ComboBox.background", null));
1264         else
1265             g.setColor(DefaultLookup.getColor(comboBox, this,
1266                                      "ComboBox.disabledBackground", null));
1267         g.fillRect(bounds.x,bounds.y,bounds.width,bounds.height);
1268         g.setColor(t);
1269     }
1270 
1271     /**
1272      * Repaint the currently selected item.
1273      */
1274     void repaintCurrentValue() {
1275         Rectangle r = rectangleForCurrentValue();
1276         comboBox.repaint(r.x,r.y,r.width,r.height);
1277     }
1278 
1279     //
1280     // end Painting Utility Methods
1281     //=============================
1282 
1283 
1284     //===============================
1285     // begin Size Utility Methods
1286     //
1287 
1288     /**
1289      * Return the default size of an empty display area of the combo box using
1290      * the current renderer and font.
1291      *
1292      * @return the size of an empty display area
1293      * @see #getDisplaySize
1294      */
1295     protected Dimension getDefaultSize() {
1296         // Calculates the height and width using the default text renderer
1297         Dimension d = getSizeForComponent(getDefaultListCellRenderer().getListCellRendererComponent(listBox, " ", -1, false, false));
1298 
1299         return new Dimension(d.width, d.height);
1300     }
1301 
1302     /**
1303      * Returns the calculated size of the display area. The display area is the
1304      * portion of the combo box in which the selected item is displayed. This
1305      * method will use the prototype display value if it has been set.
1306      * <p>
1307      * For combo boxes with a non trivial number of items, it is recommended to
1308      * use a prototype display value to significantly speed up the display
1309      * size calculation.
1310      *
1311      * @return the size of the display area calculated from the combo box items
1312      * @see javax.swing.JComboBox#setPrototypeDisplayValue
1313      */
1314     protected Dimension getDisplaySize() {
1315         if (!isDisplaySizeDirty)  {
1316             return new Dimension(cachedDisplaySize);
1317         }
1318         Dimension result = new Dimension();
1319 
1320         ListCellRenderer renderer = comboBox.getRenderer();
1321         if (renderer == null)  {
1322             renderer = new DefaultListCellRenderer();
1323         }
1324 
1325         sameBaseline = true;
1326 
1327         Object prototypeValue = comboBox.getPrototypeDisplayValue();
1328         if (prototypeValue != null)  {
1329             // Calculates the dimension based on the prototype value
1330             result = getSizeForComponent(renderer.getListCellRendererComponent(listBox,
1331                                                                                prototypeValue,
1332                                                                                -1, false, false));
1333         } else {
1334             // Calculate the dimension by iterating over all the elements in the combo
1335             // box list.
1336             ComboBoxModel model = comboBox.getModel();
1337             int modelSize = model.getSize();
1338             int baseline = -1;
1339             Dimension d;
1340 
1341             Component cpn;
1342 
1343             if (modelSize > 0 ) {
1344                 for (int i = 0; i < modelSize ; i++ ) {
1345                     // Calculates the maximum height and width based on the largest
1346                     // element
1347                     Object value = model.getElementAt(i);
1348                     Component c = renderer.getListCellRendererComponent(
1349                             listBox, value, -1, false, false);
1350                     d = getSizeForComponent(c);
1351                     if (sameBaseline && value != null &&
1352                             (!(value instanceof String) || !"".equals(value))) {
1353                         int newBaseline = c.getBaseline(d.width, d.height);
1354                         if (newBaseline == -1) {
1355                             sameBaseline = false;
1356                         }
1357                         else if (baseline == -1) {
1358                             baseline = newBaseline;
1359                         }
1360                         else if (baseline != newBaseline) {
1361                             sameBaseline = false;
1362                         }
1363                     }
1364                     result.width = Math.max(result.width,d.width);
1365                     result.height = Math.max(result.height,d.height);
1366                 }
1367             } else {
1368                 result = getDefaultSize();
1369                 if (comboBox.isEditable()) {
1370                     result.width = 100;
1371                 }
1372             }
1373         }
1374 
1375         if ( comboBox.isEditable() ) {
1376             Dimension d = editor.getPreferredSize();
1377             result.width = Math.max(result.width,d.width);
1378             result.height = Math.max(result.height,d.height);
1379         }
1380 
1381         // calculate in the padding
1382         if (padding != null) {
1383             result.width += padding.left + padding.right;
1384             result.height += padding.top + padding.bottom;
1385         }
1386 
1387         // Set the cached value
1388         cachedDisplaySize.setSize(result.width, result.height);
1389         isDisplaySizeDirty = false;
1390 
1391         return result;
1392     }
1393 
1394     /**
1395      * Returns the size a component would have if used as a cell renderer.
1396      *
1397      * @param comp a {@code Component} to check
1398      * @return size of the component
1399      * @since 1.7
1400      */
1401     protected Dimension getSizeForComponent(Component comp) {
1402         // This has been refactored out in hopes that it may be investigated and
1403         // simplified for the next major release. adding/removing
1404         // the component to the currentValuePane and changing the font may be
1405         // redundant operations.
1406         currentValuePane.add(comp);
1407         comp.setFont(comboBox.getFont());
1408         Dimension d = comp.getPreferredSize();
1409         currentValuePane.remove(comp);
1410         return d;
1411     }
1412 
1413 
1414     //
1415     // end Size Utility Methods
1416     //=============================
1417 
1418 
1419     //=================================
1420     // begin Keyboard Action Management
1421     //
1422 
1423     /**
1424      * Adds keyboard actions to the JComboBox.  Actions on enter and esc are already
1425      * supplied.  Add more actions as you need them.
1426      */
1427     protected void installKeyboardActions() {
1428         InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1429         SwingUtilities.replaceUIInputMap(comboBox, JComponent.
1430                              WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km);
1431 
1432 
1433         LazyActionMap.installLazyActionMap(comboBox, BasicComboBoxUI.class,
1434                                            "ComboBox.actionMap");
1435     }
1436 
1437     InputMap getInputMap(int condition) {
1438         if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
1439             return (InputMap)DefaultLookup.get(comboBox, this,
1440                                                "ComboBox.ancestorInputMap");
1441         }
1442         return null;
1443     }
1444 
1445     boolean isTableCellEditor() {
1446         return isTableCellEditor;
1447     }
1448 
1449     /**
1450      * Removes the focus InputMap and ActionMap.
1451      */
1452     protected void uninstallKeyboardActions() {
1453         SwingUtilities.replaceUIInputMap(comboBox, JComponent.
1454                                  WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1455         SwingUtilities.replaceUIActionMap(comboBox, null);
1456     }
1457 
1458 
1459     //
1460     // Actions
1461     //
1462     private static class Actions extends UIAction {
1463         private static final String HIDE = "hidePopup";
1464         private static final String DOWN = "selectNext";
1465         private static final String DOWN_2 = "selectNext2";
1466         private static final String TOGGLE = "togglePopup";
1467         private static final String TOGGLE_2 = "spacePopup";
1468         private static final String UP = "selectPrevious";
1469         private static final String UP_2 = "selectPrevious2";
1470         private static final String ENTER = "enterPressed";
1471         private static final String PAGE_DOWN = "pageDownPassThrough";
1472         private static final String PAGE_UP = "pageUpPassThrough";
1473         private static final String HOME = "homePassThrough";
1474         private static final String END = "endPassThrough";
1475 
1476         Actions(String name) {
1477             super(name);
1478         }
1479 
1480         public void actionPerformed( ActionEvent e ) {
1481             String key = getName();
1482             JComboBox comboBox = (JComboBox)e.getSource();
1483             BasicComboBoxUI ui = (BasicComboBoxUI)BasicLookAndFeel.getUIOfType(
1484                                   comboBox.getUI(), BasicComboBoxUI.class);
1485             if (key == HIDE) {
1486                 comboBox.firePopupMenuCanceled();
1487                 comboBox.setPopupVisible(false);
1488             }
1489             else if (key == PAGE_DOWN || key == PAGE_UP ||
1490                      key == HOME || key == END) {
1491                 int index = getNextIndex(comboBox, key);
1492                 if (index >= 0 && index < comboBox.getItemCount()) {
1493                     comboBox.setSelectedIndex(index);
1494                 }
1495             }
1496             else if (key == DOWN) {
1497                 if (comboBox.isShowing() ) {
1498                     if ( comboBox.isPopupVisible() ) {
1499                         if (ui != null) {
1500                             ui.selectNextPossibleValue();
1501                         }
1502                     } else {
1503                         comboBox.setPopupVisible(true);
1504                     }
1505                 }
1506             }
1507             else if (key == DOWN_2) {
1508                 // Special case in which pressing the arrow keys will not
1509                 // make the popup appear - except for editable combo boxes
1510                 // and combo boxes inside a table.
1511                 if (comboBox.isShowing() ) {
1512                     if ( (comboBox.isEditable() ||
1513                             (ui != null && ui.isTableCellEditor()))
1514                          && !comboBox.isPopupVisible() ) {
1515                         comboBox.setPopupVisible(true);
1516                     } else {
1517                         if (ui != null) {
1518                             ui.selectNextPossibleValue();
1519                         }
1520                     }
1521                 }
1522             }
1523             else if (key == TOGGLE || key == TOGGLE_2) {
1524                 if (ui != null && (key == TOGGLE || !comboBox.isEditable())) {
1525                     if ( ui.isTableCellEditor() ) {
1526                         // Forces the selection of the list item if the
1527                         // combo box is in a JTable.
1528                         comboBox.setSelectedIndex(ui.popup.getList().
1529                                                   getSelectedIndex());
1530                     }
1531                     else {
1532                         comboBox.setPopupVisible(!comboBox.isPopupVisible());
1533                     }
1534                 }
1535             }
1536             else if (key == UP) {
1537                 if (ui != null) {
1538                     if (ui.isPopupVisible(comboBox)) {
1539                         ui.selectPreviousPossibleValue();
1540                     }
1541                     else if (DefaultLookup.getBoolean(comboBox, ui,
1542                                     "ComboBox.showPopupOnNavigation", false)) {
1543                         ui.setPopupVisible(comboBox, true);
1544                     }
1545                 }
1546             }
1547             else if (key == UP_2) {
1548                  // Special case in which pressing the arrow keys will not
1549                  // make the popup appear - except for editable combo boxes.
1550                  if (comboBox.isShowing() && ui != null) {
1551                      if ( comboBox.isEditable() && !comboBox.isPopupVisible()) {
1552                          comboBox.setPopupVisible(true);
1553                      } else {
1554                          ui.selectPreviousPossibleValue();
1555                      }
1556                  }
1557              }
1558 
1559             else if (key == ENTER) {
1560                 if (comboBox.isPopupVisible()) {
1561                     // Forces the selection of the list item
1562                     boolean isEnterSelectablePopup =
1563                             UIManager.getBoolean("ComboBox.isEnterSelectablePopup");
1564                     if (!comboBox.isEditable() || isEnterSelectablePopup
1565                             || ui.isTableCellEditor) {
1566                         Object listItem = ui.popup.getList().getSelectedValue();
1567                         if (listItem != null) {
1568                             // Use the selected value from popup
1569                             // to set the selected item in combo box,
1570                             // but ensure before that JComboBox.actionPerformed()
1571                             // won't use editor's value to set the selected item
1572                             comboBox.getEditor().setItem(listItem);
1573                             comboBox.setSelectedItem(listItem);
1574                         }
1575                     }
1576                     comboBox.setPopupVisible(false);
1577                 }
1578                 else {
1579                     // Hide combo box if it is a table cell editor
1580                     if (ui.isTableCellEditor && !comboBox.isEditable()) {
1581                         comboBox.setSelectedItem(comboBox.getSelectedItem());
1582                     }
1583                     // Call the default button binding.
1584                     // This is a pretty messy way of passing an event through
1585                     // to the root pane.
1586                     JRootPane root = SwingUtilities.getRootPane(comboBox);
1587                     if (root != null) {
1588                         InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1589                         ActionMap am = root.getActionMap();
1590                         if (im != null && am != null) {
1591                             Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0));
1592                             if (obj != null) {
1593                                 Action action = am.get(obj);
1594                                 if (action != null) {
1595                                     action.actionPerformed(new ActionEvent(
1596                                      root, e.getID(), e.getActionCommand(),
1597                                      e.getWhen(), e.getModifiers()));
1598                                 }
1599                             }
1600                         }
1601                     }
1602                 }
1603             }
1604         }
1605 
1606         private int getNextIndex(JComboBox comboBox, String key) {
1607             if (key == PAGE_UP) {
1608                 int listHeight = comboBox.getMaximumRowCount();
1609                 int index = comboBox.getSelectedIndex() - listHeight;
1610                 return (index < 0 ? 0: index);
1611             }
1612             else if (key == PAGE_DOWN) {
1613                 int listHeight = comboBox.getMaximumRowCount();
1614                 int index = comboBox.getSelectedIndex() + listHeight;
1615                 int max = comboBox.getItemCount();
1616                 return (index < max ? index: max-1);
1617             }
1618             else if (key == HOME) {
1619                 return 0;
1620             }
1621             else if (key == END) {
1622                 return comboBox.getItemCount() - 1;
1623             }
1624             return comboBox.getSelectedIndex();
1625         }
1626 
1627         public boolean isEnabled(Object c) {
1628             if (getName() == HIDE) {
1629                 return (c != null && ((JComboBox)c).isPopupVisible());
1630             }
1631             return true;
1632         }
1633     }
1634     //
1635     // end Keyboard Action Management
1636     //===============================
1637 
1638 
1639     //
1640     // Shared Handler, implements all listeners
1641     //
1642     private class Handler implements ActionListener, FocusListener,
1643                                      KeyListener, LayoutManager,
1644                                      ListDataListener, PropertyChangeListener {
1645         //
1646         // PropertyChangeListener
1647         //
1648         public void propertyChange(PropertyChangeEvent e) {
1649             String propertyName = e.getPropertyName();
1650             if (e.getSource() == editor){
1651                 // If the border of the editor changes then this can effect
1652                 // the size of the editor which can cause the combo's size to
1653                 // become invalid so we need to clear size caches
1654                 if ("border".equals(propertyName)){
1655                     isMinimumSizeDirty = true;
1656                     isDisplaySizeDirty = true;
1657                     comboBox.revalidate();
1658                 }
1659             } else {
1660                 JComboBox comboBox = (JComboBox)e.getSource();
1661                 if ( propertyName == "model" ) {
1662                     ComboBoxModel newModel = (ComboBoxModel)e.getNewValue();
1663                     ComboBoxModel oldModel = (ComboBoxModel)e.getOldValue();
1664 
1665                     if ( oldModel != null && listDataListener != null ) {
1666                         oldModel.removeListDataListener( listDataListener );
1667                     }
1668 
1669                     if ( newModel != null && listDataListener != null ) {
1670                         newModel.addListDataListener( listDataListener );
1671                     }
1672 
1673                     if ( editor != null ) {
1674                         comboBox.configureEditor( comboBox.getEditor(), comboBox.getSelectedItem() );
1675                     }
1676                     isMinimumSizeDirty = true;
1677                     isDisplaySizeDirty = true;
1678                     comboBox.revalidate();
1679                     comboBox.repaint();
1680                 }
1681                 else if ( propertyName == "editor" && comboBox.isEditable() ) {
1682                     addEditor();
1683                     comboBox.revalidate();
1684                 }
1685                 else if ( propertyName == "editable" ) {
1686                     if ( comboBox.isEditable() ) {
1687                         comboBox.setRequestFocusEnabled( false );
1688                         addEditor();
1689                     } else {
1690                         comboBox.setRequestFocusEnabled( true );
1691                         removeEditor();
1692                     }
1693                     updateToolTipTextForChildren();
1694                     comboBox.revalidate();
1695                 }
1696                 else if ( propertyName == "enabled" ) {
1697                     boolean enabled = comboBox.isEnabled();
1698                     if ( editor != null )
1699                         editor.setEnabled(enabled);
1700                     if ( arrowButton != null )
1701                         arrowButton.setEnabled(enabled);
1702                     comboBox.repaint();
1703                 }
1704                 else if ( propertyName == "focusable" ) {
1705                     boolean focusable = comboBox.isFocusable();
1706                     if ( editor != null )
1707                         editor.setFocusable(focusable);
1708                     if ( arrowButton != null )
1709                         arrowButton.setFocusable(focusable);
1710                     comboBox.repaint();
1711                 }
1712                 else if ( propertyName == "maximumRowCount" ) {
1713                     if ( isPopupVisible( comboBox ) ) {
1714                         setPopupVisible(comboBox, false);
1715                         setPopupVisible(comboBox, true);
1716                     }
1717                 }
1718                 else if ( propertyName == "font" ) {
1719                     listBox.setFont( comboBox.getFont() );
1720                     if ( editor != null ) {
1721                         editor.setFont( comboBox.getFont() );
1722                     }
1723                     isMinimumSizeDirty = true;
1724                     isDisplaySizeDirty = true;
1725                     comboBox.validate();
1726                 }
1727                 else if ( propertyName == JComponent.TOOL_TIP_TEXT_KEY ) {
1728                     updateToolTipTextForChildren();
1729                 }
1730                 else if ( propertyName == BasicComboBoxUI.IS_TABLE_CELL_EDITOR ) {
1731                     Boolean inTable = (Boolean)e.getNewValue();
1732                     isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false;
1733                 }
1734                 else if (propertyName == "prototypeDisplayValue") {
1735                     isMinimumSizeDirty = true;
1736                     isDisplaySizeDirty = true;
1737                     comboBox.revalidate();
1738                 }
1739                 else if (propertyName == "renderer") {
1740                     isMinimumSizeDirty = true;
1741                     isDisplaySizeDirty = true;
1742                     comboBox.revalidate();
1743                 }
1744             }
1745         }
1746 
1747 
1748         //
1749         // KeyListener
1750         //
1751 
1752         // This listener checks to see if the key event isn't a navigation
1753         // key.  If it finds a key event that wasn't a navigation key it
1754         // dispatches it to JComboBox.selectWithKeyChar() so that it can do
1755         // type-ahead.
1756         public void keyPressed( KeyEvent e ) {
1757             if ( isNavigationKey(e.getKeyCode(), e.getModifiers()) ) {
1758                 lastTime = 0L;
1759             } else if ( comboBox.isEnabled() && comboBox.getModel().getSize()!=0 &&
1760                         isTypeAheadKey( e ) && e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
1761                 time = e.getWhen();
1762                 if ( comboBox.selectWithKeyChar(e.getKeyChar()) ) {
1763                     e.consume();
1764                 }
1765             }
1766         }
1767 
1768         public void keyTyped(KeyEvent e) {
1769         }
1770 
1771         public void keyReleased(KeyEvent e) {
1772         }
1773 
1774         private boolean isTypeAheadKey( KeyEvent e ) {
1775             return !e.isAltDown() && !BasicGraphicsUtils.isMenuShortcutKeyDown(e);
1776         }
1777 
1778         //
1779         // FocusListener
1780         //
1781         // NOTE: The class is added to both the Editor and ComboBox.
1782         // The combo box listener hides the popup when the focus is lost.
1783         // It also repaints when focus is gained or lost.
1784 
1785         public void focusGained( FocusEvent e ) {
1786             ComboBoxEditor comboBoxEditor = comboBox.getEditor();
1787 
1788             if ( (comboBoxEditor != null) &&
1789                  (e.getSource() == comboBoxEditor.getEditorComponent()) ) {
1790                 return;
1791             }
1792             hasFocus = true;
1793             comboBox.repaint();
1794 
1795             if (comboBox.isEditable() && editor != null) {
1796                 editor.requestFocus();
1797             }
1798         }
1799 
1800         public void focusLost( FocusEvent e ) {
1801             ComboBoxEditor editor = comboBox.getEditor();
1802             if ( (editor != null) &&
1803                  (e.getSource() == editor.getEditorComponent()) ) {
1804                 Object item = editor.getItem();
1805 
1806                 Object selectedItem = comboBox.getSelectedItem();
1807                 if (!e.isTemporary() && item != null &&
1808                     !item.equals((selectedItem == null) ? "" : selectedItem )) {
1809                     comboBox.actionPerformed
1810                         (new ActionEvent(editor, 0, "",
1811                                       EventQueue.getMostRecentEventTime(), 0));
1812                 }
1813             }
1814 
1815             hasFocus = false;
1816             if (!e.isTemporary()) {
1817                 setPopupVisible(comboBox, false);
1818             }
1819             comboBox.repaint();
1820         }
1821 
1822         //
1823         // ListDataListener
1824         //
1825 
1826         // This listener watches for changes in the ComboBoxModel
1827         public void contentsChanged( ListDataEvent e ) {
1828             if ( !(e.getIndex0() == -1 && e.getIndex1() == -1) ) {
1829                 isMinimumSizeDirty = true;
1830                 comboBox.revalidate();
1831             }
1832 
1833             // set the editor with the selected item since this
1834             // is the event handler for a selected item change.
1835             if (comboBox.isEditable() && editor != null) {
1836                 comboBox.configureEditor( comboBox.getEditor(),
1837                                           comboBox.getSelectedItem() );
1838             }
1839 
1840             isDisplaySizeDirty = true;
1841             comboBox.repaint();
1842         }
1843 
1844         public void intervalAdded( ListDataEvent e ) {
1845             contentsChanged( e );
1846         }
1847 
1848         public void intervalRemoved( ListDataEvent e ) {
1849             contentsChanged( e );
1850         }
1851 
1852         //
1853         // LayoutManager
1854         //
1855 
1856         // This layout manager handles the 'standard' layout of combo boxes.
1857         // It puts the arrow button to the right and the editor to the left.
1858         // If there is no editor it still keeps the arrow button to the right.
1859         public void addLayoutComponent(String name, Component comp) {}
1860 
1861         public void removeLayoutComponent(Component comp) {}
1862 
1863         public Dimension preferredLayoutSize(Container parent) {
1864             return parent.getPreferredSize();
1865         }
1866 
1867         public Dimension minimumLayoutSize(Container parent) {
1868             return parent.getMinimumSize();
1869         }
1870 
1871         public void layoutContainer(Container parent) {
1872             JComboBox cb = (JComboBox)parent;
1873             int width = cb.getWidth();
1874             int height = cb.getHeight();
1875 
1876             Insets insets = getInsets();
1877             int buttonHeight = height - (insets.top + insets.bottom);
1878             int buttonWidth = buttonHeight;
1879             if (arrowButton != null) {
1880                 Insets arrowInsets = arrowButton.getInsets();
1881                 buttonWidth = squareButton ?
1882                     buttonHeight :
1883                     arrowButton.getPreferredSize().width + arrowInsets.left + arrowInsets.right;
1884             }
1885             Rectangle cvb;
1886 
1887             if (arrowButton != null) {
1888                 if (BasicGraphicsUtils.isLeftToRight(cb)) {
1889                     arrowButton.setBounds(width - (insets.right + buttonWidth),
1890                             insets.top, buttonWidth, buttonHeight);
1891                 } else {
1892                     arrowButton.setBounds(insets.left, insets.top,
1893                             buttonWidth, buttonHeight);
1894                 }
1895             }
1896             if ( editor != null ) {
1897                 cvb = rectangleForCurrentValue();
1898                 editor.setBounds(cvb);
1899             }
1900         }
1901 
1902         //
1903         // ActionListener
1904         //
1905         // Fix for 4515752: Forward the Enter pressed on the
1906         // editable combo box to the default button
1907 
1908         // Note: This could depend on event ordering. The first ActionEvent
1909         // from the editor may be handled by the JComboBox in which case, the
1910         // enterPressed action will always be invoked.
1911         public void actionPerformed(ActionEvent evt) {
1912             Object item = comboBox.getEditor().getItem();
1913             if (item != null) {
1914              if(!comboBox.isPopupVisible() && !item.equals(comboBox.getSelectedItem())) {
1915               comboBox.setSelectedItem(comboBox.getEditor().getItem());
1916              }
1917              ActionMap am = comboBox.getActionMap();
1918              if (am != null) {
1919                 Action action = am.get("enterPressed");
1920                 if (action != null) {
1921                     action.actionPerformed(new ActionEvent(comboBox, evt.getID(),
1922                                            evt.getActionCommand(),
1923                                            evt.getModifiers()));
1924                 }
1925             }
1926        }
1927    }
1928   }
1929 
1930     class DefaultKeySelectionManager implements JComboBox.KeySelectionManager, UIResource {
1931         private String prefix = "";
1932         private String typedString = "";
1933 
1934         public int selectionForKey(char aKey,ComboBoxModel aModel) {
1935             if (lastTime == 0L) {
1936                 prefix = "";
1937                 typedString = "";
1938             }
1939             boolean startingFromSelection = true;
1940 
1941             int startIndex = comboBox.getSelectedIndex();
1942             if (time - lastTime < timeFactor) {
1943                 typedString += aKey;
1944                 if((prefix.length() == 1) && (aKey == prefix.charAt(0))) {
1945                     // Subsequent same key presses move the keyboard focus to the next
1946                     // object that starts with the same letter.
1947                     startIndex++;
1948                 } else {
1949                     prefix = typedString;
1950                 }
1951             } else {
1952                 startIndex++;
1953                 typedString = "" + aKey;
1954                 prefix = typedString;
1955             }
1956             lastTime = time;
1957 
1958             if (startIndex < 0 || startIndex >= aModel.getSize()) {
1959                 startingFromSelection = false;
1960                 startIndex = 0;
1961             }
1962             int index = listBox.getNextMatch(prefix, startIndex,
1963                                              Position.Bias.Forward);
1964             if (index < 0 && startingFromSelection) { // wrap
1965                 index = listBox.getNextMatch(prefix, 0,
1966                                              Position.Bias.Forward);
1967             }
1968             return index;
1969         }
1970     }
1971 
1972 }