org.jdesktop.layout.AquaBaseline Maven / Gradle / Ivy
Show all versions of swing-layout Show documentation
/*
* Copyright (C) 2005 Sun Microsystems, Inc. All rights reserved. Use is
* subject to license terms.
*/
package org.jdesktop.layout;
import org.jdesktop.layout.*;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import javax.swing.text.View;
import java.util.Dictionary;
import java.util.Enumeration;
import javax.swing.border.*;
import javax.swing.plaf.metal.MetalLookAndFeel;
/**
* Convenience class that can be used to determine the baseline of a
* particular component.
*
* This class is primarily useful for JREs prior to 1.6. In 1.6 API for this
* was added directly to Component, JComponent and the
* appropriate ComponentUIs. When run on a JRE of 1.6 or greater this will directly
* call into the getBaseline method of Component.
*
* @author Werner Randelshofer
* @version $Revision$
*/
class AquaBaseline extends Baseline {
static final AquaBaseline INSTANCE = new AquaBaseline();
//
// 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;
/**
* 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) {
String uid = component.getUIClassID();
int baseline = -1;
if (uid == "ButtonUI" || uid == "ToggleButtonUI") {
baseline = getButtonBaseline((AbstractButton)component,
height);
}
else if (uid == "CheckBoxUI" || uid == "RadioButtonUI") {
baseline = getCheckBoxBaseline((AbstractButton)component, height) + 1;
}
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 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 int getTabbedPaneBaseline(JTabbedPane tp, int height) {
if (tp.getTabCount() > 0) {
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 + 3;
/*
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:
baseline += insets.top + tabAreaInsets.top;
/*
if (isWindows()) {
baseline += (maxHeight % 2);
}*/
return baseline;
}
}
return -1;
}
private int getSliderBaseline(JSlider slider, int height) {
if (slider.getPaintLabels()) {
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()) {
// PENDING: 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 (slider.getPaintTicks()) {
// centerSpacing += getTickLength();
centerSpacing += tickLength;
}
// Assume uniform labels.
centerSpacing += metrics.getAscent() + metrics.getDescent();
int trackY = insets.top + focusInsets.top +
(contentHeight - centerSpacing - 1) / 2;
int trackHeight = thumbHeight;
int tickY = trackY + trackHeight;
int tickHeight = tickLength;
if (!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);
int yPosition;
if (!inverted) {
yPosition = trackY;
yPosition += Math.round(pixelsPerValue *
((double)max - value.intValue()));
}
else {
yPosition = trackY;
yPosition += Math.round(pixelsPerValue *
((double)value.intValue() - min) );
}
yPosition = Math.max(trackY, yPosition);
yPosition = Math.min(trackBottom, yPosition);
return yPosition - metrics.getHeight() / 2 +
metrics.getAscent();
}
}
}
return -1;
}
private 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 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 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 {*/
height -= insets.top + insets.bottom;
//}
return y + (height + metrics.getAscent() -
metrics.getLeading() -
metrics.getDescent()) / 2;
}
return -1;
}
private 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 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 int getTextAreaBaseline(JTextArea text, int height) {
Insets insets = text.getInsets();
FontMetrics fm = text.getFontMetrics(text.getFont());
return insets.top + fm.getAscent();
}
private 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 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 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 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 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 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);
}
}
y -= 1;
// Use the renderer to calculate baseline
ListCellRenderer renderer = combobox.getRenderer();
if (renderer instanceof JLabel) {
return y + getLabelBaseline((JLabel)renderer, height);
}
// 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 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 int getCheckBoxBaseline(AbstractButton button, int height) {
FontMetrics fm = button.getFontMetrics(button.getFont());
resetRects(button, height);
// 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,
button.getText() == null ? 0 : button.getIconTextGap());
return textRect.y + fm.getAscent();
}
/**
* Returns the baseline for buttons.
*/
private int getButtonBaseline(AbstractButton button, int height) {
FontMetrics fm = button.getFontMetrics(button.getFont());
resetRects(button, height);
// 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,
button.getText() == null ? 0 : button.getIconTextGap());
return textRect.y + fm.getAscent() + 1;
}
private 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 isMac() {
return UIManager.getLookAndFeel().getID() == "Mac";
}
private static boolean isAqua() {
return UIManager.getLookAndFeel().getID() == "Aqua";
}
}