MultiLine JLabel
Posted by admin on
January 13, 2011
To create a multi-line JLabel, Samuel Sjoberg provided a custom class that extended the BasicLabelUI class in order to paint text and wrap them to multiple lines rather than displaying the text as one line and truncated with a …
To use the class, just do
label.setUI(MultiLineLabelUI.labelUI);
Here is the full MultiLineLabelUI class.
public class MultiLineLabelUI extends BasicLabelUI implements ComponentListener {
/** Shared instance of the UI delegate. */
public static LabelUI labelUI = new MultiLineLabelUI();
/**
* Client property key used to store the calculated wrapped lines on the
* JLabel.
*/
public static final String PROPERTY_KEY = "WrappedText";
// Static references to avoid heap allocations.
protected static Rectangle paintIconR = new Rectangle();
protected static Rectangle paintTextR = new Rectangle();
protected static Rectangle paintViewR = new Rectangle();
protected static Insets paintViewInsets = new Insets(0, 0, 0, 0);
/** Font metrics of the JLabel being rendered. */
protected FontMetrics metrics;
/** Default size of the lines list. */
protected static int defaultSize = 4;
/**
* Get the shared UI instance.
*
* @param c
* the component about to be installed
* @return the shared UI delegate instance
*/
public static ComponentUI createUI(JComponent c) {
return labelUI;
}
/** {@inheritDoc} */
@Override
protected void uninstallDefaults(JLabel c) {
super.uninstallDefaults(c);
clearCache(c);
}
/** {@inheritDoc} */
@Override
protected void installListeners(JLabel c) {
super.installListeners(c);
c.addComponentListener(this);
}
/** {@inheritDoc} */
@Override
protected void uninstallListeners(JLabel c) {
super.uninstallListeners(c);
c.removeComponentListener(this);
}
/**
* Clear the wrapped line cache.
*
* @param l
* the label containing a cached value
*/
protected void clearCache(JLabel l) {
l.putClientProperty(PROPERTY_KEY, null);
}
/** {@inheritDoc} */
@Override
public void propertyChange(PropertyChangeEvent e) {
super.propertyChange(e);
final String name = e.getPropertyName();
if (name.equals("text") || "font".equals(name)) {
clearCache((JLabel) e.getSource());
}
}
/**
* Calculate the paint rectangles for the icon and text for the passed
* label.
*
* @param l
* a label
* @param fm
* the font metrics to use, or null to get the font
* metrics from the label
* @param width
* label width
* @param height
* label height
*/
protected void updateLayout(JLabel l, FontMetrics fm, int width, int height) {
if (fm == null) {
fm = l.getFontMetrics(l.getFont());
}
metrics = fm;
String text = l.getText();
Icon icon = l.getIcon();
Insets insets = l.getInsets(paintViewInsets);
paintViewR.x = insets.left;
paintViewR.y = insets.top;
paintViewR.width = width - (insets.left + insets.right);
paintViewR.height = height - (insets.top + insets.bottom);
paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
layoutCL(l, fm, text, icon, paintViewR, paintIconR, paintTextR);
}
protected void prepareGraphics(Graphics g) {
}
/** {@inheritDoc} */
@Override
public void paint(Graphics g, JComponent c) {
// parent's update method fills the background
prepareGraphics(g);
JLabel label = (JLabel) c;
String text = label.getText();
Icon icon = (label.isEnabled()) ? label.getIcon() : label
.getDisabledIcon();
if ((icon == null) && (text == null)) {
return;
}
FontMetrics fm = g.getFontMetrics();
updateLayout(label, fm, c.getWidth(), c.getHeight());
if (icon != null) {
icon.paintIcon(c, g, paintIconR.x, paintIconR.y);
}
if (text != null) {
View v = (View) c.getClientProperty("html");
if (v != null) {
// HTML view disables multi-line painting.
v.paint(g, paintTextR);
} else {
// Paint the multi line text
paintTextLines(g, label, fm);
}
}
}
/**
* Paint the wrapped text lines.
*
* @param g
* graphics component to paint on
* @param label
* the label being painted
* @param fm
* font metrics for current font
*/
protected void paintTextLines(Graphics g, JLabel label, FontMetrics fm) {
List lines = getTextLines(label);
// Available component height to paint on.
int height = getAvailableHeight(label);
int textHeight = lines.size() * fm.getHeight();
while (textHeight > height) {
// Remove one line until no. of visible lines is found.
textHeight -= fm.getHeight();
}
paintTextR.height = Math.min(textHeight, height);
paintTextR.y = alignmentY(label, fm, paintTextR);
int textX = paintTextR.x;
int textY = paintTextR.y;
for (Iterator it = lines.iterator(); it.hasNext()
&& paintTextR.contains(textX, textY + getAscent(fm)); textY += fm
.getHeight()) {
String text = it.next().trim();
if (it.hasNext()
&& !paintTextR.contains(textX, textY + fm.getHeight()
+ getAscent(fm))) {
// The last visible row, add a clip indication.
text = clip(text, fm, paintTextR);
}
int x = alignmentX(label, fm, text, paintTextR);
if (label.isEnabled()) {
paintEnabledText(label, g, text, x, textY);
} else {
paintDisabledText(label, g, text, x, textY);
}
}
}
/**
* Returns the available height to paint text on. This is the height of the
* passed component with insets subtracted.
*
* @param l
* a component
* @return the available height
*/
protected int getAvailableHeight(JLabel l) {
l.getInsets(paintViewInsets);
return l.getHeight() - paintViewInsets.top - paintViewInsets.bottom;
}
/**
* Add a clip indication to the string. It is important that the string
* length does not exceed the length or the original string.
*
* @param text
* the to be painted
* @param fm
* font metrics
* @param bounds
* the text bounds
* @return the clipped string
*/
protected String clip(String text, FontMetrics fm, Rectangle bounds) {
// Fast and lazy way to insert a clip indication is to simply replace
// the last characters in the string with the clip indication.
// A better way would be to use metrics and calculate how many (if any)
// characters that need to be replaced.
if (text.length() < 3) {
return "...";
}
return text.substring(0, text.length() - 3) + "...";
}
/**
* Establish the vertical text alignment. The default alignment is to center
* the text in the label.
*
* @param label
* the label to paint
* @param fm
* font metrics
* @param bounds
* the text bounds rectangle
* @return the vertical text alignment, defaults to CENTER.
*/
protected int alignmentY(JLabel label, FontMetrics fm, Rectangle bounds) {
final int height = getAvailableHeight(label);
int textHeight = bounds.height;
if (label instanceof MultiLineLabel) {
int align = ((MultiLineLabel) label).getVerticalTextAlignment();
switch (align) {
case JLabel.TOP:
return getAscent(fm) + paintViewInsets.top;
case JLabel.BOTTOM:
return getAscent(fm) + height - paintViewInsets.top
+ paintViewInsets.bottom - textHeight;
default:
}
}
// Center alignment
int textY = paintViewInsets.top + (height - textHeight) / 2
+ getAscent(fm);
// set top by default
textY = paintViewInsets.top;
return Math.max(textY, getAscent(fm) + paintViewInsets.top);
}
private static int getAscent(FontMetrics fm) {
return fm.getAscent() + fm.getLeading();
}
/**
* Establish the horizontal text alignment. The default alignment is left
* aligned text.
*
* @param label
* the label to paint
* @param fm
* font metrics
* @param s
* the string to paint
* @param bounds
* the text bounds rectangle
* @return the x-coordinate to use when painting for proper alignment
*/
protected int alignmentX(JLabel label, FontMetrics fm, String s,
Rectangle bounds) {
if (label instanceof MultiLineLabel) {
int align = ((MultiLineLabel) label).getHorizontalTextAlignment();
switch (align) {
case JLabel.RIGHT:
return bounds.x + paintViewR.width - fm.stringWidth(s);
case JLabel.CENTER:
return bounds.x + paintViewR.width / 2 - fm.stringWidth(s) / 2;
default:
return bounds.x;
}
}
return bounds.x;
}
/**
* Check the given string to see if it should be rendered as HTML. Code
* based on implementation found in
* BasicHTML.isHTMLString(String) in future JDKs.
*
* @param s
* the string
* @return true if string is HTML, otherwise false
*/
private static boolean isHTMLString(String s) {
if (s != null) {
if ((s.length() >= 6) && (s.charAt(0) == '<')
&& (s.charAt(5) == '>')) {
String tag = s.substring(1, 5);
return tag.equalsIgnoreCase("html");
}
}
return false;
}
/** {@inheritDoc} */
@Override
public Dimension getPreferredSize(JComponent c) {
Dimension d = super.getPreferredSize(c);
JLabel label = (JLabel) c;
if (isHTMLString(label.getText())) {
return d; // HTML overrides everything and we don't need to process
}
// Width calculated by super is OK. The preferred width is the width of
// the unwrapped content as long as it does not exceed the width of the
// parent container.
if (c.getParent() != null) {
// Ensure that preferred width never exceeds the available width
// (including its border insets) of the parent container.
Insets insets = c.getParent().getInsets();
Dimension size = c.getParent().getSize();
if (size.width > 0) {
// If width isn't set component shouldn't adjust.
d.width = size.width - insets.left - insets.right;
}
}
updateLayout(label, null, d.width, d.height);
// The preferred height is either the preferred height of the text
// lines, or the height of the icon.
d.height = Math.max(d.height, getPreferredHeight(label));
return d;
}
/**
* The preferred height of the label is the height of the lines with added
* top and bottom insets.
*
* @param label
* the label
* @return the preferred height of the wrapped lines.
*/
protected int getPreferredHeight(JLabel label) {
int numOfLines = getTextLines(label).size();
Insets insets = label.getInsets(paintViewInsets);
return numOfLines * metrics.getHeight() + insets.top + insets.bottom;
}
/**
* Get the lines of text contained in the text label. The prepared lines is
* cached as a client property, accessible via {@link #PROPERTY_KEY}.
*
* @param l
* the label
* @return the text lines of the label.
*/
@SuppressWarnings("unchecked")
protected List getTextLines(JLabel l) {
List lines = (List) l.getClientProperty(PROPERTY_KEY);
if (lines == null) {
lines = prepareLines(l);
l.putClientProperty(PROPERTY_KEY, lines);
}
return lines;
}
/** {@inheritDoc} */
public void componentHidden(ComponentEvent e) {
// Don't care
}
/** {@inheritDoc} */
public void componentMoved(ComponentEvent e) {
// Don't care
}
/** {@inheritDoc} */
public void componentResized(ComponentEvent e) {
clearCache((JLabel) e.getSource());
}
/** {@inheritDoc} */
public void componentShown(ComponentEvent e) {
// Don't care
}
/**
* Prepare the text lines for rendering. The lines are wrapped to fit in the
* current available space for text. Explicit line breaks are preserved.
*
* @param l
* the label to render
* @return a list of text lines to render
*/
protected List prepareLines(JLabel l) {
List lines = new ArrayList(defaultSize);
String text = l.getText();
if (text == null) {
return null; // Null guard
}
PlainDocument doc = new PlainDocument();
try {
doc.insertString(0, text, null);
} catch (BadLocationException e) {
return null;
}
Element root = doc.getDefaultRootElement();
for (int i = 0, j = root.getElementCount(); i < j; i++) {
wrap(lines, root.getElement(i));
}
return lines;
}
/**
* If necessary, wrap the text into multiple lines.
*
* @param lines
* line array in which to store the wrapped lines
* @param elem
* the document element containing the text content
*/
protected void wrap(List lines, Element elem) {
int p1 = elem.getEndOffset();
Document doc = elem.getDocument();
for (int p0 = elem.getStartOffset(); p0 < p1;) {
int p = calculateBreakPosition(doc, p0, p1);
try {
lines.add(doc.getText(p0, p - p0));
} catch (BadLocationException e) {
throw new Error("Can't get line text. p0=" + p0 + " p=" + p);
}
p0 = (p == p0) ? p1 : p;
}
}
/**
* Calculate the position on which to break (wrap) the line.
*
* @param doc
* the document
* @param p0
* start position
* @param p1
* end position
* @return the actual end position, will be p1 if content does
* not need to wrap, otherwise it will be less than p1.
*/
protected int calculateBreakPosition(Document doc, int p0, int p1) {
Segment segment = SegmentCache.getSegment();
try {
doc.getText(p0, p1 - p0, segment);
} catch (BadLocationException e) {
throw new Error("Can't get line text");
}
int width = paintTextR.width;
int p = p0
+ Utilities.getBreakLocation(segment, metrics, 0, width, null,
p0);
SegmentCache.releaseSegment(segment);
return p;
}
/**
* Static singleton {@link Segment} cache.
*
* @see javax.swing.text.SegmentCache
*
* @author Samuel Sjoberg
*/
protected static final class SegmentCache {
/** Reused segments. */
private ArrayList segments = new ArrayList(2);
/** Singleton instance. */
private static SegmentCache cache = new SegmentCache();
/** Private constructor. */
private SegmentCache() {
}
/**
* Returns a Segment. When done, the Segment
* should be recycled by invoking {@link #releaseSegment(Segment)}.
*
* @return a Segment.
*/
public static Segment getSegment() {
int size = cache.segments.size();
if (size > 0) {
return cache.segments.remove(size - 1);
}
return new Segment();
}
/**
* Releases a Segment. A segment should not be used after
* it is released, and a segment should never be released more than
* once.
*/
public static void releaseSegment(Segment segment) {
segment.array = null;
segment.count = 0;
cache.segments.add(segment);
}
}
}
tags: jlabel, multi-line, multiline
No Comments
How To Unbold A JLabel
Posted by admin on
January 13, 2011
This code unbolds a JLabel on the fly without having to create a new instance of a Font object not having the bold attribute.
Font f = jlabel.getFont(); jlabel.setFont(f.deriveFont(f.getStyle() ^ Font.BOLD));
tags: jlabel, unbold
No Comments
Rotate JLabel Vertically
Posted by admin on
November 13, 2009
To rotate a JLabel‘s text vertically, you only need to use this class and pass it as a parameter to the JLabel‘s setUI() method. Kudos to the developer who created this class to make it easy for other developers to plug this class in and create a vertical JLabel in an instant.
To use this class, check the code below:
JLabel jl = new JLabel("TEST"); jl.setUI(new VerticalLabelUI(true));
This is the code for the VerticalLabelUI class.
public class VerticalLabelUI extends BasicLabelUI { static { labelUI = new VerticalLabelUI(false); } protected boolean clockwise; public VerticalLabelUI(boolean clockwise) { super(); this.clockwise = clockwise; } @Override public Dimension getPreferredSize(JComponent c) { Dimension dim = super.getPreferredSize(c); return new Dimension( dim.height, dim.width ); } private static Rectangle paintIconR = new Rectangle(); private static Rectangle paintTextR = new Rectangle(); private static Rectangle paintViewR = new Rectangle(); private static Insets paintViewInsets = new Insets(0, 0, 0, 0); @Override public void paint(Graphics g, JComponent c) { JLabel label = (JLabel)c; String text = label.getText(); Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon(); if ((icon == null) && (text == null)) { return; } FontMetrics fm = g.getFontMetrics(); paintViewInsets = c.getInsets(paintViewInsets); paintViewR.x = paintViewInsets.left; paintViewR.y = paintViewInsets.top; // Use inverted height & width paintViewR.height = c.getWidth() - (paintViewInsets.left + paintViewInsets.right); paintViewR.width = c.getHeight() - (paintViewInsets.top + paintViewInsets.bottom); paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0; paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0; String clippedText = layoutCL(label, fm, text, icon, paintViewR, paintIconR, paintTextR); Graphics2D g2 = (Graphics2D) g; AffineTransform tr = g2.getTransform(); if (clockwise) { g2.rotate( Math.PI / 2 ); g2.translate( 0, - c.getWidth() ); } else { g2.rotate( - Math.PI / 2 ); g2.translate( - c.getHeight(), 0 ); } if (icon != null) { icon.paintIcon(c, g, paintIconR.x, paintIconR.y); } if (text != null) { int textX = paintTextR.x; int textY = paintTextR.y + fm.getAscent(); if (label.isEnabled()) { paintEnabledText(label, g, clippedText, textX, textY); } else { paintDisabledText(label, g, clippedText, textX, textY); } } g2.setTransform( tr ); } }







