javax.swing.plaf.basic.BasicComboBoxUI.java Source code

Java tutorial

Introduction

Here is the source code for javax.swing.plaf.basic.BasicComboBoxUI.java

Source

/*
 * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javax.swing.plaf.basic;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.accessibility.*;
import javax.swing.plaf.*;
import javax.swing.text.*;
import javax.swing.event.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import sun.awt.AppContext;
import sun.swing.DefaultLookup;
import sun.swing.SwingUtilities2;
import sun.swing.UIAction;

/**
 * Basic UI implementation for JComboBox.
 * <p>
 * The combo box is a compound component which means that it is an aggregate of
 * many simpler components. This class creates and manages the listeners
 * on the combo box and the combo box model. These listeners update the user
 * interface in response to changes in the properties and state of the combo box.
 * <p>
 * All event handling is handled by listener classes created with the
 * <code>createxxxListener()</code> methods and internal classes.
 * You can change the behavior of this class by overriding the
 * <code>createxxxListener()</code> methods and supplying your own
 * event listeners or subclassing from the ones supplied in this class.
 * <p>
 * For adding specific actions,
 * overide <code>installKeyboardActions</code> to add actions in response to
 * KeyStroke bindings. See the article <a href="https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html">How to Use Key Bindings</a>
 *
 * @author Arnaud Weber
 * @author Tom Santos
 * @author Mark Davidson
 */
public class BasicComboBoxUI extends ComboBoxUI {

    /**
     * The instance of {@code JComboBox}.
     */
    protected JComboBox<Object> comboBox;
    /**
     * This protected field is implementation specific. Do not access directly
     * or override.
     */
    protected boolean hasFocus = false;

    // Control the selection behavior of the JComboBox when it is used
    // in the JTable DefaultCellEditor.
    private boolean isTableCellEditor = false;
    private static final String IS_TABLE_CELL_EDITOR = "JComboBox.isTableCellEditor";

    /**
     * This list is for drawing the current item in the combo box.
     */
    protected JList<Object> listBox;

    /**
     * Used to render the currently selected item in the combo box.
     * It doesn't have anything to do with the popup's rendering.
     */
    protected CellRendererPane currentValuePane = new CellRendererPane();

    /**
     * The implementation of {@code ComboPopup} that is used to show the popup.
     */
    protected ComboPopup popup;

    /**
     * The Component that the @{code ComboBoxEditor} uses for editing.
     */
    protected Component editor;

    /**
     * The arrow button that invokes the popup.
     */
    protected JButton arrowButton;

    // Listeners that are attached to the JComboBox
    /**
     * This protected field is implementation specific. Do not access directly
     * or override. Override the listener construction method instead.
     *
     * @see #createKeyListener
     */
    protected KeyListener keyListener;
    /**
     * This protected field is implementation specific. Do not access directly
     * or override. Override the listener construction method instead.
     *
     * @see #createFocusListener
     */
    protected FocusListener focusListener;
    /**
     * This protected field is implementation specific. Do not access directly
     * or override. Override the listener construction method instead.
     *
     * @see #createPropertyChangeListener
     */
    protected PropertyChangeListener propertyChangeListener;

    /**
     * This protected field is implementation specific. Do not access directly
     * or override. Override the listener construction method instead.
     *
     * @see #createItemListener
     */
    protected ItemListener itemListener;

    // Listeners that the ComboPopup produces.
    /**
     * The {@code MouseListener} listens to events.
     */
    protected MouseListener popupMouseListener;

    /**
     * The {@code MouseMotionListener} listens to events.
     */
    protected MouseMotionListener popupMouseMotionListener;

    /**
     * The {@code KeyListener} listens to events.
     */
    protected KeyListener popupKeyListener;

    // This is used for knowing when to cache the minimum preferred size.
    // If the data in the list changes, the cached value get marked for recalc.
    // Added to the current JComboBox model
    /**
     * This protected field is implementation specific. Do not access directly
     * or override. Override the listener construction method instead.
     *
     * @see #createListDataListener
     */
    protected ListDataListener listDataListener;

    /**
     * Implements all the Listeners needed by this class, all existing
     * listeners redirect to it.
     */
    private Handler handler;

    /**
     * The time factor to treate the series of typed alphanumeric key
     * as prefix for first letter navigation.
     */
    private long timeFactor = 1000L;

    /**
     * This is tricky, this variables is needed for DefaultKeySelectionManager
     * to take into account time factor.
     */
    private long lastTime = 0L;
    private long time = 0L;

    /**
     * The default key selection manager
     */
    JComboBox.KeySelectionManager keySelectionManager;

    /**
     * The flag for recalculating the minimum preferred size.
     */
    protected boolean isMinimumSizeDirty = true;

    /**
     * The cached minimum preferred size.
     */
    protected Dimension cachedMinimumSize = new Dimension(0, 0);

    // Flag for calculating the display size
    private boolean isDisplaySizeDirty = true;

    // Cached the size that the display needs to render the largest item
    private Dimension cachedDisplaySize = new Dimension(0, 0);

    // Key used for lookup of the DefaultListCellRenderer in the AppContext.
    private static final Object COMBO_UI_LIST_CELL_RENDERER_KEY = new StringBuffer("DefaultListCellRendererKey");

    static final StringBuffer HIDE_POPUP_KEY = new StringBuffer("HidePopupKey");

    /**
     * Whether or not all cells have the same baseline.
     */
    private boolean sameBaseline;

    /**
     * Indicates whether or not the combo box button should be square.
     * If square, then the width and height are equal, and are both set to
     * the height of the combo minus appropriate insets.
     *
     * @since 1.7
     */
    protected boolean squareButton = true;

    /**
     * If specified, these insets act as padding around the cell renderer when
     * laying out and painting the "selected" item in the combo box. These
     * insets add to those specified by the cell renderer.
     *
     * @since 1.7
     */
    protected Insets padding;

    // Used for calculating the default size.
    private static ListCellRenderer<Object> getDefaultListCellRenderer() {
        @SuppressWarnings("unchecked")
        ListCellRenderer<Object> renderer = (ListCellRenderer) AppContext.getAppContext()
                .get(COMBO_UI_LIST_CELL_RENDERER_KEY);

        if (renderer == null) {
            renderer = new DefaultListCellRenderer();
            AppContext.getAppContext().put(COMBO_UI_LIST_CELL_RENDERER_KEY, new DefaultListCellRenderer());
        }
        return renderer;
    }

    /**
     * Populates ComboBox's actions.
     */
    static void loadActionMap(LazyActionMap map) {
        map.put(new Actions(Actions.HIDE));
        map.put(new Actions(Actions.PAGE_DOWN));
        map.put(new Actions(Actions.PAGE_UP));
        map.put(new Actions(Actions.HOME));
        map.put(new Actions(Actions.END));
        map.put(new Actions(Actions.DOWN));
        map.put(new Actions(Actions.DOWN_2));
        map.put(new Actions(Actions.TOGGLE));
        map.put(new Actions(Actions.TOGGLE_2));
        map.put(new Actions(Actions.UP));
        map.put(new Actions(Actions.UP_2));
        map.put(new Actions(Actions.ENTER));
    }

    //========================
    // begin UI Initialization
    //

    /**
     * Constructs a new instance of {@code BasicComboBoxUI}.
     *
     * @param c a component
     * @return a new instance of {@code BasicComboBoxUI}
     */
    public static ComponentUI createUI(JComponent c) {
        return new BasicComboBoxUI();
    }

