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