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
187 g.translate(x+fWidth,y+fHeight);
188 ((Graphics2D)g).rotate(-NINETY_DEGREES);
189 g.setColor( fComponent.getBackground() );
190 g.fillRect( 1, 1, fWidth-2, fHeight-2 );
191 g.setColor( fComponent.getForeground() );
192 g.drawString(fLabel, kBufferSpace, -fDescent);
193 g.setColor( fComponent.getBackground() );
194 ((Graphics2D)g).rotate(NINETY_DEGREES);
195 g.translate(-(x+fWidth),-(y+fHeight));
196 }
197 else if (fRotation == ROTATE_RIGHT) {
198 g.translate(x,y);
199 ((Graphics2D)g).rotate(NINETY_DEGREES);
200 g.setColor( fComponent.getBackground() );
201 g.fillRect( 0, 0, fWidth, fHeight );
202 g.setColor( fComponent.getForeground() );
203 g.drawString(fLabel, kBufferSpace, -fDescent);
204 g.setColor( fComponent.getBackground() );
205 ((Graphics2D)g).rotate(-NINETY_DEGREES);
206 g.translate(-x,-y);
207 }
208
209 }
210
211 /***
212 * Returns the icon's width.
213 *
214 * @return an int specifying the fixed width of the icon.
215 */
216 public int getIconWidth() {
217 return fWidth;
218 }
219
220 /***
221 * Returns the icon's height.
222 *
223 * @return an int specifying the fixed height of the icon.
224 */
225 public int getIconHeight() {
226 return fHeight;
227 }
228
229 /***
230 verifyRotation
231
232 returns the best rotation for the string (ROTATE_NONE, ROTATE_LEFT, ROTATE_RIGHT)
233
234 This is public static so you can use it to test a string without creating a VTextIcon
235
236 from http://www.unicode.org/unicode/reports/tr9/tr9-3.html
237 When setting text using the Arabic script in vertical lines,
238 it is more common to employ a horizontal baseline that
239 is rotated by 90¡ counterclockwise so that the characters
240 are ordered from top to bottom. Latin text and numbers
241 may be rotated 90¡ clockwise so that the characters
242 are also ordered from top to bottom.
243
244 Rotation rules
245 - Roman can rotate left, right, or none - default right (counterclockwise)
246 - CJK can't rotate
247 - Arabic must rotate - default left (clockwise)
248
249 from the online edition of _The Unicode Standard, Version 3.0_, file ch10.pdf page 4
250 Ideographs are found in three blocks of the Unicode Standard...
251 U+4E00-U+9FFF, U+3400-U+4DFF, U+F900-U+FAFF
252
253 Hiragana is U+3040-U+309F, katakana is U+30A0-U+30FF
254
255 from http://www.unicode.org/unicode/faq/writingdirections.html
256 East Asian scripts are frequently written in vertical lines
257 which run from top-to-bottom and are arrange columns either
258 from left-to-right (Mongolian) or right-to-left (other scripts).
259 Most characters use the same shape and orientation when displayed
260 horizontally or vertically, but many punctuation characters
261 will change their shape when displayed vertically.
262
263 Letters and words from other scripts are generally rotated through
264 ninety degree angles so that they, too, will read from top to bottom.
265 That is, letters from left-to-right scripts will be rotated clockwise
266 and letters from right-to-left scripts counterclockwise, both
267 through ninety degree angles.
268
269 Unlike the bidirectional case, the choice of vertical layout
270 is usually treated as a formatting style; therefore,
271 the Unicode Standard does not define default rendering behavior
272 for vertical text nor provide directionality controls designed to override such behavior
273
274 */
275 public static int verifyRotation(String label, int rotateHint) {
276 boolean hasCJK = false;
277 boolean hasMustRotate = false;
278
279 int len = label.length();
280 char data[] = new char[len];
281 char ch;
282 label.getChars(0, len, data, 0);
283 for (int i = 0; i < len; i++) {
284 ch = data[i];
285 if ((ch >= '\u4E00' && ch <= '\u9FFF') ||
286 (ch >= '\u3400' && ch <= '\u4DFF') ||
287 (ch >= '\uF900' && ch <= '\uFAFF') ||
288 (ch >= '\u3040' && ch <= '\u309F') ||
289 (ch >= '\u30A0' && ch <= '\u30FF') )
290 hasCJK = true;
291 if ((ch >= '\u0590' && ch <= '\u05FF') ||
292 (ch >= '\u0600' && ch <= '\u06FF') ||
293 (ch >= '\u0700' && ch <= '\u074F') )
294 hasMustRotate = true;
295 }
296
297 if (hasCJK)
298 return DEFAULT_CJK;
299
300 int legal = hasMustRotate ? LEGAL_MUST_ROTATE : LEGAL_ROMAN;
301 if ((rotateHint & legal) > 0)
302 return rotateHint;
303
304
305 return hasMustRotate ? DEFAULT_MUST_ROTATE : DEFAULT_ROMAN;
306 }
307
308
309
310 static final String sDrawsInTopRight =
311 "\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E" +
312 "\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6";
313 static final String sDrawsInFarTopRight = "\u3001\u3002";
314
315 static final int DEFAULT_CJK = ROTATE_NONE;
316 static final int LEGAL_ROMAN = ROTATE_NONE | ROTATE_LEFT | ROTATE_RIGHT;
317 static final int DEFAULT_ROMAN = ROTATE_RIGHT;
318 static final int LEGAL_MUST_ROTATE = ROTATE_LEFT | ROTATE_RIGHT;
319 static final int DEFAULT_MUST_ROTATE = ROTATE_LEFT;
320
321 static final double NINETY_DEGREES = Math.toRadians(90.0);
322 static final int kBufferSpace = 5;
323 }