    @Override
    public void installUI(JComponent c) {
        isMinimumSizeDirty = true;

        @SuppressWarnings("unchecked")
        JComboBox<Object> tmp = (JComboBox) c;
        comboBox = tmp;
        installDefaults();
        popup = createPopup();
        listBox = popup.getList();

        // Is this combo box a cell editor?
        Boolean inTable = (Boolean) c.getClientProperty(IS_TABLE_CELL_EDITOR);
        if (inTable != null) {
            isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false;
        }

        if (comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource) {
            comboBox.setRenderer(createRenderer());
        }

        if (comboBox.getEditor() == null || comboBox.getEditor() instanceof UIResource) {
            comboBox.setEditor(createEditor());
        }

        installListeners();
        installComponents();

        comboBox.setLayout(createLayoutManager());

        comboBox.setRequestFocusEnabled(true);

        installKeyboardActions();

        comboBox.putClientProperty("doNotCancelPopup", HIDE_POPUP_KEY);

        if (keySelectionManager == null || keySelectionManager instanceof UIResource) {
            keySelectionManager = new DefaultKeySelectionManager();
        }
        comboBox.setKeySelectionManager(keySelectionManager);
    }

    @Override
    public void uninstallUI(JComponent c) {
        setPopupVisible(comboBox, false);
        popup.uninstallingUI();

        uninstallKeyboardActions();

        comboBox.setLayout(null);

        uninstallComponents();
        uninstallListeners();
        uninstallDefaults();

        if (comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource) {
            comboBox.setRenderer(null);
        }

        ComboBoxEditor comboBoxEditor = comboBox.getEditor();
        if (comboBoxEditor instanceof UIResource) {
            if (comboBoxEditor.getEditorComponent().hasFocus()) {
                // Leave focus in JComboBox.
                comboBox.requestFocusInWindow();
            }
            comboBox.setEditor(null);
        }

        if (keySelectionManager instanceof UIResource) {
            comboBox.setKeySelectionManager(null);
        }

        handler = null;
        keyListener = null;
        focusListener = null;
        listDataListener = null;
        propertyChangeListener = null;
        popup = null;
        listBox = null;
        comboBox = null;
    }

    /**
     * Installs the default colors, default font, default renderer, and default
     * editor into the JComboBox.
     */
    protected void installDefaults() {
        LookAndFeel.installColorsAndFont(comboBox, "ComboBox.background", "ComboBox.foreground", "ComboBox.font");
        LookAndFeel.installBorder(comboBox, "ComboBox.border");
        LookAndFeel.installProperty(comboBox, "opaque", Boolean.TRUE);

        Long l = (Long) UIManager.get("ComboBox.timeFactor");
        timeFactor = l == null ? 1000L : l.longValue();

        //NOTE: this needs to default to true if not specified
        Boolean b = (Boolean) UIManager.get("ComboBox.squareButton");
        squareButton = b == null ? true : b;

        padding = UIManager.getInsets("ComboBox.padding");
    }

    /**
     * Creates and installs listeners for the combo box and its model.
     * This method is called when the UI is installed.
     */
    protected void installListeners() {
        if ((itemListener = createItemListener()) != null) {
            comboBox.addItemListener(itemListener);
        }
        if ((propertyChangeListener = createPropertyChangeListener()) != null) {
            comboBox.addPropertyChangeListener(propertyChangeListener);
        }
        if ((keyListener = createKeyListener()) != null) {
            comboBox.addKeyListener(keyListener);
        }
        if ((focusListener = createFocusListener()) != null) {
            comboBox.addFocusListener(focusListener);
        }
        if ((popupMouseListener = popup.getMouseListener()) != null) {
            comboBox.addMouseListener(popupMouseListener);
        }
        if ((popupMouseMotionListener = popup.getMouseMotionListener()) != null) {
            comboBox.addMouseMotionListener(popupMouseMotionListener);
        }
        if ((popupKeyListener = popup.getKeyListener()) != null) {
            comboBox.addKeyListener(popupKeyListener);
        }

        if (comboBox.getModel() != null) {
            if ((listDataListener = createListDataListener()) != null) {
                comboBox.getModel().addListDataListener(listDataListener);
            }
        }
    }

    /**
     * Uninstalls the default colors, default font, default renderer,
     * and default editor from the combo box.
     */
    protected void uninstallDefaults() {
        LookAndFeel.installColorsAndFont(comboBox, "ComboBox.background", "ComboBox.foreground", "ComboBox.font");
        LookAndFeel.uninstallBorder(comboBox);
    }

    /**
     * Removes the installed listeners from the combo box and its model.
     * The number and types of listeners removed and in this method should be
     * the same that was added in <code>installListeners</code>
     */
    protected void uninstallListeners() {
        if (keyListener != null) {
            comboBox.removeKeyListener(keyListener);
        }
        if (itemListener != null) {
            comboBox.removeItemListener(itemListener);
        }
        if (propertyChangeListener != null) {
            comboBox.removePropertyChangeListener(propertyChangeListener);
        }
        if (focusListener != null) {
            comboBox.removeFocusListener(focusListener);
        }
        if (popupMouseListener != null) {
            comboBox.removeMouseListener(popupMouseListener);
        }
        if (popupMouseMotionListener != null) {
            comboBox.removeMouseMotionListener(popupMouseMotionListener);
        }
        if (popupKeyListener != null) {
            comboBox.removeKeyListener(popupKeyListener);
        }
        if (comboBox.getModel() != null) {
            if (listDataListener != null) {
                comboBox.getModel().removeListDataListener(listDataListener);
            }
        }
    }

    /**
     * Creates the popup portion of the combo box.
     *
     * @return an instance of <code>ComboPopup</code>
     * @see ComboPopup
     */
    protected ComboPopup createPopup() {
        return new BasicComboPopup(comboBox);
    }

    /**
     * Creates a <code>KeyListener</code> which will be added to the
     * combo box. If this method returns null then it will not be added
     * to the combo box.
     *
     * @return an instance <code>KeyListener</code> or null
     */
    protected KeyListener createKeyListener() {
        return getHandler();
    }

    /**
     * Creates a <code>FocusListener</code> which will be added to the combo box.
     * If this method returns null then it will not be added to the combo box.
     *
     * @return an instance of a <code>FocusListener</code> or null
     */
    protected FocusListener createFocusListener() {
        return getHandler();
    }

    /**
     * Creates a list data listener which will be added to the
     * <code>ComboBoxModel</code>. If this method returns null then
     * it will not be added to the combo box model.
     *
     * @return an instance of a <code>ListDataListener</code> or null
     */
    protected ListDataListener createListDataListener() {
        return getHandler();
    }

    /**
     * Creates an <code>ItemListener</code> which will be added to the
     * combo box. If this method returns null then it will not
     * be added to the combo box.
     * <p>
     * Subclasses may override this method to return instances of their own
     * ItemEvent handlers.
     *
     * @return an instance of an <code>ItemListener</code> or null
     */
    protected ItemListener createItemListener() {
        return null;
    }

    /**
     * Creates a <code>PropertyChangeListener</code> which will be added to
     * the combo box. If this method returns null then it will not
     * be added to the combo box.
     *
     * @return an instance of a <code>PropertyChangeListener</code> or null
     */
    protected PropertyChangeListener createPropertyChangeListener() {
        return getHandler();
    }

