/*
* @(#)QuaquaButtonBorder.java
*
* Copyright (c) 2005-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.border.VisualMarginBorder;
import ch.randelshofer.quaqua.border.OverlayBorder;
import ch.randelshofer.quaqua.border.FocusBorder;
import ch.randelshofer.quaqua.border.ButtonStateBorder;
import ch.randelshofer.quaqua.border.AnimatedBorder;
import ch.randelshofer.quaqua.border.CompositeVisualMarginBorder;
import ch.randelshofer.quaqua.border.PressedCueBorder;
import ch.randelshofer.quaqua.osx.OSXAquaPainter.SegmentPosition;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JToolBar;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.UIResource;
import ch.randelshofer.quaqua.util.Images;
import ch.randelshofer.quaqua.util.InsetsUtil;
import javax.swing.plaf.InsetsUIResource;
/**
* QuaquaButtonBorder.
* This border uses client properties and font sizes of a JComponent to
* determine which style the border shall have.
* For some styles, the JComponent should honour size constrictions.
* <p>
* The following values of the client property <code>Quaqua.Button.style</code>
* are supported:
* <ul>
* <li><code>push</code> Rounded push button. Maximum height of the JComponent
* shall be constrained to its preferred height.</li>
* <li><code>square</code> Square button. No size constraints.</li>
* <li><code>placard</code> or <code>gradient</code> Placard button. No size constraints.</li>
* <li><code>colorWell</code> Square button with color area in the center.
* No size constraints.</li>
* <li><code>bevel</code> Rounded Bevel button. No size constraints.</li>
* <li><code>toggle</code> or <code>segmented</code> Toggle button. Maximum height of the JComponent
* shall be constrained to its preferred height.</li>
* <li><code>toggleWest</code> West Toggle button. Maximum height of the JComponent
* shall be constrained to its preferred height.</li>
* <li><code>toggleEast</code> East Toggle button. Maximum height of the JComponent
* shall be constrained to its preferred height.</li>
* <li><code>toggleCenter</code> Center Toggle button. Maximum height of the JComponent
* shall be constrained to its preferred height.</li>
* <li><code>toolBar</code> ToolBar button. No size constraints.</li>
* <li><code>toolBarTab</code> ToolBar Tab button. No size constraints.</li>
* <li><code>toolBarRollover</code> ToolBar button with rollover effect. No size constraints.</li>
* </ul>
* If the <code>Quaqua.Button.style</code> property is missing, then the
* following values of the client property <code>JButton.buttonType</code>
* are supported:
* <ul>
* <li><code>text</code> Rounded push button. Maximum height of the JComponent
* shall be constrained to its preferred height.</li>
* <li><code>toolBar</code> Square button. No size constraints.</li>
* <li><code>icon</code> Rounded Bevel button. No size constraints.</li>
* </ul>
*
* @author Werner Randelshofer
* @version $Id: QuaquaButtonBorder.java 438 2011-08-09 22:13:41Z wrandelshofer $
*/
public class QuaquaButtonBorder implements Border, PressedCueBorder, UIResource {
// Shared borders
private static Border regularPushButtonBorder;
private static Border smallPushButtonBorder;
private static Border squareBorder;
private static Border placardBorder;
private static Border colorWellBorder;
private static Border bevelBorder;
private static Border toolBarBorder;
private static Border toolBarRolloverBorder;
private static Border toolBarTabBorder;
private static Border toggleWestBorder;
private static Border toggleEastBorder;
private static Border toggleCenterBorder;
private static Border toggleBorder;
private static Border helpBorder;
private static Border tableHeaderBorder;
/**
* The default client property value to be used, when no client property
* has been specified for the JComponent.
*/
private String defaultStyle;
/** Creates a new instance. */
public QuaquaButtonBorder(String defaultStyle) {
this.defaultStyle = defaultStyle;
}
/** Returns a Border that implements the VisualMargin interface. */
public Border getActualBorder(Component c) {
Border b = null;
String style = getStyle(c);
JComponent jc = c instanceof JComponent ? (JComponent) c : null;
String segpos = (jc == null) ? "only" : (String) jc.getClientProperty("JButton.segmentPosition");
if ("toggleEast".equals(style)) {
segpos = "first";
} else if ("toggleCenter".equals(style)) {
segpos = "middle";
} else if ("toggleWest".equals(style)) {
segpos = "last";
}
if (segpos == null//
|| !"first".equals(segpos) && !"middle".equals(segpos) && ! !"last".equals(segpos)) {
segpos = "only";
}
// Explicitly chosen styles
if ("text".equals(style) || "push".equals(style)) {
switch (QuaquaUtilities.getSizeVariant(c)) {
case SMALL:
case MINI:
b = getSmallPushButtonBorder();
break;
default:
b = getRegularPushButtonBorder();
break;
}
} else if ("toolBar".equals(style)) {
if (toolBarBorder == null) {
toolBarBorder = new CompositeVisualMarginBorder(new CompoundBorder(
new EmptyBorder(-1, -1, -1, -2),
new QuaquaToolBarButtonStateBorder(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Toggle.borders.png")),
10, true,
new Insets(8, 10, 15, 10), new Insets(4, 6, 4, 6), true, false)),//
0, 0, 0, 0);
}
b = toolBarBorder;
} else if ("toolBarRollover".equals(style)) {
if (toolBarRolloverBorder == null) {
toolBarRolloverBorder = new CompositeVisualMarginBorder(new CompoundBorder(
new EmptyBorder(-1, -1, -1, -2),
new QuaquaToolBarButtonStateBorder(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Toggle.borders.png")),
10, true,
new Insets(8, 10, 15, 10), new Insets(4, 6, 4, 6), true, true)),//
0, 0, 0, 0);
}
b = toolBarRolloverBorder;
} else if ("toolBarTab".equals(style)) {
if (toolBarTabBorder == null) {
toolBarTabBorder = new QuaquaToolBarTabButtonBorder();
}
b = toolBarTabBorder;
} else if ("square".equals(style) || "toolbar".equals(style)) {
b = getSquareBorder();
} else if ("gradient".equals(style)) {
b = getPlacardBorder();
} else if ("tableHeader".equals(style)) {
b = getTableHeaderBorder();
} else if ("colorWell".equals(style)) {
if (colorWellBorder == null) {
colorWellBorder = new CompositeVisualMarginBorder(
new OverlayBorder(
new QuaquaColorWellBorder() /*)*/,
new CompoundBorder(
new EmptyBorder(-2, -2, -2, -2),
new FocusBorder(
QuaquaBorderFactory.create(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Square.focusRing.png")),
new Insets(10, 9, 10, 8), new Insets(6, 9, 6, 9), true)))),//
0, 0, 0, 0);
}
b = colorWellBorder;
} else if ("icon".equals(style) || "bevel".equals(style)) {
if (bevelBorder == null) {
Insets borderInsets = new Insets(4, 3, 3, 3);
Border focusBorder = new FocusBorder(
QuaquaBorderFactory.create(
Images.createImage(QuaquaButtonBorder.class.getResource("images/RoundedBevel.focusRing.png")),
new Insets(10, 9, 10, 8), borderInsets, true));
bevelBorder = new CompositeVisualMarginBorder(
new CompoundBorder(
new EmptyBorder(-3, -2, -2, -2),
new OverlayBorder(
new ButtonStateBorder(
Images.createImage(QuaquaButtonBorder.class.getResource("images/RoundedBevel.borders.png")),
10, true,
new Insets(10, 9, 10, 8), borderInsets, true),
new CompoundBorder(
new EmptyBorder(0, -1, 0, -1),
focusBorder))),//
0, 0, 0, 0);
}
b = bevelBorder;
} else if ("only".equals(segpos) && ("toggle".equals(style) || "segmented".equals(style)
|| "segmentedRoundRect".equals(style) || "segmentedCapsule".equals(style)
|| style.contains("segmentedTextured"))) {
if (toggleBorder == null) {
Insets borderInsets = new Insets(3, 5, 3, 5);
toggleBorder = new CompositeVisualMarginBorder(
new OverlayBorder(
new ButtonStateBorder(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Toggle.borders.png")),
10, true,
new Insets(8, 10, 15, 10), borderInsets, true),
new FocusBorder(
QuaquaBorderFactory.create(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Toggle.focusRing.png")),
new Insets(8, 10, 15, 10), borderInsets, false))),
2, 2, 2, 2);
}
b = toggleBorder;
} else if ("first".equals(segpos)
|| "toggleEast".equals(style)) {
if (toggleEastBorder == null) {
Insets borderInsets = new Insets(3, 1, 3, 5);
toggleEastBorder = new CompositeVisualMarginBorder(
new OverlayBorder(
new ButtonStateBorder(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Toggle.east.borders.png")),
10, true,
new Insets(8, 1, 15, 10), borderInsets, true),
new FocusBorder(
QuaquaBorderFactory.create(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Toggle.east.focusRing.png")),
new Insets(8, 4, 15, 10), borderInsets, false))),
2, 0, 2, 2, false, true, false, false);
}
b = toggleEastBorder;
} else if ("middle".equals(segpos) || "toggleCenter".equals(style)) {
if (toggleCenterBorder == null) {
Insets borderInsets = new Insets(3, 1, 3, 1);
toggleCenterBorder = new CompositeVisualMarginBorder(
new OverlayBorder(
new ButtonStateBorder(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Toggle.center.borders.png")),
10, true,
new Insets(8, 0, 15, 1), borderInsets, true),
new FocusBorder(
QuaquaBorderFactory.create(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Toggle.center.focusRing.png")),
new Insets(8, 4, 15, 4), borderInsets, false))),
2, 0, 2, 0, false, true, false, true);
}
b = toggleCenterBorder;
} else if ("last".equals(segpos) || "toggleWest".equals(style)) {
if (toggleWestBorder == null) {
Insets borderInsets = new Insets(3, 5, 3, 1);
toggleWestBorder = new CompositeVisualMarginBorder(
new OverlayBorder(
new ButtonStateBorder(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Toggle.west.borders.png")),
10, true,
new Insets(8, 10, 15, 1), borderInsets, true),
new FocusBorder(
QuaquaBorderFactory.create(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Toggle.west.focusRing.png")),
new Insets(8, 10, 15, 4), borderInsets, false))),
2, 2, 2, 0, false, false, false, true);
}
b = toggleWestBorder;
} else if ("help".equals(style)) {
if (helpBorder == null) {
helpBorder = new VisualMarginBorder(2, 3, 2, 3);
}
b = helpBorder;
// Implicit styles
} else if (c.getParent() instanceof JToolBar) {
b = getSquareBorder();
} else {
switch (QuaquaUtilities.getSizeVariant(c)) {
case SMALL:
case MINI:
b = getSmallPushButtonBorder();
break;
default:
b = getRegularPushButtonBorder();
break;
}
}
if (b == null) {
throw new InternalError(style);
}
return b;
}
private Border getRegularPushButtonBorder() {
if (regularPushButtonBorder == null) {
Insets borderInsets = new Insets(1, 5, 1, 5);
BufferedImage[] imageFrames = Images.split(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Button.default.png")),
12, true);
Border[] borderFrames = new Border[12];
for (int i = 0; i < 12; i++) {
borderFrames[i] = QuaquaBorderFactory.create(
imageFrames[i],
new Insets(11, 13, 13, 13),
borderInsets,
true);
}
ButtonStateBorder buttonStateBorder = new ButtonStateBorder(
Images.split(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Button.borders.png")),
10, true),
new Insets(11, 13, 13, 13),
borderInsets,
true);
buttonStateBorder.setBorder(
ButtonStateBorder.DEFAULT,
new AnimatedBorder(borderFrames, 100));
regularPushButtonBorder = new CompositeVisualMarginBorder(
new OverlayBorder(
buttonStateBorder,
new FocusBorder(
QuaquaBorderFactory.create(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Button.focusRing.png")),
new Insets(12, 13, 12, 13),
borderInsets,
false))),
2, 4, 2, 4);
}
return regularPushButtonBorder;
}
private Border getSquareBorder() {
if (squareBorder == null) {
squareBorder = new CompositeVisualMarginBorder(
new OverlayBorder(
QuaquaBorderFactory.createSquareButtonBorder(),
new CompoundBorder(
new EmptyBorder(-2, -2, -2, -2),
new FocusBorder(
QuaquaBorderFactory.create(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Square.focusRing.png")),
new Insets(10, 9, 10, 8), new Insets(6, 9, 6, 9), true)))),
0, 0, 0, 0) {
@Override
protected Insets getVisualMargin(Component c, Insets insets) {
String s = getStyle(c);
insets = super.getVisualMargin(c, new InsetsUIResource(0, 0, 0, 0));
if (insets instanceof javax.swing.plaf.UIResource) {
switch (getSegmentPosition(c)) {
case first:
insets.right = -1;
break;
case middle:
insets.left = 0;
insets.right = -1;
break;
case last:
insets.left = 0;
break;
}
}
return insets;
}
};
}
return squareBorder;
}
private Border getPlacardBorder() {
if (placardBorder == null) {
placardBorder = new CompositeVisualMarginBorder(
new OverlayBorder(
new CompoundBorder(
new EmptyBorder(-1, 0, -1, 0),
QuaquaBorderFactory.createPlacardButtonBorder()),
new CompoundBorder(
new EmptyBorder(-1, -1, -1, -1),
new FocusBorder(
QuaquaBorderFactory.create(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Square.focusRing.png")),
new Insets(10, 9, 10, 8), new Insets(6, 9, 6, 9), true)))),
0, 0, 0, 0) {
@Override
protected Insets getVisualMargin(Component c, Insets insets) {
String s = getStyle(c);
insets = super.getVisualMargin(c, new InsetsUIResource(0, 0, 0, 0));
if (insets instanceof javax.swing.plaf.UIResource) {
if ("gradient".equals(s) && (c.getParent() instanceof JToolBar)) {
String ts = (String) ((JToolBar) c.getParent()).getClientProperty("Quaqua.ToolBar.style");
if (ts != null && ("placard".equals(ts) || "gradient".equals(ts))) {
InsetsUtil.clear(insets);
}
}
}
if (insets instanceof javax.swing.plaf.UIResource) {
switch (getSegmentPosition(c)) {
case first:
insets.right = -1;
break;
case middle:
insets.left = 0;
insets.right = -1;
break;
case last:
insets.left = 0;
break;
}
}
return insets;
}
};
}
return placardBorder;
}
private Border getTableHeaderBorder() {
if (tableHeaderBorder == null) {
tableHeaderBorder = new CompositeVisualMarginBorder(
new ButtonStateBorder(
Images.createImage(QuaquaButtonBorder.class.getResource("images/TableHeader.borders.png")),
4, true, new Insets(7, 1, 8, 1), new Insets(1, 2, 1, 2), true),
0, 0, 0, 0);
}
return tableHeaderBorder;
}
private Border getSmallPushButtonBorder() {
if (smallPushButtonBorder == null) {
Insets borderInsets = new Insets(3, 8, 3, 8);
BufferedImage[] imageFrames = Images.split(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Button.small.default.png")),
12, true);
Border[] borderFrames = new Border[12];
for (int i = 0; i < 12; i++) {
borderFrames[i] = QuaquaBorderFactory.create(
imageFrames[i],
new Insets(9, 13, 12, 13),
borderInsets,
true);
}
ButtonStateBorder buttonStateBorder = new ButtonStateBorder(
Images.split(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Button.small.borders.png")),
10, true),
new Insets(9, 13, 12, 13),
borderInsets,
true);
buttonStateBorder.setBorder(
ButtonStateBorder.DEFAULT,
new AnimatedBorder(borderFrames, 100));
smallPushButtonBorder = new CompositeVisualMarginBorder(
new CompoundBorder(
new EmptyBorder(-2, -3, -2, -3),
new OverlayBorder(
buttonStateBorder,
new FocusBorder(
QuaquaBorderFactory.create(
Images.createImage(QuaquaButtonBorder.class.getResource("images/Button.small.focusRing.png")),
new Insets(9, 14, 12, 14),
borderInsets,
false)))),
0, 0, 0, 0);
}
return smallPushButtonBorder;
}
/**
* Returns the default button margin for the specified component.
*
* FIXME: We should not create a new Insets instance on each method call.
*/
public Insets getDefaultMargin(JComponent c) {
Insets margin = null;
String style = getStyle(c);
QuaquaUtilities.SizeVariant sizeVariant = QuaquaUtilities.getSizeVariant(c);
boolean isSmall = sizeVariant == QuaquaUtilities.SizeVariant.SMALL //
|| sizeVariant == QuaquaUtilities.SizeVariant.MINI;
// Explicitly chosen styles
if ("text".equals(style) || "push".equals(style)) {
if (isSmall) {
margin = new Insets(1, 3, 1, 3);
} else {
margin = new Insets(1, 6, 2, 6);
}
} else if ("toolBar".equals(style)) {
margin = new Insets(0, 0, 0, 0);
} else if ("toolBarRollover".equals(style)) {
margin = new Insets(0, 0, 0, 0);
} else if ("toolBarTab".equals(style)) {
margin = new Insets(0, 0, 0, 0);
} else if ("square".equals(style)) {
if (isSmall) {
margin = new Insets(3, 6, 3, 6);
} else {
margin = new Insets(3, 6, 3, 6);
}
} else if ("gradient".equals(style)) {
if (isSmall) {
margin = new Insets(2, 6, 2, 6);
} else {
margin = new Insets(2, 6, 2, 6);
}
} else if ("colorWell".equals(style)) {
if (isSmall) {
margin = new Insets(1, 6, 1, 6);
} else {
margin = new Insets(1, 6, 2, 6);
}
} else if ("icon".equals(style) || "bevel".equals(style)) {
if (isSmall) {
margin = new Insets(1, 6, 1, 6);
} else {
margin = new Insets(1, 6, 2, 6);
}
} else if ("toggle".equals(style)) {
if (isSmall) {
margin = new Insets(1, 5, 1, 5);
} else {
margin = new Insets(1, 5, 2, 5);
}
} else if ("toggleEast".equals(style)) {
if (isSmall) {
margin = new Insets(1, 5, 1, 5);
} else {
margin = new Insets(1, 5, 2, 5);
}
} else if ("toggleCenter".equals(style)) {
if (isSmall) {
margin = new Insets(1, 5, 1, 5);
} else {
margin = new Insets(1, 5, 2, 5);
}
} else if ("toggleWest".equals(style)) {
if (isSmall) {
margin = new Insets(1, 5, 1, 5);
} else {
margin = new Insets(1, 5, 2, 5);
}
} else if ("help".equals(style)) {
margin = new Insets(0, 0, 0, 0);
// Implicit styles
} else if (c.getParent() instanceof JToolBar) {
margin = new Insets(0, 0, 0, 0);
} else {
if (isSmall) {
margin = new Insets(1, 4, 1, 4);
} else {
margin = new Insets(1, 8, 2, 8);
}
}
return margin;
}
public boolean isFixedHeight(JComponent c) {
String style = getStyle(c).toLowerCase();
return "text".equals(style) || "push".equals(style) || style.startsWith("toggle");
}
protected String getStyle(Component c) {
return QuaquaButtonUI.getStyle(c,defaultStyle);
}
/**
* Returns true, if this border has a visual cue for the pressed
* state of the button.
* If the border has no visual cue, then the ButtonUI has to provide
* it by some other means.
*/
public boolean hasPressedCue(JComponent c) {
Border b = getActualBorder(c);
boolean haspc;
if (b instanceof PressedCueBorder) {
haspc = ((PressedCueBorder) b).hasPressedCue(c);
}
haspc = b != toolBarBorder;
return haspc;
}
public Insets getVisualMargin(Component c) {
return ((VisualMargin) getActualBorder(c)).getVisualMargin(c);
}
/**
* Returns true, if this border has a visual cue for the disabled
* state of the button.
* If the border has no visual cue, then the ButtonUI has to provide
* it by some other means.
* /
* public boolean hasDisabledCue(JComponent c) {
* return false;
* }*/
public Insets getBorderInsets(Component c) {
if (c instanceof JComponent) {
JComponent jc = (JComponent) c;
Insets insets = (Insets) jc.getClientProperty("Quaqua.Border.insets");
if (insets != null) {
return (Insets) insets.clone();
}
}
boolean isBorderPainted = true;
if (c instanceof AbstractButton) {
isBorderPainted = ((AbstractButton) c).isBorderPainted();
}
Insets insets;
if (!isBorderPainted) {
insets = (Insets) UIManager.getInsets("Component.visualMargin").clone();
} else {
insets = getActualBorder((JComponent) c).getBorderInsets(c);
}
if (c instanceof AbstractButton) {
AbstractButton b = (AbstractButton) c;
Insets margin = b.getMargin();
if (margin == null || (margin instanceof UIResource)) {
if (isBorderPainted) {
margin = getDefaultMargin((JComponent) c);
}
}
if (margin != null) {
InsetsUtil.addTo(margin, insets);
}
}
return insets;
}
public boolean isBorderOpaque() {
return false;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
getActualBorder((JComponent) c).paintBorder(c, g, x, y, width, height);
}
private SegmentPosition getSegmentPosition(Component c) {
String s = null;
if (c instanceof JComponent) {
JComponent jc = (JComponent) c;
s = (String) jc.getClientProperty("Quaqua.Button.style");
if (s != null) {
if ("toggleWest".equals(s)) {
return SegmentPosition.first;
} else if ("toggleCenter".equals(s)) {
return SegmentPosition.middle;
} else if ("toggleEast".equals(s)) {
return SegmentPosition.last;
}
}
s = (String) jc.getClientProperty("JButton.segmentPosition");
if (s != null) {
if ("first".equals(s)) {
return SegmentPosition.first;
} else if ("middle".equals(s)) {
return SegmentPosition.middle;
} else if ("last".equals(s)) {
return SegmentPosition.last;
}
}
}
return SegmentPosition.only;
}
}