/*
* @(#)QuaquaComboBoxUI.java
*
* Copyright (c) 2004-2010 Werner Randelshofer, Immensee, Switzerland.
* All rights reserved.
*
* You may not use, copy or modify this file, except in compliance with the
* license agreement you entered into with Werner Randelshofer.
* For details see accompanying license terms.
*/
package ch.randelshofer.quaqua;
import ch.randelshofer.quaqua.util.*;
import ch.randelshofer.quaqua.util.Debug;
import java.awt.*;
import java.awt.event.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.border.*;
import javax.swing.plaf.basic.*;
import java.beans.*;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
/**
* Quaqua UI for JComboBox.
*
* @author Werner Randelshofer
* @version $Id: QuaquaComboBoxUI.java 445 2011-10-02 11:15:50Z wrandelshofer $
*/
public class QuaquaComboBoxUI extends BasicComboBoxUI implements VisuallyLayoutable {
//private HierarchyListener hierarchyListener;
//MetalComboBoxUI
// Control the selection behavior of the JComboBox when it is used
// in the JTable DefaultCellEditor.
private boolean isTableCellEditor = false;
public static final String IS_TABLE_CELL_EDITOR = "JComboBox.isTableCellEditor";
private final static Border tableCellEditorBorder = new EmptyBorder(0, 2, 0, 0);
static final StringBuffer HIDE_POPUP_KEY = new StringBuffer("HidePopupKey");
/**
* Optional: if specified, these insets act as padding around the cell
* renderer when laying out and painting the "selected" item in the
* combo box. BasicComboBoxUI uses a single combo box renderer for rendering
* both the main combo box item and also all the items in the dropdown
* for the combo box. padding allows you to specify addition insets in
* addition to those specified by the cell renderer.
*/
private Insets padding;
// 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);
private boolean sameBaseline;
private QuaquaComboBoxUIHandler handler;
/**
* This is tricky, this variables is needed for DefaultKeySelectionManager
* to take into account time factor.
*/
private long lastTime = 0L;
private long time = 0L;
/**
* Preferred spacing between combo boxes and other components.
* /
* private final static Insets regularSpacing = new Insets(12,12,12,12);
* private final static Insets smallSpacing = new Insets(10,10,10,10);
* private final static Insets miniSpacing = new Insets(8,8,8,8);
*/
public static ComponentUI createUI(JComponent c) {
return new QuaquaComboBoxUI();
}
@Override
public void installUI(JComponent c) {
super.installUI(c);
// Is this combo box a cell editor?
Boolean value = (Boolean) c.getClientProperty(IS_TABLE_CELL_EDITOR);
if (value == null) {
value = (Boolean) c.getClientProperty("JComboBox.lightweightKeyboardNavigation");
}
setTableCellEditor(value != null && value.equals(Boolean.TRUE));
// Note: we need to invoke c.setOpaque explicitly, installProperty does
// not seem to work.
//LookAndFeel.installProperty(c, "opaque", UIManager.get("ComboBox.opaque"));
c.setOpaque(UIManager.getBoolean("ComboBox.opaque"));
comboBox.setRequestFocusEnabled(UIManager.getBoolean("ComboBox.requestFocusEnabled"));
// We can't set this property because it breaks the behavior of editable
// combo boxes.
comboBox.setFocusable(comboBox.isEditable() || UIManager.getBoolean("ComboBox.focusable"));
//
QuaquaUtilities.applySizeVariant(comboBox);
if (arrowButton != null) {
arrowButton.putClientProperty("JComponent.sizeVariant", comboBox.getClientProperty("JComponent.sizeVariant"));
}
}
@Override
protected void installDefaults() {
super.installDefaults();
comboBox.setMaximumRowCount(UIManager.getInt("ComboBox.maximumRowCount"));
padding = UIManager.getInsets("ComboBox.padding");
}
/**
* Create and install the listeners for the combo box and its model.
* This method is called when the UI is installed.
*/
@Override
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 ((hierarchyListener = createHierarchyListener()) != null) {
comboBox.addHierarchyListener(hierarchyListener);
}
*/
if (comboBox.getModel() != null) {
if ((listDataListener = createListDataListener()) != null) {
comboBox.getModel().addListDataListener(listDataListener);
}
}
}
/**
* Remove 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>
*/
@Override
protected void uninstallListeners() {
super.uninstallListeners();
/*
if (hierarchyListener != null) {
comboBox.removeHierarchyListener(hierarchyListener);
hierarchyListener = null;
}*/
}
public KeyListener getKeyListener() {
return keyListener;
}
/*
protected HierarchyListener createHierarchyListener() {
return new ComponentActivationHandler(comboBox);
}*/
boolean isTableCellEditor() {
return isTableCellEditor;
}
@Override
protected ComboBoxEditor createEditor() {
return new QuaquaComboBoxEditor.UIResource();
}
@Override
protected ComboPopup createPopup() {
QuaquaComboPopup p = new QuaquaComboPopup(comboBox, this);
p.getAccessibleContext().setAccessibleParent(comboBox);
return p;
}
@Override
protected JButton createArrowButton() {
JButton button = new QuaquaComboBoxButton(this, comboBox, getArrowIcon(),
comboBox.isEditable(),
currentValuePane,
listBox);
button.putClientProperty("Quaqua.Component.cellRendererFor", comboBox);
button.setMargin(new Insets(0, 1, 1, 3));
return button;
}
/* 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
*/
@Override
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
*/
@Override
protected ListDataListener createListDataListener() {
return getHandler();
}
@Override
public PropertyChangeListener createPropertyChangeListener() {
return getHandler();
}
// 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());
}
}
}
private void setTableCellEditor(boolean b) {
isTableCellEditor = b;
updateTableCellEditor();
}
private void updateTableCellEditor() {
boolean b = isTableCellEditor();
//comboBox.setOpaque(b);
if (editor instanceof JComponent) {
JComponent jeditor = (JComponent) editor;
jeditor.setBorder(b ? tableCellEditorBorder : UIManager.getBorder("ComboBox.editorBorder"));
}
}
@Override
public void paint(Graphics g, JComponent c) {
if (editor != null
&& UIManager.getBoolean("ComboBox.changeEditorForeground")) {
editor.setForeground(c.getForeground());
}
Debug.paint(g, c, this);
}
@Override
public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus) {
}
/**
* Paints the background of the currently selected item.
*/
@Override
public void paintCurrentValueBackground(Graphics g, Rectangle bounds, boolean hasFocus) {
}
/**
* 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.
*/
@Override
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;
}
/**
* This inner class is marked "public" due to a compiler bug.
* This class should be treated as a "protected" inner class.
* Instantiate it only within subclasses of <FooUI>.
*/
//
// Shared Handler, implements all listeners
//
private class QuaquaComboBoxUIHandler implements ActionListener, FocusListener,
KeyListener, LayoutManager,
ListDataListener, PropertyChangeListener {
//
//
// PropertyChangeListener
//
private void superPropertyChange(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;
comboBox.validate();
} else if (propertyName == JComponent.TOOL_TIP_TEXT_KEY) {
updateToolTipTextForChildren();
} else if (propertyName == QuaquaComboBoxUI.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();
}
}
}
@Override
public void propertyChange(PropertyChangeEvent e) {
superPropertyChange(e);
String name = 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 (name == null || "border".equals(name)) {
isMinimumSizeDirty = true;
}
}
if (name == null) {
} else if (name.equals("model")//
|| name.equals("prototypeDisplayValue")//
|| name.equals("renderer")) {
isMinimumSizeDirty = true;
} else if (name.equals("editable")) {
QuaquaComboBoxButton button = (QuaquaComboBoxButton) arrowButton;
button.setIconOnly(comboBox.isEditable());
updateTableCellEditor();
// FIXME - This may cause mayhem!
comboBox.setFocusable(comboBox.isEditable() || UIManager.getBoolean("ComboBox.focusable"));
comboBox.repaint();
} else if (name.equals("background")) {
Color color = (Color) e.getNewValue();
arrowButton.setBackground(color);
} else if (name.equals("foreground")) {
Color color = (Color) e.getNewValue();
arrowButton.setForeground(color);
listBox.setForeground(color);
} else if (name.equals(IS_TABLE_CELL_EDITOR)) {
Boolean inTable = (Boolean) e.getNewValue();
setTableCellEditor(inTable.equals(Boolean.TRUE) ? true : false);
} else if (name.equals("JComboBox.lightweightKeyboardNavigation")) {
// In Java 1.3 we have to use this property to guess whether we
// are a table cell editor or not.
setTableCellEditor(e.getNewValue() != null && e.getNewValue().equals("Lightweight"));
} else if (name.equals("JComponent.sizeVariant")) {
QuaquaUtilities.applySizeVariant(comboBox);
arrowButton.putClientProperty("JComponent.sizeVariant", e.getNewValue());
}
}
//
// 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() && !e.isControlDown() && !e.isMetaDown();
}
//
// 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();
}
//
// 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()));
}
}
}
}
@Override
public void layoutContainer(Container parent) {
layoutComboBox(parent, this);
}
public void superLayout(Container parent) {
JComboBox cb = (JComboBox) parent;
int width = cb.getWidth();
int height = cb.getHeight();
Insets insets = getInsets();
int buttonSize = height - (insets.top + insets.bottom);
Rectangle cvb;
if (arrowButton != null) {
if (QuaquaUtilities.isLeftToRight(cb)) {
// FIXME - This should be 6 minus 2, whereas two needs to be
// derived from the TextFieldUI
//int plusHeight = (isSmallSizeVariant()) ? 4 : 4;
int plusHeight = (isSmall()) ? - 2 : - 2;
arrowButton.setBounds(
width - getArrowWidth() - insets.right,
insets.top /*+ margin.top - 3*/,
getArrowWidth(),
buttonSize /*- margin.top - margin.bottom*/ + plusHeight);
} else {
arrowButton.setBounds(insets.left, insets.top,
getArrowWidth(), buttonSize);
}
}
if (editor != null) {
cvb = rectangleForCurrentValue();
editor.setBounds(cvb);
}
}
}
/**
* As of Java 2 platform v1.4 this method is no longer used. Do not call or
* override. All the functionality of this method is in the
* QuaquaComboBoxPropertyChangeListener.
*
* @deprecated As of Java 2 platform v1.4.
*/
protected void editablePropertyChanged(PropertyChangeEvent e) {
}
@Override
protected LayoutManager createLayoutManager() {
return getHandler();
}
// This is here because of a bug in the compiler.
// When a protected-inner-class-savvy compiler comes out we
// should move this into QuaquaComboBoxLayoutManager.
public void layoutComboBox(Container parent, QuaquaComboBoxUIHandler manager) {
if (comboBox.isEditable()) {
manager.superLayout(parent);
} else {
if (arrowButton != null) {
Insets insets = comboBox.getInsets();
Insets buttonInsets = UIManager.getInsets("ComboBox.buttonInsets");
if (buttonInsets != null) {
insets = new Insets(insets.top + buttonInsets.top,
insets.left + buttonInsets.left, insets.bottom + buttonInsets.bottom, insets.right + buttonInsets.right);
}
int width = comboBox.getWidth();
int height = comboBox.getHeight();
arrowButton.setBounds(insets.left, insets.top,
width - (insets.left + insets.right),
height - (insets.top + insets.bottom));
}
}
}
protected Icon getArrowIcon() {
if (isTableCellEditor()) {
return UIManager.getIcon("ComboBox.cellEditorPopupIcon");
/* The following does not work as expected:
if (comboBox.isEditable()) {
return UIManager.getIcon("ComboBox.smallDropDownIcon");
} else {
return UIManager.getIcon("ComboBox.smallPopupIcon");
}*/
} else {
if (comboBox.isEditable()) {
switch (QuaquaUtilities.getSizeVariant(comboBox)) {
default:
return UIManager.getIcon("ComboBox.dropDownIcon");
case SMALL:
return UIManager.getIcon("ComboBox.smallDropDownIcon");
case MINI:
return UIManager.getIcon("ComboBox.miniDropDownIcon");
}
} else {
switch (QuaquaUtilities.getSizeVariant(comboBox)) {
default:
return UIManager.getIcon("ComboBox.popupIcon");
case SMALL:
return UIManager.getIcon("ComboBox.smallPopupIcon");
case MINI:
return UIManager.getIcon("ComboBox.miniPopupIcon");
}
}
}
}
protected int getArrowWidth() {
if (isTableCellEditor()) {
return 7;
} else {
if (comboBox.isEditable()) {
switch (QuaquaUtilities.getSizeVariant(comboBox)) {
default:
return UIManager.getInt("ComboBox.dropDownWidth");
case SMALL:
return UIManager.getInt("ComboBox.smallDropDownWidth");
case MINI:
return UIManager.getInt("ComboBox.miniDropDownWidth");
}
} else {
switch (QuaquaUtilities.getSizeVariant(comboBox)) {
default:
return UIManager.getInt("ComboBox.popupWidth");
case SMALL:
return UIManager.getInt("ComboBox.smallPopupWidth");
case MINI:
return UIManager.getInt("ComboBox.miniPopupWidth");
}
}
}
}
/**
* As of Java 2 platform v1.4 this method is no
* longer used.
*
* @deprecated As of Java 2 platform v1.4.
*/
protected void removeListeners() {
if (propertyChangeListener != null) {
comboBox.removePropertyChangeListener(propertyChangeListener);
}
}
protected boolean isSmall() {
boolean isSmall = QuaquaUtilities.getSizeVariant(comboBox) == QuaquaUtilities.SizeVariant.SMALL;
return isSmall;
}
/**
* Returns the area that is reserved for drawing the currently selected item.
* Note: Changes in this method also require changes in method getMinimumSize.
*/
@Override
protected Rectangle rectangleForCurrentValue() {
return rectangleForCurrentValue(comboBox.getWidth(), comboBox.getHeight());
}
/**
* Returns the area that is reserved for drawing the currently selected item.
* Note: Changes in this method also require changes in method getMinimumSize.
*/
protected Rectangle rectangleForCurrentValue(int width, int height) {
Insets insets = getInsets();
Insets margin = getMargin();
if (comboBox.isEditable()) {
if (!isTableCellEditor()) {
insets.right -= margin.right;
/*
insets.left--;
insets.top--;
insets.bottom--;*/
insets.left -= margin.left - 2;
insets.top -= margin.top - 2;
insets.bottom -= margin.bottom - 2;
}
} else {
if (isTableCellEditor()) {
insets.top -= 1;
} else {
// no right-margin because we
// want no gap between button and renderer!
switch (QuaquaUtilities.getSizeVariant(comboBox)) {
default:
insets.left += 6;
insets.top += margin.top;
insets.left += margin.left;
insets.bottom += margin.bottom;
break;
case SMALL:
insets.left += 4;
insets.top += margin.top;
insets.left += margin.left;
insets.bottom += margin.bottom;
break;
case MINI:
insets.left += 3;
insets.top += margin.top;
insets.left += margin.left;
insets.bottom += margin.bottom;
break;
}
}
}
return new Rectangle(
insets.left,
insets.top,
width - getArrowWidth() - insets.right - insets.left,
height - insets.top - insets.bottom);
}
protected Insets getMargin() {
Insets margin = (Insets) comboBox.getClientProperty("Quaqua.Component.visualMargin");
if (margin == null) {
margin = UIManager.getInsets("Component.visualMargin");
}
return (margin == null) ? new Insets(0, 0, 0, 0) : (Insets) margin.clone();
}
/**
* 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
*/
@Override
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))) {
// BEGIN FIX QUAQUA-151 JComponent.getBaseline() is not available in J2SE5.
int newBaseline;//=c.getBaseline(d.width, d.height);
try {
newBaseline = (Integer) Methods.invoke(c, "getBaseline", new Class[]{Integer.TYPE, Integer.TYPE}, new Object[]{d.width, d.height});
} catch (NoSuchMethodException ex) {
newBaseline = -1;
}
// END FIX QUAQUA-151
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;
}
/**
* 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.
*/
protected Dimension getSizeForComponent(Component comp) {
currentValuePane.add(comp);
comp.setFont(comboBox.getFont());
Dimension d = comp.getPreferredSize();
currentValuePane.remove(comp);
return d;
}
/**
* Note: Changes in this method also require changes in method rectangelForCurrentValue.
*/
@Override
public Dimension getMinimumSize(JComponent c) {
if (!isMinimumSizeDirty) {
return new Dimension(cachedMinimumSize);
}
Dimension size = null;
if (!comboBox.isEditable()
&& arrowButton != null
&& arrowButton instanceof QuaquaComboBoxButton) {
Insets buttonInsets;
switch (QuaquaUtilities.getSizeVariant(comboBox)) {
default:
buttonInsets = UIManager.getInsets("ComboBox.arrowButtonInsets");
break;
case SMALL:
buttonInsets = UIManager.getInsets("ComboBox.smallArrowButtonInsets");
break;
case MINI:
buttonInsets = UIManager.getInsets("ComboBox.miniArrowButtonInsets");
break;
}
buttonInsets = (Insets) buttonInsets.clone();
buttonInsets.right += getArrowWidth();
Insets insets = getInsets();
size = getDisplaySize();
size.width += insets.left + insets.right
+ buttonInsets.left + buttonInsets.right;
size.height += insets.top + insets.bottom
+ buttonInsets.top + buttonInsets.bottom;
} else if (comboBox.isEditable()
&& arrowButton != null
&& editor != null) {
Insets buttonInsets;
Insets insets = comboBox.getInsets();
Insets margin = getMargin();
buttonInsets = new Insets(2 - margin.top, 4 - margin.left, 2 - margin.bottom, getArrowWidth());
// Margin is included in display size, therefore no need to add
// it to size. We subtract the margin at the right, because we
// want the text field's focus ring to glow over the right button.
size = getDisplaySize();
size.width += insets.left + insets.right
+ buttonInsets.left + buttonInsets.right;
size.height += insets.top + insets.bottom
+ buttonInsets.top + buttonInsets.bottom;
size.width += getArrowWidth();
} else {
size = super.getMinimumSize(c);
if (size == null) {
size = new Dimension(0, 0);
}
}
cachedMinimumSize.setSize(size.width, size.height);
isMinimumSizeDirty = false;
return new Dimension(cachedMinimumSize);
}
@Override
public Dimension getMaximumSize(JComponent c) {
Dimension size = getPreferredSize(c);
if (size != null && !(c.getParent() instanceof JToolBar)) {
size.width = Short.MAX_VALUE;
}
return size;
}
/**
* Returns the shared listener.
*/
private QuaquaComboBoxUIHandler getHandler() {
if (handler == null) {
handler = new QuaquaComboBoxUIHandler();
}
return handler;
}
@Override
public int getBaseline(JComponent c, int width, int height) {
Rectangle vb = getVisualBounds(c, VisuallyLayoutable.TEXT_BOUNDS, width, height);
return (vb == null) ? -1 : vb.y + vb.height;
}
public Rectangle getVisualBounds(JComponent c, int layoutType, int width, int height) {
Rectangle bounds = new Rectangle(0, 0, width, height);
if (layoutType == VisuallyLayoutable.CLIP_BOUNDS) {
return bounds;
}
if (c != comboBox) {
return null;
}
Rectangle buttonRect = new Rectangle();
Rectangle editorRect = null;
Insets insets = getInsets();
Insets margin = getMargin();
int buttonSize = height - (insets.top + insets.bottom);
Rectangle cvb;
if (arrowButton != null) {
if (QuaquaUtilities.isLeftToRight(comboBox)) {
int plusHeight = (isSmall()) ? 5 : 4;
buttonRect.setBounds(
width - getArrowWidth() - insets.right,
insets.top + margin.top - 2,
getArrowWidth(),
buttonSize - margin.top - margin.bottom + plusHeight);
} else {
buttonRect.setBounds(insets.left, insets.top,
getArrowWidth(), buttonSize);
}
}
editorRect = rectangleForCurrentValue(width, height);
// FIXME we shouldn't hardcode this and determine the real visual
// bounds of the renderer instead.
// Subtract 2 from x because of the insets of the renderer
editorRect.x += 1;
editorRect.width -= 2;
switch (layoutType) {
case VisuallyLayoutable.COMPONENT_BOUNDS:
if (!isTableCellEditor()) {
if (editor != null) {
bounds.x += margin.left;
bounds.y += margin.top;
bounds.width -= margin.left + margin.right;
bounds.height -= margin.top + margin.bottom + 1;
} else {
bounds.x += margin.left;
bounds.y += margin.top;
bounds.width -= margin.left + margin.right;
bounds.height -= margin.top + margin.bottom;
}
}
break;
case VisuallyLayoutable.TEXT_BOUNDS:
Object renderer = (editor == null)
? (Object) comboBox.getRenderer().getListCellRendererComponent(listBox, comboBox.getSelectedItem(), comboBox.getSelectedIndex(), false, comboBox.hasFocus())
: (Object) editor;
if ((renderer instanceof JComponent)
&& (Methods.invokeGetter(renderer, "getUI", null) instanceof VisuallyLayoutable)) {
bounds = ((VisuallyLayoutable) Methods.invokeGetter(renderer, "getUI", null)).getVisualBounds((JComponent) renderer, layoutType, editorRect.width, editorRect.height);
bounds.x += editorRect.x;
bounds.y += editorRect.y;
} else {
bounds.setBounds(editorRect);
}
break;
}
return bounds;
}
/**
* 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 GlowFocusHandler extends BasicComboBoxUI.FocusHandler {
@Override
public void focusGained(FocusEvent e) {
super.focusGained(e);
glowyRepaint();
}
@Override
public void focusLost(FocusEvent e) {
super.focusLost(e);
glowyRepaint();
}
private void glowyRepaint() {
if (comboBox.getParent() != null) {
Rectangle r = comboBox.getBounds();
r.grow(2, 2);
comboBox.getParent().repaint(r.x, r.y, r.width, r.height);
}
}
}
}