001: /*
002: * @(#)QuaquaUtilities.java 3.1 2006-09-04
003: *
004: * Copyright (c) 2003-2006 Werner Randelshofer
005: * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
006: * All rights reserved.
007: *
008: * This software is the confidential and proprietary information of
009: * Werner Randelshofer. ("Confidential Information"). You shall not
010: * disclose such Confidential Information and shall use it only in
011: * accordance with the terms of the license agreement you entered into
012: * with Werner Randelshofer.
013: */
014:
015: package contrib.ch.randelshofer.quaqua;
016:
017: import java.applet.*;
018: import java.awt.*;
019: import java.awt.event.*;
020: import java.awt.font.*;
021: import java.awt.image.*;
022: import java.awt.peer.*;
023: import java.net.*;
024: import javax.swing.*;
025: import javax.swing.text.*;
026: import javax.swing.border.*;
027: import javax.swing.plaf.*;
028: import javax.swing.plaf.basic.*;
029:
030: import contrib.ch.randelshofer.quaqua.util.*;
031:
032: /**
033: * Utility class for the Quaqua LAF.
034: *
035: * @author Werner Randelshofer, Staldenmattweg 2, CH-6405 Immensee, Switzerland
036: * @version 3.1 2006-09-04 Added method compositeRequestFocus.
037: * <br>3.0.5 2006-08-20 Method endGraphics must not set
038: * KEY_TEXT_ANTIALIASING to null.
039: * <br>3.0.4 2006-02-19 Catch Throwable in method setWindowAlpha instead
040: * of catching NoSuchMethodException.
041: * <br>3.0.3 2006-01-08 Don't set Window alpha, when running on
042: * Java 1.4.2_05 on Mac OS X 10.3.5. Because this only has the effect of turning
043: * the background color of the Window to white.
044: * <br>3.0.2 2005-12-10 Method isOnActiveWindow() did not reliably
045: * return true.
046: * <br>3.0.1 2005-11-12 Fixed NPE in method repaint border.
047: * <br>3.0 2005-09-24 Removed all reflection helper methods. Moved Sheet
048: * helper methods out into class Sheets.
049: * <br>2.6 2005-09-17 Method isOnFocusedWindow returns true, if
050: * the window returns false on "getFocusableWindowState".
051: * <br>2.5 2005-03-13 Renamed method isFrameActive to isOnActiveFrame.
052: * <br>2.4 2004-12-28 Method createBufferdImage added. Method
053: * isOnActiveWindow() renamed to isFrameActive().
054: * <br>2.3 2004-12-14 Method getUI added.
055: * <br>2.2.1 2004-12-01 Methods setDragEnabled and getDragEnabled never
056: * worked because the attempted to get method objects on the wrong class.
057: * <br>2.2 2004-09-19 Refined algorithm of method isFrameActive.
058: * <br>2.1 2004-07-04 Methods repaintBorder, beginFont, endFont and
059: * isFocused added.
060: * <br>2.0 2004-04-27 Renamed from QuaquaGraphicUtils to QuaquaUtilities.
061: * Added method isFrameActive(Component).
062: * <br>1.1.1 2003-10-08 Diagnostic output to System.out removed.
063: * <br>1.1 2003-10-05 Methods getModifiersText and getModifiersUnicode
064: * added.
065: * <br>1.0 2003-07-19 Created.
066: */
067: public class QuaquaUtilities extends BasicGraphicsUtils implements
068: SwingConstants {
069: /** Prevent instance creation. */
070: private QuaquaUtilities() {
071: }
072:
073: /*
074: * Convenience function for determining ComponentOrientation. Helps us
075: * avoid having Munge directives throughout the code.
076: */
077: public static boolean isLeftToRight(Component c) {
078: return c.getComponentOrientation().isLeftToRight();
079: }
080:
081: /**
082: * Draw a string with the graphics <code>g</code> at location
083: * (<code>x</code>, <code>y</code>)
084: * just like <code>g.drawString</code> would.
085: * The character at index <code>underlinedIndex</code>
086: * in text will be underlined. If <code>index</code> is beyond the
087: * bounds of <code>text</code> (including < 0), nothing will be
088: * underlined.
089: *
090: * @param g Graphics to draw with
091: * @param text String to draw
092: * @param underlinedIndex Index of character in text to underline
093: * @param x x coordinate to draw at
094: * @param y y coordinate to draw at
095: * @since 1.4
096: */
097: public static void drawStringUnderlineCharAt(Graphics g,
098: String text, int underlinedIndex, int x, int y) {
099: g.drawString(text, x, y);
100: if (underlinedIndex >= 0 && underlinedIndex < text.length()) {
101: FontMetrics fm = g.getFontMetrics();
102: int underlineRectX = x
103: + fm
104: .stringWidth(text.substring(0,
105: underlinedIndex));
106: int underlineRectY = y;
107: int underlineRectWidth = fm.charWidth(text
108: .charAt(underlinedIndex));
109: int underlineRectHeight = 1;
110: g.fillRect(underlineRectX, underlineRectY + fm.getDescent()
111: - 1, underlineRectWidth, underlineRectHeight);
112: }
113: }
114:
115: /**
116: * Returns index of the first occurrence of <code>mnemonic</code>
117: * within string <code>text</code>. Matching algorithm is not
118: * case-sensitive.
119: *
120: * @param text The text to search through, may be null
121: * @param mnemonic The mnemonic to find the character for.
122: * @return index into the string if exists, otherwise -1
123: */
124: static int findDisplayedMnemonicIndex(String text, int mnemonic) {
125: if (text == null || mnemonic == '\0') {
126: return -1;
127: }
128:
129: char uc = Character.toUpperCase((char) mnemonic);
130: char lc = Character.toLowerCase((char) mnemonic);
131:
132: int uci = text.indexOf(uc);
133: int lci = text.indexOf(lc);
134:
135: if (uci == -1) {
136: return lci;
137: } else if (lci == -1) {
138: return uci;
139: } else {
140: return (lci < uci) ? lci : uci;
141: }
142: }
143:
144: /**
145: * Returns true if the component is on a Dialog or a Frame, which is active,
146: * or if it is on a Window, which is focused.
147: * Always returns true, if the component has no parent window.
148: */
149: public static boolean isOnActiveWindow(Component c) {
150: // In the RootPaneUI, we set a client property on the whole component
151: // tree, if the ancestor Frame gets activated or deactivated.
152: if (c instanceof JComponent) {
153: Boolean value = (Boolean) ((JComponent) c)
154: .getClientProperty("Frame.active");
155: // Unfortunately, the value is not always reliable.
156: // Therefore we can only do a short circuit, if the value is true.
157: if (value != null && value.booleanValue()) {
158: return true;
159: //return value.booleanValue();
160: }
161: }
162: /*
163: // This is how I would have implemented the code, if Quaqua would
164: // not be required to work an a Java 1.3 VM.
165: Window window = SwingUtilities.getWindowAncestor(c);
166: if (window == null) {
167: return true;
168: } else if ((window instanceof Frame) || (window instanceof Dialog)) {
169: return window.isActive();
170: } else {
171: if (window.getFocusableWindowState()) {
172: return window.isFocused();
173: } else {
174: return true;
175: }
176: }
177: */
178:
179: // If we missed the client property or if it was false, we have to
180: // figure out the activation state on our own.
181: // The following code works from Java 1.3 onwards.
182: Window window = SwingUtilities.getWindowAncestor(c);
183: boolean isOnActiveWindow;
184: if (window == null) {
185: isOnActiveWindow = true;
186: } else if ((window instanceof Frame)
187: || (window instanceof Dialog)) {
188: isOnActiveWindow = Methods.invokeGetter(window, "isActive",
189: true);
190: } else {
191: if (Methods.invokeGetter(window, "getFocusableWindowState",
192: true)) {
193: isOnActiveWindow = Methods.invokeGetter(window,
194: "isFocused", true);
195: } else {
196: isOnActiveWindow = true;
197: }
198: }
199:
200: // In case the activation property is true, we fix the value of the
201: // client property, so that we can do a short circuit next time.
202: if (isOnActiveWindow && (c instanceof JComponent)) {
203: ((JComponent) c).putClientProperty("Frame.active",
204: new Boolean(isOnActiveWindow));
205: }
206: return isOnActiveWindow;
207: }
208:
209: /**
210: * Returns a Mac OS X specific String describing the modifier key(s),
211: * such as "Shift", or "Ctrl+Shift".
212: *
213: * @return string a text description of the combination of modifier
214: * keys that were held down during the event
215: */
216: public static String getKeyModifiersText(int modifiers,
217: boolean leftToRight) {
218: return getKeyModifiersUnicode(modifiers, leftToRight);
219: }
220:
221: static String getKeyModifiersUnicode(int modifiers,
222: boolean leftToRight) {
223: char[] cs = new char[4];
224: int count = 0;
225: if (leftToRight) {
226: if ((modifiers & InputEvent.CTRL_MASK) != 0)
227: cs[count++] = '\u2303'; // Unicode: UP ARROWHEAD
228: if ((modifiers & (InputEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0)
229: cs[count++] = '\u2325'; // Unicode: OPTION KEY
230: if ((modifiers & InputEvent.SHIFT_MASK) != 0)
231: cs[count++] = '\u21e7'; // Unicode: UPWARDS WHITE ARROW
232: if ((modifiers & InputEvent.META_MASK) != 0)
233: cs[count++] = '\u2318'; // Unicode: PLACE OF INTEREST SIGN
234: } else {
235: if ((modifiers & InputEvent.META_MASK) != 0)
236: cs[count++] = '\u2318'; // Unicode: PLACE OF INTEREST SIGN
237: if ((modifiers & InputEvent.SHIFT_MASK) != 0)
238: cs[count++] = '\u21e7'; // Unicode: UPWARDS WHITE ARROW
239: if ((modifiers & (InputEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0)
240: cs[count++] = '\u2325'; // Unicode: OPTION KEY
241: if ((modifiers & InputEvent.CTRL_MASK) != 0)
242: cs[count++] = '\u2303'; // Unicode: UP ARROWHEAD
243: }
244: return new String(cs, 0, count);
245: }
246:
247: public static void repaintBorder(JComponent component) {
248: JComponent c = component;
249: Border border = null;
250: Container container = component.getParent();
251: if (container instanceof JViewport) {
252: c = (JComponent) container.getParent();
253: if (c != null) {
254: border = c.getBorder();
255: }
256: }
257: if (border == null) {
258: border = component.getBorder();
259: c = component;
260: }
261: if (border != null && c != null) {
262: int w = c.getWidth();
263: int h = c.getHeight();
264: Insets insets = c.getInsets();
265: c.repaint(0, 0, w, insets.top);
266: c.repaint(0, 0, insets.left, h);
267: c.repaint(0, h - insets.bottom, w, insets.bottom);
268: c.repaint(w - insets.right, 0, insets.right, h);
269: }
270: }
271:
272: public static final Object beginGraphics(Graphics2D graphics2d) {
273: Object object = graphics2d
274: .getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
275: graphics2d.setRenderingHint(
276: RenderingHints.KEY_TEXT_ANTIALIASING,
277: RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
278: return object;
279: }
280:
281: public static final void endGraphics(Graphics2D graphics2d,
282: Object oldHints) {
283: if (oldHints != null) {
284: graphics2d.setRenderingHint(
285: RenderingHints.KEY_TEXT_ANTIALIASING, oldHints);
286: }
287: }
288:
289: public static final boolean isFocused(Component component) {
290: if (QuaquaUtilities.isOnActiveWindow(component)) {
291: Component c = component;
292: if (c instanceof JComponent) {
293: if (c instanceof JScrollPane) {
294: JViewport viewport = ((JScrollPane) component)
295: .getViewport();
296: if (viewport != null) {
297: c = viewport.getView();
298: }
299: }
300: if (c instanceof JTextComponent
301: && !((JTextComponent) c).isEditable()) {
302: return false;
303: }
304: return c != null
305: && (((JComponent) c).hasFocus() || ((JComponent) c)
306: .getClientProperty("Quaqua.drawFocusBorder") == Boolean.TRUE);
307: }
308: }
309: return false;
310: }
311:
312: static boolean isHeadless() {
313: return Methods.invokeStaticGetter(GraphicsEnvironment.class,
314: "isHeadless", false);
315: }
316:
317: public static int getLeftSideBearing(Font f, String string) {
318: return ((Integer) Methods.invokeStatic(
319: "com.sun.java.swing.SwingUtilities2",
320: "getLeftSideBearing", new Class[] { Font.class,
321: String.class }, new Object[] { f, string },
322: new Integer(0))).intValue();
323: }
324:
325: /**
326: * Invoked when the user attempts an invalid operation,
327: * such as pasting into an uneditable <code>JTextField</code>
328: * that has focus. The default implementation beeps. Subclasses
329: * that wish different behavior should override this and provide
330: * the additional feedback.
331: *
332: * @param component Component the error occured in, may be null
333: * indicating the error condition is not directly
334: * associated with a <code>Component</code>.
335: */
336: static void provideErrorFeedback(Component component) {
337: Toolkit toolkit = null;
338: if (component != null) {
339: toolkit = component.getToolkit();
340: } else {
341: toolkit = Toolkit.getDefaultToolkit();
342: }
343: toolkit.beep();
344: } // provideErrorFeedback()
345:
346: public static BufferedImage createBufferedImage(URL location) {
347: Image image = Toolkit.getDefaultToolkit().createImage(location);
348: BufferedImage buf;
349: if (image instanceof BufferedImage) {
350: buf = (BufferedImage) image;
351: } else {
352: loadImage(image);
353: //buf = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
354: buf = GraphicsEnvironment.getLocalGraphicsEnvironment()
355: .getDefaultScreenDevice().getDefaultConfiguration()
356: .createCompatibleImage(image.getWidth(null),
357: image.getHeight(null), Transparency.OPAQUE);
358:
359: Graphics g = buf.getGraphics();
360: g.drawImage(image, 0, 0, null);
361: g.dispose();
362: image.flush();
363: }
364: return buf;
365: }
366:
367: public static TexturePaint createTexturePaint(URL location) {
368: BufferedImage texture = createBufferedImage(location);
369: TexturePaint paint = new TexturePaint(texture, new Rectangle(0,
370: 0, texture.getWidth(), texture.getHeight()));
371: return paint;
372: }
373:
374: /**
375: * Loads the image, returning only when the image is loaded.
376: * @param image the image
377: */
378: private static void loadImage(Image image) {
379: Component component = new Component() {
380: };
381: MediaTracker tracker = new MediaTracker(component);
382: synchronized (tracker) {
383: int id = 0;
384:
385: tracker.addImage(image, id);
386: try {
387: tracker.waitForID(id, 0);
388: } catch (InterruptedException e) {
389: System.out.println("INTERRUPTED while loading Image");
390: }
391: int loadStatus = tracker.statusID(id, false);
392: tracker.removeImage(image, id);
393: }
394: }
395:
396: /**
397: * Compute and return the location of the icons origin, the
398: * location of origin of the text baseline, and a possibly clipped
399: * version of the compound labels string. Locations are computed
400: * relative to the viewR rectangle.
401: * The JComponents orientation (LEADING/TRAILING) will also be taken
402: * into account and translated into LEFT/RIGHT values accordingly.
403: */
404: public static String layoutCompoundLabel(JComponent c,
405: FontMetrics fm, String text, Icon icon,
406: int verticalAlignment, int horizontalAlignment,
407: int verticalTextPosition, int horizontalTextPosition,
408: Rectangle viewR, Rectangle iconR, Rectangle textR,
409: int textIconGap) {
410: boolean orientationIsLeftToRight = true;
411: int hAlign = horizontalAlignment;
412: int hTextPos = horizontalTextPosition;
413:
414: if (c != null) {
415: if (!(c.getComponentOrientation().isLeftToRight())) {
416: orientationIsLeftToRight = false;
417: }
418: }
419:
420: // Translate LEADING/TRAILING values in horizontalAlignment
421: // to LEFT/RIGHT values depending on the components orientation
422: switch (horizontalAlignment) {
423: case LEADING:
424: hAlign = (orientationIsLeftToRight) ? LEFT : RIGHT;
425: break;
426: case TRAILING:
427: hAlign = (orientationIsLeftToRight) ? RIGHT : LEFT;
428: break;
429: }
430:
431: // Translate LEADING/TRAILING values in horizontalTextPosition
432: // to LEFT/RIGHT values depending on the components orientation
433: switch (horizontalTextPosition) {
434: case LEADING:
435: hTextPos = (orientationIsLeftToRight) ? LEFT : RIGHT;
436: break;
437: case TRAILING:
438: hTextPos = (orientationIsLeftToRight) ? RIGHT : LEFT;
439: break;
440: }
441:
442: return layoutCompoundLabelImpl(c, fm, text, icon,
443: verticalAlignment, hAlign, verticalTextPosition,
444: hTextPos, viewR, iconR, textR, textIconGap);
445: }
446:
447: /**
448: * Compute and return the location of the icons origin, the
449: * location of origin of the text baseline, and a possibly clipped
450: * version of the compound labels string. Locations are computed
451: * relative to the viewR rectangle.
452: * This layoutCompoundLabel() does not know how to handle LEADING/TRAILING
453: * values in horizontalTextPosition (they will default to RIGHT) and in
454: * horizontalAlignment (they will default to CENTER).
455: * Use the other version of layoutCompoundLabel() instead.
456: */
457: public static String layoutCompoundLabel(FontMetrics fm,
458: String text, Icon icon, int verticalAlignment,
459: int horizontalAlignment, int verticalTextPosition,
460: int horizontalTextPosition, Rectangle viewR,
461: Rectangle iconR, Rectangle textR, int textIconGap) {
462: return layoutCompoundLabelImpl(null, fm, text, icon,
463: verticalAlignment, horizontalAlignment,
464: verticalTextPosition, horizontalTextPosition, viewR,
465: iconR, textR, textIconGap);
466: }
467:
468: /**
469: * Compute and return the location of the icons origin, the
470: * location of origin of the text baseline, and a possibly clipped
471: * version of the compound labels string. Locations are computed
472: * relative to the viewR rectangle.
473: * This layoutCompoundLabel() does not know how to handle LEADING/TRAILING
474: * values in horizontalTextPosition (they will default to RIGHT) and in
475: * horizontalAlignment (they will default to CENTER).
476: * Use the other version of layoutCompoundLabel() instead.
477: *
478: * This is the same as SwingUtilities.layoutCompoundLabelImpl, except for
479: * the algorithm for clipping the text. If a text is too long, "..." are
480: * inserted at the middle of the text instead of at the end.
481: */
482: private static String layoutCompoundLabelImpl(JComponent c,
483: FontMetrics fm, String text, Icon icon,
484: int verticalAlignment, int horizontalAlignment,
485: int verticalTextPosition, int horizontalTextPosition,
486: Rectangle viewR, Rectangle iconR, Rectangle textR,
487: int textIconGap) {
488: /* Initialize the icon bounds rectangle iconR.
489: */
490:
491: if (icon != null) {
492: iconR.width = icon.getIconWidth();
493: iconR.height = icon.getIconHeight();
494: } else {
495: iconR.width = iconR.height = 0;
496: }
497:
498: /* Initialize the text bounds rectangle textR. If a null
499: * or and empty String was specified we substitute "" here
500: * and use 0,0,0,0 for textR.
501: */
502:
503: boolean textIsEmpty = (text == null) || text.equals("");
504: int lsb = 0;
505:
506: View v = null;
507: if (textIsEmpty) {
508: textR.width = textR.height = 0;
509: text = "";
510: } else {
511: v = (c != null) ? (View) c.getClientProperty("html") : null;
512: if (v != null) {
513: textR.width = (int) v.getPreferredSpan(View.X_AXIS);
514: textR.height = (int) v.getPreferredSpan(View.Y_AXIS);
515: } else {
516: textR.width = SwingUtilities.computeStringWidth(fm,
517: text);
518:
519: lsb = getLeftSideBearing(fm.getFont(), text);
520: if (lsb < 0) {
521: // If lsb is negative, add it to the width, the
522: // text bounds will later be adjusted accordingly.
523: textR.width -= lsb;
524: }
525: textR.height = fm.getHeight();
526: }
527: }
528:
529: /* Unless both text and icon are non-null, we effectively ignore
530: * the value of textIconGap. The code that follows uses the
531: * value of gap instead of textIconGap.
532: */
533:
534: int gap = (textIsEmpty || (icon == null)) ? 0 : textIconGap;
535:
536: if (!textIsEmpty) {
537:
538: /* If the label text string is too wide to fit within the available
539: * space "..." and as many characters as will fit will be
540: * displayed instead.
541: */
542:
543: int availTextWidth;
544:
545: if (horizontalTextPosition == CENTER) {
546: availTextWidth = viewR.width;
547: } else {
548: availTextWidth = viewR.width - (iconR.width + gap);
549: }
550:
551: if (textR.width > availTextWidth) {
552: if (v != null) {
553: textR.width = availTextWidth;
554: } else {
555: String clipString = "...";
556: int totalWidth = SwingUtilities.computeStringWidth(
557: fm, clipString);
558: int nChars;
559: int len = text.length();
560: for (nChars = 0; nChars < len; nChars++) {
561: int charIndex = (nChars % 2 == 0) ? nChars / 2
562: : len - 1 - nChars / 2;
563: totalWidth += fm.charWidth(text
564: .charAt(charIndex));
565: if (totalWidth > availTextWidth) {
566: break;
567: }
568: }
569: text = text.substring(0, nChars / 2) + clipString
570: + text.substring(len - nChars / 2);
571: textR.width = SwingUtilities.computeStringWidth(fm,
572: text);
573: }
574: }
575: }
576:
577: /* Compute textR.x,y given the verticalTextPosition and
578: * horizontalTextPosition properties
579: */
580:
581: if (verticalTextPosition == TOP) {
582: if (horizontalTextPosition != CENTER) {
583: textR.y = 0;
584: } else {
585: textR.y = -(textR.height + gap);
586: }
587: } else if (verticalTextPosition == CENTER) {
588: textR.y = (iconR.height / 2) - (textR.height / 2);
589: } else { // (verticalTextPosition == BOTTOM)
590: if (horizontalTextPosition != CENTER) {
591: textR.y = iconR.height - textR.height;
592: } else {
593: textR.y = (iconR.height + gap);
594: }
595: }
596:
597: if (horizontalTextPosition == LEFT) {
598: textR.x = -(textR.width + gap);
599: } else if (horizontalTextPosition == CENTER) {
600: textR.x = (iconR.width / 2) - (textR.width / 2);
601: } else { // (horizontalTextPosition == RIGHT)
602: textR.x = (iconR.width + gap);
603: }
604:
605: /* labelR is the rectangle that contains iconR and textR.
606: * Move it to its proper position given the labelAlignment
607: * properties.
608: *
609: * To avoid actually allocating a Rectangle, Rectangle.union
610: * has been inlined below.
611: */
612: int labelR_x = Math.min(iconR.x, textR.x);
613: int labelR_width = Math.max(iconR.x + iconR.width, textR.x
614: + textR.width)
615: - labelR_x;
616: int labelR_y = Math.min(iconR.y, textR.y);
617: int labelR_height = Math.max(iconR.y + iconR.height, textR.y
618: + textR.height)
619: - labelR_y;
620:
621: int dx, dy;
622:
623: if (verticalAlignment == TOP) {
624: dy = viewR.y - labelR_y;
625: } else if (verticalAlignment == CENTER) {
626: dy = (viewR.y + (viewR.height / 2))
627: - (labelR_y + (labelR_height / 2));
628: } else { // (verticalAlignment == BOTTOM)
629: dy = (viewR.y + viewR.height) - (labelR_y + labelR_height);
630: }
631:
632: if (horizontalAlignment == LEFT) {
633: dx = viewR.x - labelR_x;
634: } else if (horizontalAlignment == RIGHT) {
635: dx = (viewR.x + viewR.width) - (labelR_x + labelR_width);
636: } else { // (horizontalAlignment == CENTER)
637: dx = (viewR.x + (viewR.width / 2))
638: - (labelR_x + (labelR_width / 2));
639: }
640:
641: /* Translate textR and glypyR by dx,dy.
642: */
643:
644: textR.x += dx;
645: textR.y += dy;
646:
647: iconR.x += dx;
648: iconR.y += dy;
649:
650: if (lsb < 0) {
651: // lsb is negative. We previously adjusted the bounds by lsb,
652: // we now need to shift the x location so that the text is
653: // drawn at the right location. The result is textR does not
654: // line up with the actual bounds (on the left side), but we will
655: // have provided enough space for the text.
656: textR.width += lsb;
657: textR.x -= lsb;
658: }
659:
660: return text;
661: }
662:
663: public static void configureGraphics(Graphics gr) {
664: Graphics2D g = (Graphics2D) gr;
665: g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
666: RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
667: g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
668: RenderingHints.VALUE_ANTIALIAS_ON);
669: g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
670: RenderingHints.VALUE_FRACTIONALMETRICS_ON);
671: }
672:
673: /** Copied from BasicLookAndFeel.
674: */
675: public static Component compositeRequestFocus(Component component) {
676: try {
677: if (component instanceof Container) {
678: Container container = (Container) component;
679: if (Methods.invokeGetter(container, "isFocusCycleRoot",
680: false)) {
681:
682: Object policy = Methods.invokeGetter(container,
683: "getFocusTraversalPolicy", null);
684: Component comp = (Component) Methods.invoke(policy,
685: "getDefaultComponent", Container.class,
686: container);
687: if (comp != null) {
688: comp.requestFocus();
689: return comp;
690: }
691: }
692: Container rootAncestor = (Container) Methods
693: .invokeGetter(container,
694: "getFocusCycleRootAncestor", null);
695: if (rootAncestor != null) {
696: Object policy = Methods.invokeGetter(rootAncestor,
697: "getFocusTraversalPolicy", null);
698: Component comp = (Component) Methods.invoke(policy,
699: "getComponentAfter", new Class[] {
700: Container.class, Component.class },
701: new Object[] { rootAncestor, container });
702:
703: if (comp != null
704: && SwingUtilities.isDescendingFrom(comp,
705: container)) {
706: comp.requestFocus();
707: return comp;
708: }
709: }
710: }
711: } catch (NoSuchMethodException e) {
712: // ignore
713: }
714: if (Methods.invokeGetter(component, "isFocusable", true)) {
715: component.requestFocus();
716: return component;
717: }
718: return null;
719: }
720: }
|