1 /*
2  * @(#)QuaquaUtilities.java  5.0  2008-05-10
3  *
4  * Copyright (c) 2003-2008 Werner Randelshofer
5  * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
6  * All rights reserved.
7  *
8  * The copyright of this software is owned by Werner Randelshofer.
9  * You may not use, copy or modify this software, except in
10  * accordance with the license agreement you entered into with
11  * Werner Randelshofer. For details see accompanying license terms.
12  */
13 package ch.randelshofer.quaqua;
14 
15 import ch.randelshofer.quaqua.util.*;
16 import java.awt.*;
17 import java.awt.event.*;
18 import java.awt.image.*;
19 import java.net.*;
20 import javax.swing.*;
21 import javax.swing.text.*;
22 import javax.swing.border.*;
23 import javax.swing.plaf.*;
24 import javax.swing.plaf.basic.*;
25 
26 /**
27  * Utility class for the Quaqua LAF.
28  *
29  * @author Werner Randelshofer, Staldenmattweg 2, CH-6405 Immensee, Switzerland
30  * @version 5.0 2008-05-10 Added support for client property "JComponent.sizeVariant".
31  * <br>4.1.1 2008-04-17 Tried to use fractional font metrics but it
32  * does not work well.
33  * <br>4.1 2007-11-10 Added method adjustFocus and shouldIgnore from
34  * SwingUtilities2.
35  * <br>4.0 2007-11-01 Rewrote method isFocused for Java 1.4 or higher.
36  * <br>3.6 2007-08-01 Method isFocused did not always work as expected.
37  * <br>3.5 2007-03-04 Added support for client property
38  * "Quaqua.Component.cellRendererFor".
39  * <br>3.4 2007-02-27 Added method getUIOfType.
40  * <br>3.3 2007-02-10 Method installProperty added.
41  * <br>3.2 2007-01-18 Changed method isFocused to return true, if the
42  * component is focusOwner or permanentFocusOwner.
43  * <br>3.1 2006-09-04 Added method compositeRequestFocus.
44  * <br>3.0.5 2006-08-20 Method endGraphics must not set
45  * KEY_TEXT_ANTIALIASING to null.
46  * <br>3.0.4 2006-02-19 Catch Throwable in method setWindowAlpha instead
47  * of catching NoSuchMethodException.
48  * <br>3.0.3 2006-01-08 Don't set Window alpha, when running on
49  * Java 1.4.2_05 on Mac OS X 10.3.5. Because this only has the effect of turning
50  * the background color of the Window to white.
51  * <br>3.0.2 2005-12-10 Method isOnActiveWindow() did not reliably
52  * return true.
53  * <br>3.0.1 2005-11-12 Fixed NPE in method repaint border.
54  * <br>3.0 2005-09-24 Removed all reflection helper methods. Moved Sheet
55  * helper methods out into class Sheets.
56  * <br>2.6 2005-09-17 Method isOnFocusedWindow returns true, if
57  * the window returns false on "getFocusableWindowState".
58  * <br>2.5 2005-03-13 Renamed method isFrameActive to isOnActiveFrame.
59  * <br>2.4 2004-12-28 Method createBufferdImage added. Method
60  * isOnActiveWindow() renamed to isFrameActive().
61  * <br>2.3 2004-12-14 Method getUI added.
62  * <br>2.2.1 2004-12-01 Methods setDragEnabled and getDragEnabled never
63  * worked because the attempted to get method objects on the wrong class.
64  * <br>2.2 2004-09-19 Refined algorithm of method isFrameActive.
65  * <br>2.1 2004-07-04 Methods repaintBorder, beginFont, endFont and
66  * isFocused added.
67  * <br>2.0 2004-04-27 Renamed from QuaquaGraphicUtils to QuaquaUtilities.
68  * Added method isFrameActive(Component).
69  * <br>1.1.1 2003-10-08 Diagnostic output to System.out removed.
70  * <br>1.1 2003-10-05 Methods getModifiersText and getModifiersUnicode
71  * added.
72  * <br>1.0 2003-07-19 Created.
73  */
74 public class QuaquaUtilities extends BasicGraphicsUtils implements SwingConstants {
75 
76     private final static boolean DEBUG = false;
77     /**
78      * This is set to false, if we fail to install properties
79      * directly.
80      */
81     private static boolean canInstallProperty = true;
82 
83     /** Prevent instance creation. */
QuaquaUtilities()84     private QuaquaUtilities() {
85     }
86 
87     /*
88      * Convenience function for determining ComponentOrientation.  Helps us
89      * avoid having Munge directives throughout the code.
90      */
isLeftToRight(Component c)91     public static boolean isLeftToRight(Component c) {
92         return c.getComponentOrientation().isLeftToRight();
93     }
94 
95     /**
96      * Draw a string with the graphics <code>g</code> at location
97      * (<code>x</code>, <code>y</code>)
98      * just like <code>g.drawString</code> would.
99      * The character at index <code>underlinedIndex</code>
100      * in text will be underlined. If <code>index</code> is beyond the
101      * bounds of <code>text</code> (including < 0), nothing will be
102      * underlined.
103      *
104      * @param g Graphics to draw with
105      * @param text String to draw
106      * @param underlinedIndex Index of character in text to underline
107      * @param x x coordinate to draw at
108      * @param y y coordinate to draw at
109      * @since 1.4
110      */
drawStringUnderlineCharAt(Graphics g, String text, int underlinedIndex, int x, int y)111     public static void drawStringUnderlineCharAt(Graphics g, String text,
112             int underlinedIndex, int x, int y) {
113         g.drawString(text, x, y);
114         if (underlinedIndex >= 0 && underlinedIndex < text.length()) {
115             FontMetrics fm = g.getFontMetrics();
116             int underlineRectX = x + fm.stringWidth(text.substring(0, underlinedIndex));
117             int underlineRectY = y;
118             int underlineRectWidth = fm.charWidth(text.charAt(underlinedIndex));
119             int underlineRectHeight = 1;
120             g.fillRect(underlineRectX, underlineRectY + fm.getDescent() - 1,
121                     underlineRectWidth, underlineRectHeight);
122         }
123     }
124 
125     /**
126      * Returns index of the first occurrence of <code>mnemonic</code>
127      * within string <code>text</code>. Matching algorithm is not
128      * case-sensitive.
129      *
130      * @param text The text to search through, may be null
131      * @param mnemonic The mnemonic to find the character for.
132      * @return index into the string if exists, otherwise -1
133      */
findDisplayedMnemonicIndex(String text, int mnemonic)134     static int findDisplayedMnemonicIndex(String text, int mnemonic) {
135         if (text == null || mnemonic == '\0') {
136             return -1;
137         }
138 
139         char uc = Character.toUpperCase((char) mnemonic);
140         char lc = Character.toLowerCase((char) mnemonic);
141 
142         int uci = text.indexOf(uc);
143         int lci = text.indexOf(lc);
144 
145         if (uci == -1) {
146             return lci;
147         } else if (lci == -1) {
148             return uci;
149         } else {
150             return (lci < uci) ? lci : uci;
151         }
152     }
153 
154     /**
155      * Returns true if the component is on a Dialog or a Frame, which is active,
156      * or if it is on a Window, which is focused.
157      * Always returns true, if the component has no parent window.
158      */
isOnActiveWindow(Component c)159     public static boolean isOnActiveWindow(Component c) {
160         // In the RootPaneUI, we set a client property on the whole component
161         // tree, if the ancestor Frame gets activated or deactivated.
162         if (c instanceof JComponent) {
163             Boolean value = (Boolean) ((JComponent) c).getClientProperty("Frame.active");
164             // Unfortunately, the value is not always reliable.
165             // Therefore we can only do a short circuit, if the value is true.
166             if (value != null && value.booleanValue()) {
167                 return true;
168             //return value.booleanValue();
169             }
170         }
171 
172         Window window = SwingUtilities.getWindowAncestor(c);
173         boolean isOnActiveWindow;
174         if (window == null) {
175             isOnActiveWindow = true;
176         } else if ((window instanceof Frame) || (window instanceof Dialog)) {
177             isOnActiveWindow = window.isActive();
178         } else {
179             if (window.getFocusableWindowState()) {
180                 isOnActiveWindow = window.isFocused();
181             } else {
182                 isOnActiveWindow = true;
183             }
184         }
185 
186         // In case the activation property is true, we fix the value of the
187         // client property, so that we can do a short circuit next time.
188         if (isOnActiveWindow && (c instanceof JComponent)) {
189             ((JComponent) c).putClientProperty("Frame.active", new Boolean(isOnActiveWindow));
190         }
191         return isOnActiveWindow;
192     }
193 
194     /**
195      * Returns a Mac OS X specific String describing the modifier key(s),
196      * such as "Shift", or "Ctrl+Shift".
197      *
198      * @return string a text description of the combination of modifier
199      *                keys that were held down during the event
200      */
getKeyModifiersText(int modifiers, boolean leftToRight)201     public static String getKeyModifiersText(int modifiers, boolean leftToRight) {
202         return getKeyModifiersUnicode(modifiers, leftToRight);
203     }
204 
getKeyModifiersUnicode(int modifiers, boolean leftToRight)205     static String getKeyModifiersUnicode(int modifiers, boolean leftToRight) {
206         char[] cs = new char[4];
207         int count = 0;
208         if (leftToRight) {
209             if ((modifiers & InputEvent.CTRL_MASK) != 0) {
210                 cs[count++] = '\u2303';
211             } // Unicode: UP ARROWHEAD
212 
213             if ((modifiers & (InputEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0) {
214                 cs[count++] = '\u2325';
215             } // Unicode: OPTION KEY
216 
217             if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
218                 cs[count++] = '\u21e7';
219             } // Unicode: UPWARDS WHITE ARROW
220 
221             if ((modifiers & InputEvent.META_MASK) != 0) {
222                 cs[count++] = '\u2318';
223             } // Unicode: PLACE OF INTEREST SIGN
224 
225         } else {
226             if ((modifiers & InputEvent.META_MASK) != 0) {
227                 cs[count++] = '\u2318';
228             } // Unicode: PLACE OF INTEREST SIGN
229 
230             if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
231                 cs[count++] = '\u21e7';
232             } // Unicode: UPWARDS WHITE ARROW
233 
234             if ((modifiers & (InputEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0) {
235                 cs[count++] = '\u2325';
236             } // Unicode: OPTION KEY
237 
238             if ((modifiers & InputEvent.CTRL_MASK) != 0) {
239                 cs[count++] = '\u2303';
240             } // Unicode: UP ARROWHEAD
241 
242         }
243         return new String(cs, 0, count);
244     }
245 
repaintBorder(JComponent component)246     public static void repaintBorder(JComponent component) {
247         JComponent c = component;
248         Border border = null;
249         Container container = component.getParent();
250         if (container instanceof JViewport) {
251             c = (JComponent) container.getParent();
252             if (c != null) {
253                 border = c.getBorder();
254             }
255         }
256         if (border == null) {
257             border = component.getBorder();
258             c = component;
259         }
260         if (border != null && c != null) {
261             int w = c.getWidth();
262             int h = c.getHeight();
263             Insets insets = c.getInsets();
264             c.repaint(0, 0, w, insets.top);
265             c.repaint(0, insets.top, insets.left, h - insets.bottom - insets.top);
266             c.repaint(0, h - insets.bottom, w, insets.bottom);
267             c.repaint(w - insets.right, insets.top, insets.right, h - insets.bottom - insets.top);
268         }
269     }
270 
beginGraphics(Graphics2D graphics2d)271     public static final Object beginGraphics(Graphics2D graphics2d) {
272         Object object = graphics2d.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
273 
274         /*
275         AffineTransform tx = graphics2d.getTransform();
276         AffineTransform savedTransform = (AffineTransform) tx.clone();
277         tx.scale(0.9,0.9);
278         graphics2d.setTransform(tx);
279          */
280         graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
281                 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
282         /*graphics2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
283         RenderingHints.VALUE_FRACTIONALMETRICS_ON);*/
284 
285         //if (true) return savedTransform;
286         return object;
287     }
288 
endGraphics(Graphics2D graphics2d, Object oldHints)289     public static final void endGraphics(Graphics2D graphics2d, Object oldHints) {
290         /*
291         if (true) {
292         graphics2d.setTransform((AffineTransform) oldHints);
293         return;
294         }*/
295 
296         if (oldHints != null) {
297             graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
298                     oldHints);
299         }
300     }
301 
302     /**
303      * Returns true, if the specified component is focus owner or permanent
304      * focus owner and if the component is on an the active window.
305      */
isFocused(Component component)306     public static final boolean isFocused(Component component) {
307         // When a component is used as a cell renderer, the focus can
308         // not be determined from the component itself.
309         if (component instanceof JComponent) {
310             if (((JComponent) component).getClientProperty("Quaqua.Component.cellRendererFor") != null) {
311                 component = (Component) ((JComponent) component).getClientProperty("Quaqua.Component.cellRendererFor");
312             }
313         }
314 
315         //---
316         try {
317             boolean isFocusOwner = ((Boolean) Methods.invoke(component, "isFocusOwner")).booleanValue();
318 
319             Window ancestor = SwingUtilities.getWindowAncestor(component);
320             Object kfm = Methods.invokeStatic("java.awt.KeyboardFocusManager", "getCurrentKeyboardFocusManager");
321 
322             return isFocusOwner ||
323                     component == Methods.invoke(kfm, "getPermanentFocusOwner") &&
324                     ancestor != null &&
325                     Methods.invokeGetter(ancestor, "isFocused", false);
326         } catch (NoSuchMethodException e) {
327             return component.hasFocus();
328         }
329 
330 
331     }
332 
isHeadless()333     static boolean isHeadless() {
334         return Methods.invokeStaticGetter(GraphicsEnvironment.class, "isHeadless", false);
335     }
336 
getLeftSideBearing(Font f, String string)337     public static int getLeftSideBearing(Font f, String string) {
338         return ((Integer) Methods.invokeStatic(
339                 "com.sun.java.swing.SwingUtilities2", "getLeftSideBearing",
340                 new Class[]{Font.class, String.class}, new Object[]{f, string},
341                 new Integer(0))).intValue();
342     }
343 
344     /**
345      * Invoked when the user attempts an invalid operation,
346      * such as pasting into an uneditable <code>JTextField</code>
347      * that has focus. The default implementation beeps. Subclasses
348      * that wish different behavior should override this and provide
349      * the additional feedback.
350      *
351      * @param component Component the error occured in, may be null
352      *			indicating the error condition is not directly
353      *			associated with a <code>Component</code>.
354      */
provideErrorFeedback(Component component)355     static void provideErrorFeedback(Component component) {
356         Toolkit toolkit = null;
357         if (component != null) {
358             toolkit = component.getToolkit();
359         } else {
360             toolkit = Toolkit.getDefaultToolkit();
361         }
362         toolkit.beep();
363     } // provideErrorFeedback()
364 
365 
createBufferedImage(URL location)366     public static BufferedImage createBufferedImage(URL location) {
367         Image image = Toolkit.getDefaultToolkit().createImage(location);
368         BufferedImage buf;
369         if (image instanceof BufferedImage) {
370             buf = (BufferedImage) image;
371         } else {
372             loadImage(image);
373             //buf = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
374             buf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(image.getWidth(null), image.getHeight(null), Transparency.OPAQUE);
375 
376             Graphics g = buf.getGraphics();
377             g.drawImage(image, 0, 0, null);
378             g.dispose();
379             image.flush();
380         }
381         return buf;
382     }
383 
createTexturePaint(URL location)384     public static TexturePaint createTexturePaint(URL location) {
385         BufferedImage texture = createBufferedImage(location);
386         TexturePaint paint = new TexturePaint(texture, new Rectangle(0, 0, texture.getWidth(), texture.getHeight()));
387         return paint;
388     }
389 
390     /**
391      * Loads the image, returning only when the image is loaded.
392      * @param image the image
393      */
loadImage(Image image)394     private static void loadImage(Image image) {
395         Component component = new Component() {
396         };
397         MediaTracker tracker = new MediaTracker(component);
398         synchronized (tracker) {
399             int id = 0;
400 
401             tracker.addImage(image, id);
402             try {
403                 tracker.waitForID(id, 0);
404             } catch (InterruptedException e) {
405                 if (DEBUG) {
406                     System.out.println("INTERRUPTED while loading Image");
407                 }
408             }
409             int loadStatus = tracker.statusID(id, false);
410             tracker.removeImage(image, id);
411         }
412     }
413 
414     /**
415      * Compute and return the location of the icons origin, the
416      * location of origin of the text baseline, and a possibly clipped
417      * version of the compound labels string.  Locations are computed
418      * relative to the viewR rectangle.
419      * The JComponents orientation (LEADING/TRAILING) will also be taken
420      * into account and translated into LEFT/RIGHT values accordingly.
421      */
layoutCompoundLabel(JComponent c, FontMetrics fm, String text, Icon icon, int verticalAlignment, int horizontalAlignment, int verticalTextPosition, int horizontalTextPosition, Rectangle viewR, Rectangle iconR, Rectangle textR, int textIconGap)422     public static String layoutCompoundLabel(JComponent c,
423             FontMetrics fm,
424             String text,
425             Icon icon,
426             int verticalAlignment,
427             int horizontalAlignment,
428             int verticalTextPosition,
429             int horizontalTextPosition,
430             Rectangle viewR,
431             Rectangle iconR,
432             Rectangle textR,
433             int textIconGap) {
434         boolean orientationIsLeftToRight = true;
435         int hAlign = horizontalAlignment;
436         int hTextPos = horizontalTextPosition;
437 
438         if (c != null) {
439             if (!(c.getComponentOrientation().isLeftToRight())) {
440                 orientationIsLeftToRight = false;
441             }
442         }
443 
444         // Translate LEADING/TRAILING values in horizontalAlignment
445         // to LEFT/RIGHT values depending on the components orientation
446         switch (horizontalAlignment) {
447             case LEADING:
448                 hAlign = (orientationIsLeftToRight) ? LEFT : RIGHT;
449                 break;
450             case TRAILING:
451                 hAlign = (orientationIsLeftToRight) ? RIGHT : LEFT;
452                 break;
453         }
454 
455         // Translate LEADING/TRAILING values in horizontalTextPosition
456         // to LEFT/RIGHT values depending on the components orientation
457         switch (horizontalTextPosition) {
458             case LEADING:
459                 hTextPos = (orientationIsLeftToRight) ? LEFT : RIGHT;
460                 break;
461             case TRAILING:
462                 hTextPos = (orientationIsLeftToRight) ? RIGHT : LEFT;
463                 break;
464         }
465 
466         return layoutCompoundLabelImpl(c,
467                 fm,
468                 text,
469                 icon,
470                 verticalAlignment,
471                 hAlign,
472                 verticalTextPosition,
473                 hTextPos,
474                 viewR,
475                 iconR,
476                 textR,
477                 textIconGap);
478     }
479 
480     /**
481      * Compute and return the location of the icons origin, the
482      * location of origin of the text baseline, and a possibly clipped
483      * version of the compound labels string.  Locations are computed
484      * relative to the viewR rectangle.
485      * This layoutCompoundLabel() does not know how to handle LEADING/TRAILING
486      * values in horizontalTextPosition (they will default to RIGHT) and in
487      * horizontalAlignment (they will default to CENTER).
488      * Use the other version of layoutCompoundLabel() instead.
489      */
layoutCompoundLabel( FontMetrics fm, String text, Icon icon, int verticalAlignment, int horizontalAlignment, int verticalTextPosition, int horizontalTextPosition, Rectangle viewR, Rectangle iconR, Rectangle textR, int textIconGap)490     public static String layoutCompoundLabel(
491             FontMetrics fm,
492             String text,
493             Icon icon,
494             int verticalAlignment,
495             int horizontalAlignment,
496             int verticalTextPosition,
497             int horizontalTextPosition,
498             Rectangle viewR,
499             Rectangle iconR,
500             Rectangle textR,
501             int textIconGap) {
502         return layoutCompoundLabelImpl(null, fm, text, icon,
503                 verticalAlignment,
504                 horizontalAlignment,
505                 verticalTextPosition,
506                 horizontalTextPosition,
507                 viewR, iconR, textR, textIconGap);
508     }
509 
510     /**
511      * Compute and return the location of the icons origin, the
512      * location of origin of the text baseline, and a possibly clipped
513      * version of the compound labels string.  Locations are computed
514      * relative to the viewR rectangle.
515      * This layoutCompoundLabel() does not know how to handle LEADING/TRAILING
516      * values in horizontalTextPosition (they will default to RIGHT) and in
517      * horizontalAlignment (they will default to CENTER).
518      * Use the other version of layoutCompoundLabel() instead.
519      *
520      * This is the same as SwingUtilities.layoutCompoundLabelImpl, except for
521      * the algorithm for clipping the text. If a text is too long, "..." are
522      * inserted at the middle of the text instead of at the end.
523      */
layoutCompoundLabelImpl( JComponent c, FontMetrics fm, String text, Icon icon, int verticalAlignment, int horizontalAlignment, int verticalTextPosition, int horizontalTextPosition, Rectangle viewR, Rectangle iconR, Rectangle textR, int textIconGap)524     private static String layoutCompoundLabelImpl(
525             JComponent c,
526             FontMetrics fm,
527             String text,
528             Icon icon,
529             int verticalAlignment,
530             int horizontalAlignment,
531             int verticalTextPosition,
532             int horizontalTextPosition,
533             Rectangle viewR,
534             Rectangle iconR,
535             Rectangle textR,
536             int textIconGap) {
537         /* Initialize the icon bounds rectangle iconR.
538          */
539 
540         if (icon != null) {
541             iconR.width = icon.getIconWidth();
542             iconR.height = icon.getIconHeight();
543         } else {
544             iconR.width = iconR.height = 0;
545         }
546 
547         /* Initialize the text bounds rectangle textR.  If a null
548          * or and empty String was specified we substitute "" here
549          * and use 0,0,0,0 for textR.
550          */
551 
552         boolean textIsEmpty = (text == null) || text.equals("");
553         int lsb = 0;
554 
555         View v = null;
556         if (textIsEmpty) {
557             textR.width = textR.height = 0;
558             text = "";
559         } else {
560             v = (c != null) ? (View) c.getClientProperty("html") : null;
561             if (v != null) {
562                 textR.width = (int) v.getPreferredSpan(View.X_AXIS);
563                 textR.height = (int) v.getPreferredSpan(View.Y_AXIS);
564             } else {
565                 textR.width = SwingUtilities.computeStringWidth(fm, text);
566 
567                 lsb = getLeftSideBearing(fm.getFont(), text);
568                 if (lsb < 0) {
569                     // If lsb is negative, add it to the width, the
570                     // text bounds will later be adjusted accordingly.
571                     textR.width -= lsb;
572                 }
573                 textR.height = fm.getHeight();
574             }
575         }
576 
577         /* Unless both text and icon are non-null, we effectively ignore
578          * the value of textIconGap.  The code that follows uses the
579          * value of gap instead of textIconGap.
580          */
581 
582         int gap = (textIsEmpty || (icon == null)) ? 0 : textIconGap;
583 
584         if (!textIsEmpty) {
585 
586             /* If the label text string is too wide to fit within the available
587              * space "..." and as many characters as will fit will be
588              * displayed instead.
589              */
590 
591             int availTextWidth;
592 
593             if (horizontalTextPosition == CENTER) {
594                 availTextWidth = viewR.width;
595             } else {
596                 availTextWidth = viewR.width - (iconR.width + gap);
597             }
598 
599 
600             if (textR.width > availTextWidth) {
601                 if (v != null) {
602                     textR.width = availTextWidth;
603                 } else {
604                     String clipString = "...";
605                     int totalWidth = SwingUtilities.computeStringWidth(fm, clipString);
606                     int nChars;
607                     int len = text.length();
608                     for (nChars = 0; nChars < len; nChars++) {
609                         int charIndex = (nChars % 2 == 0) ? nChars / 2 : len - 1 - nChars / 2;
610                         totalWidth += fm.charWidth(text.charAt(charIndex));
611                         if (totalWidth > availTextWidth) {
612                             break;
613                         }
614                     }
615                     text = text.substring(0, nChars / 2) + clipString + text.substring(len - nChars / 2);
616                     textR.width = SwingUtilities.computeStringWidth(fm, text);
617                 }
618             }
619         }
620 
621 
622         /* Compute textR.x,y given the verticalTextPosition and
623          * horizontalTextPosition properties
624          */
625 
626         if (verticalTextPosition == TOP) {
627             if (horizontalTextPosition != CENTER) {
628                 textR.y = 0;
629             } else {
630                 textR.y = -(textR.height + gap);
631             }
632         } else if (verticalTextPosition == CENTER) {
633             textR.y = (iconR.height / 2) - (textR.height / 2);
634         } else { // (verticalTextPosition == BOTTOM)
635 
636             if (horizontalTextPosition != CENTER) {
637                 textR.y = iconR.height - textR.height;
638             } else {
639                 textR.y = (iconR.height + gap);
640             }
641         }
642 
643         if (horizontalTextPosition == LEFT) {
644             textR.x = -(textR.width + gap);
645         } else if (horizontalTextPosition == CENTER) {
646             textR.x = (iconR.width / 2) - (textR.width / 2);
647         } else { // (horizontalTextPosition == RIGHT)
648 
649             textR.x = (iconR.width + gap);
650         }
651 
652         /* labelR is the rectangle that contains iconR and textR.
653          * Move it to its proper position given the labelAlignment
654          * properties.
655          *
656          * To avoid actually allocating a Rectangle, Rectangle.union
657          * has been inlined below.
658          */
659         int labelR_x = Math.min(iconR.x, textR.x);
660         int labelR_width = Math.max(iconR.x + iconR.width,
661                 textR.x + textR.width) - labelR_x;
662         int labelR_y = Math.min(iconR.y, textR.y);
663         int labelR_height = Math.max(iconR.y + iconR.height,
664                 textR.y + textR.height) - labelR_y;
665 
666         int dx, dy;
667 
668         if (verticalAlignment == TOP) {
669             dy = viewR.y - labelR_y;
670         } else if (verticalAlignment == CENTER) {
671             dy = (viewR.y + (viewR.height / 2)) - (labelR_y + (labelR_height / 2));
672         } else { // (verticalAlignment == BOTTOM)
673 
674             dy = (viewR.y + viewR.height) - (labelR_y + labelR_height);
675         }
676 
677         if (horizontalAlignment == LEFT) {
678             dx = viewR.x - labelR_x;
679         } else if (horizontalAlignment == RIGHT) {
680             dx = (viewR.x + viewR.width) - (labelR_x + labelR_width);
681         } else { // (horizontalAlignment == CENTER)
682 
683             dx = (viewR.x + (viewR.width / 2)) -
684                     (labelR_x + (labelR_width / 2));
685         }
686 
687         /* Translate textR and glypyR by dx,dy.
688          */
689 
690         textR.x += dx;
691         textR.y += dy;
692 
693         iconR.x += dx;
694         iconR.y += dy;
695 
696         if (lsb < 0) {
697             // lsb is negative. We previously adjusted the bounds by lsb,
698             // we now need to shift the x location so that the text is
699             // drawn at the right location. The result is textR does not
700             // line up with the actual bounds (on the left side), but we will
701             // have provided enough space for the text.
702             textR.width += lsb;
703             textR.x -= lsb;
704         }
705 
706         return text;
707     }
708 
configureGraphics(Graphics gr)709     public static void configureGraphics(Graphics gr) {
710         Graphics2D g = (Graphics2D) gr;
711         g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
712         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
713     /*g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);*/
714     }
715 
716     /**
717      * Uses some unsupported (dangerous) API calls on the native peers to make
718      * a window translucent. If the API is not found, this method leaves the
719      * window opaque.
720      *
721      * @param w The Window.
722      * @param value The alpha channel for the window.
723      */
setWindowAlpha(Window w, int value)724     static final void setWindowAlpha(Window w, int value) {
725         if (w == null) {
726             return;
727         }
728 
729         // Java 1.4.2_05 does not support window alpha.
730         // Setting window alpha only sets the background color of the window
731         // to white.
732 
733         if (w.getPeer() == null) {
734             w.pack();
735         }
736         java.awt.peer.ComponentPeer peer = w.getPeer();
737         try {
738             // Alpha API for Apple's Java 1.4 + 1.5 on Mac OS X 10.4 Tiger.
739             Methods.invoke(peer, "setAlpha", (float) (value / 255f));
740         } catch (Throwable e) {
741             // Alpha API for Apple's Java 1.3.
742             if (QuaquaManager.getProperty("java.version").startsWith("1.3")) {
743                 try {
744                     Methods.invoke(peer, "_setAlpha", value);
745                 } catch (Throwable e2) {
746                     // Platform neutral API
747                     w.setBackground(new Color(255, 255, 255, value));
748                     if (w instanceof RootPaneContainer) {
749                         ((RootPaneContainer) w).getContentPane().setBackground(new Color(255, 255, 255, 0));
750                     }
751                 }
752             }
753         }
754     }
755 
756     /** Copied from BasicLookAndFeel.
757      */
compositeRequestFocus(Component component)758     public static Component compositeRequestFocus(Component component) {
759         try {
760             if (component instanceof Container) {
761                 Container container = (Container) component;
762                 if (Methods.invokeGetter(container, "isFocusCycleRoot", false)) {
763 
764                     Object policy = Methods.invokeGetter(container, "getFocusTraversalPolicy", null);
765                     Component comp = (Component) Methods.invoke(policy, "getDefaultComponent", Container.class, container);
766                     if (comp != null) {
767                         comp.requestFocus();
768                         return comp;
769                     }
770                 }
771                 Container rootAncestor = (Container) Methods.invokeGetter(container, "getFocusCycleRootAncestor", null);
772                 if (rootAncestor != null) {
773                     Object policy = Methods.invokeGetter(rootAncestor, "getFocusTraversalPolicy", null);
774                     Component comp = (Component) Methods.invoke(policy, "getComponentAfter",
775                             new Class[]{Container.class, Component.class},
776                             new Object[]{rootAncestor, container});
777 
778                     if (comp != null && SwingUtilities.isDescendingFrom(comp, container)) {
779                         comp.requestFocus();
780                         return comp;
781                     }
782                 }
783             }
784         } catch (NoSuchMethodException e) {
785             // ignore
786         }
787         if (Methods.invokeGetter(component, "isFocusable", true)) {
788             component.requestFocus();
789             return component;
790         }
791         return null;
792     }
793 
794     /**
795      * Convenience method for installing a property with the specified name
796      * and value on a component if that property has not already been set
797      * by the client program.  This method is intended to be used by
798      * UI delegate instances that need to specify a default value for a
799      * property of primitive type (boolean, int, ..), but do not wish
800      * to override a value set by the client.  Since primitive property
801      * values cannot be wrapped with the UIResource marker, this method
802      * uses private state to determine whether the property has been set
803      * by the client.
804      * @throws IllegalArgumentException if the specified property is not
805      *         one which can be set using this method
806      * @throws ClassCastException may be thrown if the property value
807      *         specified does not match the property's type
808      * @throws NullPointerException may be thrown if c or propertyValue is null
809      * @param c the target component for installing the property
810      * @param propertyName String containing the name of the property to be set
811      */
installProperty(JComponent c, String propertyName, Object value)812     public static void installProperty(JComponent c,
813             String propertyName, Object value) {
814         if (canInstallProperty) {
815             //LookAndFeel.installProperty(c, propertyName, value);
816             try {
817                 Methods.invokeStatic(LookAndFeel.class, "setUIProperty",
818                         new Class[]{JComponent.class, String.class, Object.class},
819                         new Object[]{c, propertyName, value});
820                 return;
821             } catch (NoSuchMethodException e) {
822                 // System.err.println("Warning: QuaquaUtilities failed to installProperty "+propertyName);
823                 canInstallProperty = false;
824             }
825         }
826         if (propertyName == "opaque") {
827             c.setOpaque(((Boolean) value).booleanValue());
828         } else if (propertyName == "autoscrolls") {
829             c.setAutoscrolls(((Boolean) value).booleanValue());
830         /*
831         } else if (propertyName == "focusTraversalKeysForward") {
832         c.setFocusTraversalKeys(KeyboardFocusManager.
833         FORWARD_TRAVERSAL_KEYS,
834         (java.util.Set)value);*/
835         /*
836         } else if (propertyName == "focusTraversalKeysBackward") {
837         c.setFocusTraversalKeys(KeyboardFocusManager.
838         BACKWARD_TRAVERSAL_KEYS,
839         (java.util.Set)value);
840          */
841         } else {
842             throw new IllegalArgumentException("property \"" +
843                     propertyName + "\" cannot be set using this method");
844         }
845     }
846 
847     /**
848      * Returns the ui that is of type <code>klass</code>, or null if
849      * one can not be found.
850      */
getUIOfType(ComponentUI ui, Class klass)851     public static Object getUIOfType(ComponentUI ui, Class klass) {
852         if (klass.isInstance(ui)) {
853             return ui;
854         }
855         return null;
856     }
857 
adjustFocus(JComponent tree)858     public static void adjustFocus(JComponent tree) {
859         //SwingUtilities2.adjustFocus(tree);
860         try {
861             Methods.invokeStatic("com.sun.java.swing.SwingUtilities2", "adjustFocus", JComponent.class, tree);
862         } catch (NoSuchMethodException ex) {
863             tree.requestFocusInWindow();
864         }
865     }
866 
shouldIgnore(MouseEvent e, JComponent tree)867     static boolean shouldIgnore(MouseEvent e, JComponent tree) {
868         //return QuaquaUtilities2.shouldIgnore(e, tree);
869         try {
870             return ((Boolean) Methods.invokeStatic("com.sun.java.swing.SwingUtilities2", "shouldIgnore",
871                     new Class[]{MouseEvent.class, JComponent.class},
872                     new Object[]{e, tree})).booleanValue();
873         } catch (NoSuchMethodException ex) {
874             return false;
875         }
876     }
877 
878     /**
879      * Returns true, if the component should use the small appearance.
880      * @param c
881      * @return true, , if the component should use the small appearance.
882      */
isSmallSizeVariant(JComponent c)883     public static boolean isSmallSizeVariant(JComponent c) {
884         Font f = c.getFont();
885         if (f != null && f.getSize() <= 11) {
886             return true;
887         }
888         String p = (String) c.getClientProperty("JComponent.sizeVariant");
889         return p != null && p.equals("small");
890     }
891 
applySizeVariant(JComponent c)892     public static void applySizeVariant(JComponent c) {
893         String p = (String) c.getClientProperty("JComponent.sizeVariant");
894         if (p == null) {
895         } else if (p.equals("regular")) {
896             c.setFont(UIManager.getFont("SystemFont"));
897         } else if (p.equals("small")) {
898             c.setFont(UIManager.getFont("SmallSystemFont"));
899         } else if (p.equals("mini")) {
900             c.setFont(UIManager.getFont("MiniSystemFont"));
901         }
902     }
903 }
904