1
2 package hoplugins.commons.ui;
3
4 import javax.swing.Icon;
5
6 import java.awt.Component;
7 import java.awt.FontMetrics;
8 import java.awt.Graphics;
9 import java.awt.Graphics2D;
10
11 import java.beans.PropertyChangeEvent;
12 import java.beans.PropertyChangeListener;
13
14 /***
15 * VTextIcon is an Icon implementation which draws a short string vertically. It's useful for
16 * JTabbedPanes with LEFT or RIGHT tabs but can be used in any component which supports Icons,
17 * such as JLabel or JButton You can provide a hint to indicate whether to rotate the string to
18 * the left or right, or not at all, and it checks to make sure that the rotation is legal for
19 * the given string (for example, Chinese/Japanese/Korean scripts have special rules when drawn
20 * vertically and should never be rotated)
21 */
22 public class VTextIcon implements Icon, PropertyChangeListener {
23 /*** Normal Position */
24 static final int POSITION_NORMAL = 0;
25
26 /*** Top Right Position */
27 static final int POSITION_TOP_RIGHT = 1;
28
29 /*** Far Top Right Position */
30 static final int POSITION_FAR_TOP_RIGHT = 2;
31
32 /*** Rotate */
33 public static final int ROTATE_DEFAULT = 0x00;
34
35 /*** Don't Rotate */
36 public static final int ROTATE_NONE = 0x01;
37
38 /*** Rotate LEft */
39 public static final int ROTATE_LEFT = 0x02;
40
41 /*** Ratate Right */
42 public static final int ROTATE_RIGHT = 0x04;
43
44
45
46
47 /*** */
48 /*** Missing Parameter Documentation */
49 static final String sDrawsInTopRight =
50 "\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E"
51 + "\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6";
52
53 /*** */
54 /*** Missing Parameter Documentation */
55 static final String sDrawsInFarTopRight = "\u3001\u3002";
56
57 /*** */
58 /*** Missing Parameter Documentation */
59 static final int DEFAULT_CJK = ROTATE_NONE;
60
61 /*** */
62 /*** Missing Parameter Documentation */
63 static final int LEGAL_ROMAN = ROTATE_NONE | ROTATE_LEFT | ROTATE_RIGHT;
64
65 /*** */
66 /*** Missing Parameter Documentation */
67 static final int DEFAULT_ROMAN = ROTATE_RIGHT;
68
69 /*** */
70 /*** Missing Parameter Documentation */
71 static final int LEGAL_MUST_ROTATE = ROTATE_LEFT | ROTATE_RIGHT;
72
73 /*** */
74 /*** Missing Parameter Documentation */
75 static final int DEFAULT_MUST_ROTATE = ROTATE_LEFT;
76
77 /*** */
78 /*** Missing Parameter Documentation */
79 static final double NINETY_DEGREES = Math.toRadians(90.0);
80
81 /*** */
82 /*** Missing Parameter Documentation */
83 static final int kBufferSpace = 5;
84
85 /*** */
86 /*** Missing Parameter Documentation */
87 Component fComponent;
88
89 /*** */
90 /*** Missing Parameter Documentation */
91 String fLabel;
92
93 /*** */
94 /*** Missing Parameter Documentation */
95 String[] fCharStrings;
96
97 /*** */
98 /*** Missing Parameter Documentation */
99 int[] fCharWidths;
100
101 /*** */
102 /*** Missing Parameter Documentation */
103 int[] fPosition;
104
105 /*** */
106 /*** Missing Parameter Documentation */
107 int fCharHeight;
108
109 /*** */
110 /*** Missing Parameter Documentation */
111 int fDescent;
112
113 /*** */
114 /*** Missing Parameter Documentation */
115 int fHeight;
116
117 /*** */
118 /*** Missing Parameter Documentation */
119 int fRotation;
120
121 /*** */
122 /*** Missing Parameter Documentation */
123 int fWidth;
124
125 /***
126 * Creates a <code>VTextIcon</code> for the specified <code>component</code> with the specified
127 * <code>label</code>. It sets the orientation to the default for the string
128 *
129 * @see #verifyRotation
130 */
131 public VTextIcon(Component component, String label) {
132 this(component, label, ROTATE_DEFAULT);
133 }
134
135 /***
136 * Creates a <code>VTextIcon</code> for the specified <code>component</code> with the specified
137 * <code>label</code>. It sets the orientation to the provided value if it's legal for the
138 * string
139 *
140 * @see #verifyRotation
141 */
142 public VTextIcon(Component component, String label, int rotateHint) {
143 fComponent = component;
144 fLabel = label;
145 fRotation = verifyRotation(label, rotateHint);
146 calcDimensions();
147 fComponent.addPropertyChangeListener(this);
148 }
149
150 /***
151 * Returns the icon's height.
152 *
153 * @return an int specifying the fixed height of the icon.
154 */
155 public int getIconHeight() {
156 return fHeight;
157 }
158
159 /***
160 * Returns the icon's width.
161 *
162 * @return an int specifying the fixed width of the icon.
163 */
164 public int getIconWidth() {
165 return fWidth;
166 }
167
168 /***
169 * sets the label to the given string, updating the orientation as needed and invalidating the
170 * layout if the size changes
171 *
172 * @see #verifyRotation
173 */
174 public void setLabel(String label) {
175 fLabel = label;
176 fRotation = verifyRotation(label, fRotation);
177 recalcDimensions();
178 }
179
180 /***
181 * Draw the icon at the specified location. Icon implementations may use the Component
182 * argument to get properties useful for painting, e.g. the foreground or background color.
183 *
184 * @param c
185 * @param g
186 * @param x
187 * @param y
188 */
189 public void paintIcon(Component c, Graphics g, int x, int y) {
190
191 g.setColor(c.getForeground());
192 g.setFont(c.getFont());
193
194 if (fRotation == ROTATE_NONE) {
195 int yPos = y + fCharHeight;
196
197 for (int i = 0; i < fCharStrings.length; i++) {
198
199
200
201 int tweak;
202
203 switch (fPosition[i]) {
204 case POSITION_NORMAL:
205
206
207 g.drawString(fCharStrings[i],
208 x + ((fWidth - fCharWidths[i]) / 2), yPos);
209
210 break;
211
212 case POSITION_TOP_RIGHT:
213 tweak = fCharHeight / 3;
214 g.drawString(fCharStrings[i], x + (tweak / 2), yPos - tweak);
215
216 break;
217
218 case POSITION_FAR_TOP_RIGHT:
219 tweak = fCharHeight - (fCharHeight / 3);
220 g.drawString(fCharStrings[i], x + (tweak / 2), yPos - tweak);
221
222 break;
223 }
224
225 yPos += fCharHeight;
226 }
227 }
228 else if (fRotation == ROTATE_LEFT) {
229 g.translate(x + fWidth, y + fHeight);
230 ((Graphics2D) g).rotate(-NINETY_DEGREES);
231 g.drawString(fLabel, kBufferSpace, -fDescent);
232 ((Graphics2D) g).rotate(NINETY_DEGREES);
233 g.translate(-(x + fWidth), -(y + fHeight));
234 }
235 else if (fRotation == ROTATE_RIGHT) {
236 g.translate(x, y);
237 ((Graphics2D) g).rotate(NINETY_DEGREES);
238 g.drawString(fLabel, kBufferSpace, -fDescent);
239 ((Graphics2D) g).rotate(-NINETY_DEGREES);
240 g.translate(-x, -y);
241 }
242 }
243
244 /***
245 * Checks for changes to the font on the fComponent so that it can invalidate the layout if the
246 * size changes
247 *
248 * @param e
249 */
250 public void propertyChange(PropertyChangeEvent e) {
251 String prop = e.getPropertyName();
252
253 if ("font".equals(prop)) {
254 recalcDimensions();
255 }
256 }
257
258 /***
259 * verifyRotation returns the best rotation for the string (ROTATE_NONE, ROTATE_LEFT,
260 * ROTATE_RIGHT) This is public static so you can use it to test a string without creating a
261 * VTextIcon from http://www.unicode.org/unicode/reports/tr9/tr9-3.html When setting text
262 * using the Arabic script in vertical lines, it is more common to employ a horizontal
263 * baseline that is rotated by 90¡ counterclockwise so that the characters are ordered from
264 * top to bottom. Latin text and numbers may be rotated 90¡ clockwise so that the characters
265 * are also ordered from top to bottom. Rotation rules - Roman can rotate left, right, or
266 * none - default right (counterclockwise) - CJK can't rotate - Arabic must rotate - default
267 * left (clockwise) from the online edition of _The Unicode Standard, Version 3.0_, file
268 * ch10.pdf page 4 Ideographs are found in three blocks of the Unicode Standard...
269 * U+4E00-U+9FFF, U+3400-U+4DFF, U+F900-U+FAFF Hiragana is U+3040-U+309F, katakana is
270 * U+30A0-U+30FF from http://www.unicode.org/unicode/faq/writingdirections.html East Asian
271 * scripts are frequently written in vertical lines which run from top-to-bottom and are
272 * arrange columns either from left-to-right (Mongolian) or right-to-left (other scripts).
273 * Most characters use the same shape and orientation when displayed horizontally or
274 * vertically, but many punctuation characters will change their shape when displayed
275 * vertically. Letters and words from other scripts are generally rotated through ninety
276 * degree angles so that they, too, will read from top to bottom. That is, letters from
277 * left-to-right scripts will be rotated clockwise and letters from right-to-left scripts
278 * counterclockwise, both through ninety degree angles. Unlike the bidirectional case,
279 * the choice of vertical layout is usually treated as a formatting style; therefore, the
280 * Unicode Standard does not define default rendering behavior for vertical text nor
281 * provide directionality controls designed to override such behavior
282 *
283 * @param label
284 * @param rotateHint
285 *
286 * @return
287 */
288 public static int verifyRotation(String label, int rotateHint) {
289 boolean hasCJK = false;
290 boolean hasMustRotate = false;
291
292 int len = label.length();
293 char[] data = new char[len];
294 char ch;
295
296 label.getChars(0, len, data, 0);
297
298 for (int i = 0; i < len; i++) {
299 ch = data[i];
300
301 if (((ch >= '\u4E00') && (ch <= '\u9FFF'))
302 || ((ch >= '\u3400') && (ch <= '\u4DFF'))
303 || ((ch >= '\uF900') && (ch <= '\uFAFF'))
304 || ((ch >= '\u3040') && (ch <= '\u309F'))
305 || ((ch >= '\u30A0') && (ch <= '\u30FF'))) {
306 hasCJK = true;
307 }
308
309 if (((ch >= '\u0590') && (ch <= '\u05FF'))
310 || ((ch >= '\u0600') && (ch <= '\u06FF'))
311 || ((ch >= '\u0700') && (ch <= '\u074F'))) {
312 hasMustRotate = true;
313 }
314 }
315
316
317 if (hasCJK) {
318 return DEFAULT_CJK;
319 }
320
321 int legal = hasMustRotate ? LEGAL_MUST_ROTATE : LEGAL_ROMAN;
322
323 if ((rotateHint & legal) > 0) {
324 return rotateHint;
325 }
326
327
328 return hasMustRotate ? DEFAULT_MUST_ROTATE : DEFAULT_ROMAN;
329 }
330
331 /***
332 * Method that calculate the optimized dimensions
333 */
334 void calcDimensions() {
335 FontMetrics fm = fComponent.getFontMetrics(fComponent.getFont());
336
337 fCharHeight = fm.getAscent() + fm.getDescent();
338 fDescent = fm.getDescent();
339
340 if (fRotation == ROTATE_NONE) {
341 int len = fLabel.length();
342 char[] data = new char[len];
343
344 fLabel.getChars(0, len, data, 0);
345
346
347 fWidth = 0;
348
349
350 fCharStrings = new String[len];
351 fCharWidths = new int[len];
352 fPosition = new int[len];
353
354 char ch;
355
356 for (int i = 0; i < len; i++) {
357 ch = data[i];
358 fCharWidths[i] = fm.charWidth(ch);
359
360 if (fCharWidths[i] > fWidth) {
361 fWidth = fCharWidths[i];
362 }
363
364 fCharStrings[i] = new String(data, i, 1);
365
366
367 if (sDrawsInTopRight.indexOf(ch) >= 0) {
368 fPosition[i] = POSITION_TOP_RIGHT;
369 }
370 else if (sDrawsInFarTopRight.indexOf(ch) >= 0) {
371 fPosition[i] = POSITION_FAR_TOP_RIGHT;
372 }
373 else {
374 fPosition[i] = POSITION_NORMAL;
375 }
376 }
377
378
379 fHeight = (fCharHeight * len) + fDescent;
380 }
381 else {
382
383 fWidth = fCharHeight;
384
385
386 fHeight = fm.stringWidth(fLabel) + (2 * kBufferSpace);
387 }
388 }
389
390 /***
391 * Calculates the dimensions. If they've changed, invalidates the component
392 */
393 void recalcDimensions() {
394 int wOld = getIconWidth();
395 int hOld = getIconHeight();
396
397 calcDimensions();
398
399 if ((wOld != getIconWidth()) || (hOld != getIconHeight())) {
400 fComponent.invalidate();
401 }
402 }
403 }