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