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