View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2010 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
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; // for efficiency, break the fLabel into one-char
42  									// strings to be passed to drawString
43  	int[] fCharWidths; // Roman characters should be centered when not rotated
44  								// (Japanese fonts are monospaced)
45  	int[] fPosition; // Japanese half-height characters need to be shifted when
46  							// drawn vertically
47  	int fWidth, fHeight, fCharHeight, fDescent; // Cached for speed
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 ); // Make sure the current
98  																		// rotation is still legal
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 			// if not rotated, width is that of the widest char in the string
138 			fWidth = 0;
139 			// we need an array of one-char strings for drawString
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 				// small kana and punctuation
152 				if( sDrawsInTopRight.indexOf( ch ) >= 0 ) // if ch is in
153 																		// sDrawsInTopRight
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 			// and height is the font height * the char count, + one extra leading
161 			// at the bottom
162 			fHeight = fCharHeight * len + fDescent;
163 		}
164 		else
165 		{
166 			// if rotated, width is the height of the string
167 			fWidth = fCharHeight;
168 			// and height is the width, plus some buffer space
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 		// We don't insist that it be on the same Component
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 				// Special rules for Japanese - "half-height" characters (like ya,
190 				// yu, yo in combinations)
191 				// should draw in the top-right quadrant when drawn vertically
192 				// - they draw in the bottom-left normally
193 				int tweak;
194 				switch( fPosition[i] )
195 				{
196 				case POSITION_NORMAL :
197 					// Roman fonts should be centered. Japanese fonts are always
198 					// monospaced.
199 					g.drawString( fCharStrings[i], x + ( ( fWidth - fCharWidths[i] ) / 2 ), yPos );
200 					break;
201 				case POSITION_TOP_RIGHT :
202 					tweak = fCharHeight / 3; // Should be 2, but they aren't actually
203 														// half-height
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 			//g.setColor( fComponent.getBackground() );
219 			//g.fillRect( 1, 1, fWidth - 2, fHeight - 2 );
220 			//g.setColor( fComponent.getForeground() );
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; // Arabic, etc
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' ) || // Hebrew
322 					( ch >= '\u0600' && ch <= '\u06FF' ) || // Arabic
323 					( ch >= '\u0700' && ch <= '\u074F' ) ) // Syriac
324 				hasMustRotate = true;
325 		}
326 		// If you mix Arabic with Chinese, you're on your own
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 		// The hint wasn't legal, or it was zero
335 		return hasMustRotate ? DEFAULT_MUST_ROTATE : DEFAULT_ROMAN;
336 	}
337 
338 	// The small kana characters and Japanese punctuation that draw in the top
339 	// right quadrant:
340 	// small a, i, u, e, o, tsu, ya, yu, yo, wa (katakana only) ka ke
341 	static final String sDrawsInTopRight = "\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E" + // hiragana
342 			"\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6"; // katakana
343 	static final String sDrawsInFarTopRight = "\u3001\u3002"; // comma, full stop
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 }