    /**
     * Creates a layout manager for managing the components which make up the
     * combo box.
     *
     * @return an instance of a layout manager
     */
    protected LayoutManager createLayoutManager() {
        return getHandler();
    }

    /**
     * Creates the default renderer that will be used in a non-editiable combo
     * box. A default renderer will used only if a renderer has not been
     * explicitly set with <code>setRenderer</code>.
     *
     * @return a <code>ListCellRender</code> used for the combo box
     * @see javax.swing.JComboBox#setRenderer
     */
    protected ListCellRenderer<Object> createRenderer() {
        return new BasicComboBoxRenderer.UIResource();
    }

    /**
     * Creates the default editor that will be used in editable combo boxes.
     * A default editor will be used only if an editor has not been
     * explicitly set with <code>setEditor</code>.
     *
     * @return a <code>ComboBoxEditor</code> used for the combo box
     * @see javax.swing.JComboBox#setEditor
     */
    protected ComboBoxEditor createEditor() {
        return new BasicComboBoxEditor.UIResource();
    }

    /**
     * Returns the shared listener.
     */
    private Handler getHandler() {
        if (handler == null) {
            handler = new Handler();
        }
        return handler;
    }

    //
    // end UI Initialization
    //======================

    //======================
    // begin Inner classes
    //

    /**
     * This listener checks to see if the key event isn't a navigation key.  If
     * it finds a key event that wasn't a navigation key it dispatches it to
     * JComboBox.selectWithKeyChar() so that it can do type-ahead.
     *
     * This public inner class should be treated as protected.
     * Instantiate it only within subclasses of
     * <code>BasicComboBoxUI</code>.
     */
    public class KeyHandler extends KeyAdapter {
        @Override
        public void keyPressed(KeyEvent e) {
            getHandler().keyPressed(e);
        }
    }

    /**
     * This listener hides the popup when the focus is lost.  It also repaints
     * when focus is gained or lost.
     *
     * This public inner class should be treated as protected.
     * Instantiate it only within subclasses of
     * <code>BasicComboBoxUI</code>.
     */
    public class FocusHandler implements FocusListener {
        public void focusGained(FocusEvent e) {
            getHandler().focusGained(e);
        }

        public void focusLost(FocusEvent e) {
            getHandler().focusLost(e);
        }
    }

    /**
     * This listener watches for changes in the
     * <code>ComboBoxModel</code>.
     * <p>
     * This public inner class should be treated as protected.
     * Instantiate it only within subclasses of
     * <code>BasicComboBoxUI</code>.
     *
     * @see #createListDataListener
     */
    public class ListDataHandler implements ListDataListener {
        public void contentsChanged(ListDataEvent e) {
            getHandler().contentsChanged(e);
        }

        public void intervalAdded(ListDataEvent e) {
            getHandler().intervalAdded(e);
        }

        public void intervalRemoved(ListDataEvent e) {
            getHandler().intervalRemoved(e);
        }
    }

    /**
     * This listener watches for changes to the selection in the
     * combo box.
     * <p>
     * This public inner class should be treated as protected.
     * Instantiate it only within subclasses of
     * <code>BasicComboBoxUI</code>.
     *
     * @see #createItemListener
     */
    public class ItemHandler implements ItemListener {
        // This class used to implement behavior which is now redundant.
        public void itemStateChanged(ItemEvent e) {
        }
    }

