fixed vertical labels

git-svn-id: https://svn2.assembla.com/svn/romraider/trunk@100 38686702-15cf-42e4-a595-3071df8bf5ea
This commit is contained in:
kascade 2008-06-02 13:05:24 +00:00
parent 348eddac71
commit 718668687b
3 changed files with 104 additions and 332 deletions

View File

@ -23,7 +23,7 @@ package com.romraider.maps;
import com.romraider.Settings; import com.romraider.Settings;
import com.romraider.swing.TableFrame; import com.romraider.swing.TableFrame;
import com.romraider.swing.VTextIcon; import com.romraider.swing.VerticalLabelUI;
import com.romraider.util.AxisRange; import com.romraider.util.AxisRange;
import static com.romraider.util.ColorScaler.getScaledColor; import static com.romraider.util.ColorScaler.getScaledColor;
import static com.romraider.util.ParamChecker.isNullOrEmpty; import static com.romraider.util.ParamChecker.isNullOrEmpty;
@ -205,9 +205,8 @@ public class Table3D extends Table {
topPanel.add(new JLabel(name, JLabel.CENTER), BorderLayout.NORTH); topPanel.add(new JLabel(name, JLabel.CENTER), BorderLayout.NORTH);
topPanel.add(new JLabel(xAxis.getName() + " (" + xAxis.getScale().getUnit() + ")", JLabel.CENTER), BorderLayout.NORTH); topPanel.add(new JLabel(xAxis.getName() + " (" + xAxis.getScale().getUnit() + ")", JLabel.CENTER), BorderLayout.NORTH);
JLabel yLabel = new JLabel(); JLabel yLabel = new JLabel(yAxis.getName() + " (" + yAxis.getScale().getUnit() + ")");
VTextIcon icon = new VTextIcon(yLabel, yAxis.getName() + " (" + yAxis.getScale().getUnit() + ")", VTextIcon.ROTATE_LEFT); yLabel.setUI(new VerticalLabelUI(false));
yLabel.setIcon(icon);
add(yLabel, BorderLayout.WEST); add(yLabel, BorderLayout.WEST);
add(new JLabel(getScale().getUnit(), JLabel.CENTER), BorderLayout.SOUTH); add(new JLabel(getScale().getUnit(), JLabel.CENTER), BorderLayout.SOUTH);

View File

@ -1,328 +0,0 @@
/*
*
* RomRaider Open-Source Tuning, Logging and Reflashing
* Copyright (C) 2006-2008 RomRaider.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
package com.romraider.swing;
import javax.swing.Icon;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
/**
* VTextIcon is an Icon implementation which draws a short string vertically.
* It's useful for JTabbedPanes with LEFT or RIGHT tabs but can be used in any
* component which supports Icons, such as JLabel or JButton
* <p/>
* You can provide a hint to indicate whether to rotate the string
* to the left or right, or not at all, and it checks to make sure
* that the rotation is legal for the given string
* (for example, Chinese/Japanese/Korean scripts have special rules when
* drawn vertically and should never be rotated)
*/
public class VTextIcon extends Component implements Icon, PropertyChangeListener {
String fLabel;
String[] fCharStrings; // for efficiency, break the fLabel into one-char strings to be passed to drawString
int[] fCharWidths; // Roman characters should be centered when not rotated (Japanese fonts are monospaced)
int[] fPosition; // Japanese half-height characters need to be shifted when drawn vertically
int fWidth, fHeight, fCharHeight, fDescent; // Cached for speed
int fRotation;
//Component fComponent;
static final int POSITION_NORMAL = 0;
static final int POSITION_TOP_RIGHT = 1;
static final int POSITION_FAR_TOP_RIGHT = 2;
public static final int ROTATE_DEFAULT = 0x00;
public static final int ROTATE_NONE = 0x01;
public static final int ROTATE_LEFT = 0x02;
public static final int ROTATE_RIGHT = 0x04;
/**
* Creates a <code>VTextIcon</code> for the specified <code>component</code>
* with the specified <code>label</code>.
* It sets the orientation to the default for the string
*
* @see #verifyRotation
*/
public VTextIcon(Component component, String label) {
this(component, label, ROTATE_DEFAULT);
}
/**
* Creates a <code>VTextIcon</code> for the specified <code>component</code>
* with the specified <code>label</code>.
* It sets the orientation to the provided value if it's legal for the string
*
* @see #verifyRotation
*/
public VTextIcon(Component component, String label, int rotateHint) {
//fComponent = component;
fLabel = label;
fRotation = verifyRotation(label, rotateHint);
calcDimensions();
//fComponent.addPropertyChangeListener(this);
}
/**
* sets the label to the given string, updating the orientation as needed
* and invalidating the layout if the size changes
*
* @see #verifyRotation
*/
public void setLabel(String label) {
fLabel = label;
fRotation = verifyRotation(label, fRotation); // Make sure the current rotation is still legal
recalcDimensions();
}
/**
* Checks for changes to the font on the fComponent
* so that it can invalidate the layout if the size changes
*/
public void propertyChange(PropertyChangeEvent e) {
String prop = e.getPropertyName();
if ("font".equals(prop)) {
recalcDimensions();
}
}
/**
* Calculates the dimensions. If they've changed,
* invalidates the component
*/
void recalcDimensions() {
int wOld = getIconWidth();
int hOld = getIconHeight();
calcDimensions();
if (wOld != getIconWidth() || hOld != getIconHeight()) {
//fComponent.invalidate();
}
}
void calcDimensions() {
//FontMetrics fm = fComponent.getFontMetrics(fComponent.getFont());
// fCharHeight = fm.getAscent() + fm.getDescent();
// fDescent = fm.getDescent();
if (fRotation == ROTATE_NONE) {
int len = fLabel.length();
char data[] = new char[len];
fLabel.getChars(0, len, data, 0);
// if not rotated, width is that of the widest char in the string
fWidth = 0;
// we need an array of one-char strings for drawString
fCharStrings = new String[len];
fCharWidths = new int[len];
fPosition = new int[len];
char ch;
for (int i = 0; i < len; i++) {
ch = data[i];
// fCharWidths[i] = fm.charWidth(ch);
if (fCharWidths[i] > fWidth) {
fWidth = fCharWidths[i];
}
fCharStrings[i] = new String(data, i, 1);
// small kana and punctuation
if (sDrawsInTopRight.indexOf(ch) >= 0) // if ch is in sDrawsInTopRight
{
fPosition[i] = POSITION_TOP_RIGHT;
} else if (sDrawsInFarTopRight.indexOf(ch) >= 0) {
fPosition[i] = POSITION_FAR_TOP_RIGHT;
} else {
fPosition[i] = POSITION_NORMAL;
}
}
// and height is the font height * the char count, + one extra leading at the bottom
fHeight = fCharHeight * len + fDescent;
} else {
// if rotated, width is the height of the string
fWidth = fCharHeight;
// and height is the width, plus some buffer space
//fHeight = fm.stringWidth(fLabel) + 2 * kBufferSpace;
}
}
/**
* Draw the icon at the specified location. Icon implementations
* may use the Component argument to get properties useful for
* painting, e.g. the foreground or background color.
*/
public void paintIcon(Component c, Graphics g, int x, int y) {
// We don't insist that it be on the same Component
g.setColor(c.getForeground());
g.setFont(c.getFont());
if (fRotation == ROTATE_NONE) {
int yPos = y + fCharHeight;
for (int i = 0; i < fCharStrings.length; i++) {
// Special rules for Japanese - "half-height" characters (like ya, yu, yo in combinations)
// should draw in the top-right quadrant when drawn vertically
// - they draw in the bottom-left normally
int tweak;
switch (fPosition[i]) {
case POSITION_NORMAL:
// Roman fonts should be centered. Japanese fonts are always monospaced.
g.drawString(fCharStrings[i], x + ((fWidth - fCharWidths[i]) / 2), yPos);
break;
case POSITION_TOP_RIGHT:
tweak = fCharHeight / 3; // Should be 2, but they aren't actually half-height
g.drawString(fCharStrings[i], x + (tweak / 2), yPos - tweak);
break;
case POSITION_FAR_TOP_RIGHT:
tweak = fCharHeight - fCharHeight / 3;
g.drawString(fCharStrings[i], x + (tweak / 2), yPos - tweak);
break;
}
yPos += fCharHeight;
}
} else if (fRotation == ROTATE_LEFT) {
g.translate(x + fWidth, y + fHeight);
((Graphics2D) g).rotate(-NINETY_DEGREES);
g.drawString(fLabel, kBufferSpace, -fDescent);
((Graphics2D) g).rotate(NINETY_DEGREES);
g.translate(-(x + fWidth), -(y + fHeight));
} else if (fRotation == ROTATE_RIGHT) {
g.translate(x, y);
((Graphics2D) g).rotate(NINETY_DEGREES);
g.drawString(fLabel, kBufferSpace, -fDescent);
((Graphics2D) g).rotate(-NINETY_DEGREES);
g.translate(-x, -y);
}
}
/**
* Returns the icon's width.
*
* @return an int specifying the fixed width of the icon.
*/
public int getIconWidth() {
return fWidth;
}
/**
* Returns the icon's height.
*
* @return an int specifying the fixed height of the icon.
*/
public int getIconHeight() {
return fHeight;
}
/**
* verifyRotation
* <p/>
* returns the best rotation for the string (ROTATE_NONE, ROTATE_LEFT, ROTATE_RIGHT)
* <p/>
* This is public static so you can use it to test a string without creating a VTextIcon
* <p/>
* from http://www.unicode.org/unicode/reports/tr9/tr9-3.html
* When setting text using the Arabic script in vertical lines,
* it is more common to employ a horizontal baseline that
* is rotated by 90deg counterclockwise so that the characters
* are ordered from top to bottom. Latin text and numbers
* may be rotated 90deg clockwise so that the characters
* are also ordered from top to bottom.
* <p/>
* Rotation rules
* - Roman can rotate left, right, or none - default right (counterclockwise)
* - CJK can't rotate
* - Arabic must rotate - default left (clockwise)
* <p/>
* from the online edition of _The Unicode Standard, Version 3.0_, file ch10.pdf page 4
* Ideographs are found in three blocks of the Unicode Standard...
* U+4E00-U+9FFF, U+3400-U+4DFF, U+F900-U+FAFF
* <p/>
* Hiragana is U+3040-U+309F, katakana is U+30A0-U+30FF
* <p/>
* from http://www.unicode.org/unicode/faq/writingdirections.html
* East Asian scripts are frequently written in vertical lines
* which run from top-to-bottom and are arrange columns either
* from left-to-right (Mongolian) or right-to-left (other scripts).
* Most characters use the same shape and orientation when displayed
* horizontally or vertically, but many punctuation characters
* will change their shape when displayed vertically.
* <p/>
* Letters and words from other scripts are generally rotated through
* ninety degree angles so that they, too, will read from top to bottom.
* That is, letters from left-to-right scripts will be rotated clockwise
* and letters from right-to-left scripts counterclockwise, both
* through ninety degree angles.
* <p/>
* Unlike the bidirectional case, the choice of vertical layout
* is usually treated as a formatting style; therefore,
* the Unicode Standard does not define default rendering behavior
* for vertical text nor provide directionality controls designed to override such behavior
*/
public static int verifyRotation(String label, int rotateHint) {
boolean hasCJK = false;
boolean hasMustRotate = false; // Arabic, etc
int len = label.length();
char data[] = new char[len];
char ch;
label.getChars(0, len, data, 0);
for (int i = 0; i < len; i++) {
ch = data[i];
if ((ch >= '\u4E00' && ch <= '\u9FFF') ||
(ch >= '\u3400' && ch <= '\u4DFF') ||
(ch >= '\uF900' && ch <= '\uFAFF') ||
(ch >= '\u3040' && ch <= '\u309F') ||
(ch >= '\u30A0' && ch <= '\u30FF')) {
hasCJK = true;
}
if ((ch >= '\u0590' && ch <= '\u05FF') || // Hebrew
(ch >= '\u0600' && ch <= '\u06FF') || // Arabic
(ch >= '\u0700' && ch <= '\u074F')) // Syriac
{
hasMustRotate = true;
}
}
// If you mix Arabic with Chinese, you're on your own
if (hasCJK) {
return DEFAULT_CJK;
}
int legal = hasMustRotate ? LEGAL_MUST_ROTATE : LEGAL_ROMAN;
if ((rotateHint & legal) > 0) {
return rotateHint;
}
// The hint wasn't legal, or it was zero
return hasMustRotate ? DEFAULT_MUST_ROTATE : DEFAULT_ROMAN;
}
// The small kana characters and Japanese punctuation that draw in the top right quadrant:
// small a, i, u, e, o, tsu, ya, yu, yo, wa (katakana only) ka ke
static final String sDrawsInTopRight =
"\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E" + // hiragana
"\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6"; // katakana
static final String sDrawsInFarTopRight = "\u3001\u3002"; // comma, full stop
static final int DEFAULT_CJK = ROTATE_NONE;
static final int LEGAL_ROMAN = ROTATE_NONE | ROTATE_LEFT | ROTATE_RIGHT;
static final int DEFAULT_ROMAN = ROTATE_RIGHT;
static final int LEGAL_MUST_ROTATE = ROTATE_LEFT | ROTATE_RIGHT;
static final int DEFAULT_MUST_ROTATE = ROTATE_LEFT;
static final double NINETY_DEGREES = Math.toRadians(90.0);
static final int kBufferSpace = 5;
}

View File

@ -0,0 +1,101 @@
/*
*
* RomRaider Open-Source Tuning, Logging and Reflashing
* Copyright (C) 2006-2008 RomRaider.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
package com.romraider.swing;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.plaf.basic.BasicLabelUI;
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.geom.AffineTransform;
public final class VerticalLabelUI extends BasicLabelUI {
private static Rectangle PAINT_ICON_R = new Rectangle();
private static Rectangle PAINT_TEXT_R = new Rectangle();
private static Rectangle PAINT_VIEW_R = new Rectangle();
private static Insets PAINT_VIEW_INSETS = new Insets(0, 0, 0, 0);
protected boolean clockwise;
static {
labelUI = new VerticalLabelUI(false);
}
public VerticalLabelUI(boolean clockwise) {
super();
this.clockwise = clockwise;
}
public Dimension getPreferredSize(JComponent c) {
Dimension dim = super.getPreferredSize(c);
return new Dimension(dim.height, dim.width);
}
public void paint(Graphics graphics, JComponent component) {
JLabel label = (JLabel) component;
String text = label.getText();
Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon();
if ((icon == null) && (text == null)) return;
FontMetrics fm = graphics.getFontMetrics();
PAINT_VIEW_INSETS = component.getInsets(PAINT_VIEW_INSETS);
PAINT_VIEW_R.x = PAINT_VIEW_INSETS.left;
PAINT_VIEW_R.y = PAINT_VIEW_INSETS.top;
// Use inverted height & width
PAINT_VIEW_R.height = component.getWidth() - (PAINT_VIEW_INSETS.left + PAINT_VIEW_INSETS.right);
PAINT_VIEW_R.width = component.getHeight() - (PAINT_VIEW_INSETS.top + PAINT_VIEW_INSETS.bottom);
PAINT_ICON_R.x = PAINT_ICON_R.y = PAINT_ICON_R.width = PAINT_ICON_R.height = 0;
PAINT_TEXT_R.x = PAINT_TEXT_R.y = PAINT_TEXT_R.width = PAINT_TEXT_R.height = 0;
String clippedText = layoutCL(label, fm, text, icon, PAINT_VIEW_R, PAINT_ICON_R, PAINT_TEXT_R);
int textWidth = fm.stringWidth(clippedText);
Graphics2D g2 = (Graphics2D) graphics;
AffineTransform tr = g2.getTransform();
if (clockwise) {
g2.rotate(Math.PI / 2);
g2.translate(component.getHeight() / 2 - textWidth / 2, -component.getWidth());
} else {
g2.rotate(-Math.PI / 2);
g2.translate(-component.getHeight() / 2 - textWidth / 2, 0);
}
if (icon != null) icon.paintIcon(component, graphics, PAINT_ICON_R.x, PAINT_ICON_R.y);
if (text != null) {
int textX = PAINT_TEXT_R.x;
int textY = PAINT_TEXT_R.y + fm.getAscent();
if (label.isEnabled()) paintEnabledText(label, graphics, clippedText, textX, textY);
else paintDisabledText(label, graphics, clippedText, textX, textY);
}
g2.setTransform(tr);
}
}