View Javadoc

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