4199622: RFE: JComboBox shouldn't sending ActionEvents for keyboard navigation
Reviewed-by: alexp, alexsch
/*
* Copyright (c) 1997, 2013, 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.UIAction;
/**
* Basic UI implementation for JComboBox.
* <p>
* The combo box is a compound component which means that it is an agregate 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="http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html">Keyboard Bindings in Swing</a>
* at <a href="http://java.sun.com/products/jfc/tsc"><em>The Swing Connection</em></a>.
*
* @author Arnaud Weber
* @author Tom Santos
* @author Mark Davidson
*/
public class BasicComboBoxUI extends ComboBoxUI {
protected JComboBox 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 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 ComboPopup that is used to show the popup.
protected ComboPopup popup;
// The Component that the 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.
protected MouseListener popupMouseListener;
protected MouseMotionListener popupMouseMotionListener;
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;
// Flag for recalculating the minimum preferred size.
protected boolean isMinimumSizeDirty = true;
// 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 getDefaultListCellRenderer() {
ListCellRenderer 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
//
public static ComponentUI createUI(JComponent c) {
return new BasicComboBoxUI();
}
@Override
public void installUI( JComponent c ) {
isMinimumSizeDirty = true;
comboBox = (JComboBox)c;
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 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();
comboBox.add( arrowButton );
if (arrowButton != null) {
configureArrowButton();
}
if ( comboBox.isEditable() ) {
addEditor();
}
comboBox.add( currentValuePane );
}
/**
* The aggregate components which compise 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 minumum 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 = height - insets.top - insets.bottom;
if (!comboBox.isEditable()) {
ListCellRenderer 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) {
value = " ";
} else if (value instanceof String && "".equals(value)) {
value = " ";
}
Component component = renderer.
getListCellRendererComponent(listBox, value, -1,
false, false);
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 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.
*/
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.
*/
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.
*/
protected Insets getInsets() {
return comboBox.getInsets();
}
//
// end Utility Methods
//====================
//===============================
// begin Painting Utility Methods
//
/**
* Paints the currently selected item.
*/
public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) {
ListCellRenderer 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.
*/
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 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 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();
JComboBox 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();
}
public boolean isEnabled(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 {
JComboBox comboBox = (JComboBox)e.getSource();
if ( propertyName == "model" ) {
ComboBoxModel newModel = (ComboBoxModel)e.getNewValue();
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 ( 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.
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) {
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;
}
}
}