2
* @(#)QuaquaUtilities.java 3.1 2006-09-04
4
* Copyright (c) 2003-2006 Werner Randelshofer
5
* Staldenmattweg 2, Immensee, CH-6405, Switzerland.
8
* This software is the confidential and proprietary information of
9
* Werner Randelshofer. ("Confidential Information"). You shall not
10
* disclose such Confidential Information and shall use it only in
11
* accordance with the terms of the license agreement you entered into
12
* with Werner Randelshofer.
15
package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua;
19
import java.awt.event.*;
20
import java.awt.font.*;
21
import java.awt.image.*;
22
import java.awt.peer.*;
25
import javax.swing.text.*;
26
import javax.swing.border.*;
27
import javax.swing.plaf.*;
28
import javax.swing.plaf.basic.*;
30
import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
33
* Utility class for the Quaqua LAF.
35
* @author Werner Randelshofer, Staldenmattweg 2, CH-6405 Immensee, Switzerland
36
* @version 3.1 2006-09-04 Added method compositeRequestFocus.
37
* <br>3.0.5 2006-08-20 Method endGraphics must not set
38
* KEY_TEXT_ANTIALIASING to null.
39
* <br>3.0.4 2006-02-19 Catch Throwable in method setWindowAlpha instead
40
* of catching NoSuchMethodException.
41
* <br>3.0.3 2006-01-08 Don't set Window alpha, when running on
42
* Java 1.4.2_05 on Mac OS X 10.3.5. Because this only has the effect of turning
43
* the background color of the Window to white.
44
* <br>3.0.2 2005-12-10 Method isOnActiveWindow() did not reliably
46
* <br>3.0.1 2005-11-12 Fixed NPE in method repaint border.
47
* <br>3.0 2005-09-24 Removed all reflection helper methods. Moved Sheet
48
* helper methods out into class Sheets.
49
* <br>2.6 2005-09-17 Method isOnFocusedWindow returns true, if
50
* the window returns false on "getFocusableWindowState".
51
* <br>2.5 2005-03-13 Renamed method isFrameActive to isOnActiveFrame.
52
* <br>2.4 2004-12-28 Method createBufferdImage added. Method
53
* isOnActiveWindow() renamed to isFrameActive().
54
* <br>2.3 2004-12-14 Method getUI added.
55
* <br>2.2.1 2004-12-01 Methods setDragEnabled and getDragEnabled never
56
* worked because the attempted to get method objects on the wrong class.
57
* <br>2.2 2004-09-19 Refined algorithm of method isFrameActive.
58
* <br>2.1 2004-07-04 Methods repaintBorder, beginFont, endFont and
60
* <br>2.0 2004-04-27 Renamed from QuaquaGraphicUtils to QuaquaUtilities.
61
* Added method isFrameActive(Component).
62
* <br>1.1.1 2003-10-08 Diagnostic output to System.out removed.
63
* <br>1.1 2003-10-05 Methods getModifiersText and getModifiersUnicode
65
* <br>1.0 2003-07-19 Created.
67
public class QuaquaUtilities extends BasicGraphicsUtils implements SwingConstants {
68
/** Prevent instance creation. */
69
private QuaquaUtilities() {
73
* Convenience function for determining ComponentOrientation. Helps us
74
* avoid having Munge directives throughout the code.
76
public static boolean isLeftToRight( Component c ) {
77
return c.getComponentOrientation().isLeftToRight();
81
* Draw a string with the graphics <code>g</code> at location
82
* (<code>x</code>, <code>y</code>)
83
* just like <code>g.drawString</code> would.
84
* The character at index <code>underlinedIndex</code>
85
* in text will be underlined. If <code>index</code> is beyond the
86
* bounds of <code>text</code> (including < 0), nothing will be
89
* @param g Graphics to draw with
90
* @param text String to draw
91
* @param underlinedIndex Index of character in text to underline
92
* @param x x coordinate to draw at
93
* @param y y coordinate to draw at
96
public static void drawStringUnderlineCharAt(Graphics g, String text,
97
int underlinedIndex, int x,int y) {
98
g.drawString(text,x,y);
99
if (underlinedIndex >= 0 && underlinedIndex < text.length() ) {
100
FontMetrics fm = g.getFontMetrics();
101
int underlineRectX = x + fm.stringWidth(text.substring(0,underlinedIndex));
102
int underlineRectY = y;
103
int underlineRectWidth = fm.charWidth(text.charAt(underlinedIndex));
104
int underlineRectHeight = 1;
105
g.fillRect(underlineRectX, underlineRectY + fm.getDescent() - 1,
106
underlineRectWidth, underlineRectHeight);
110
* Returns index of the first occurrence of <code>mnemonic</code>
111
* within string <code>text</code>. Matching algorithm is not
114
* @param text The text to search through, may be null
115
* @param mnemonic The mnemonic to find the character for.
116
* @return index into the string if exists, otherwise -1
118
static int findDisplayedMnemonicIndex(String text, int mnemonic) {
119
if (text == null || mnemonic == '\0') {
123
char uc = Character.toUpperCase((char)mnemonic);
124
char lc = Character.toLowerCase((char)mnemonic);
126
int uci = text.indexOf(uc);
127
int lci = text.indexOf(lc);
131
} else if(lci == -1) {
134
return (lci < uci) ? lci : uci;
139
* Returns true if the component is on a Dialog or a Frame, which is active,
140
* or if it is on a Window, which is focused.
141
* Always returns true, if the component has no parent window.
143
public static boolean isOnActiveWindow(Component c) {
144
// In the RootPaneUI, we set a client property on the whole component
145
// tree, if the ancestor Frame gets activated or deactivated.
146
if (c instanceof JComponent) {
147
Boolean value = (Boolean) ((JComponent) c).getClientProperty("Frame.active");
148
// Unfortunately, the value is not always reliable.
149
// Therefore we can only do a short circuit, if the value is true.
150
if (value != null && value) {
152
//return value.booleanValue();
156
// This is how I would have implemented the code, if Quaqua would
157
// not be required to work an a Java 1.3 VM.
158
Window window = SwingUtilities.getWindowAncestor(c);
159
if (window == null) {
161
} else if ((window instanceof Frame) || (window instanceof Dialog)) {
162
return window.isActive();
164
if (window.getFocusableWindowState()) {
165
return window.isFocused();
172
// If we missed the client property or if it was false, we have to
173
// figure out the activation state on our own.
174
// The following code works from Java 1.3 onwards.
175
Window window = SwingUtilities.getWindowAncestor(c);
176
boolean isOnActiveWindow;
177
if (window == null) {
178
isOnActiveWindow = true;
179
} else if ((window instanceof Frame) || (window instanceof Dialog)) {
180
isOnActiveWindow = Methods.invokeGetter(window, "isActive", true);
182
if (Methods.invokeGetter(window, "getFocusableWindowState", true)) {
183
isOnActiveWindow = Methods.invokeGetter(window, "isFocused", true);
185
isOnActiveWindow = true;
189
// In case the activation property is true, we fix the value of the
190
// client property, so that we can do a short circuit next time.
191
if (isOnActiveWindow && (c instanceof JComponent)) {
192
((JComponent) c).putClientProperty("Frame.active", isOnActiveWindow);
194
return isOnActiveWindow;
198
* Returns a Mac OS X specific String describing the modifier key(s),
199
* such as "Shift", or "Ctrl+Shift".
201
* @return string a text description of the combination of modifier
202
* keys that were held down during the event
204
public static String getKeyModifiersText(int modifiers, boolean leftToRight) {
205
return getKeyModifiersUnicode(modifiers, leftToRight);
207
static String getKeyModifiersUnicode(int modifiers, boolean leftToRight) {
208
char[] cs = new char[4];
211
if ((modifiers & InputEvent.CTRL_MASK) != 0)
212
cs[count++] = '\u2303'; // Unicode: UP ARROWHEAD
213
if ((modifiers & (InputEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0)
214
cs[count++] = '\u2325'; // Unicode: OPTION KEY
215
if ((modifiers & InputEvent.SHIFT_MASK) != 0)
216
cs[count++] = '\u21e7'; // Unicode: UPWARDS WHITE ARROW
217
if ((modifiers & InputEvent.META_MASK) != 0)
218
cs[count++] = '\u2318'; // Unicode: PLACE OF INTEREST SIGN
220
if ((modifiers & InputEvent.META_MASK) != 0)
221
cs[count++] = '\u2318'; // Unicode: PLACE OF INTEREST SIGN
222
if ((modifiers & InputEvent.SHIFT_MASK) != 0)
223
cs[count++] = '\u21e7'; // Unicode: UPWARDS WHITE ARROW
224
if ((modifiers & (InputEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0)
225
cs[count++] = '\u2325'; // Unicode: OPTION KEY
226
if ((modifiers & InputEvent.CTRL_MASK) != 0)
227
cs[count++] = '\u2303'; // Unicode: UP ARROWHEAD
229
return new String(cs, 0, count);
232
public static void repaintBorder(JComponent component) {
233
JComponent c = component;
234
Border border = null;
235
Container container = component.getParent();
236
if (container instanceof JViewport) {
237
c = (JComponent) container.getParent();
239
border = c.getBorder();
242
if (border == null) {
243
border = component.getBorder();
246
if (border != null && c != null) {
247
int w = c.getWidth();
248
int h = c.getHeight();
249
Insets insets = c.getInsets();
250
c.repaint(0, 0, w, insets.top);
251
c.repaint(0, 0, insets.left, h);
252
c.repaint(0, h - insets.bottom, w, insets.bottom);
253
c.repaint(w - insets.right, 0, insets.right, h);
256
public static final Object beginGraphics(Graphics2D graphics2d) {
257
Object object = graphics2d.getRenderingHint(RenderingHints
258
.KEY_TEXT_ANTIALIASING);
259
graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
260
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
264
public static final void endGraphics(Graphics2D graphics2d, Object oldHints) {
265
if (oldHints != null) {
266
graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
270
public static final boolean isFocused(Component component) {
271
if (QuaquaUtilities.isOnActiveWindow(component)) {
272
Component c = component;
273
if (c instanceof JComponent) {
274
if (c instanceof JScrollPane) {
275
JViewport viewport = ((JScrollPane) component).getViewport();
276
if (viewport != null) {
277
c = viewport.getView();
280
if (c instanceof JTextComponent
281
&& !((JTextComponent) c).isEditable()) {
285
&& (((JComponent) c).hasFocus() || ((JComponent) c).getClientProperty("Quaqua.drawFocusBorder") == Boolean.TRUE);
291
static boolean isHeadless() {
292
return Methods.invokeStaticGetter(GraphicsEnvironment.class, "isHeadless", false);
295
public static int getLeftSideBearing(Font f, String string) {
296
return (Integer) Methods.invokeStatic(
297
"com.sun.java.swing.SwingUtilities2", "getLeftSideBearing",
298
new Class[]{Font.class, String.class}, new Object[]{f, string},
303
* Invoked when the user attempts an invalid operation,
304
* such as pasting into an uneditable <code>JTextField</code>
305
* that has focus. The default implementation beeps. Subclasses
306
* that wish different behavior should override this and provide
307
* the additional feedback.
309
* @param component Component the error occured in, may be null
310
* indicating the error condition is not directly
311
* associated with a <code>Component</code>.
313
static void provideErrorFeedback(Component component) {
314
Toolkit toolkit = null;
315
if (component != null) {
316
toolkit = component.getToolkit();
318
toolkit = Toolkit.getDefaultToolkit();
321
} // provideErrorFeedback()
323
public static BufferedImage createBufferedImage(URL location) {
324
Image image = Toolkit.getDefaultToolkit().createImage(location);
326
if (image instanceof BufferedImage) {
327
buf = (BufferedImage) image;
330
//buf = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
331
buf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(image.getWidth(null), image.getHeight(null), Transparency.OPAQUE);
333
Graphics g = buf.getGraphics();
334
g.drawImage(image, 0, 0, null);
340
public static TexturePaint createTexturePaint(URL location) {
341
BufferedImage texture = createBufferedImage(location);
342
TexturePaint paint = new TexturePaint(texture, new Rectangle(0, 0, texture.getWidth(), texture.getHeight()));
348
* Loads the image, returning only when the image is loaded.
349
* @param image the image
351
private static void loadImage(Image image) {
352
Component component = new Component() {};
353
MediaTracker tracker = new MediaTracker(component);
354
synchronized (tracker) {
357
tracker.addImage(image, id);
359
tracker.waitForID(id, 0);
360
} catch (InterruptedException e) {
361
System.out.println("INTERRUPTED while loading Image");
363
int loadStatus = tracker.statusID(id, false);
364
tracker.removeImage(image, id);
369
* Compute and return the location of the icons origin, the
370
* location of origin of the text baseline, and a possibly clipped
371
* version of the compound labels string. Locations are computed
372
* relative to the viewR rectangle.
373
* The JComponents orientation (LEADING/TRAILING) will also be taken
374
* into account and translated into LEFT/RIGHT values accordingly.
376
public static String layoutCompoundLabel(JComponent c,
380
int verticalAlignment,
381
int horizontalAlignment,
382
int verticalTextPosition,
383
int horizontalTextPosition,
388
boolean orientationIsLeftToRight = true;
389
int hAlign = horizontalAlignment;
390
int hTextPos = horizontalTextPosition;
393
if (!(c.getComponentOrientation().isLeftToRight())) {
394
orientationIsLeftToRight = false;
398
// Translate LEADING/TRAILING values in horizontalAlignment
399
// to LEFT/RIGHT values depending on the components orientation
400
switch (horizontalAlignment) {
402
hAlign = (orientationIsLeftToRight) ? LEFT : RIGHT;
405
hAlign = (orientationIsLeftToRight) ? RIGHT : LEFT;
409
// Translate LEADING/TRAILING values in horizontalTextPosition
410
// to LEFT/RIGHT values depending on the components orientation
411
switch (horizontalTextPosition) {
413
hTextPos = (orientationIsLeftToRight) ? LEFT : RIGHT;
416
hTextPos = (orientationIsLeftToRight) ? RIGHT : LEFT;
420
return layoutCompoundLabelImpl(c,
426
verticalTextPosition,
435
* Compute and return the location of the icons origin, the
436
* location of origin of the text baseline, and a possibly clipped
437
* version of the compound labels string. Locations are computed
438
* relative to the viewR rectangle.
439
* This layoutCompoundLabel() does not know how to handle LEADING/TRAILING
440
* values in horizontalTextPosition (they will default to RIGHT) and in
441
* horizontalAlignment (they will default to CENTER).
442
* Use the other version of layoutCompoundLabel() instead.
444
public static String layoutCompoundLabel(
448
int verticalAlignment,
449
int horizontalAlignment,
450
int verticalTextPosition,
451
int horizontalTextPosition,
456
return layoutCompoundLabelImpl(null, fm, text, icon,
459
verticalTextPosition,
460
horizontalTextPosition,
461
viewR, iconR, textR, textIconGap);
465
* Compute and return the location of the icons origin, the
466
* location of origin of the text baseline, and a possibly clipped
467
* version of the compound labels string. Locations are computed
468
* relative to the viewR rectangle.
469
* This layoutCompoundLabel() does not know how to handle LEADING/TRAILING
470
* values in horizontalTextPosition (they will default to RIGHT) and in
471
* horizontalAlignment (they will default to CENTER).
472
* Use the other version of layoutCompoundLabel() instead.
474
* This is the same as SwingUtilities.layoutCompoundLabelImpl, except for
475
* the algorithm for clipping the text. If a text is too long, "..." are
476
* inserted at the middle of the text instead of at the end.
478
private static String layoutCompoundLabelImpl(
483
int verticalAlignment,
484
int horizontalAlignment,
485
int verticalTextPosition,
486
int horizontalTextPosition,
491
/* Initialize the icon bounds rectangle iconR.
495
iconR.width = icon.getIconWidth();
496
iconR.height = icon.getIconHeight();
498
iconR.width = iconR.height = 0;
501
/* Initialize the text bounds rectangle textR. If a null
502
* or and empty String was specified we substitute "" here
503
* and use 0,0,0,0 for textR.
506
boolean textIsEmpty = (text == null) || text.equals("");
511
textR.width = textR.height = 0;
514
v = (c != null) ? (View) c.getClientProperty("html") : null;
516
textR.width = (int) v.getPreferredSpan(View.X_AXIS);
517
textR.height = (int) v.getPreferredSpan(View.Y_AXIS);
519
textR.width = SwingUtilities.computeStringWidth(fm,text);
521
lsb = getLeftSideBearing(fm.getFont(), text);
523
// If lsb is negative, add it to the width, the
524
// text bounds will later be adjusted accordingly.
527
textR.height = fm.getHeight();
531
/* Unless both text and icon are non-null, we effectively ignore
532
* the value of textIconGap. The code that follows uses the
533
* value of gap instead of textIconGap.
536
int gap = (textIsEmpty || (icon == null)) ? 0 : textIconGap;
540
/* If the label text string is too wide to fit within the available
541
* space "..." and as many characters as will fit will be
547
if (horizontalTextPosition == CENTER) {
548
availTextWidth = viewR.width;
550
availTextWidth = viewR.width - (iconR.width + gap);
554
if (textR.width > availTextWidth) {
556
textR.width = availTextWidth;
558
String clipString = "...";
559
int totalWidth = SwingUtilities.computeStringWidth(fm,clipString);
561
int len = text.length();
562
for(nChars = 0; nChars < len; nChars++) {
563
int charIndex = (nChars % 2 == 0) ? nChars / 2 : len - 1 - nChars / 2;
564
totalWidth += fm.charWidth(text.charAt(charIndex));
565
if (totalWidth > availTextWidth) {
569
text = text.substring(0, nChars / 2) + clipString + text.substring(len - nChars / 2);
570
textR.width = SwingUtilities.computeStringWidth(fm,text);
576
/* Compute textR.x,y given the verticalTextPosition and
577
* horizontalTextPosition properties
580
if (verticalTextPosition == TOP) {
581
if (horizontalTextPosition != CENTER) {
584
textR.y = -(textR.height + gap);
586
} else if (verticalTextPosition == CENTER) {
587
textR.y = (iconR.height / 2) - (textR.height / 2);
588
} else { // (verticalTextPosition == BOTTOM)
589
if (horizontalTextPosition != CENTER) {
590
textR.y = iconR.height - textR.height;
592
textR.y = (iconR.height + gap);
596
if (horizontalTextPosition == LEFT) {
597
textR.x = -(textR.width + gap);
598
} else if (horizontalTextPosition == CENTER) {
599
textR.x = (iconR.width / 2) - (textR.width / 2);
600
} else { // (horizontalTextPosition == RIGHT)
601
textR.x = (iconR.width + gap);
604
/* labelR is the rectangle that contains iconR and textR.
605
* Move it to its proper position given the labelAlignment
608
* To avoid actually allocating a Rectangle, Rectangle.union
609
* has been inlined below.
611
int labelR_x = Math.min(iconR.x, textR.x);
612
int labelR_width = Math.max(iconR.x + iconR.width,
613
textR.x + textR.width) - labelR_x;
614
int labelR_y = Math.min(iconR.y, textR.y);
615
int labelR_height = Math.max(iconR.y + iconR.height,
616
textR.y + textR.height) - labelR_y;
620
if (verticalAlignment == TOP) {
621
dy = viewR.y - labelR_y;
622
} else if (verticalAlignment == CENTER) {
623
dy = (viewR.y + (viewR.height / 2)) - (labelR_y + (labelR_height / 2));
624
} else { // (verticalAlignment == BOTTOM)
625
dy = (viewR.y + viewR.height) - (labelR_y + labelR_height);
628
if (horizontalAlignment == LEFT) {
629
dx = viewR.x - labelR_x;
630
} else if (horizontalAlignment == RIGHT) {
631
dx = (viewR.x + viewR.width) - (labelR_x + labelR_width);
632
} else { // (horizontalAlignment == CENTER)
633
dx = (viewR.x + (viewR.width / 2)) -
634
(labelR_x + (labelR_width / 2));
637
/* Translate textR and glypyR by dx,dy.
647
// lsb is negative. We previously adjusted the bounds by lsb,
648
// we now need to shift the x location so that the text is
649
// drawn at the right location. The result is textR does not
650
// line up with the actual bounds (on the left side), but we will
651
// have provided enough space for the text.
659
public static void configureGraphics(Graphics gr) {
660
Graphics2D g = (Graphics2D) gr;
661
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
662
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
663
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
666
/** Copied from BasicLookAndFeel.
668
public static Component compositeRequestFocus(Component component) {
670
if (component instanceof Container) {
671
Container container = (Container)component;
672
if (Methods.invokeGetter(container,"isFocusCycleRoot", false)) {
674
Object policy = Methods.invokeGetter(container,"getFocusTraversalPolicy", null);
675
Component comp = (Component) Methods.invoke(policy,"getDefaultComponent", Container.class, container);
681
Container rootAncestor = (Container) Methods.invokeGetter(container, "getFocusCycleRootAncestor", null);
682
if (rootAncestor!=null) {
683
Object policy = Methods.invokeGetter(rootAncestor,"getFocusTraversalPolicy", null);
684
Component comp = (Component) Methods.invoke(policy,"getComponentAfter",
685
new Class[] {Container.class, Component.class},
686
new Object[] {rootAncestor, container});
688
if (comp!=null && SwingUtilities.isDescendingFrom(comp, container)) {
694
} catch (NoSuchMethodException e) {
697
if (Methods.invokeGetter(component,"isFocusable", true)) {
698
component.requestFocus();
b'\\ No newline at end of file'