You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2001 lines
70 KiB
2001 lines
70 KiB
/*
|
|
* Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
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 aggregate of
|
|
* many simpler components. This class creates and manages the listeners
|
|
* on the combo box and the combo box model. These listeners update the user
|
|
* interface in response to changes in the properties and state of the combo box.
|
|
* <p>
|
|
* All event handling is handled by listener classes created with the
|
|
* <code>createxxxListener()</code> methods and internal classes.
|
|
* You can change the behavior of this class by overriding the
|
|
* <code>createxxxListener()</code> methods and supplying your own
|
|
* event listeners or subclassing from the ones supplied in this class.
|
|
* <p>
|
|
* For adding specific actions,
|
|
* overide <code>installKeyboardActions</code> to add actions in response to
|
|
* KeyStroke bindings. See the article <a href="https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html">How to Use Key Bindings</a>
|
|
*
|
|
* @author Arnaud Weber
|
|
* @author Tom Santos
|
|
* @author Mark Davidson
|
|
*/
|
|
public class BasicComboBoxUI extends ComboBoxUI {
|
|
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();
|
|
|
|
if (arrowButton != null) {
|
|
comboBox.add(arrowButton);
|
|
configureArrowButton();
|
|
}
|
|
|
|
if ( comboBox.isEditable() ) {
|
|
addEditor();
|
|
}
|
|
|
|
comboBox.add( currentValuePane );
|
|
}
|
|
|
|
/**
|
|
* The aggregate components which comprise the combo box are
|
|
* unregistered and uninitialized. This method is called as part of the
|
|
* UI uninstallation process.
|
|
*/
|
|
protected void uninstallComponents() {
|
|
if ( arrowButton != null ) {
|
|
unconfigureArrowButton();
|
|
}
|
|
if ( editor != null ) {
|
|
unconfigureEditor();
|
|
}
|
|
comboBox.removeAll(); // Just to be safe.
|
|
arrowButton = null;
|
|
}
|
|
|
|
/**
|
|
* This public method is implementation specific and should be private.
|
|
* do not call or override. To implement a specific editor create a
|
|
* custom <code>ComboBoxEditor</code>
|
|
*
|
|
* @see #createEditor
|
|
* @see javax.swing.JComboBox#setEditor
|
|
* @see javax.swing.ComboBoxEditor
|
|
*/
|
|
public void addEditor() {
|
|
removeEditor();
|
|
editor = comboBox.getEditor().getEditorComponent();
|
|
if ( editor != null ) {
|
|
configureEditor();
|
|
comboBox.add(editor);
|
|
if(comboBox.isFocusOwner()) {
|
|
// Switch focus to the editor component
|
|
editor.requestFocusInWindow();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This public method is implementation specific and should be private.
|
|
* do not call or override.
|
|
*
|
|
* @see #addEditor
|
|
*/
|
|
public void removeEditor() {
|
|
if ( editor != null ) {
|
|
unconfigureEditor();
|
|
comboBox.remove( editor );
|
|
editor = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This protected method is implementation specific and should be private.
|
|
* do not call or override.
|
|
*
|
|
* @see #addEditor
|
|
*/
|
|
protected void configureEditor() {
|
|
// Should be in the same state as the combobox
|
|
editor.setEnabled(comboBox.isEnabled());
|
|
|
|
editor.setFocusable(comboBox.isFocusable());
|
|
|
|
editor.setFont( comboBox.getFont() );
|
|
|
|
if (focusListener != null) {
|
|
editor.addFocusListener(focusListener);
|
|
}
|
|
|
|
editor.addFocusListener( getHandler() );
|
|
|
|
comboBox.getEditor().addActionListener(getHandler());
|
|
|
|
if(editor instanceof JComponent) {
|
|
((JComponent)editor).putClientProperty("doNotCancelPopup",
|
|
HIDE_POPUP_KEY);
|
|
((JComponent)editor).setInheritsPopupMenu(true);
|
|
}
|
|
|
|
comboBox.configureEditor(comboBox.getEditor(),comboBox.getSelectedItem());
|
|
|
|
editor.addPropertyChangeListener(propertyChangeListener);
|
|
}
|
|
|
|
/**
|
|
* This protected method is implementation specific and should be private.
|
|
* Do not call or override.
|
|
*
|
|
* @see #addEditor
|
|
*/
|
|
protected void unconfigureEditor() {
|
|
if (focusListener != null) {
|
|
editor.removeFocusListener(focusListener);
|
|
}
|
|
|
|
editor.removePropertyChangeListener(propertyChangeListener);
|
|
editor.removeFocusListener(getHandler());
|
|
comboBox.getEditor().removeActionListener(getHandler());
|
|
}
|
|
|
|
/**
|
|
* This public method is implementation specific and should be private. Do
|
|
* not call or override.
|
|
*
|
|
* @see #createArrowButton
|
|
*/
|
|
public void configureArrowButton() {
|
|
if ( arrowButton != null ) {
|
|
arrowButton.setEnabled( comboBox.isEnabled() );
|
|
arrowButton.setFocusable(comboBox.isFocusable());
|
|
arrowButton.setRequestFocusEnabled(false);
|
|
arrowButton.addMouseListener( popup.getMouseListener() );
|
|
arrowButton.addMouseMotionListener( popup.getMouseMotionListener() );
|
|
arrowButton.resetKeyboardActions();
|
|
arrowButton.putClientProperty("doNotCancelPopup", HIDE_POPUP_KEY);
|
|
arrowButton.setInheritsPopupMenu(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This public method is implementation specific and should be private. Do
|
|
* not call or override.
|
|
*
|
|
* @see #createArrowButton
|
|
*/
|
|
public void unconfigureArrowButton() {
|
|
if ( arrowButton != null ) {
|
|
arrowButton.removeMouseListener( popup.getMouseListener() );
|
|
arrowButton.removeMouseMotionListener( popup.getMouseMotionListener() );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a button which will be used as the control to show or hide
|
|
* the popup portion of the combo box.
|
|
*
|
|
* @return a button which represents the popup control
|
|
*/
|
|
protected JButton createArrowButton() {
|
|
JButton button = new BasicArrowButton(BasicArrowButton.SOUTH,
|
|
UIManager.getColor("ComboBox.buttonBackground"),
|
|
UIManager.getColor("ComboBox.buttonShadow"),
|
|
UIManager.getColor("ComboBox.buttonDarkShadow"),
|
|
UIManager.getColor("ComboBox.buttonHighlight"));
|
|
button.setName("ComboBox.arrowButton");
|
|
return button;
|
|
}
|
|
|
|
//
|
|
// end Sub-Component Management
|
|
//===============================
|
|
|
|
|
|
//================================
|
|
// begin ComboBoxUI Implementation
|
|
//
|
|
|
|
/**
|
|
* Tells if the popup is visible or not.
|
|
*/
|
|
public boolean isPopupVisible( JComboBox c ) {
|
|
return popup.isVisible();
|
|
}
|
|
|
|
/**
|
|
* Hides the popup.
|
|
*/
|
|
public void setPopupVisible( JComboBox c, boolean v ) {
|
|
if ( v ) {
|
|
popup.show();
|
|
} else {
|
|
popup.hide();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines if the JComboBox is focus traversable. If the JComboBox is editable
|
|
* this returns false, otherwise it returns true.
|
|
*/
|
|
public boolean isFocusTraversable( JComboBox c ) {
|
|
return !comboBox.isEditable();
|
|
}
|
|
|
|
//
|
|
// end ComboBoxUI Implementation
|
|
//==============================
|
|
|
|
|
|
//=================================
|
|
// begin ComponentUI Implementation
|
|
@Override
|
|
public void paint( Graphics g, JComponent c ) {
|
|
hasFocus = comboBox.hasFocus();
|
|
if ( !comboBox.isEditable() ) {
|
|
Rectangle r = rectangleForCurrentValue();
|
|
paintCurrentValueBackground(g,r,hasFocus);
|
|
paintCurrentValue(g,r,hasFocus);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Dimension getPreferredSize( JComponent c ) {
|
|
return getMinimumSize(c);
|
|
}
|
|
|
|
/**
|
|
* The minimum size is the size of the display area plus insets plus the button.
|
|
*/
|
|
@Override
|
|
public Dimension getMinimumSize( JComponent c ) {
|
|
if ( !isMinimumSizeDirty ) {
|
|
return new Dimension(cachedMinimumSize);
|
|
}
|
|
Dimension size = getDisplaySize();
|
|
Insets insets = getInsets();
|
|
//calculate the width and height of the button
|
|
int buttonHeight = size.height;
|
|
int buttonWidth = squareButton ? buttonHeight : arrowButton.getPreferredSize().width;
|
|
//adjust the size based on the button width
|
|
size.height += insets.top + insets.bottom;
|
|
size.width += insets.left + insets.right + buttonWidth;
|
|
|
|
cachedMinimumSize.setSize( size.width, size.height );
|
|
isMinimumSizeDirty = false;
|
|
|
|
return new Dimension(size);
|
|
}
|
|
|
|
@Override
|
|
public Dimension getMaximumSize( JComponent c ) {
|
|
return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
|
|
}
|
|
|
|
/**
|
|
* Returns the baseline.
|
|
*
|
|
* @throws NullPointerException {@inheritDoc}
|
|
* @throws IllegalArgumentException {@inheritDoc}
|
|
* @see javax.swing.JComponent#getBaseline(int, int)
|
|
* @since 1.6
|
|
*/
|
|
@Override
|
|
public int getBaseline(JComponent c, int width, int height) {
|
|
super.getBaseline(c, width, height);
|
|
int baseline = -1;
|
|
// force sameBaseline to be updated.
|
|
getDisplaySize();
|
|
if (sameBaseline) {
|
|
Insets insets = c.getInsets();
|
|
height = 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);
|
|
}
|
|
Component component = renderer.
|
|
getListCellRendererComponent(listBox, value, -1,
|
|
false, false);
|
|
if (component instanceof JLabel) {
|
|
JLabel label = (JLabel) component;
|
|
String text = label.getText();
|
|
if ((text == null) || text.isEmpty()) {
|
|
label.setText(" ");
|
|
}
|
|
}
|
|
if (component instanceof JComponent) {
|
|
component.setFont(comboBox.getFont());
|
|
}
|
|
baseline = component.getBaseline(width, height);
|
|
}
|
|
else {
|
|
baseline = editor.getBaseline(width, height);
|
|
}
|
|
if (baseline > 0) {
|
|
baseline += insets.top;
|
|
}
|
|
}
|
|
return baseline;
|
|
}
|
|
|
|
/**
|
|
* Returns an enum indicating how the baseline of the component
|
|
* changes as the size changes.
|
|
*
|
|
* @throws NullPointerException {@inheritDoc}
|
|
* @see javax.swing.JComponent#getBaseline(int, int)
|
|
* @since 1.6
|
|
*/
|
|
@Override
|
|
public Component.BaselineResizeBehavior getBaselineResizeBehavior(
|
|
JComponent c) {
|
|
super.getBaselineResizeBehavior(c);
|
|
// Force sameBaseline to be updated.
|
|
getDisplaySize();
|
|
if (comboBox.isEditable()) {
|
|
return editor.getBaselineResizeBehavior();
|
|
}
|
|
else if (sameBaseline) {
|
|
ListCellRenderer 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;
|
|
}
|
|
}
|
|
|
|
}
|