001: /*
002: * Copyright (C) 2005 - 2008 JasperSoft Corporation. All rights reserved.
003: * http://www.jaspersoft.com.
004: *
005: * Unless you have purchased a commercial license agreement from JasperSoft,
006: * the following license terms apply:
007: *
008: * This program is free software; you can redistribute it and/or modify
009: * it under the terms of the GNU General Public License version 2 as published by
010: * the Free Software Foundation.
011: *
012: * This program is distributed WITHOUT ANY WARRANTY; and without the
013: * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
014: * See the GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, see http://www.gnu.org/licenses/gpl.txt
018: * or write to:
019: *
020: * Free Software Foundation, Inc.,
021: * 59 Temple Place - Suite 330,
022: * Boston, MA USA 02111-1307
023: *
024: *
025: *
026: *
027: * VTextIcon.java
028: *
029: * Created on March 18, 2006, 10:57 PM
030: *
031: */
032:
033: package it.businesslogic.ireport.gui.docking;
034:
035: import java.awt.*;
036: import javax.swing.*;
037: import java.beans.*;
038:
039: /**
040: VTextIcon is an Icon implementation which draws a short string vertically.
041: It's useful for JTabbedPanes with LEFT or RIGHT tabs but can be used in any
042: component which supports Icons, such as JLabel or JButton
043:
044: You can provide a hint to indicate whether to rotate the string
045: to the left or right, or not at all, and it checks to make sure
046: that the rotation is legal for the given string
047: (for example, Chinese/Japanese/Korean scripts have special rules when
048: drawn vertically and should never be rotated)
049: */
050: public class VTextIcon implements Icon, PropertyChangeListener {
051: String fLabel;
052: String[] fCharStrings; // for efficiency, break the fLabel into one-char strings to be passed to drawString
053: int[] fCharWidths; // Roman characters should be centered when not rotated (Japanese fonts are monospaced)
054: int[] fPosition; // Japanese half-height characters need to be shifted when drawn vertically
055: int fWidth, fHeight, fCharHeight, fDescent; // Cached for speed
056: int fRotation;
057: Component fComponent;
058:
059: static final int POSITION_NORMAL = 0;
060: static final int POSITION_TOP_RIGHT = 1;
061: static final int POSITION_FAR_TOP_RIGHT = 2;
062:
063: public static final int ROTATE_DEFAULT = 0x00;
064: public static final int ROTATE_NONE = 0x01;
065: public static final int ROTATE_LEFT = 0x02;
066: public static final int ROTATE_RIGHT = 0x04;
067:
068: /**
069: * Creates a <code>VTextIcon</code> for the specified <code>component</code>
070: * with the specified <code>label</code>.
071: * It sets the orientation to the default for the string
072: * @see #verifyRotation
073: */
074: public VTextIcon(Component component, String label) {
075: this (component, label, ROTATE_DEFAULT);
076: }
077:
078: /**
079: * Creates a <code>VTextIcon</code> for the specified <code>component</code>
080: * with the specified <code>label</code>.
081: * It sets the orientation to the provided value if it's legal for the string
082: * @see #verifyRotation
083: */
084: public VTextIcon(Component component, String label, int rotateHint) {
085: fComponent = component;
086: fLabel = label;
087: fRotation = verifyRotation(label, rotateHint);
088: calcDimensions();
089: fComponent.addPropertyChangeListener(this );
090: }
091:
092: /**
093: * sets the label to the given string, updating the orientation as needed
094: * and invalidating the layout if the size changes
095: * @see #verifyRotation
096: */
097: public void setLabel(String label) {
098: fLabel = label;
099: fRotation = verifyRotation(label, fRotation); // Make sure the current rotation is still legal
100: recalcDimensions();
101: }
102:
103: /**
104: * Checks for changes to the font on the fComponent
105: * so that it can invalidate the layout if the size changes
106: */
107: public void propertyChange(PropertyChangeEvent e) {
108: String prop = e.getPropertyName();
109: if ("font".equals(prop)) {
110: recalcDimensions();
111: }
112: }
113:
114: /**
115: * Calculates the dimensions. If they've changed,
116: * invalidates the component
117: */
118: void recalcDimensions() {
119: int wOld = getIconWidth();
120: int hOld = getIconHeight();
121: calcDimensions();
122: if (wOld != getIconWidth() || hOld != getIconHeight())
123: fComponent.invalidate();
124: }
125:
126: void calcDimensions() {
127: FontMetrics fm = fComponent
128: .getFontMetrics(fComponent.getFont());
129: fCharHeight = fm.getAscent() + fm.getDescent();
130: fDescent = fm.getDescent();
131: if (fRotation == ROTATE_NONE) {
132: int len = fLabel.length();
133: char data[] = new char[len];
134: fLabel.getChars(0, len, data, 0);
135: // if not rotated, width is that of the widest char in the string
136: fWidth = 0;
137: // we need an array of one-char strings for drawString
138: fCharStrings = new String[len];
139: fCharWidths = new int[len];
140: fPosition = new int[len];
141: char ch;
142: for (int i = 0; i < len; i++) {
143: ch = data[i];
144: fCharWidths[i] = fm.charWidth(ch);
145: if (fCharWidths[i] > fWidth)
146: fWidth = fCharWidths[i];
147: fCharStrings[i] = new String(data, i, 1);
148: // small kana and punctuation
149: if (sDrawsInTopRight.indexOf(ch) >= 0) // if ch is in sDrawsInTopRight
150: fPosition[i] = POSITION_TOP_RIGHT;
151: else if (sDrawsInFarTopRight.indexOf(ch) >= 0)
152: fPosition[i] = POSITION_FAR_TOP_RIGHT;
153: else
154: fPosition[i] = POSITION_NORMAL;
155: }
156: // and height is the font height * the char count, + one extra leading at the bottom
157: fHeight = fCharHeight * len + fDescent;
158: } else {
159: // if rotated, width is the height of the string
160: fWidth = fCharHeight;
161: // and height is the width, plus some buffer space
162: fHeight = fm.stringWidth(fLabel) + 2 * kBufferSpace;
163: }
164: }
165:
166: /**
167: * Draw the icon at the specified location. Icon implementations
168: * may use the Component argument to get properties useful for
169: * painting, e.g. the foreground or background color.
170: */
171: public void paintIcon(Component c, Graphics g, int x, int y) {
172: // We don't insist that it be on the same Component
173: g.setColor(c.getForeground());
174: g.setFont(c.getFont());
175: //
176: Graphics2D g2 = (Graphics2D) g;
177: g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
178: RenderingHints.VALUE_ANTIALIAS_ON);
179: g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
180: RenderingHints.VALUE_STROKE_DEFAULT);
181: g2.setRenderingHint(RenderingHints.KEY_DITHERING,
182: RenderingHints.VALUE_DITHER_ENABLE);
183:
184: if (fRotation == ROTATE_NONE) {
185: int yPos = y + fCharHeight;
186: for (int i = 0; i < fCharStrings.length; i++) {
187: // Special rules for Japanese - "half-height" characters (like ya, yu, yo in combinations)
188: // should draw in the top-right quadrant when drawn vertically
189: // - they draw in the bottom-left normally
190: int tweak;
191: switch (fPosition[i]) {
192: case POSITION_NORMAL:
193: // Roman fonts should be centered. Japanese fonts are always monospaced.
194: g.drawString(fCharStrings[i], x
195: + ((fWidth - fCharWidths[i]) / 2), yPos);
196: break;
197: case POSITION_TOP_RIGHT:
198: tweak = fCharHeight / 3; // Should be 2, but they aren't actually half-height
199: g.drawString(fCharStrings[i], x + (tweak / 2), yPos
200: - tweak);
201: break;
202: case POSITION_FAR_TOP_RIGHT:
203: tweak = fCharHeight - fCharHeight / 3;
204: g.drawString(fCharStrings[i], x + (tweak / 2), yPos
205: - tweak);
206: break;
207: }
208: yPos += fCharHeight;
209: }
210: } else if (fRotation == ROTATE_LEFT) {
211: g.translate(x + fWidth, y + fHeight);
212: ((Graphics2D) g).rotate(-NINETY_DEGREES);
213: g.drawString(fLabel, kBufferSpace, -fDescent);
214: ((Graphics2D) g).rotate(NINETY_DEGREES);
215: g.translate(-(x + fWidth), -(y + fHeight));
216: } else if (fRotation == ROTATE_RIGHT) {
217: g.translate(x, y);
218: ((Graphics2D) g).rotate(NINETY_DEGREES);
219: g.drawString(fLabel, kBufferSpace, -fDescent);
220: ((Graphics2D) g).rotate(-NINETY_DEGREES);
221: g.translate(-x, -y);
222: }
223:
224: }
225:
226: /**
227: * Returns the icon's width.
228: *
229: * @return an int specifying the fixed width of the icon.
230: */
231: public int getIconWidth() {
232: return fWidth;
233: }
234:
235: /**
236: * Returns the icon's height.
237: *
238: * @return an int specifying the fixed height of the icon.
239: */
240: public int getIconHeight() {
241: return fHeight;
242: }
243:
244: /**
245: verifyRotation
246:
247: returns the best rotation for the string (ROTATE_NONE, ROTATE_LEFT, ROTATE_RIGHT)
248:
249: This is public static so you can use it to test a string without creating a VTextIcon
250:
251: from http://www.unicode.org/unicode/reports/tr9/tr9-3.html
252: When setting text using the Arabic script in vertical lines,
253: it is more common to employ a horizontal baseline that
254: is rotated by 90 counterclockwise so that the characters
255: are ordered from top to bottom. Latin text and numbers
256: may be rotated 90 clockwise so that the characters
257: are also ordered from top to bottom.
258:
259: Rotation rules
260: - Roman can rotate left, right, or none - default right (counterclockwise)
261: - CJK can't rotate
262: - Arabic must rotate - default left (clockwise)
263:
264: from the online edition of _The Unicode Standard, Version 3.0_, file ch10.pdf page 4
265: Ideographs are found in three blocks of the Unicode Standard...
266: U+4E00-U+9FFF, U+3400-U+4DFF, U+F900-U+FAFF
267:
268: Hiragana is U+3040-U+309F, katakana is U+30A0-U+30FF
269:
270: from http://www.unicode.org/unicode/faq/writingdirections.html
271: East Asian scripts are frequently written in vertical lines
272: which run from top-to-bottom and are arrange columns either
273: from left-to-right (Mongolian) or right-to-left (other scripts).
274: Most characters use the same shape and orientation when displayed
275: horizontally or vertically, but many punctuation characters
276: will change their shape when displayed vertically.
277:
278: Letters and words from other scripts are generally rotated through
279: ninety degree angles so that they, too, will read from top to bottom.
280: That is, letters from left-to-right scripts will be rotated clockwise
281: and letters from right-to-left scripts counterclockwise, both
282: through ninety degree angles.
283:
284: Unlike the bidirectional case, the choice of vertical layout
285: is usually treated as a formatting style; therefore,
286: the Unicode Standard does not define default rendering behavior
287: for vertical text nor provide directionality controls designed to override such behavior
288:
289: */
290: public static int verifyRotation(String label, int rotateHint) {
291: boolean hasCJK = false;
292: boolean hasMustRotate = false; // Arabic, etc
293:
294: int len = label.length();
295: char data[] = new char[len];
296: char ch;
297: label.getChars(0, len, data, 0);
298: for (int i = 0; i < len; i++) {
299: ch = data[i];
300: if ((ch >= '\u4E00' && ch <= '\u9FFF')
301: || (ch >= '\u3400' && ch <= '\u4DFF')
302: || (ch >= '\uF900' && ch <= '\uFAFF')
303: || (ch >= '\u3040' && ch <= '\u309F')
304: || (ch >= '\u30A0' && ch <= '\u30FF'))
305: hasCJK = true;
306: if ((ch >= '\u0590' && ch <= '\u05FF') || // Hebrew
307: (ch >= '\u0600' && ch <= '\u06FF') || // Arabic
308: (ch >= '\u0700' && ch <= '\u074F')) // Syriac
309: hasMustRotate = true;
310: }
311: // If you mix Arabic with Chinese, you're on your own
312: if (hasCJK)
313: return DEFAULT_CJK;
314:
315: int legal = hasMustRotate ? LEGAL_MUST_ROTATE : LEGAL_ROMAN;
316: if ((rotateHint & legal) > 0)
317: return rotateHint;
318:
319: // The hint wasn't legal, or it was zero
320: return hasMustRotate ? DEFAULT_MUST_ROTATE : DEFAULT_ROMAN;
321: }
322:
323: // The small kana characters and Japanese punctuation that draw in the top right quadrant:
324: // small a, i, u, e, o, tsu, ya, yu, yo, wa (katakana only) ka ke
325: static final String sDrawsInTopRight = "\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E"
326: + // hiragana
327: "\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6"; // katakana
328: static final String sDrawsInFarTopRight = "\u3001\u3002"; // comma, full stop
329:
330: static final int DEFAULT_CJK = ROTATE_NONE;
331: static final int LEGAL_ROMAN = ROTATE_NONE | ROTATE_LEFT
332: | ROTATE_RIGHT;
333: static final int DEFAULT_ROMAN = ROTATE_RIGHT;
334: static final int LEGAL_MUST_ROTATE = ROTATE_LEFT | ROTATE_RIGHT;
335: static final int DEFAULT_MUST_ROTATE = ROTATE_LEFT;
336:
337: static final double NINETY_DEGREES = Math.toRadians(90.0);
338: static final int kBufferSpace = 5;
339: }
|