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