/*
* 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.
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.
All event handling is handled by listener classes created with the
createxxxListener()
methods and internal classes.
You can change the behavior of this class by overriding the
createxxxListener()
methods and supplying your own
event listeners or subclassing from the ones supplied in this class.
For adding specific actions,
overide installKeyboardActions
to add actions in response to
KeyStroke bindings. See the article How to Use Key Bindings
Author: Arnaud Weber, Tom Santos, Mark Davidson
/**
* 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="http://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 JComboBox
. /**
* The instance of {@code JComboBox}.
*/
protected JComboBox<Object> comboBox;
This protected field is implementation specific. Do not access directly
or override.
/**
* 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.
/**
* 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.
/**
* 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. /**
* The implementation of {@code ComboPopup} that is used to show the popup.
*/
protected ComboPopup popup;
The Component that the @{code ComboBoxEditor} uses for editing.
/**
* The Component that the @{code ComboBoxEditor} uses for editing.
*/
protected Component editor;
The arrow button that invokes the popup.
/**
* 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 Also: - createKeyListener
/**
* 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 Also: - createFocusListener
/**
* 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 Also: - createPropertyChangeListener
/**
* 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 Also: - createItemListener
/**
* 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 MouseListener
listens to events. /**
* The {@code MouseListener} listens to events.
*/
protected MouseListener popupMouseListener;
The MouseMotionListener
listens to events. /**
* The {@code MouseMotionListener} listens to events.
*/
protected MouseMotionListener popupMouseMotionListener;
The KeyListener
listens to events. /**
* 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 Also: - createListDataListener
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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
/**
* The default key selection manager
*/
JComboBox.KeySelectionManager keySelectionManager;
The flag for recalculating the minimum preferred size.
/**
* The flag for recalculating the minimum preferred size.
*/
protected boolean isMinimumSizeDirty = true;
The cached minimum preferred size.
/**
* 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.
/**
* 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
/**
* 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
/**
* 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.
/**
* 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 BasicComboBoxUI
. Params: - c – a component
Returns: a new instance of BasicComboBoxUI
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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 installListeners
/**
* 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.
See Also: Returns: an instance of ComboPopup
/**
* 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 KeyListener
which will be added to the
combo box. If this method returns null then it will not be added
to the combo box.
Returns: an instance KeyListener
or null
/**
* 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 FocusListener
which will be added to the combo box.
If this method returns null then it will not be added to the combo box.
Returns: an instance of a FocusListener
or null
/**
* 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
ComboBoxModel
. If this method returns null then
it will not be added to the combo box model.
Returns: an instance of a ListDataListener
or null
/**
* 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 ItemListener
which will be added to the
combo box. If this method returns null then it will not
be added to the combo box.
Subclasses may override this method to return instances of their own
ItemEvent handlers.
Returns: an instance of an ItemListener
or null
/**
* 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 PropertyChangeListener
which will be added to
the combo box. If this method returns null then it will not
be added to the combo box.
Returns: an instance of a PropertyChangeListener
or 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.
Returns: an instance of a layout manager
/**
* 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 setRenderer
.
See Also: Returns: a ListCellRender
used for the combo box
/**
* 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 setEditor
.
See Also: Returns: a ComboBoxEditor
used for the combo box
/**
* 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.
/**
* 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
BasicComboBoxUI
.
/**
* 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
BasicComboBoxUI
.
/**
* 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
ComboBoxModel
.
This public inner class should be treated as protected.
Instantiate it only within subclasses of
BasicComboBoxUI
.
See Also: - createListDataListener
/**
* 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.
This public inner class should be treated as protected.
Instantiate it only within subclasses of
BasicComboBoxUI
.
See Also: - createItemListener
/**
* 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.
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.
This public inner class should be treated as protected.
Instantiate it only within subclasses of
BasicComboBoxUI
.
See Also: - createPropertyChangeListener
/**
* 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
BasicComboBoxUI
.
/**
* 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.
/**
* 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.
/**
* 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 ComboBoxEditor
See Also: - createEditor
- JComboBox.setEditor
- ComboBoxEditor
/**
* 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 Also: - addEditor
/**
* 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 Also: - addEditor
/**
* 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 Also: - addEditor
/**
* 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 Also: - createArrowButton
/**
* 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 Also: - createArrowButton
/**
* 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.
Returns: a button which represents the popup control
/**
* 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.
/**
* Tells if the popup is visible or not.
*/
public boolean isPopupVisible( JComboBox<?> c ) {
return popup.isVisible();
}
Hides the popup.
/**
* 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.
/**
* 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.
/**
* 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}
- IllegalArgumentException – {@inheritDoc}
See Also: Since: 1.6
/**
* 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 Also: Since: 1.6
/**
* 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.
Params: - keyCode – a key code
Returns: true
if the supplied keyCode
maps to a navigation key
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
Returns: the area that is reserved for drawing the currently selected item
/**
* 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.
Returns: the insets
/**
* 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.
Params: - g – an instance of
Graphics
- bounds – a bounding rectangle to render to
- hasFocus – is focused
/**
* 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.
Params: - g – an instance of
Graphics
- bounds – a bounding rectangle to render to
- hasFocus – is focused
/**
* 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.
/**
* 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.
See Also: Returns: the size of an empty display area
/**
* 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.
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.
See Also: Returns: the size of the display area calculated from the combo box items
/**
* 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.
Params: - comp – a
Component
to check
Returns: size of the component Since: 1.7
/**
* 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.
/**
* 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.
/**
* 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;
}
}
}