org.jdesktop.layout.Baseline Maven / Gradle / Ivy
Show all versions of swing-layout Show documentation
/*
* Copyright (C) 2005-2006 Sun Microsystems, Inc. All rights reserved. Use is
* subject to license terms.
*/
package org.jdesktop.layout;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import javax.swing.text.View;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.swing.border.*;
import javax.swing.plaf.metal.MetalLookAndFeel;
/**
* Convenience class that can be used to determine the baseline of a
* particular component. The static method getBaseline
uses the
* following algorithm to determine the baseline:
*
* - If the component has a
getBaseline(JComponent,int,int)
* method, invoke it.
* - If there is a
UIManager
property of the name
* Baseline.instance
, forward the call to that Baseline.
* - Otherwise use the built in support.
*
*
* In addition to determining the baseline, this class also allows for
* determining how the baseline changes as the size of the component changes.
* The method getBaselineResizeBehavior can be used for this. This will return
* one of BRB_OTHER, BRB_CONSTANT_ASCENT, BRB_CONSTANT_DESCENT or
* BRB_CENTER_OFFSET. The following algorithm is used in determining the
* baseline resize behavior.
*
* - If the Component defines the method
* getBaselineResizeBehaviorInt, the return value from that method is used.
*
- If running on 1.6, the Component method getBaselineResizeBehavior is
* invoked and the return value converted to one of the constants defined
* by this class.
*
- If the component is one of the known Swing components,the baseline resize
* behavior is calculated and returned.
*
- Otherwise, BRB_OTHER is returned.
*
*
* This class is primarily useful for JREs prior to 1.6. In 1.6 API for this
* was added directly to Component. When run on 1.6 or newer, this class calls
* into the appropriate Component methods.
*
* @version $Revision: 1.12 $
*/
public class Baseline {
static final int BRB_NONE = 0;
/**
* Baseline resize behavior constant. Indicates as the size of the component
* changes the baseline remains a fixed distance from the top of the
* component.
*/
public static final int BRB_CONSTANT_ASCENT = 1;
/**
* Baseline resize behavior constant. Indicates as the size of the component
* changes the baseline remains a fixed distance from the bottom of the
* component.
*/
public static final int BRB_CONSTANT_DESCENT = 2;
/**
* Baseline resize behavior constant. Indicates as the size of the component
* changes the baseline remains a fixed distance from the center of the
* component.
*/
public static final int BRB_CENTER_OFFSET = 3;
/**
* Baseline resize behavior constant. Indicates as the size of the component
* changes the baseline can not be determined using one of the other
* constants.
*/
public static final int BRB_OTHER = 4;
//
// Used by button and label baseline code, cached to avoid excessive
// garbage.
//
private static final Rectangle viewRect = new Rectangle();
private static final Rectangle textRect = new Rectangle();
private static final Rectangle iconRect = new Rectangle();
//
// These come from TitleBorder. NOTE that these are NOT final in
// TitledBorder
//
private static final int EDGE_SPACING = 2;
private static final int TEXT_SPACING = 2;
private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
// Prototype label for calculating baseline of tables.
private static JLabel TABLE_LABEL;
// Prototype label for calculating baseline of lists.
private static JLabel LIST_LABEL;
// Prototype label for calculating baseline of trees.
private static JLabel TREE_LABEL;
// Corresponds to com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel
private static Class CLASSIC_WINDOWS;
// Whether or not we've tried to load WindowsClassicLookAndFeel.
private static boolean checkedForClassic;
// Corresponds to com.sun.java.swing.plaf.windows.WindowsLookAndFeel
private static Class WINDOWS_CLASS;
// Whether we've tried to load WindowsLookAndFeel
private static boolean checkedForWindows;
// Whether or not we are running in a sandbox. This is used to determine
// how we should decide if we're using ocean.
private static boolean inSandbox;
// If in the sandbox, this is set after we've determine if using ocean.
private static boolean checkedForOcean;
// Whether or not using ocean. This is only used if inSandbox.
private static boolean usingOcean;
// Map
private static final Map BASELINE_MAP = Collections.
synchronizedMap(new HashMap(1));
// Map Method is getBaselineResizeBehaviorAsInt
private static final Map BRB_I_MAP = Collections.
synchronizedMap(new HashMap(1));
private static final Method COMPONENT_BASELINE_METHOD;
private static final Method COMPONENT_BRB_METHOD;
private static final Object ENUM_BRB_CENTER_OFFSET;
private static final Object ENUM_BRB_CONSTANT_ASCENT;
private static final Object ENUM_BRB_CONSTANT_DESCENT;
private static final Object ENUM_BRB_OTHER;
// Temporary JList: used to determine baseline resize behavior for
// comboboxs.
private static JList brbList;
// Temporary ListCellRenderer: used to determine baseline resize behavior
// for comboboxs.
private static ListCellRenderer brbListCellRenderer;
static {
Method componentBaselineMethod = null;
Method componentBRBMethod = null;
Method componentBRBIMethod = null;
Object brbCenterOffset = null;
Object brbConstantAscent = null;
Object brbConstantDescent = null;
Object brbOther = null;
try {
componentBaselineMethod = Component.class.getMethod(
"getBaseline", new Class[] { int.class, int.class});
componentBRBMethod = Component.class.getMethod(
"getBaselineResizeBehavior", new Class[] { });
Class brbClass = Class.forName("java.awt.Component$BaselineResizeBehavior");
brbCenterOffset = getFieldValue(brbClass, "CENTER_OFFSET");
brbConstantAscent = getFieldValue(brbClass, "CONSTANT_ASCENT");
brbConstantDescent = getFieldValue(brbClass, "CONSTANT_DESCENT");
brbOther = getFieldValue(brbClass, "OTHER");
} catch (NoSuchMethodException nsme) {
} catch (ClassNotFoundException cnfe) {
} catch (NoSuchFieldException nsfe) {
} catch (IllegalAccessException iae) {
}
if (componentBaselineMethod == null ||
componentBRBMethod == null ||
brbCenterOffset == null ||
brbConstantDescent == null ||
brbConstantAscent == null ||
brbOther == null) {
componentBaselineMethod = componentBRBMethod = null;
brbCenterOffset = brbConstantAscent = brbConstantDescent =
brbOther = null;
}
COMPONENT_BASELINE_METHOD = componentBaselineMethod;
COMPONENT_BRB_METHOD = componentBRBMethod;
ENUM_BRB_CENTER_OFFSET = brbCenterOffset;
ENUM_BRB_CONSTANT_ASCENT = brbConstantAscent;
ENUM_BRB_CONSTANT_DESCENT = brbConstantDescent;
ENUM_BRB_OTHER = brbOther;
}
private static Object getFieldValue(Class type, String name) throws IllegalAccessException, NoSuchFieldException {
return type.getField(name).get(null);
}
static int getBaselineResizeBehavior(Component c) {
if (c instanceof JComponent) {
return getBaselineResizeBehavior((JComponent)c);
}
return BRB_OTHER;
}
/**
* Returns a constant indicating how the baseline varies with the size
* of the component.
*
* @param c the JComponent to get the baseline resize behavior for
* @return one of BRB_CONSTANT_ASCENT, BRB_CONSTANT_DESCENT,
* BRB_CENTER_OFFSET or BRB_OTHER
*/
public static int getBaselineResizeBehavior(JComponent c) {
Method brbIMethod = getBRBIMethod(c);
if (brbIMethod != null) {
return invokeBRBIMethod(brbIMethod, c);
}
if (COMPONENT_BRB_METHOD != null) {
return getBaselineResizeBehaviorUsingMustang(c);
}
String uid = c.getUIClassID();
if (uid == "ButtonUI" || uid == "CheckBoxUI" ||
uid == "RadioButtonUI" || uid == "ToggleButtonUI") {
return getButtonBaselineResizeBehavior((AbstractButton)c);
}
else if (uid == "ComboBoxUI") {
return getComboBoxBaselineResizeBehavior((JComboBox)c);
}
else if (uid == "TextAreaUI") {
return getTextAreaBaselineResizeBehavior((JTextArea)c);
}
else if (uid == "TextFieldUI" ||
uid == "FormattedTextFieldUI" ||
uid == "PasswordFieldUI") {
return getSingleLineTextBaselineResizeBehavior((JTextField)c);
}
else if (uid == "LabelUI") {
return getLabelBaselineResizeBehavior((JLabel)c);
}
else if (uid == "ListUI") {
return getListBaselineResizeBehavior((JList)c);
}
else if (uid == "PanelUI") {
return getPanelBaselineResizeBehavior((JPanel)c);
}
else if (uid == "ProgressBarUI") {
return getProgressBarBaselineResizeBehavior((JProgressBar)c);
}
else if (uid == "SliderUI") {
return getSliderBaselineResizeBehavior((JSlider)c);
}
else if (uid == "SpinnerUI") {
return getSpinnerBaselineResizeBehavior((JSpinner)c);
}
else if (uid == "ScrollPaneUI") {
return getScrollPaneBaselineBaselineResizeBehavior((JScrollPane)c);
}
else if (uid == "TabbedPaneUI") {
return getTabbedPaneBaselineResizeBehavior((JTabbedPane)c);
}
else if (uid == "TableUI") {
return getTableBaselineResizeBehavior((JTable)c);
}
else if (uid == "TreeUI") {
return getTreeBaselineResizeBehavior((JTree)c);
}
return BRB_OTHER;
}
private static int getBaselineResizeBehaviorUsingMustang(JComponent c) {
try {
Object result = COMPONENT_BRB_METHOD.invoke(c, null);
if (result == ENUM_BRB_CENTER_OFFSET) {
return BRB_CENTER_OFFSET;
} else if (result == ENUM_BRB_CONSTANT_ASCENT) {
return BRB_CONSTANT_ASCENT;
} else if (result == ENUM_BRB_CONSTANT_DESCENT) {
return BRB_CONSTANT_DESCENT;
}
} catch (IllegalAccessException iae) {
assert false;
} catch (IllegalArgumentException iae2) {
assert false;
} catch (InvocationTargetException ite) {
assert false;
}
return BRB_OTHER;
}
private static Method getBRBIMethod(Component component) {
Class klass = component.getClass();
while (klass != null) {
if (BRB_I_MAP.containsKey(klass)) {
Method method = (Method)BRB_I_MAP.get(klass);
return method;
}
klass = klass.getSuperclass();
}
klass = component.getClass();
Method[] methods = klass.getMethods();
for (int i = methods.length - 1; i >= 0; i--) {
Method method = methods[i];
if ("getBaselineResizeBehaviorInt".equals(method.getName())) {
Class[] params = method.getParameterTypes();
if (params.length == 0) {
BRB_I_MAP.put(klass, method);
return method;
}
}
}
BRB_I_MAP.put(klass, null);
return null;
}
private static int invokeBRBIMethod(Method method, Component c) {
int brb = BRB_OTHER;
try {
brb = ((Integer)method.invoke(c, null)).intValue();
} catch (IllegalAccessException iae) {
} catch (IllegalArgumentException iae2) {
} catch (InvocationTargetException ite2) {
}
return brb;
}
private static int getTreeBaselineResizeBehavior(JTree tree) {
return BRB_CONSTANT_ASCENT;
}
private static int getSingleLineTextBaselineResizeBehavior(JTextField tf) {
return BRB_CENTER_OFFSET;
}
private static int getTextAreaBaselineResizeBehavior(JTextArea ta) {
return BRB_CONSTANT_ASCENT;
}
private static int getTableBaselineResizeBehavior(JTable table) {
return BRB_CONSTANT_ASCENT;
}
private static int getTabbedPaneBaselineResizeBehavior(JTabbedPane tp) {
switch(tp.getTabPlacement()) {
case JTabbedPane.LEFT:
case JTabbedPane.RIGHT:
case JTabbedPane.TOP:
return BRB_CONSTANT_ASCENT;
case JTabbedPane.BOTTOM:
return BRB_CONSTANT_DESCENT;
}
return BRB_OTHER;
}
private static int getSpinnerBaselineResizeBehavior(JSpinner spinner) {
return getBaselineResizeBehavior(spinner.getEditor());
}
private static int getSliderBaselineResizeBehavior(JSlider slider) {
return BRB_OTHER;
}
private static int getScrollPaneBaselineBaselineResizeBehavior(JScrollPane sp) {
return BRB_CONSTANT_ASCENT;
}
private static int getProgressBarBaselineResizeBehavior(JProgressBar pb) {
if (pb.isStringPainted() &&
pb.getOrientation() == JProgressBar.HORIZONTAL) {
return BRB_CENTER_OFFSET;
}
return BRB_OTHER;
}
private static int getPanelBaselineResizeBehavior(JPanel panel) {
Border b = panel.getBorder();
if (b instanceof TitledBorder) {
switch(((TitledBorder)b).getTitlePosition()) {
case TitledBorder.ABOVE_TOP:
case TitledBorder.TOP:
case TitledBorder.DEFAULT_POSITION:
case TitledBorder.BELOW_TOP:
return BRB_CONSTANT_ASCENT;
case TitledBorder.ABOVE_BOTTOM:
case TitledBorder.BOTTOM:
case TitledBorder.BELOW_BOTTOM:
return BRB_CONSTANT_DESCENT;
}
}
return BRB_OTHER;
}
private static int getListBaselineResizeBehavior(JList list) {
return BRB_CONSTANT_ASCENT;
}
private static int getLabelBaselineResizeBehavior(JLabel label) {
if (label.getClientProperty("html") != null) {
return BRB_OTHER;
}
switch(label.getVerticalAlignment()) {
case JLabel.TOP:
return BRB_CONSTANT_ASCENT;
case JLabel.BOTTOM:
return BRB_CONSTANT_DESCENT;
case JLabel.CENTER:
return BRB_CENTER_OFFSET;
}
return BRB_OTHER;
}
private static int getButtonBaselineResizeBehavior(AbstractButton button) {
if (button.getClientProperty("html") != null) {
return BRB_OTHER;
}
switch(button.getVerticalAlignment()) {
case AbstractButton.TOP:
return BRB_CONSTANT_ASCENT;
case AbstractButton.BOTTOM:
return BRB_CONSTANT_DESCENT;
case AbstractButton.CENTER:
return BRB_CENTER_OFFSET;
}
return BRB_OTHER;
}
private static int getComboBoxBaselineResizeBehavior(JComboBox cb) {
if (cb.isEditable()) {
return getBaselineResizeBehavior(cb.getEditor().getEditorComponent());
}
ListCellRenderer renderer = cb.getRenderer();
if (renderer == null) {
if (brbListCellRenderer == null) {
brbListCellRenderer = new DefaultListCellRenderer();
}
renderer = brbListCellRenderer;
}
Object value = null;
Object prototypeValue = cb.getPrototypeDisplayValue();
if (prototypeValue != null) {
value = prototypeValue;
} else if (cb.getModel().getSize() > 0) {
value = cb.getModel().getElementAt(0);
}
if (value != null) {
if (brbList == null) {
brbList = new JList();
}
Component component = renderer.
getListCellRendererComponent(brbList, value, -1,
false, false);
return getBaselineResizeBehavior(component);
}
return BRB_OTHER;
}
/**
* Returns the baseline for the specified component, or -1 if the
* baseline can not be determined. The baseline is measured from
* the top of the component. This method returns the baseline based
* on the preferred size.
*
* @param component JComponent to calculate baseline for
* @return baseline for the specified component
*/
public static int getBaseline(JComponent component) {
Dimension pref = component.getPreferredSize();
return getBaseline(component, pref.width, pref.height);
}
private static Method getBaselineMethod(JComponent component) {
if (COMPONENT_BASELINE_METHOD != null) {
return COMPONENT_BASELINE_METHOD;
}
Class klass = component.getClass();
while (klass != null) {
if (BASELINE_MAP.containsKey(klass)) {
Method method = (Method)BASELINE_MAP.get(klass);
return method;
}
klass = klass.getSuperclass();
}
klass = component.getClass();
Method[] methods = klass.getMethods();
for (int i = methods.length - 1; i >= 0; i--) {
Method method = methods[i];
if ("getBaseline".equals(method.getName())) {
Class[] params = method.getParameterTypes();
if (params.length == 2 && params[0] == int.class &&
params[1] == int.class) {
BASELINE_MAP.put(klass, method);
return method;
}
}
}
BASELINE_MAP.put(klass, null);
return null;
}
private static int invokeBaseline(Method method, JComponent c, int width,
int height) {
int baseline = -1;
try {
baseline = ((Integer)method.invoke(c,
new Object[] { new Integer(width),
new Integer(height) })).intValue();
} catch (IllegalAccessException iae) {
} catch (IllegalArgumentException iae2) {
} catch (InvocationTargetException ite2) {
}
return baseline;
}
private static boolean isKnownLookAndFeel() {
LookAndFeel laf = UIManager.getLookAndFeel();
String lookAndFeelID = laf.getID();
return (lookAndFeelID == "GTK" || lookAndFeelID == "Aqua" ||
isMetal(laf) || isWindows(laf));
}
/**
* Returns the baseline for the specified component, or a value less
* than 0 if the baseline can not be determined. The baseline is measured
* from the top of the component.
*
* @param component JComponent to calculate baseline for
* @param width Width of the component to determine baseline for.
* @param height Height of the component to determine baseline for.
* @return baseline for the specified component
*/
public static int getBaseline(JComponent component, int width, int height) {
Method baselineMethod = getBaselineMethod(component);
if (baselineMethod != null) {
return invokeBaseline(baselineMethod, component, width, height);
}
Object baselineImpl = UIManager.get("Baseline.instance");
if (baselineImpl != null && (baselineImpl instanceof Baseline)) {
return ((Baseline)baselineImpl).getComponentBaseline(
component, width, height);
}
if (!isKnownLookAndFeel()) {
return -1;
}
String uid = component.getUIClassID();
int baseline = -1;
if (uid == "ButtonUI" || uid == "CheckBoxUI" ||
uid == "RadioButtonUI" || uid == "ToggleButtonUI") {
baseline = getButtonBaseline((AbstractButton)component,
height);
}
else if (uid == "ComboBoxUI") {
return getComboBoxBaseline((JComboBox)component,
height);
}
else if (uid == "TextAreaUI") {
return getTextAreaBaseline((JTextArea)component, height);
}
else if (uid == "FormattedTextFieldUI" ||
uid == "PasswordFieldUI" ||
uid == "TextFieldUI") {
baseline = getSingleLineTextBaseline((JTextComponent)component,
height);
}
else if (uid == "LabelUI") {
baseline = getLabelBaseline((JLabel)component, height);
}
else if (uid == "ListUI") {
baseline = getListBaseline((JList)component, height);
}
else if (uid == "PanelUI") {
baseline = getPanelBaseline((JPanel)component, height);
}
else if (uid == "ProgressBarUI") {
baseline = getProgressBarBaseline((JProgressBar)component, height);
}
else if (uid == "SliderUI") {
baseline = getSliderBaseline((JSlider)component, height);
}
else if (uid == "SpinnerUI") {
baseline = getSpinnerBaseline((JSpinner)component, height);
}
else if (uid == "ScrollPaneUI") {
baseline = getScrollPaneBaseline((JScrollPane)component, height);
}
else if (uid == "TabbedPaneUI") {
baseline = getTabbedPaneBaseline((JTabbedPane)component, height);
}
else if (uid == "TableUI") {
baseline = getTableBaseline((JTable)component, height);
}
else if (uid == "TreeUI") {
baseline = getTreeBaseline((JTree)component, height);
}
return Math.max(baseline, -1);
}
private static Insets rotateInsets(Insets topInsets, int targetPlacement) {
switch(targetPlacement) {
case JTabbedPane.LEFT:
return new Insets(topInsets.left, topInsets.top,
topInsets.right, topInsets.bottom);
case JTabbedPane.BOTTOM:
return new Insets(topInsets.bottom, topInsets.left,
topInsets.top, topInsets.right);
case JTabbedPane.RIGHT:
return new Insets(topInsets.left, topInsets.bottom,
topInsets.right, topInsets.top);
default:
return new Insets(topInsets.top, topInsets.left,
topInsets.bottom, topInsets.right);
}
}
private static int getMaxTabHeight(JTabbedPane tp) {
int fontHeight = tp.getFontMetrics(tp.getFont()).getHeight();
int height = fontHeight;
boolean tallerIcons = false;
for (int counter = tp.getTabCount() - 1; counter >= 0; counter--) {
Icon icon = tp.getIconAt(counter);
if (icon != null) {
int iconHeight = icon.getIconHeight();
height = Math.max(height, iconHeight);
if (iconHeight > fontHeight) {
tallerIcons = true;
}
}
}
Insets tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
height += 2;
if (!isMetal() || !tallerIcons) {
height += tabInsets.top + tabInsets.bottom;
}
return height;
}
private static int getTabbedPaneBaseline(JTabbedPane tp, int height) {
if (tp.getTabCount() > 0) {
if (isAqua()) {
return getAquaTabbedPaneBaseline(tp, height);
}
Insets insets = tp.getInsets();
Insets contentBorderInsets = UIManager.getInsets(
"TabbedPane.contentBorderInsets");
Insets tabAreaInsets = rotateInsets(UIManager.getInsets(
"TabbedPane.tabAreaInsets"),
tp.getTabPlacement());
FontMetrics metrics = tp.getFontMetrics(tp.getFont());
int maxHeight = getMaxTabHeight(tp);
iconRect.setBounds(0, 0, 0, 0);
textRect.setBounds(0, 0, 0, 0);
viewRect.setBounds(0, 0, Short.MAX_VALUE, maxHeight);
SwingUtilities.layoutCompoundLabel(tp, metrics, "A", null,
SwingUtilities.CENTER,
SwingUtilities.CENTER,
SwingUtilities.CENTER,
SwingUtilities.TRAILING,
viewRect,
iconRect,
textRect,
0);
int baseline = textRect.y + metrics.getAscent();
switch(tp.getTabPlacement()) {
case JTabbedPane.TOP:
baseline += insets.top + tabAreaInsets.top;
if (isWindows()) {
if (tp.getTabCount() > 1) {
baseline += 1;
}
else {
baseline -= 1;
}
}
return baseline;
case JTabbedPane.BOTTOM:
baseline = tp.getHeight() - insets.bottom -
tabAreaInsets.bottom - maxHeight + baseline;
if (isWindows()) {
if (tp.getTabCount() > 1) {
baseline += -1;
}
else {
baseline += 1;
}
}
return baseline;
case JTabbedPane.LEFT:
case JTabbedPane.RIGHT:
if (isAqua()) {
// Aqua rotates left/right text, so that there isn't a good
// baseline.
return -1;
}
baseline += insets.top + tabAreaInsets.top;
if (isWindows()) {
baseline += (maxHeight % 2);
}
return baseline;
}
}
return -1;
}
private static int getAquaTabbedPaneBaseline(JTabbedPane tp, int height) {
Font font = tp.getFont();
FontMetrics metrics = tp.getFontMetrics(font);
int ascent = metrics.getAscent();
int offset;
switch(tp.getTabPlacement()) {
case JTabbedPane.TOP:
offset = 5;
if (tp.getFont().getSize() > 12) {
offset = 6;
}
int yOffset = 20 - metrics.getHeight();
yOffset /= 2;
return offset + yOffset + ascent - 1;
case JTabbedPane.BOTTOM:
if (tp.getFont().getSize() > 12) {
offset = 6;
} else {
offset = 4;
}
return height - (20 -
((20 - metrics.getHeight()) / 2 + ascent)) - offset;
case JTabbedPane.LEFT:
case JTabbedPane.RIGHT:
// Aqua rotates left/right text, so that there isn't a good
// baseline.
return -1;
}
return -1;
}
private static int getSliderBaseline(JSlider slider, int height) {
// We don't handle GTK as too much is hidden to be able to calculate it
if (slider.getPaintLabels() && !isGTK()) {
boolean isAqua = isAqua();
FontMetrics metrics = slider.getFontMetrics(slider.getFont());
Insets insets = slider.getInsets();
Insets focusInsets = (Insets)UIManager.get("Slider.focusInsets");
if (slider.getOrientation() == JSlider.HORIZONTAL) {
int tickLength = 8;
int contentHeight = height - insets.top - insets.bottom -
focusInsets.top - focusInsets.bottom;
int thumbHeight = 20;
if (isMetal()) {
tickLength = ((Integer)UIManager.get(
"Slider.majorTickLength")).intValue() + 5;
thumbHeight = UIManager.getIcon(
"Slider.horizontalThumbIcon" ).getIconHeight();
}
else if (isWindows() && isXP()) {
// NOTE: this is not correct, this should come from
// the skin (in >= 1.5), but short of reflection
// hacks we don't have access to the real value.
thumbHeight++;
}
int centerSpacing = thumbHeight;
if (isAqua || slider.getPaintTicks()) {
// centerSpacing += getTickLength();
centerSpacing += tickLength;
}
// Assume uniform labels.
centerSpacing += metrics.getAscent() + metrics.getDescent();
int trackY = insets.top + focusInsets.top +
(contentHeight - centerSpacing - 1) / 2;
if (isAqua) {
if (slider.getPaintTicks()) {
int prefHeight = slider.getUI().getPreferredSize(slider).
height;
int prefDelta = height - prefHeight;
if (prefDelta > 0) {
trackY -= Math.min(1, prefDelta);
}
} else {
trackY--;
}
}
int trackHeight = thumbHeight;
int tickY = trackY + trackHeight;
int tickHeight = tickLength;
if (!isAqua && !slider.getPaintTicks()) {
tickHeight = 0;
}
int labelY = tickY + tickHeight;
return labelY + metrics.getAscent();
}
else { // vertical
boolean inverted = slider.getInverted();
Integer value = inverted ? getMinSliderValue(slider) :
getMaxSliderValue(slider);
if (value != null) {
int thumbHeight = 11;
if (isMetal()) {
thumbHeight = UIManager.getIcon(
"Slider.verticalThumbIcon").getIconHeight();
}
int trackBuffer = Math.max(metrics.getHeight() / 2,
thumbHeight / 2);
int contentY = focusInsets.top + insets.top;
int trackY = contentY + trackBuffer;
int trackHeight = height - focusInsets.top -
focusInsets.bottom - insets.top - insets.bottom -
trackBuffer - trackBuffer;
int maxValue = getMaxSliderValue(slider).intValue();
int min = slider.getMinimum();
int max = slider.getMaximum();
double valueRange = (double)max - (double)min;
double pixelsPerValue = (double)trackHeight /
(double)valueRange;
int trackBottom = trackY + (trackHeight - 1);
if (isAqua) {
trackY -= 3;
trackBottom += 6;
}
int yPosition = trackY;
double offset;
if (!inverted) {
offset = pixelsPerValue *
((double)max - value.intValue());
}
else {
offset = pixelsPerValue *
((double)value.intValue() - min);
}
if (isAqua) {
yPosition += Math.floor(offset);
} else {
yPosition += Math.round(offset);
}
yPosition = Math.max(trackY, yPosition);
yPosition = Math.min(trackBottom, yPosition);
if (isAqua) {
return yPosition + metrics.getAscent();
}
return yPosition - metrics.getHeight() / 2 +
metrics.getAscent();
}
}
}
return -1;
}
private static Integer getMaxSliderValue(JSlider slider) {
Dictionary dictionary = slider.getLabelTable();
if (dictionary != null) {
Enumeration keys = dictionary.keys();
int max = slider.getMinimum() - 1;
while (keys.hasMoreElements()) {
max = Math.max(max, ((Integer)keys.nextElement()).intValue());
}
if (max == slider.getMinimum() - 1) {
return null;
}
return new Integer(max);
}
return null;
}
private static Integer getMinSliderValue(JSlider slider) {
Dictionary dictionary = slider.getLabelTable();
if (dictionary != null) {
Enumeration keys = dictionary.keys();
int min = slider.getMaximum() + 1;
while (keys.hasMoreElements()) {
min = Math.min(min, ((Integer)keys.nextElement()).intValue());
}
if (min == slider.getMaximum() + 1) {
return null;
}
return new Integer(min);
}
return null;
}
private static int getProgressBarBaseline(JProgressBar pb, int height) {
if (pb.isStringPainted() &&
pb.getOrientation() == JProgressBar.HORIZONTAL) {
FontMetrics metrics = pb.getFontMetrics(pb.getFont());
Insets insets = pb.getInsets();
int y = insets.top;
if (isWindows() && isXP()) {
if (pb.isIndeterminate()) {
y = -1;
height--;
}
else {
y = 0;
height -= 3;
}
}
else if (isGTK()) {
return (height - metrics.getAscent() -
metrics.getDescent()) / 2 + metrics.getAscent();
}
else if (isAqua()) {
if (pb.isIndeterminate()) {
// Aqua doesn't appear to support text on indeterminate
// progress bars.
return -1;
}
y -= 1;
height -= (insets.top + insets.bottom);
}
else {
height -= insets.top + insets.bottom;
}
return y + (height + metrics.getAscent() -
metrics.getLeading() -
metrics.getDescent()) / 2;
}
return -1;
}
private static int getTreeBaseline(JTree tree, int height) {
int rowHeight = tree.getRowHeight();
if (TREE_LABEL == null) {
TREE_LABEL = new JLabel("X");
TREE_LABEL.setIcon(UIManager.getIcon("Tree.closedIcon"));
}
JLabel label = TREE_LABEL;
label.setFont(tree.getFont());
if (rowHeight <= 0) {
rowHeight = label.getPreferredSize().height;
}
return getLabelBaseline(label, rowHeight) + tree.getInsets().top;
}
private static int getTableBaseline(JTable table, int height) {
if (TABLE_LABEL == null) {
TABLE_LABEL = new JLabel("");
TABLE_LABEL.setBorder(new EmptyBorder(1, 1, 1, 1));
}
JLabel label = TABLE_LABEL;
label.setFont(table.getFont());
int rowMargin = table.getRowMargin();
int baseline = getLabelBaseline(label, table.getRowHeight() -
rowMargin);
return baseline += rowMargin / 2;
}
private static int getTextAreaBaseline(JTextArea text, int height) {
Insets insets = text.getInsets();
FontMetrics fm = text.getFontMetrics(text.getFont());
return insets.top + fm.getAscent();
}
private static int getListBaseline(JList list, int height) {
int rowHeight = list.getFixedCellHeight();
if (LIST_LABEL == null) {
LIST_LABEL = new JLabel("X");
LIST_LABEL.setBorder(new EmptyBorder(1, 1, 1, 1));
}
JLabel label = LIST_LABEL;
label.setFont(list.getFont());
// JList actually has much more complex behavior here.
// If rowHeight != -1 the rowHeight is either the max of all cell
// heights (layout orientation != VERTICAL), or is variable depending
// upon the cell. We assume a default size.
// We could theoretically query the real renderer, but that would
// not work for an empty model and the results may vary with
// the content.
if (rowHeight == -1) {
rowHeight = label.getPreferredSize().height;
}
return getLabelBaseline(label, rowHeight) + list.getInsets().top;
}
private static int getScrollPaneBaseline(JScrollPane sp, int height) {
Component view = sp.getViewport().getView();
if (view instanceof JComponent) {
int baseline = getBaseline((JComponent)view);
if (baseline > 0) {
return baseline + sp.getViewport().getY();
}
}
return -1;
}
private static int getPanelBaseline(JPanel panel, int height) {
Border border = panel.getBorder();
if (border instanceof TitledBorder) {
TitledBorder titledBorder = (TitledBorder)border;
if (titledBorder.getTitle() != null &&
!"".equals(titledBorder.getTitle())) {
Font font = titledBorder.getTitleFont();
if (font == null) {
font = panel.getFont();
if (font == null) {
font = new Font("Dialog", Font.PLAIN, 12);
}
}
Border border2 = titledBorder.getBorder();
Insets borderInsets;
if (border2 != null) {
borderInsets = border2.getBorderInsets(panel);
}
else {
borderInsets = EMPTY_INSETS;
}
FontMetrics fm = panel.getFontMetrics(font);
int fontHeight = fm.getHeight();
int descent = fm.getDescent();
int ascent = fm.getAscent();
int y = EDGE_SPACING;
int h = height - EDGE_SPACING * 2;
int diff;
switch (((TitledBorder)border).getTitlePosition()) {
case TitledBorder.ABOVE_TOP:
diff = ascent + descent + (Math.max(EDGE_SPACING,
TEXT_SPACING*2) - EDGE_SPACING);
return y + diff - (descent + TEXT_SPACING);
case TitledBorder.TOP:
case TitledBorder.DEFAULT_POSITION:
diff = Math.max(0, ((ascent/2) + TEXT_SPACING) -
EDGE_SPACING);
return (y + diff - descent) +
(borderInsets.top + ascent + descent)/2;
case TitledBorder.BELOW_TOP:
return y + borderInsets.top + ascent + TEXT_SPACING;
case TitledBorder.ABOVE_BOTTOM:
return (y + h) -
(borderInsets.bottom + descent + TEXT_SPACING);
case TitledBorder.BOTTOM:
h -= fontHeight / 2;
return ((y + h) - descent) +
((ascent + descent) - borderInsets.bottom)/2;
case TitledBorder.BELOW_BOTTOM:
h -= fontHeight;
return y + h + ascent + TEXT_SPACING;
}
}
}
return -1;
}
private static int getSpinnerBaseline(JSpinner spinner, int height) {
JComponent editor = spinner.getEditor();
if (editor instanceof JSpinner.DefaultEditor) {
JSpinner.DefaultEditor defaultEditor = (JSpinner.DefaultEditor)
editor;
JTextField tf = defaultEditor.getTextField();
Insets spinnerInsets = spinner.getInsets();
Insets editorInsets = defaultEditor.getInsets();
int offset = spinnerInsets.top + editorInsets.top;
height -= (offset + spinnerInsets.bottom + editorInsets.bottom);
if (height <= 0) {
return -1;
}
return offset + getSingleLineTextBaseline(tf, height);
}
Insets insets = spinner.getInsets();
FontMetrics fm = spinner.getFontMetrics(spinner.getFont());
return insets.top + fm.getAscent();
}
private static int getLabelBaseline(JLabel label, int height) {
Icon icon = (label.isEnabled()) ? label.getIcon() :
label.getDisabledIcon();
FontMetrics fm = label.getFontMetrics(label.getFont());
resetRects(label, height);
SwingUtilities.layoutCompoundLabel(label, fm,
"a", icon, label.getVerticalAlignment(),
label.getHorizontalAlignment(), label.getVerticalTextPosition(),
label.getHorizontalTextPosition(), viewRect, iconRect, textRect,
label.getIconTextGap());
return textRect.y + fm.getAscent();
}
private static int getComboBoxBaseline(JComboBox combobox, int height) {
Insets insets = combobox.getInsets();
int y = insets.top;
height -= (insets.top + insets.bottom);
if (combobox.isEditable()) {
ComboBoxEditor editor = combobox.getEditor();
if (editor != null && (editor.getEditorComponent() instanceof
JTextField)) {
JTextField tf = (JTextField)editor.getEditorComponent();
return y + getSingleLineTextBaseline(tf, height);
}
}
// Use the renderer to calculate baseline
if (isMetal()) {
if (isOceanTheme()) {
y += 2;
height -= 4;
}
}
else if (isWindows()) {
// This doesn't guarantee an XP style will be active,
// but we don't offer public API to detect if XP is active.
String osVersion = System.getProperty("os.version");
if (osVersion != null) {
Float version = Float.valueOf(osVersion);
if (version.floatValue() > 4.0) {
y += 2;
height -= 4;
}
}
}
ListCellRenderer renderer = combobox.getRenderer();
if (renderer instanceof JLabel) {
int baseline = y + getLabelBaseline((JLabel)renderer, height);
if (isAqua()) {
return baseline - 1;
}
return baseline;
}
// Renderer isn't a label, use metrics directly.
FontMetrics fm = combobox.getFontMetrics(combobox.getFont());
return y + fm.getAscent();
}
/**
* Returns the baseline for single line text components, like
* JTextField
.
*/
private static int getSingleLineTextBaseline(JTextComponent textComponent,
int h) {
View rootView = textComponent.getUI().getRootView(textComponent);
if (rootView.getViewCount() > 0) {
Insets insets = textComponent.getInsets();
int height = h - insets.top - insets.bottom;
int y = insets.top;
View fieldView = rootView.getView(0);
int vspan = (int)fieldView.getPreferredSpan(View.Y_AXIS);
if (height != vspan) {
int slop = height - vspan;
y += slop / 2;
}
FontMetrics fm = textComponent.getFontMetrics(
textComponent.getFont());
y += fm.getAscent();
return y;
}
return -1;
}
/**
* Returns the baseline for buttons.
*/
private static int getButtonBaseline(AbstractButton button, int height) {
FontMetrics fm = button.getFontMetrics(button.getFont());
resetRects(button, height);
String text = button.getText();
if (text != null && text.startsWith("")) {
return -1;
}
// NOTE: that we use "a" here to make sure we get a valid value, if
// we were to pass in an empty string or null we would not get
// back the right thing.
SwingUtilities.layoutCompoundLabel(
button, fm, "a", button.getIcon(),
button.getVerticalAlignment(), button.getHorizontalAlignment(),
button.getVerticalTextPosition(),
button.getHorizontalTextPosition(),
viewRect, iconRect, textRect,
text == null ? 0 : button.getIconTextGap());
if (isAqua()) {
return textRect.y + fm.getAscent() + 1;
}
return textRect.y + fm.getAscent();
}
private static void resetRects(JComponent c, int height) {
Insets insets = c.getInsets();
viewRect.x = insets.left;
viewRect.y = insets.top;
viewRect.width = c.getWidth() - (insets.right + viewRect.x);
viewRect.height = height - (insets.bottom + viewRect.y);
textRect.x = textRect.y = textRect.width = textRect.height = 0;
iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
}
private static boolean isOceanTheme() {
if (!inSandbox) {
try {
java.lang.reflect.Field field = MetalLookAndFeel.class.getDeclaredField("currentTheme");
field.setAccessible(true);
Object theme = field.get(null);
return "javax.swing.plaf.metal.OceanTheme".equals(theme.getClass().getName());
} catch (Exception ex) {
// We're in a sandbox and can't access the field
inSandbox = true;
}
}
if (!checkedForOcean) {
checkedForOcean = true;
checkForOcean();
}
return usingOcean;
}
private static void checkForOcean() {
String version = System.getProperty("java.specification.version");
int firstDot = version.indexOf('.');
String majorString;
String minorString;
if (firstDot != -1) {
majorString = version.substring(0, firstDot);
int secondDot = version.indexOf('.', firstDot + 1);
if (secondDot == -1) {
minorString = version.substring(firstDot + 1);
} else {
minorString = version.substring(firstDot + 1, secondDot);
}
} else {
majorString = version;
minorString = null;
}
try {
int majorVersion = Integer.parseInt(majorString);
int minorVersion = (minorString != null) ? Integer.parseInt(minorString) : 0;
usingOcean = (majorVersion > 1 || minorVersion > 4);
} catch (NumberFormatException nfe) {
}
}
private static boolean isWindows() {
return isWindows(UIManager.getLookAndFeel());
}
private static boolean isWindows(LookAndFeel laf) {
if (laf.getID() == "Windows") {
return true;
}
if (!checkedForWindows) {
try {
WINDOWS_CLASS = Class.forName(
"com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (ClassNotFoundException e) {
}
checkedForWindows = true;
}
return (WINDOWS_CLASS != null && WINDOWS_CLASS.isInstance(laf));
}
private static boolean isMetal() {
return isMetal(UIManager.getLookAndFeel());
}
private static boolean isMetal(LookAndFeel laf) {
return (laf.getID() == "Metal" || laf instanceof MetalLookAndFeel);
}
private static boolean isGTK() {
return UIManager.getLookAndFeel().getID() == "GTK";
}
private static boolean isAqua() {
return UIManager.getLookAndFeel().getID() == "Aqua";
}
private static boolean isXP() {
if (!checkedForClassic) {
try {
CLASSIC_WINDOWS = Class.forName(
"com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");
} catch (ClassNotFoundException e) {
}
checkedForClassic = true;
}
if (CLASSIC_WINDOWS != null && CLASSIC_WINDOWS.
isInstance(UIManager.getLookAndFeel())) {
return false;
}
Toolkit toolkit = Toolkit.getDefaultToolkit();
Boolean themeActive = (Boolean)toolkit.getDesktopProperty(
"win.xpstyle.themeActive");
if (themeActive == null) {
themeActive = Boolean.FALSE;
}
return themeActive.booleanValue();
}
/**
* Creates an instance of Baseline. You typically don't create a
* Baseline. The constructor is provided by look and feels that wish
* to provide baseline support.
*
* A custom look and feel that wants to provide Baseline
* support should put the instance in the defaults returned
* from getDefaults
. If you want to override the
* baseline suport for a look and feel place the instance in the defaults
* returned from UIManager.getLookAndFeelDefaults(). Tthis will ensure
* that if the look and feel changes the appropriate baseline can be used.
*/
protected Baseline() {
}
/**
* Returns the baseline for the specified component, or -1 if the
* baseline can not be determined. The baseline is measured from
* the top of the component.
*
* @param component JComponent to calculate baseline for
* @param width Width of the component to determine baseline for.
* @param height Height of the component to determine baseline for.
* @return baseline for the specified component
*/
public int getComponentBaseline(JComponent component, int width,
int height) {
return -1;
}
}