    /**
     * This listener watches for bound properties that have changed in the
     * combo box.
     * <p>
     * Subclasses which wish to listen to combo box property changes should
     * call the superclass methods to ensure that the combo box ui correctly
     * handles property changes.
     * <p>
     * This public inner class should be treated as protected.
     * Instantiate it only within subclasses of
     * <code>BasicComboBoxUI</code>.
     *
     * @see #createPropertyChangeListener
     */
    public class PropertyChangeHandler implements PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent e) {
            getHandler().propertyChange(e);
        }
    }

    // Syncronizes the ToolTip text for the components within the combo box to be the
    // same value as the combo box ToolTip text.
    private void updateToolTipTextForChildren() {
        Component[] children = comboBox.getComponents();
        for (int i = 0; i < children.length; ++i) {
            if (children[i] instanceof JComponent) {
                ((JComponent) children[i]).setToolTipText(comboBox.getToolTipText());
            }
        }
    }

    /**
     * This layout manager handles the 'standard' layout of combo boxes.  It puts
     * the arrow button to the right and the editor to the left.  If there is no
     * editor it still keeps the arrow button to the right.
     *
     * This public inner class should be treated as protected.
     * Instantiate it only within subclasses of
     * <code>BasicComboBoxUI</code>.
     */
    public class ComboBoxLayoutManager implements LayoutManager {
        public void addLayoutComponent(String name, Component comp) {
        }

        public void removeLayoutComponent(Component comp) {
        }

        public Dimension preferredLayoutSize(Container parent) {
            return getHandler().preferredLayoutSize(parent);
        }

        public Dimension minimumLayoutSize(Container parent) {
            return getHandler().minimumLayoutSize(parent);
        }

        public void layoutContainer(Container parent) {
            getHandler().layoutContainer(parent);
        }
    }

    //
    // end Inner classes
    //====================

    //===============================
    // begin Sub-Component Management
    //

    /**
     * Creates and initializes the components which make up the
     * aggregate combo box. This method is called as part of the UI
     * installation process.
     */
    protected void installComponents() {
        arrowButton = createArrowButton();

        if (arrowButton != null) {
            comboBox.add(arrowButton);
            configureArrowButton();
        }

        if (comboBox.isEditable()) {
            addEditor();
        }

        comboBox.add(currentValuePane);
    }

    /**
     * The aggregate components which comprise the combo box are
     * unregistered and uninitialized. This method is called as part of the
     * UI uninstallation process.
     */
    protected void uninstallComponents() {
        if (arrowButton != null) {
            unconfigureArrowButton();
        }
        if (editor != null) {
            unconfigureEditor();
        }
        comboBox.removeAll(); // Just to be safe.
        arrowButton = null;
    }

    /**
     * This public method is implementation specific and should be private.
     * do not call or override. To implement a specific editor create a
     * custom <code>ComboBoxEditor</code>
     *
     * @see #createEditor
     * @see javax.swing.JComboBox#setEditor
     * @see javax.swing.ComboBoxEditor
     */
    public void addEditor() {
        removeEditor();
        editor = comboBox.getEditor().getEditorComponent();
        if (editor != null) {
            configureEditor();
            comboBox.add(editor);
            if (comboBox.isFocusOwner()) {
                // Switch focus to the editor component
                editor.requestFocusInWindow();
            }
        }
    }

    /**
     * This public method is implementation specific and should be private.
     * do not call or override.
     *
     * @see #addEditor
     */
    public void removeEditor() {
        if (editor != null) {
            unconfigureEditor();
            comboBox.remove(editor);
            editor = null;
        }
    }

    /**
     * This protected method is implementation specific and should be private.
     * do not call or override.
     *
     * @see #addEditor
     */
    protected void configureEditor() {
        // Should be in the same state as the combobox
        editor.setEnabled(comboBox.isEnabled());

        editor.setFocusable(comboBox.isFocusable());

        editor.setFont(comboBox.getFont());

        if (focusListener != null) {
            editor.addFocusListener(focusListener);
        }

        editor.addFocusListener(getHandler());

        comboBox.getEditor().addActionListener(getHandler());

        if (editor instanceof JComponent) {
            ((JComponent) editor).putClientProperty("doNotCancelPopup", HIDE_POPUP_KEY);
            ((JComponent) editor).setInheritsPopupMenu(true);
        }

        comboBox.configureEditor(comboBox.getEditor(), comboBox.getSelectedItem());

        editor.addPropertyChangeListener(propertyChangeListener);
    }

    /**
     * This protected method is implementation specific and should be private.
     * Do not call or override.
     *
     * @see #addEditor
     */
    protected void unconfigureEditor() {
        if (focusListener != null) {
            editor.removeFocusListener(focusListener);
        }

        editor.removePropertyChangeListener(propertyChangeListener);
        editor.removeFocusListener(getHandler());
        comboBox.getEditor().removeActionListener(getHandler());
    }

    /**
     * This public method is implementation specific and should be private. Do
     * not call or override.
     *
     * @see #createArrowButton
     */
    public void configureArrowButton() {
        if (arrowButton != null) {
            arrowButton.setEnabled(comboBox.isEnabled());
            arrowButton.setFocusable(comboBox.isFocusable());
            arrowButton.setRequestFocusEnabled(false);
            arrowButton.addMouseListener(popup.getMouseListener());
            arrowButton.addMouseMotionListener(popup.getMouseMotionListener());
            arrowButton.resetKeyboardActions();
            arrowButton.putClientProperty("doNotCancelPopup", HIDE_POPUP_KEY);
            arrowButton.setInheritsPopupMenu(true);
        }
    }

    /**
     * This public method is implementation specific and should be private. Do
     * not call or override.
     *
     * @see #createArrowButton
     */
    public void unconfigureArrowButton() {
        if (arrowButton != null) {
            arrowButton.removeMouseListener(popup.getMouseListener());
            arrowButton.removeMouseMotionListener(popup.getMouseMotionListener());
        }
    }

    /**
     * Creates a button which will be used as the control to show or hide
     * the popup portion of the combo box.
     *
     * @return a button which represents the popup control
     */
    protected JButton createArrowButton() {
        JButton button = new BasicArrowButton(BasicArrowButton.SOUTH,
                UIManager.getColor("ComboBox.buttonBackground"), UIManager.getColor("ComboBox.buttonShadow"),
                UIManager.getColor("ComboBox.buttonDarkShadow"), UIManager.getColor("ComboBox.buttonHighlight"));
        button.setName("ComboBox.arrowButton");
        return button;
    }

    //
    // end Sub-Component Management
    //===============================

    //================================
    // begin ComboBoxUI Implementation
    //

    /**
     * Tells if the popup is visible or not.
     */
    public boolean isPopupVisible(JComboBox<?> c) {
        return popup.isVisible();
    }

    /**
     * Hides the popup.
     */
    public void setPopupVisible(JComboBox<?> c, boolean v) {
        if (v) {
            popup.show();
        } else {
            popup.hide();
        }
    }

    /**
     * Determines if the JComboBox is focus traversable.  If the JComboBox is editable
     * this returns false, otherwise it returns true.
     */
    public boolean isFocusTraversable(JComboBox<?> c) {
        return !comboBox.isEditable();
    }

    //
    // end ComboBoxUI Implementation
    //==============================

    //=================================
    // begin ComponentUI Implementation
    @Override
    public void paint(Graphics g, JComponent c) {
        hasFocus = comboBox.hasFocus();
        if (!comboBox.isEditable()) {
            Rectangle r = rectangleForCurrentValue();
            paintCurrentValueBackground(g, r, hasFocus);
            paintCurrentValue(g, r, hasFocus);
        }
    }

    @Override
    public Dimension getPreferredSize(JComponent c) {
        return getMinimumSize(c);
    }

    /**
     * The minimum size is the size of the display area plus insets plus the button.
     */
    @Override
    public Dimension getMinimumSize(JComponent c) {
        if (!isMinimumSizeDirty) {
            return new Dimension(cachedMinimumSize);
        }
        Dimension size = getDisplaySize();
        Insets insets = getInsets();
        //calculate the width and height of the button
        int buttonHeight = size.height;
        int buttonWidth = squareButton ? buttonHeight : arrowButton.getPreferredSize().width;
        //adjust the size based on the button width
        size.height += insets.top + insets.bottom;
        size.width += insets.left + insets.right + buttonWidth;

        cachedMinimumSize.setSize(size.width, size.height);
        isMinimumSizeDirty = false;

        return new Dimension(size);
    }

    @Override
    public Dimension getMaximumSize(JComponent c) {
        return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
    }

    /**
     * Returns the baseline.
     *
     * @throws NullPointerException {@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @see javax.swing.JComponent#getBaseline(int, int)
     * @since 1.6
     */
    @Override
    public int getBaseline(JComponent c, int width, int height) {
        super.getBaseline(c, width, height);
        int baseline = -1;
        // force sameBaseline to be updated.
        getDisplaySize();
        if (sameBaseline) {
            Insets insets = c.getInsets();
            height = Math.max(height - insets.top - insets.bottom, 0);
            if (!comboBox.isEditable()) {
                ListCellRenderer<Object> renderer = comboBox.getRenderer();
                if (renderer == null) {
                    renderer = new DefaultListCellRenderer();
                }
                Object value = null;
                Object prototypeValue = comboBox.getPrototypeDisplayValue();
                if (prototypeValue != null) {
                    value = prototypeValue;
                } else if (comboBox.getModel().getSize() > 0) {
                    // Note, we're assuming the baseline is the same for all
                    // cells, if not, this needs to loop through all.
                    value = comboBox.getModel().getElementAt(0);
                }
                Component component = renderer.getListCellRendererComponent(listBox, value, -1, false, false);
                if (component instanceof JLabel) {
                    JLabel label = (JLabel) component;
                    String text = label.getText();
                    if ((text == null) || text.isEmpty()) {
                        label.setText(" ");
                    }
                }
                if (component instanceof JComponent) {
                    component.setFont(comboBox.getFont());
                }
                baseline = component.getBaseline(width, height);
            } else {
                baseline = editor.getBaseline(width, height);
            }
            if (baseline > 0) {
                baseline += insets.top;
            }
        }
        return baseline;
    }

    /**
     * Returns an enum indicating how the baseline of the component
     * changes as the size changes.
     *
     * @throws NullPointerException {@inheritDoc}
     * @see javax.swing.JComponent#getBaseline(int, int)
     * @since 1.6
     */
    @Override
    public Component.BaselineResizeBehavior getBaselineResizeBehavior(JComponent c) {
        super.getBaselineResizeBehavior(c);
        // Force sameBaseline to be updated.
        getDisplaySize();
        if (comboBox.isEditable()) {
            return editor.getBaselineResizeBehavior();
        } else if (sameBaseline) {
            ListCellRenderer<Object> renderer = comboBox.getRenderer();
            if (renderer == null) {
                renderer = new DefaultListCellRenderer();
            }
            Object value = null;
            Object prototypeValue = comboBox.getPrototypeDisplayValue();
            if (prototypeValue != null) {
                value = prototypeValue;
            } else if (comboBox.getModel().getSize() > 0) {
                // Note, we're assuming the baseline is the same for all
                // cells, if not, this needs to loop through all.
                value = comboBox.getModel().getElementAt(0);
            }
            if (value != null) {
                Component component = renderer.getListCellRendererComponent(listBox, value, -1, false, false);
                return component.getBaselineResizeBehavior();
            }
        }
        return Component.BaselineResizeBehavior.OTHER;
    }

    // This is currently hacky...
    @Override
    public int getAccessibleChildrenCount(JComponent c) {
        if (comboBox.isEditable()) {
            return 2;
        } else {
            return 1;
        }
    }

    // This is currently hacky...
    @Override
    public Accessible getAccessibleChild(JComponent c, int i) {
        // 0 = the popup
        // 1 = the editor
        switch (i) {
        case 0:
            if (popup instanceof Accessible) {
                AccessibleContext ac = ((Accessible) popup).getAccessibleContext();
                ac.setAccessibleParent(comboBox);
                return (Accessible) popup;
            }
            break;
        case 1:
            if (comboBox.isEditable() && (editor instanceof Accessible)) {
                AccessibleContext ac = ((Accessible) editor).getAccessibleContext();
                ac.setAccessibleParent(comboBox);
                return (Accessible) editor;
            }
            break;
        }
        return null;
    }

    //
    // end ComponentUI Implementation
    //===============================

    //======================
    // begin Utility Methods
    //

    /**
     * Returns whether or not the supplied keyCode maps to a key that is used for
     * navigation.  This is used for optimizing key input by only passing non-
     * navigation keys to the type-ahead mechanism.  Subclasses should override this
     * if they change the navigation keys.
     *
     * @param keyCode a key code
     * @return {@code true} if the supplied {@code keyCode} maps to a navigation key
     */
    protected boolean isNavigationKey(int keyCode) {
        return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_KP_UP
                || keyCode == KeyEvent.VK_KP_DOWN;
    }

    private boolean isNavigationKey(int keyCode, int modifiers) {
        InputMap inputMap = comboBox.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        KeyStroke key = KeyStroke.getKeyStroke(keyCode, modifiers);

        if (inputMap != null && inputMap.get(key) != null) {
            return true;
        }
        return false;
    }

    /**
     * Selects the next item in the list.  It won't change the selection if the
     * currently selected item is already the last item.
     */
    protected void selectNextPossibleValue() {
        int si;

        if (comboBox.isPopupVisible()) {
            si = listBox.getSelectedIndex();
        } else {
            si = comboBox.getSelectedIndex();
        }

        if (si < comboBox.getModel().getSize() - 1) {
            listBox.setSelectedIndex(si + 1);
            listBox.ensureIndexIsVisible(si + 1);
            if (!isTableCellEditor) {
                if (!(UIManager.getBoolean("ComboBox.noActionOnKeyNavigation") && comboBox.isPopupVisible())) {
                    comboBox.setSelectedIndex(si + 1);
                }
            }
            comboBox.repaint();
        }
    }

    /**
     * Selects the previous item in the list.  It won't change the selection if the
     * currently selected item is already the first item.
     */
    protected void selectPreviousPossibleValue() {
        int si;

        if (comboBox.isPopupVisible()) {
            si = listBox.getSelectedIndex();
        } else {
            si = comboBox.getSelectedIndex();
        }

        if (si > 0) {
            listBox.setSelectedIndex(si - 1);
            listBox.ensureIndexIsVisible(si - 1);
            if (!isTableCellEditor) {
                if (!(UIManager.getBoolean("ComboBox.noActionOnKeyNavigation") && comboBox.isPopupVisible())) {
                    comboBox.setSelectedIndex(si - 1);
                }
            }
            comboBox.repaint();
        }
    }

    /**
     * Hides the popup if it is showing and shows the popup if it is hidden.
     */
    protected void toggleOpenClose() {
        setPopupVisible(comboBox, !isPopupVisible(comboBox));
    }

    /**
     * Returns the area that is reserved for drawing the currently selected item.
     *
     * @return the area that is reserved for drawing the currently selected item
     */
    protected Rectangle rectangleForCurrentValue() {
        int width = comboBox.getWidth();
        int height = comboBox.getHeight();
        Insets insets = getInsets();
        int buttonSize = height - (insets.top + insets.bottom);
        if (arrowButton != null) {
            buttonSize = arrowButton.getWidth();
        }
        if (BasicGraphicsUtils.isLeftToRight(comboBox)) {
            return new Rectangle(insets.left, insets.top, width - (insets.left + insets.right + buttonSize),
                    height - (insets.top + insets.bottom));
        } else {
            return new Rectangle(insets.left + buttonSize, insets.top,
                    width - (insets.left + insets.right + buttonSize), height - (insets.top + insets.bottom));
        }
    }

    /**
     * Gets the insets from the JComboBox.
     *
     * @return the insets
     */
    protected Insets getInsets() {
        return comboBox.getInsets();
    }

    //
    // end Utility Methods
    //====================

    //===============================
    // begin Painting Utility Methods
    //

    /**
     * Paints the currently selected item.
     *
     * @param g an instance of {@code Graphics}
     * @param bounds a bounding rectangle to render to
     * @param hasFocus is focused
     */
    public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus) {
        ListCellRenderer<Object> renderer = comboBox.getRenderer();
        Component c;

        if (hasFocus && !isPopupVisible(comboBox)) {
            c = renderer.getListCellRendererComponent(listBox, comboBox.getSelectedItem(), -1, true, false);
        } else {
            c = renderer.getListCellRendererComponent(listBox, comboBox.getSelectedItem(), -1, false, false);
            c.setBackground(UIManager.getColor("ComboBox.background"));
        }
        c.setFont(comboBox.getFont());
        if (hasFocus && !isPopupVisible(comboBox)) {
            c.setForeground(listBox.getSelectionForeground());
            c.setBackground(listBox.getSelectionBackground());
        } else {
            if (comboBox.isEnabled()) {
                c.setForeground(comboBox.getForeground());
                c.setBackground(comboBox.getBackground());
            } else {
                c.setForeground(DefaultLookup.getColor(comboBox, this, "ComboBox.disabledForeground", null));
                c.setBackground(DefaultLookup.getColor(comboBox, this, "ComboBox.disabledBackground", null));
            }
        }

        // Fix for 4238829: should lay out the JPanel.
        boolean shouldValidate = false;
        if (c instanceof JPanel) {
            shouldValidate = true;
        }

        int x = bounds.x, y = bounds.y, w = bounds.width, h = bounds.height;
        if (padding != null) {
            x = bounds.x + padding.left;
            y = bounds.y + padding.top;
            w = bounds.width - (padding.left + padding.right);
            h = bounds.height - (padding.top + padding.bottom);
        }

        currentValuePane.paintComponent(g, c, comboBox, x, y, w, h, shouldValidate);
    }

    /**
     * Paints the background of the currently selected item.
     *
     * @param g an instance of {@code Graphics}
     * @param bounds a bounding rectangle to render to
     * @param hasFocus is focused
     */
    public void paintCurrentValueBackground(Graphics g, Rectangle bounds, boolean hasFocus) {
        Color t = g.getColor();
        if (comboBox.isEnabled())
            g.setColor(DefaultLookup.getColor(comboBox, this, "ComboBox.background", null));
        else
            g.setColor(DefaultLookup.getColor(comboBox, this, "ComboBox.disabledBackground", null));
        g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
        g.setColor(t);
    }

    /**
     * Repaint the currently selected item.
     */
    void repaintCurrentValue() {
        Rectangle r = rectangleForCurrentValue();
        comboBox.repaint(r.x, r.y, r.width, r.height);
    }

    //
    // end Painting Utility Methods
    //=============================

    //===============================
    // begin Size Utility Methods
    //

    /**
     * Return the default size of an empty display area of the combo box using
     * the current renderer and font.
     *
     * @return the size of an empty display area
     * @see #getDisplaySize
     */
    protected Dimension getDefaultSize() {
        // Calculates the height and width using the default text renderer
        Dimension d = getSizeForComponent(
                getDefaultListCellRenderer().getListCellRendererComponent(listBox, " ", -1, false, false));

        return new Dimension(d.width, d.height);
    }

    /**
     * Returns the calculated size of the display area. The display area is the
     * portion of the combo box in which the selected item is displayed. This
     * method will use the prototype display value if it has been set.
     * <p>
     * For combo boxes with a non trivial number of items, it is recommended to
     * use a prototype display value to significantly speed up the display
     * size calculation.
     *
     * @return the size of the display area calculated from the combo box items
     * @see javax.swing.JComboBox#setPrototypeDisplayValue
     */
    protected Dimension getDisplaySize() {
        if (!isDisplaySizeDirty) {
            return new Dimension(cachedDisplaySize);
        }
        Dimension result = new Dimension();

        ListCellRenderer<Object> renderer = comboBox.getRenderer();
        if (renderer == null) {
            renderer = new DefaultListCellRenderer();
        }

        sameBaseline = true;

        Object prototypeValue = comboBox.getPrototypeDisplayValue();
        if (prototypeValue != null) {
            // Calculates the dimension based on the prototype value
            result = getSizeForComponent(
                    renderer.getListCellRendererComponent(listBox, prototypeValue, -1, false, false));
        } else {
            // Calculate the dimension by iterating over all the elements in the combo
            // box list.
            ComboBoxModel<Object> model = comboBox.getModel();
            int modelSize = model.getSize();
            int baseline = -1;
            Dimension d;

            Component cpn;

            if (modelSize > 0) {
                for (int i = 0; i < modelSize; i++) {
                    // Calculates the maximum height and width based on the largest
                    // element
                    Object value = model.getElementAt(i);
                    Component c = renderer.getListCellRendererComponent(listBox, value, -1, false, false);
                    d = getSizeForComponent(c);
                    if (sameBaseline && value != null && (!(value instanceof String) || !"".equals(value))) {
                        int newBaseline = c.getBaseline(d.width, d.height);
                        if (newBaseline == -1) {
                            sameBaseline = false;
                        } else if (baseline == -1) {
                            baseline = newBaseline;
                        } else if (baseline != newBaseline) {
                            sameBaseline = false;
                        }
                    }
                    result.width = Math.max(result.width, d.width);
                    result.height = Math.max(result.height, d.height);
                }
            } else {
                result = getDefaultSize();
                if (comboBox.isEditable()) {
                    result.width = 100;
                }
            }
        }

        if (comboBox.isEditable()) {
            Dimension d = editor.getPreferredSize();
            result.width = Math.max(result.width, d.width);
            result.height = Math.max(result.height, d.height);
        }

        // calculate in the padding
        if (padding != null) {
            result.width += padding.left + padding.right;
            result.height += padding.top + padding.bottom;
        }

        // Set the cached value
        cachedDisplaySize.setSize(result.width, result.height);
        isDisplaySizeDirty = false;

        return result;
    }

    /**
     * Returns the size a component would have if used as a cell renderer.
     *
     * @param comp a {@code Component} to check
     * @return size of the component
     * @since 1.7
     */
    protected Dimension getSizeForComponent(Component comp) {
        // This has been refactored out in hopes that it may be investigated and
        // simplified for the next major release. adding/removing
        // the component to the currentValuePane and changing the font may be
        // redundant operations.
        currentValuePane.add(comp);
        comp.setFont(comboBox.getFont());
        Dimension d = comp.getPreferredSize();
        currentValuePane.remove(comp);
        return d;
    }

    //
    // end Size Utility Methods
    //=============================

    //=================================
    // begin Keyboard Action Management
    //

    /**
     * Adds keyboard actions to the JComboBox.  Actions on enter and esc are already
     * supplied.  Add more actions as you need them.
     */
    protected void installKeyboardActions() {
        InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        SwingUtilities.replaceUIInputMap(comboBox, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km);

        LazyActionMap.installLazyActionMap(comboBox, BasicComboBoxUI.class, "ComboBox.actionMap");
    }

    InputMap getInputMap(int condition) {
        if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
            return (InputMap) DefaultLookup.get(comboBox, this, "ComboBox.ancestorInputMap");
        }
        return null;
    }

    boolean isTableCellEditor() {
        return isTableCellEditor;
    }

    /**
     * Removes the focus InputMap and ActionMap.
     */
    protected void uninstallKeyboardActions() {
        SwingUtilities.replaceUIInputMap(comboBox, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
        SwingUtilities.replaceUIActionMap(comboBox, null);
    }

    //
    // Actions
    //
    private static class Actions extends UIAction {
        private static final String HIDE = "hidePopup";
        private static final String DOWN = "selectNext";
        private static final String DOWN_2 = "selectNext2";
        private static final String TOGGLE = "togglePopup";
        private static final String TOGGLE_2 = "spacePopup";
        private static final String UP = "selectPrevious";
        private static final String UP_2 = "selectPrevious2";
        private static final String ENTER = "enterPressed";
        private static final String PAGE_DOWN = "pageDownPassThrough";
        private static final String PAGE_UP = "pageUpPassThrough";
        private static final String HOME = "homePassThrough";
        private static final String END = "endPassThrough";

        Actions(String name) {
            super(name);
        }

        public void actionPerformed(ActionEvent e) {
            String key = getName();
            @SuppressWarnings("unchecked")
            JComboBox<Object> comboBox = (JComboBox) e.getSource();
            BasicComboBoxUI ui = (BasicComboBoxUI) BasicLookAndFeel.getUIOfType(comboBox.getUI(),
                    BasicComboBoxUI.class);
            if (key == HIDE) {
                comboBox.firePopupMenuCanceled();
                comboBox.setPopupVisible(false);
            } else if (key == PAGE_DOWN || key == PAGE_UP || key == HOME || key == END) {
                int index = getNextIndex(comboBox, key);
                if (index >= 0 && index < comboBox.getItemCount()) {
                    if (UIManager.getBoolean("ComboBox.noActionOnKeyNavigation") && comboBox.isPopupVisible()) {
                        ui.listBox.setSelectedIndex(index);
                        ui.listBox.ensureIndexIsVisible(index);
                        comboBox.repaint();
                    } else {
                        comboBox.setSelectedIndex(index);
                    }
                }
            } else if (key == DOWN) {
                if (comboBox.isShowing()) {
                    if (comboBox.isPopupVisible()) {
                        if (ui != null) {
                            ui.selectNextPossibleValue();
                        }
                    } else {
                        comboBox.setPopupVisible(true);
                    }
                }
            } else if (key == DOWN_2) {
                // Special case in which pressing the arrow keys will not
                // make the popup appear - except for editable combo boxes
                // and combo boxes inside a table.
                if (comboBox.isShowing()) {
                    if ((comboBox.isEditable() || (ui != null && ui.isTableCellEditor()))
                            && !comboBox.isPopupVisible()) {
                        comboBox.setPopupVisible(true);
                    } else {
                        if (ui != null) {
                            ui.selectNextPossibleValue();
                        }
                    }
                }
            } else if (key == TOGGLE || key == TOGGLE_2) {
                if (ui != null && (key == TOGGLE || !comboBox.isEditable())) {
                    if (ui.isTableCellEditor()) {
                        // Forces the selection of the list item if the
                        // combo box is in a JTable.
                        comboBox.setSelectedIndex(ui.popup.getList().getSelectedIndex());
                    } else {
                        comboBox.setPopupVisible(!comboBox.isPopupVisible());
                    }
                }
            } else if (key == UP) {
                if (ui != null) {
                    if (ui.isPopupVisible(comboBox)) {
                        ui.selectPreviousPossibleValue();
                    } else if (DefaultLookup.getBoolean(comboBox, ui, "ComboBox.showPopupOnNavigation", false)) {
                        ui.setPopupVisible(comboBox, true);
                    }
                }
            } else if (key == UP_2) {
                // Special case in which pressing the arrow keys will not
                // make the popup appear - except for editable combo boxes.
                if (comboBox.isShowing() && ui != null) {
                    if (comboBox.isEditable() && !comboBox.isPopupVisible()) {
                        comboBox.setPopupVisible(true);
                    } else {
                        ui.selectPreviousPossibleValue();
                    }
                }
            }

            else if (key == ENTER) {
                if (comboBox.isPopupVisible()) {
                    // If ComboBox.noActionOnKeyNavigation is set,
                    // forse selection of list item
                    if (UIManager.getBoolean("ComboBox.noActionOnKeyNavigation")) {
                        Object listItem = ui.popup.getList().getSelectedValue();
                        if (listItem != null) {
                            comboBox.getEditor().setItem(listItem);
                            comboBox.setSelectedItem(listItem);
                        }
                        comboBox.setPopupVisible(false);
                    } else {
                        // Forces the selection of the list item
                        boolean isEnterSelectablePopup = UIManager.getBoolean("ComboBox.isEnterSelectablePopup");
                        if (!comboBox.isEditable() || isEnterSelectablePopup || ui.isTableCellEditor) {
                            Object listItem = ui.popup.getList().getSelectedValue();
                            if (listItem != null) {
                                // Use the selected value from popup
                                // to set the selected item in combo box,
                                // but ensure before that JComboBox.actionPerformed()
                                // won't use editor's value to set the selected item
                                comboBox.getEditor().setItem(listItem);
                                comboBox.setSelectedItem(listItem);
                            }
                        }
                        comboBox.setPopupVisible(false);
                    }
                } else {
                    // Hide combo box if it is a table cell editor
                    if (ui.isTableCellEditor && !comboBox.isEditable()) {
                        comboBox.setSelectedItem(comboBox.getSelectedItem());
                    }
                    // Call the default button binding.
                    // This is a pretty messy way of passing an event through
                    // to the root pane.
                    JRootPane root = SwingUtilities.getRootPane(comboBox);
                    if (root != null) {
                        InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
                        ActionMap am = root.getActionMap();
                        if (im != null && am != null) {
                            Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
                            if (obj != null) {
                                Action action = am.get(obj);
                                if (action != null) {
                                    action.actionPerformed(new ActionEvent(root, e.getID(), e.getActionCommand(),
                                            e.getWhen(), e.getModifiers()));
                                }
                            }
                        }
                    }
                }
            }
        }

        private int getNextIndex(JComboBox<?> comboBox, String key) {
            int listHeight = comboBox.getMaximumRowCount();

            int selectedIndex = comboBox.getSelectedIndex();
            if (UIManager.getBoolean("ComboBox.noActionOnKeyNavigation")
                    && (comboBox.getUI() instanceof BasicComboBoxUI)) {
                selectedIndex = ((BasicComboBoxUI) comboBox.getUI()).listBox.getSelectedIndex();
            }

            if (key == PAGE_UP) {
                int index = selectedIndex - listHeight;
                return (index < 0 ? 0 : index);
            } else if (key == PAGE_DOWN) {
                int index = selectedIndex + listHeight;
                int max = comboBox.getItemCount();
                return (index < max ? index : max - 1);
            } else if (key == HOME) {
                return 0;
            } else if (key == END) {
                return comboBox.getItemCount() - 1;
            }
            return comboBox.getSelectedIndex();
        }

        @Override
        public boolean accept(Object c) {
            if (getName() == HIDE) {
                return (c != null && ((JComboBox) c).isPopupVisible());
            }
            return true;
        }
    }
    //
    // end Keyboard Action Management
    //===============================

    //
    // Shared Handler, implements all listeners
    //
    private class Handler implements ActionListener, FocusListener, KeyListener, LayoutManager, ListDataListener,
            PropertyChangeListener {
        //
        // PropertyChangeListener
        //
        public void propertyChange(PropertyChangeEvent e) {
            String propertyName = e.getPropertyName();
            if (e.getSource() == editor) {
                // If the border of the editor changes then this can effect
                // the size of the editor which can cause the combo's size to
                // become invalid so we need to clear size caches
                if ("border".equals(propertyName)) {
                    isMinimumSizeDirty = true;
                    isDisplaySizeDirty = true;
                    comboBox.revalidate();
                }
            } else {
                @SuppressWarnings("unchecked")
                JComboBox<?> comboBox = (JComboBox) e.getSource();
                if (propertyName == "model") {
                    @SuppressWarnings("unchecked")
                    ComboBoxModel<?> newModel = (ComboBoxModel) e.getNewValue();
                    @SuppressWarnings("unchecked")
                    ComboBoxModel<?> oldModel = (ComboBoxModel) e.getOldValue();

                    if (oldModel != null && listDataListener != null) {
                        oldModel.removeListDataListener(listDataListener);
                    }

                    if (newModel != null && listDataListener != null) {
                        newModel.addListDataListener(listDataListener);
                    }

                    if (editor != null) {
                        comboBox.configureEditor(comboBox.getEditor(), comboBox.getSelectedItem());
                    }
                    isMinimumSizeDirty = true;
                    isDisplaySizeDirty = true;
                    comboBox.revalidate();
                    comboBox.repaint();
                } else if (propertyName == "editor" && comboBox.isEditable()) {
                    addEditor();
                    comboBox.revalidate();
                } else if (propertyName == "editable") {
                    if (comboBox.isEditable()) {
                        comboBox.setRequestFocusEnabled(false);
                        addEditor();
                    } else {
                        comboBox.setRequestFocusEnabled(true);
                        removeEditor();
                    }
                    updateToolTipTextForChildren();
                    comboBox.revalidate();
                } else if (propertyName == "enabled") {
                    boolean enabled = comboBox.isEnabled();
                    if (editor != null)
                        editor.setEnabled(enabled);
                    if (arrowButton != null)
                        arrowButton.setEnabled(enabled);
                    comboBox.repaint();
                } else if (propertyName == "focusable") {
                    boolean focusable = comboBox.isFocusable();
                    if (editor != null)
                        editor.setFocusable(focusable);
                    if (arrowButton != null)
                        arrowButton.setFocusable(focusable);
                    comboBox.repaint();
                } else if (propertyName == "maximumRowCount") {
                    if (isPopupVisible(comboBox)) {
                        setPopupVisible(comboBox, false);
                        setPopupVisible(comboBox, true);
                    }
                } else if (propertyName == "font") {
                    listBox.setFont(comboBox.getFont());
                    if (editor != null) {
                        editor.setFont(comboBox.getFont());
                    }
                    isMinimumSizeDirty = true;
                    isDisplaySizeDirty = true;
                    comboBox.validate();
                } else if (SwingUtilities2.isScaleChanged(e)) {
                    isMinimumSizeDirty = true;
                    isDisplaySizeDirty = true;
                    comboBox.validate();
                } else if (propertyName == JComponent.TOOL_TIP_TEXT_KEY) {
                    updateToolTipTextForChildren();
                } else if (propertyName == BasicComboBoxUI.IS_TABLE_CELL_EDITOR) {
                    Boolean inTable = (Boolean) e.getNewValue();
                    isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false;
                } else if (propertyName == "prototypeDisplayValue") {
                    isMinimumSizeDirty = true;
                    isDisplaySizeDirty = true;
                    comboBox.revalidate();
                } else if (propertyName == "renderer") {
                    isMinimumSizeDirty = true;
                    isDisplaySizeDirty = true;
                    comboBox.revalidate();
                }
            }
        }

        //
        // KeyListener
        //

        // This listener checks to see if the key event isn't a navigation
        // key.  If it finds a key event that wasn't a navigation key it
        // dispatches it to JComboBox.selectWithKeyChar() so that it can do
        // type-ahead.
        @SuppressWarnings("deprecation")
        public void keyPressed(KeyEvent e) {
            if (isNavigationKey(e.getKeyCode(), e.getModifiers())) {
                lastTime = 0L;
            } else if (comboBox.isEnabled() && comboBox.getModel().getSize() != 0 && isTypeAheadKey(e)
                    && e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
                time = e.getWhen();
                if (comboBox.selectWithKeyChar(e.getKeyChar())) {
                    e.consume();
                }
            }
        }

        public void keyTyped(KeyEvent e) {
        }

        public void keyReleased(KeyEvent e) {
        }

        private boolean isTypeAheadKey(KeyEvent e) {
            return !e.isAltDown() && !BasicGraphicsUtils.isMenuShortcutKeyDown(e);
        }

        //
        // FocusListener
        //
        // NOTE: The class is added to both the Editor and ComboBox.
        // The combo box listener hides the popup when the focus is lost.
        // It also repaints when focus is gained or lost.

        public void focusGained(FocusEvent e) {
            ComboBoxEditor comboBoxEditor = comboBox.getEditor();

            if ((comboBoxEditor != null) && (e.getSource() == comboBoxEditor.getEditorComponent())) {
                return;
            }
            hasFocus = true;
            comboBox.repaint();

            if (comboBox.isEditable() && editor != null) {
                editor.requestFocus();
            }
        }

        public void focusLost(FocusEvent e) {
            ComboBoxEditor editor = comboBox.getEditor();
            if ((editor != null) && (e.getSource() == editor.getEditorComponent())) {
                Object item = editor.getItem();

                Object selectedItem = comboBox.getSelectedItem();
                if (!e.isTemporary() && item != null && !item.equals((selectedItem == null) ? "" : selectedItem)) {
                    comboBox.actionPerformed(
                            new ActionEvent(editor, 0, "", EventQueue.getMostRecentEventTime(), 0));
                }
            }

            hasFocus = false;
            if (!e.isTemporary()) {
                setPopupVisible(comboBox, false);
            }
            comboBox.repaint();
        }

        //
        // ListDataListener
        //

        // This listener watches for changes in the ComboBoxModel
        public void contentsChanged(ListDataEvent e) {
            if (!(e.getIndex0() == -1 && e.getIndex1() == -1)) {
                isMinimumSizeDirty = true;
                comboBox.revalidate();
            }

            // set the editor with the selected item since this
            // is the event handler for a selected item change.
            if (comboBox.isEditable() && editor != null) {
                comboBox.configureEditor(comboBox.getEditor(), comboBox.getSelectedItem());
            }

            isDisplaySizeDirty = true;
            comboBox.repaint();
        }

        public void intervalAdded(ListDataEvent e) {
            contentsChanged(e);
        }

        public void intervalRemoved(ListDataEvent e) {
            contentsChanged(e);
        }

        //
        // LayoutManager
        //

        // This layout manager handles the 'standard' layout of combo boxes.
        // It puts the arrow button to the right and the editor to the left.
        // If there is no editor it still keeps the arrow button to the right.
        public void addLayoutComponent(String name, Component comp) {
        }

        public void removeLayoutComponent(Component comp) {
        }

        public Dimension preferredLayoutSize(Container parent) {
            return parent.getPreferredSize();
        }

        public Dimension minimumLayoutSize(Container parent) {
            return parent.getMinimumSize();
        }

        public void layoutContainer(Container parent) {
            @SuppressWarnings("unchecked")
            JComboBox<?> cb = (JComboBox) parent;
            int width = cb.getWidth();
            int height = cb.getHeight();

            Insets insets = getInsets();
            int buttonHeight = height - (insets.top + insets.bottom);
            int buttonWidth = buttonHeight;
            if (arrowButton != null) {
                Insets arrowInsets = arrowButton.getInsets();
                buttonWidth = squareButton ? buttonHeight
                        : arrowButton.getPreferredSize().width + arrowInsets.left + arrowInsets.right;
            }
            Rectangle cvb;

            if (arrowButton != null) {
                if (BasicGraphicsUtils.isLeftToRight(cb)) {
                    arrowButton.setBounds(width - (insets.right + buttonWidth), insets.top, buttonWidth,
                            buttonHeight);
                } else {
                    arrowButton.setBounds(insets.left, insets.top, buttonWidth, buttonHeight);
                }
            }
            if (editor != null) {
                cvb = rectangleForCurrentValue();
                editor.setBounds(cvb);
            }
        }

        //
        // ActionListener
        //
        // Fix for 4515752: Forward the Enter pressed on the
        // editable combo box to the default button

        // Note: This could depend on event ordering. The first ActionEvent
        // from the editor may be handled by the JComboBox in which case, the
        // enterPressed action will always be invoked.
        public void actionPerformed(ActionEvent evt) {
            Object item = comboBox.getEditor().getItem();
            if (item != null) {
                if (!comboBox.isPopupVisible() && !item.equals(comboBox.getSelectedItem())) {
                    comboBox.setSelectedItem(comboBox.getEditor().getItem());
                }
                ActionMap am = comboBox.getActionMap();
                if (am != null) {
                    Action action = am.get("enterPressed");
                    if (action != null) {
                        action.actionPerformed(
                                new ActionEvent(comboBox, evt.getID(), evt.getActionCommand(), evt.getModifiers()));
                    }
                }
            }
        }
    }

    class DefaultKeySelectionManager implements JComboBox.KeySelectionManager, UIResource {
        private String prefix = "";
        private String typedString = "";

        public int selectionForKey(char aKey, ComboBoxModel<?> aModel) {
            if (lastTime == 0L) {
                prefix = "";
                typedString = "";
            }
            boolean startingFromSelection = true;

            int startIndex = comboBox.getSelectedIndex();
            if (time - lastTime < timeFactor) {
                typedString += aKey;
                if ((prefix.length() == 1) && (aKey == prefix.charAt(0))) {
                    // Subsequent same key presses move the keyboard focus to the next
                    // object that starts with the same letter.
                    startIndex++;
                } else {
                    prefix = typedString;
                }
            } else {
                startIndex++;
                typedString = "" + aKey;
                prefix = typedString;
            }
            lastTime = time;

            if (startIndex < 0 || startIndex >= aModel.getSize()) {
                startingFromSelection = false;
                startIndex = 0;
            }
            int index = listBox.getNextMatch(prefix, startIndex, Position.Bias.Forward);
            if (index < 0 && startingFromSelection) { // wrap
                index = listBox.getNextMatch(prefix, 0, Position.Bias.Forward);
            }
            return index;
        }
    }

}