/* $Id$ */
/* Copyright 2010 Sebastian Haufe
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[url]http://www.apache.org/licenses/LICENSE-2.0[/url]
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
package com.ebenius;
import java.awt.*;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
/**
* A titled Border instance capable of showing HTML (uses a {@link JLabel} as
* renderer). This implementation behaves as the {@link TitledBorder} does,
* but provides nothing but the {@link TitledBorder#TOP TOP title placement}.
* <p>
* Note: This border never does line-wrapping, as borders need static border
* insets which conflicts with danymic line wrapping.
*
* @version $Revision$ as of $Date$
* @author Sebastian Haufe
*/
public class HTMLTitledBorder extends AbstractBorder
implements java.io.Serializable {
/** Serial version UID */
private static final long serialVersionUID = 1L;
/* same setup as the titled border */
// Space between the border and the component's edge
static protected final int EDGE_SPACING = 2;
// Space between the border and text
static protected final int TEXT_SPACING = 2;
// Horizontal inset of text that is left or right justified
static protected final int TEXT_INSET_H = 5;
// -------------------------------------------------------------------------
// Inner classes
// -------------------------------------------------------------------------
/**
* Evil JLable derivate, faking a parent. We do not really add the label to
* a parent, as the parent's layout manager might dislike an additional
* child.
*/
private static class EvilJLabel extends JLabel {
/** Serial version UID */
private static final long serialVersionUID = 1L;
private transient Container parent;
/** Creates a new {@code HTMLTitledBorder.EvilJLabel}. */
EvilJLabel() {}
void setParent(Container parent) {
final Container oldParent = this.parent;
if (oldParent != parent) {
this.parent = null;
if (oldParent != null) {
this.removeNotify();
}
this.parent = parent;
if (parent != null) {
this.addNotify();
}
}
}
@Override
public Container getParent() {
return parent;
}
}
// -------------------------------------------------------------------------
// Instance fields
// -------------------------------------------------------------------------
private transient EvilJLabel label;
private Border border;
private String title;
private int titleJustification = SwingConstants.LEFT;
// -------------------------------------------------------------------------
// Constructors
// -------------------------------------------------------------------------
/**
* Creates a new {@code HTMLTitledBorder}.
*
* @param title the border title, as interpreted by the {@link JLabel} class
*/
public HTMLTitledBorder(String title) {
setTitle(title);
}
/**
* Creates a new {@code HTMLTitledBorder}.
*
* @param border this titled border's border
* @param title the border title, as interpreted by the {@link JLabel} class
*/
public HTMLTitledBorder(Border border, String title) {
this(title);
this.border = border;
}
// ------------------------------------------------------------------------
// Implementing java.awt.Border
// -------------------------------------------------------------------------
@Override
public Insets getBorderInsets(Component c) {
return getBorderInsets(c, new Insets(0, 0, 0, 0));
}
@Override
public Insets getBorderInsets(Component c, Insets insets) {
Border border = getBorder();
if (border != null) {
if (border instanceof AbstractBorder) {
((AbstractBorder) border).getBorderInsets(c, insets);
} else {
final Insets i = border.getBorderInsets(c);
insets.top = i.top;
insets.right = i.right;
insets.bottom = i.bottom;
insets.left = i.left;
}
} else {
insets.left = insets.top = insets.right = insets.bottom = 0;
}
if (getTitle() == null || getTitle().equals("")) {
return insets;
}
final EvilJLabel label = getLabel();
final Dimension prefSize = label.getPreferredSize();
final int labelHeight = prefSize.height;
insets.top = Math.max(labelHeight, insets.top);
insets.left += EDGE_SPACING + TEXT_SPACING;
insets.right += EDGE_SPACING + TEXT_SPACING;
insets.top += EDGE_SPACING + TEXT_SPACING;
insets.bottom += EDGE_SPACING + TEXT_SPACING;
return insets;
}
@Override
public void paintBorder(
Component c,
Graphics g,
int x,
int y,
int width,
int height) {
final Border border = getBorder();
// no title? okay, so just the border
if (getTitle() == null || getTitle().equals("")) {
if (border != null) {
border.paintBorder(c, g, x, y, width, height);
}
return;
}
// get the evil label
final EvilJLabel label = getLabel();
final Dimension labelPrefSize = label.getPreferredSize();
final int labelPrefHeight = labelPrefSize.height;
final int labelPrefWidth = labelPrefSize.width;
// place the whole border bounds
x += EDGE_SPACING;
y += EDGE_SPACING;
width -= EDGE_SPACING * 2;
height -= EDGE_SPACING * 2;
// place the label bounds
final Rectangle labelBounds = new Rectangle();
labelBounds.x = x + TEXT_INSET_H;
labelBounds.y = y;
labelBounds.width = width - 2 * TEXT_INSET_H;
labelBounds.height = labelPrefHeight + TEXT_SPACING;
// move the label according to slave border insets
if (border != null) {
final Insets borderInsets = border.getBorderInsets(c);
labelBounds.x += borderInsets.left;
labelBounds.width -= borderInsets.left + borderInsets.right;
if (borderInsets.top >= labelBounds.height) {
labelBounds.y += (borderInsets.top - labelBounds.height) / 2;
} else {
final int diff = (labelBounds.height - borderInsets.top) / 2;
y += diff;
height -= diff;
}
}
// place the label according to alignment
final boolean ltr = c.getComponentOrientation().isLeftToRight();
switch (getAbsoluteTitleAlignment(ltr)) {
case SwingConstants.LEFT:
break;
case SwingConstants.CENTER:
labelBounds.x += (labelBounds.width - labelPrefWidth) / 2;
break;
case SwingConstants.RIGHT:
labelBounds.x += labelBounds.width - labelPrefWidth;
break;
}
labelBounds.width = Math.min(labelPrefWidth, labelBounds.width);
// paint slave border in three strips
g = g.create();
try {
final Rectangle orgClip = g.getClipBounds();
final Rectangle clipRect = new Rectangle(orgClip);
if (border != null) {
// left of text
final int split0 = labelBounds.x - TEXT_SPACING;
if (intersect(clipRect, x, y, split0 - 1, height)) {
g.setClip(clipRect);
border.paintBorder(c, g, x, y, width, height);
}
// right of text
clipRect.setBounds(orgClip.getBounds());
final int split1 = split0 + labelBounds.width + 2 * TEXT_SPACING;
if (intersect(clipRect, split1, y, width - split1 + x, height)) {
g.setClip(clipRect);
border.paintBorder(c, g, x, y, width, height);
}
// below text
clipRect.setBounds(orgClip);
if (intersect(clipRect, x + split0 - 1, labelBounds.y
+ labelBounds.height, split1 - split0 - 1, y
+ height
- labelBounds.y
- labelBounds.height)) {
g.setClip(clipRect);
border.paintBorder(c, g, x, y, width, height);
}
}
// paint the evil label
label.setParent(c.getParent()); // need any valid parent
label.validate();
clipRect.setBounds(orgClip);
if (intersect(clipRect, labelBounds.x, labelBounds.y,
labelBounds.width, labelBounds.height)) {
labelBounds.width = Integer.MAX_VALUE; // never line-wrap :-)
label.setBounds(labelBounds);
g.setClip(clipRect);
g.translate(labelBounds.x, labelBounds.y);
label.paint(g);
}
} finally {
label.setParent(null);
g.dispose(); // we created a copy, so this is okay!
}
}
// -------------------------------------------------------------------------
// private helper methods
// -------------------------------------------------------------------------
private int getAbsoluteTitleAlignment(boolean ltr) {
// make the alignment absolute
final int align;
switch (titleJustification) {
case SwingConstants.LEADING:
align = ltr ? SwingConstants.LEFT : SwingConstants.RIGHT;
break;
case SwingConstants.TRAILING:
align = ltr ? SwingConstants.RIGHT : SwingConstants.LEFT;
break;
default:
align = titleJustification;
}
return align;
}
private EvilJLabel getLabel() {
if (label == null) {
label = new EvilJLabel();
label.setBackground(new Color(255, 0, 0, 127));
label.setOpaque(false);
label.setVerticalAlignment(SwingConstants.TOP);
}
label.setText(title);
return label;
}
private static boolean intersect(
Rectangle target,
int rect2X,
int rect2Y,
int rect2Width,
int rect2Height) {
final int x1 = Math.max(rect2X, target.x);
final int x2 = Math.min(rect2X + rect2Width, target.x + target.width);
final int y1 = Math.max(rect2Y, target.y);
final int y2 = Math.min(rect2Y + rect2Height, target.y + target.height);
target.x = x1;
target.y = y1;
target.width = x2 - x1;
target.height = y2 - y1;
return target.width > 0 && target.height > 0;
}
// -------------------------------------------------------------------------
// Bean getters and setters
// -------------------------------------------------------------------------
/**
* Returns the border of the titled border.
*
* @return the border – possibly {@code null}
*/
public Border getBorder() {
Border b = border;
if (b == null)
b = UIManager.getBorder("TitledBorder.border");
return b;
}
/**
* Sets the border of the titled border.
*
* @param border the border – possibly {@code null}
*/
public void setBorder(Border border) {
this.border = border;
}
/**
* Returns the title.
*
* @return the title – possibly {@code null}
*/
public String getTitle() {
return title;
}
/**
* Sets the title of the titled border.
*
* @param title the title – possibly {@code null}
*/
public void setTitle(String title) {
this.title = title;
}
/**
* Returns the title-justification of the titled border.
*
* @return one of {@link SwingConstants#LEFT}, {@link SwingConstants#CENTER}
* , {@link SwingConstants#RIGHT}, {@link SwingConstants#LEADING},
* {@link SwingConstants#TRAILING}
*/
public int getTitleJustification() {
return titleJustification;
}
/**
* Sets the titleJustification.
*
* @param titleJustification one of {@link SwingConstants#LEFT},
* {@link SwingConstants#CENTER} , {@link SwingConstants#RIGHT},
* {@link SwingConstants#LEADING}, {@link SwingConstants#TRAILING}
* @throws IllegalArgumentException if {@code titleJustification} is none of
* the above mentioned
*/
public void setTitleJustification(int titleJustification) {
switch (titleJustification) {
case SwingConstants.LEFT:
case SwingConstants.CENTER:
case SwingConstants.RIGHT:
case SwingConstants.LEADING:
case SwingConstants.TRAILING:
break;
default:
throw new IllegalArgumentException("Illegal title justification: "
+ titleJustification);
}
this.titleJustification = titleJustification;
}
}