diff --git a/src/com/romraider/maps/Table3D.java b/src/com/romraider/maps/Table3D.java index fa2a7c65..a87b4b58 100644 --- a/src/com/romraider/maps/Table3D.java +++ b/src/com/romraider/maps/Table3D.java @@ -23,7 +23,7 @@ package com.romraider.maps; import com.romraider.Settings; import com.romraider.swing.TableFrame; -import com.romraider.swing.VTextIcon; +import com.romraider.swing.VerticalLabelUI; import com.romraider.util.AxisRange; import static com.romraider.util.ColorScaler.getScaledColor; 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(xAxis.getName() + " (" + xAxis.getScale().getUnit() + ")", JLabel.CENTER), BorderLayout.NORTH); - JLabel yLabel = new JLabel(); - VTextIcon icon = new VTextIcon(yLabel, yAxis.getName() + " (" + yAxis.getScale().getUnit() + ")", VTextIcon.ROTATE_LEFT); - yLabel.setIcon(icon); + JLabel yLabel = new JLabel(yAxis.getName() + " (" + yAxis.getScale().getUnit() + ")"); + yLabel.setUI(new VerticalLabelUI(false)); add(yLabel, BorderLayout.WEST); add(new JLabel(getScale().getUnit(), JLabel.CENTER), BorderLayout.SOUTH); diff --git a/src/com/romraider/swing/VTextIcon.java b/src/com/romraider/swing/VTextIcon.java deleted file mode 100644 index fe8e8fb8..00000000 --- a/src/com/romraider/swing/VTextIcon.java +++ /dev/null @@ -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 - *

- * 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 VTextIcon for the specified component - * with the specified label. - * 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 VTextIcon for the specified component - * with the specified label. - * 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 - *

- * returns the best rotation for the string (ROTATE_NONE, ROTATE_LEFT, ROTATE_RIGHT) - *

- * This is public static so you can use it to test a string without creating a VTextIcon - *

- * 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. - *

- * Rotation rules - * - Roman can rotate left, right, or none - default right (counterclockwise) - * - CJK can't rotate - * - Arabic must rotate - default left (clockwise) - *

- * 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 - *

- * Hiragana is U+3040-U+309F, katakana is U+30A0-U+30FF - *

- * 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. - *

- * 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. - *

- * 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; -} diff --git a/src/com/romraider/swing/VerticalLabelUI.java b/src/com/romraider/swing/VerticalLabelUI.java new file mode 100644 index 00000000..02173225 --- /dev/null +++ b/src/com/romraider/swing/VerticalLabelUI.java @@ -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); + } +}