Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/imagero/gui/swing/VTextIcon.java


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