/*******************************************************************************
* Mission Control Technologies, Copyright (c) 2009-2012, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* The MCT platform is 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
* http://www.apache.org/licenses/LICENSE-2.0.
*
* 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.
*
* MCT includes source code licensed under additional open source licenses. See
* the MCT Open Source Licenses file included with this distribution or the About
* MCT Licenses dialog available at runtime from the MCT Help menu for additional
* information.
*******************************************************************************/
package plotter;
import static javax.swing.SwingConstants.CENTER;
import static javax.swing.SwingConstants.LEADING;
import static javax.swing.SwingConstants.LEFT;
import static javax.swing.SwingConstants.RIGHT;
import static javax.swing.SwingConstants.TOP;
import static javax.swing.SwingConstants.TRAILING;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Component.BaselineResizeBehavior;
import java.awt.geom.AffineTransform;
import java.util.StringTokenizer;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import javax.swing.plaf.basic.BasicLabelUI;
/**
* This code was found at http://codeguru.earthweb.com/java/articles/198.shtml.
* This was posted by Zafir Anjum on 28-Jul-1999.
* Modified by Adam Crume.
*/
public class MultiLineLabelUI extends BasicLabelUI {
public static final String ROTATION_KEY = Rotation.class.getName();
public static final MultiLineLabelUI labelUI = new MultiLineLabelUI();
protected String layoutCL(JLabel label, FontMetrics fontMetrics, String text, Icon icon, Rectangle viewR,
Rectangle iconR, Rectangle textR) {
String s = layoutCompoundLabel((JComponent) label, fontMetrics, splitStringByLines(text), icon, label
.getVerticalAlignment(), label.getHorizontalAlignment(), label.getVerticalTextPosition(), label
.getHorizontalTextPosition(), viewR, iconR, textR, label.getIconTextGap());
if(s.equals(""))
return text;
return s;
}
/**
* Compute and return the location of the icons origin, the
* location of origin of the text baseline, and a possibly clipped
* version of the compound labels string. Locations are computed
* relative to the viewR rectangle.
* The JComponents orientation (LEADING/TRAILING) will also be taken
* into account and translated into LEFT/RIGHT values accordingly.
*/
public static String layoutCompoundLabel(JComponent c, FontMetrics fm, String[] text, Icon icon,
int verticalAlignment, int horizontalAlignment, int verticalTextPosition, int horizontalTextPosition,
Rectangle viewR, Rectangle iconR, Rectangle textR, int textIconGap) {
boolean orientationIsLeftToRight = true;
int hAlign = horizontalAlignment;
int hTextPos = horizontalTextPosition;
if(c != null) {
if(!(c.getComponentOrientation().isLeftToRight())) {
orientationIsLeftToRight = false;
}
}
// Translate LEADING/TRAILING values in horizontalAlignment
// to LEFT/RIGHT values depending on the components orientation
switch(horizontalAlignment) {
case LEADING:
hAlign = (orientationIsLeftToRight) ? LEFT : RIGHT;
break;
case TRAILING:
hAlign = (orientationIsLeftToRight) ? RIGHT : LEFT;
break;
}
// Translate LEADING/TRAILING values in horizontalTextPosition
// to LEFT/RIGHT values depending on the components orientation
switch(horizontalTextPosition) {
case LEADING:
hTextPos = (orientationIsLeftToRight) ? LEFT : RIGHT;
break;
case TRAILING:
hTextPos = (orientationIsLeftToRight) ? RIGHT : LEFT;
break;
}
return layoutCompoundLabel(fm, text, icon, verticalAlignment, hAlign, verticalTextPosition, hTextPos, viewR,
iconR, textR, textIconGap);
}
/**
* Compute and return the location of the icons origin, the
* location of origin of the text baseline, and a possibly clipped
* version of the compound labels string. Locations are computed
* relative to the viewR rectangle.
* This layoutCompoundLabel() does not know how to handle LEADING/TRAILING
* values in horizontalTextPosition (they will default to RIGHT) and in
* horizontalAlignment (they will default to CENTER).
* Use the other version of layoutCompoundLabel() instead.
*/
public static String layoutCompoundLabel(FontMetrics fm, String[] text, Icon icon, int verticalAlignment,
int horizontalAlignment, int verticalTextPosition, int horizontalTextPosition, Rectangle viewR,
Rectangle iconR, Rectangle textR, int textIconGap) {
/* Initialize the icon bounds rectangle iconR.
*/
if(icon != null) {
iconR.width = icon.getIconWidth();
iconR.height = icon.getIconHeight();
} else {
iconR.width = iconR.height = 0;
}
/* Initialize the text bounds rectangle textR. If a null
* or and empty String was specified we substitute "" here
* and use 0,0,0,0 for textR.
*/
// Fix for textIsEmpty sent by Paulo Santos
boolean textIsEmpty = (text == null) || (text.length == 0)
|| (text.length == 1 && ((text[0] == null) || text[0].equals("")));
String rettext = "";
if(textIsEmpty) {
textR.width = textR.height = 0;
} else {
Dimension dim = computeMultiLineDimension(fm, text);
textR.width = dim.width;
textR.height = dim.height;
}
/* Unless both text and icon are non-null, we effectively ignore
* the value of textIconGap. The code that follows uses the
* value of gap instead of textIconGap.
*/
int gap = (textIsEmpty || (icon == null)) ? 0 : textIconGap;
if(!textIsEmpty) {
/* If the label text string is too wide to fit within the available
* space "..." and as many characters as will fit will be
* displayed instead.
*/
int availTextWidth;
if(horizontalTextPosition == CENTER) {
availTextWidth = viewR.width;
} else {
availTextWidth = viewR.width - (iconR.width + gap);
}
if(textR.width > availTextWidth && text.length == 1) {
String clipString = "...";
int totalWidth = SwingUtilities.computeStringWidth(fm, clipString);
int nChars;
for(nChars = 0; nChars < text[0].length(); nChars++) {
totalWidth += fm.charWidth(text[0].charAt(nChars));
if(totalWidth > availTextWidth) {
break;
}
}
rettext = text[0].substring(0, nChars) + clipString;
textR.width = SwingUtilities.computeStringWidth(fm, rettext);
}
}
/* Compute textR.x,y given the verticalTextPosition and
* horizontalTextPosition properties
*/
if(verticalTextPosition == TOP) {
if(horizontalTextPosition != CENTER) {
textR.y = 0;
} else {
textR.y = -(textR.height + gap);
}
} else if(verticalTextPosition == CENTER) {
textR.y = (iconR.height / 2) - (textR.height / 2);
} else { // (verticalTextPosition == BOTTOM)
if(horizontalTextPosition != CENTER) {
textR.y = iconR.height - textR.height;
} else {
textR.y = (iconR.height + gap);
}
}
if(horizontalTextPosition == LEFT) {
textR.x = -(textR.width + gap);
} else if(horizontalTextPosition == CENTER) {
textR.x = (iconR.width / 2) - (textR.width / 2);
} else { // (horizontalTextPosition == RIGHT)
textR.x = (iconR.width + gap);
}
/* labelR is the rectangle that contains iconR and textR.
* Move it to its proper position given the labelAlignment
* properties.
*
* To avoid actually allocating a Rectangle, Rectangle.union
* has been inlined below.
*/
int labelR_x = Math.min(iconR.x, textR.x);
int labelR_width = Math.max(iconR.x + iconR.width, textR.x + textR.width) - labelR_x;
int labelR_y = Math.min(iconR.y, textR.y);
int labelR_height = Math.max(iconR.y + iconR.height, textR.y + textR.height) - labelR_y;
int dx, dy;
if(verticalAlignment == TOP) {
dy = viewR.y - labelR_y;
} else if(verticalAlignment == CENTER) {
dy = (viewR.y + (viewR.height / 2)) - (labelR_y + (labelR_height / 2));
} else { // (verticalAlignment == BOTTOM)
dy = (viewR.y + viewR.height) - (labelR_y + labelR_height);
}
if(horizontalAlignment == LEFT) {
dx = viewR.x - labelR_x;
} else if(horizontalAlignment == RIGHT) {
dx = (viewR.x + viewR.width) - (labelR_x + labelR_width);
} else { // (horizontalAlignment == CENTER)
dx = (viewR.x + (viewR.width / 2)) - (labelR_x + (labelR_width / 2));
}
/* Translate textR and glypyR by dx,dy.
*/
textR.x += dx;
textR.y += dy;
iconR.x += dx;
iconR.y += dy;
return rettext;
}
protected void paintEnabledText(JLabel l, Graphics g, String s, int textX, int textY) {
int accChar = l.getDisplayedMnemonic();
g.setColor(l.getForeground());
drawString(g, s, accChar, textX, textY);
}
protected void paintDisabledText(JLabel l, Graphics g, String s, int textX, int textY) {
int accChar = l.getDisplayedMnemonic();
g.setColor(l.getBackground());
drawString(g, s, accChar, textX, textY);
}
protected void drawString(Graphics g, String s, int accChar, int textX, int textY) {
if(s.indexOf('\n') == -1)
BasicGraphicsUtils.drawString(g, s, accChar, textX, textY);
else {
String[] strs = splitStringByLines(s);
int height = g.getFontMetrics().getHeight();
// Only the first line can have the accel char
BasicGraphicsUtils.drawString(g, strs[0], accChar, textX, textY);
for(int i = 1; i < strs.length; i++)
g.drawString(strs[i], textX, textY + (height * i));
}
}
public static Dimension computeMultiLineDimension(FontMetrics fm, String[] strs) {
int i, c, width = 0;
for(i = 0, c = strs.length; i < c; i++)
width = Math.max(width, SwingUtilities.computeStringWidth(fm, strs[i]));
return new Dimension(width, fm.getHeight() * strs.length);
}
protected String str;
protected String[] strs;
public String[] splitStringByLines(String str) {
if(str.equals(this.str))
return strs;
this.str = str;
int lines = 1;
int i, c;
for(i = 0, c = str.length(); i < c; i++) {
if(str.charAt(i) == '\n')
lines++;
}
strs = new String[lines];
StringTokenizer st = new StringTokenizer(str, "\n");
int line = 0;
while(st.hasMoreTokens())
strs[line++] = st.nextToken();
return strs;
}
@Override
public int getBaseline(JComponent c, int width, int height) {
Rotation rotation = (Rotation) c.getClientProperty(ROTATION_KEY);
if(rotation == Rotation.CCW || rotation == Rotation.CW) {
return -1;
} else if(rotation == Rotation.HALF) {
int baseline = super.getBaseline(c, width, height);
if(baseline < 0) {
return baseline;
} else {
return height - baseline;
}
}
return super.getBaseline(c, width, height);
}
@Override
public BaselineResizeBehavior getBaselineResizeBehavior(JComponent c) {
Rotation rotation = (Rotation) c.getClientProperty(ROTATION_KEY);
if(rotation == Rotation.CCW || rotation == Rotation.CW) {
return BaselineResizeBehavior.OTHER;
} else if(rotation == Rotation.HALF) {
BaselineResizeBehavior b = super.getBaselineResizeBehavior(c);
if(b == BaselineResizeBehavior.CONSTANT_ASCENT) {
return BaselineResizeBehavior.CONSTANT_DESCENT;
} else if(b == BaselineResizeBehavior.CONSTANT_DESCENT) {
return BaselineResizeBehavior.CONSTANT_ASCENT;
} else {
return b;
}
}
return super.getBaselineResizeBehavior(c);
}
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) {
// This method adapted from http://tech.chitgoks.com/2009/11/13/rotate-jlabel-vertically/
JLabel label = (JLabel) c;
String text = label.getText();
Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon();
if((icon == null) && (text == null)) {
return;
}
Rotation rotation = getRotation(c);
FontMetrics fm = g.getFontMetrics();
paintViewInsets = c.getInsets(paintViewInsets);
paintViewR.x = paintViewInsets.left;
paintViewR.y = paintViewInsets.top;
if(rotation.isXYSwitched()) {
paintViewR.height = c.getWidth() - (paintViewInsets.left + paintViewInsets.right);
paintViewR.width = c.getHeight() - (paintViewInsets.top + paintViewInsets.bottom);
} else {
paintViewR.width = c.getWidth() - (paintViewInsets.left + paintViewInsets.right);
paintViewR.height = 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();
switch(rotation) {
case NONE:
break;
case CW:
g2.rotate(Math.PI / 2);
g2.translate(0, -c.getWidth());
break;
case CCW:
g2.rotate(-Math.PI / 2);
g2.translate(-c.getHeight(), 0);
break;
case HALF:
g2.rotate(Math.PI);
g2.translate(-c.getWidth(), -c.getHeight());
break;
}
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);
}
private Rotation getRotation(JComponent c) {
Rotation rotation = (Rotation) c.getClientProperty(ROTATION_KEY);
if(rotation == null) {
rotation = Rotation.NONE;
}
return rotation;
}
@Override
public Dimension getPreferredSize(JComponent c) {
Dimension d = super.getPreferredSize(c);
if(getRotation(c).isXYSwitched()) {
d.setSize(d.getHeight(), d.getWidth());
}
return d;
}
}