1 /* 2 * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.plaf.synth; 26 27 import java.awt.*; 28 import java.beans.*; 29 import java.io.*; 30 import java.lang.ref.*; 31 import java.net.*; 32 import java.security.*; 33 import java.text.*; 34 import java.util.*; 35 import javax.swing.*; 36 import javax.swing.plaf.*; 37 import javax.swing.plaf.basic.*; 38 39 import sun.awt.*; 40 import sun.security.action.*; 41 import sun.swing.*; 42 import sun.swing.plaf.synth.*; 43 44 /** 45 * SynthLookAndFeel provides the basis for creating a customized look and 46 * feel. SynthLookAndFeel does not directly provide a look, all painting is 47 * delegated. 48 * You need to either provide a configuration file, by way of the 49 * {@link #load} method, or provide your own {@link SynthStyleFactory} 50 * to {@link #setStyleFactory}. Refer to the 51 * <a href="package-summary.html">package summary</a> for an example of 52 * loading a file, and {@link javax.swing.plaf.synth.SynthStyleFactory} for 53 * an example of providing your own <code>SynthStyleFactory</code> to 54 * <code>setStyleFactory</code>. 55 * <p> 56 * <strong>Warning:</strong> 57 * This class implements {@link Serializable} as a side effect of it 58 * extending {@link BasicLookAndFeel}. It is not intended to be serialized. 59 * An attempt to serialize it will 60 * result in {@link NotSerializableException}. 61 * 62 * @serial exclude 63 * @since 1.5 64 * @author Scott Violet 65 */ 66 public class SynthLookAndFeel extends BasicLookAndFeel { 67 /** 68 * Used in a handful of places where we need an empty Insets. 69 */ 70 static final Insets EMPTY_UIRESOURCE_INSETS = new InsetsUIResource( 71 0, 0, 0, 0); 72 73 /** 74 * AppContext key to get the current SynthStyleFactory. 75 */ 76 private static final Object STYLE_FACTORY_KEY = 77 new StringBuffer("com.sun.java.swing.plaf.gtk.StyleCache"); 78 79 /** 80 * AppContext key to get selectedUI. 81 */ 82 private static final Object SELECTED_UI_KEY = new StringBuilder("selectedUI"); 83 84 /** 85 * AppContext key to get selectedUIState. 86 */ 87 private static final Object SELECTED_UI_STATE_KEY = new StringBuilder("selectedUIState"); 88 89 /** 90 * The last SynthStyleFactory that was asked for from AppContext 91 * <code>lastContext</code>. 92 */ 93 private static SynthStyleFactory lastFactory; 94 /** 95 * AppContext lastLAF came from. 96 */ 97 private static AppContext lastContext; 98 99 /** 100 * SynthStyleFactory for the this SynthLookAndFeel. 101 */ 102 private SynthStyleFactory factory; 103 104 /** 105 * Map of defaults table entries. This is populated via the load 106 * method. 107 */ 108 private Map<String, Object> defaultsMap; 109 110 private Handler _handler; 111 112 static ComponentUI getSelectedUI() { 113 return (ComponentUI) AppContext.getAppContext().get(SELECTED_UI_KEY); 114 } 115 116 /** 117 * Used by the renderers. For the most part the renderers are implemented 118 * as Labels, which is problematic in so far as they are never selected. 119 * To accommodate this SynthLabelUI checks if the current 120 * UI matches that of <code>selectedUI</code> (which this methods sets), if 121 * it does, then a state as set by this method is returned. This provides 122 * a way for labels to have a state other than selected. 123 */ 124 static void setSelectedUI(ComponentUI uix, boolean selected, 125 boolean focused, boolean enabled, 126 boolean rollover) { 127 int selectedUIState = 0; 128 129 if (selected) { 130 selectedUIState = SynthConstants.SELECTED; 131 if (focused) { 132 selectedUIState |= SynthConstants.FOCUSED; 133 } 134 } 135 else if (rollover && enabled) { 136 selectedUIState |= 137 SynthConstants.MOUSE_OVER | SynthConstants.ENABLED; 138 if (focused) { 139 selectedUIState |= SynthConstants.FOCUSED; 140 } 141 } 142 else { 143 if (enabled) { 144 selectedUIState |= SynthConstants.ENABLED; 145 if (focused) { 146 selectedUIState |= SynthConstants.FOCUSED; 147 } 148 } 149 else { 150 selectedUIState |= SynthConstants.DISABLED; 151 } 152 } 153 154 AppContext context = AppContext.getAppContext(); 155 156 context.put(SELECTED_UI_KEY, uix); 157 context.put(SELECTED_UI_STATE_KEY, Integer.valueOf(selectedUIState)); 158 } 159 160 static int getSelectedUIState() { 161 Integer result = (Integer) AppContext.getAppContext().get(SELECTED_UI_STATE_KEY); 162 163 return result == null ? 0 : result.intValue(); 164 } 165 166 /** 167 * Clears out the selected UI that was last set in setSelectedUI. 168 */ 169 static void resetSelectedUI() { 170 AppContext.getAppContext().remove(SELECTED_UI_KEY); 171 } 172 173 174 /** 175 * Sets the SynthStyleFactory that the UI classes provided by 176 * synth will use to obtain a SynthStyle. 177 * 178 * @param cache SynthStyleFactory the UIs should use. 179 */ 180 public static void setStyleFactory(SynthStyleFactory cache) { 181 // We assume the setter is called BEFORE the getter has been invoked 182 // for a particular AppContext. 183 synchronized(SynthLookAndFeel.class) { 184 AppContext context = AppContext.getAppContext(); 185 lastFactory = cache; 186 lastContext = context; 187 context.put(STYLE_FACTORY_KEY, cache); 188 } 189 } 190 191 /** 192 * Returns the current SynthStyleFactory. 193 * 194 * @return SynthStyleFactory 195 */ 196 public static SynthStyleFactory getStyleFactory() { 197 synchronized(SynthLookAndFeel.class) { 198 AppContext context = AppContext.getAppContext(); 199 200 if (lastContext == context) { 201 return lastFactory; 202 } 203 lastContext = context; 204 lastFactory = (SynthStyleFactory) context.get(STYLE_FACTORY_KEY); 205 return lastFactory; 206 } 207 } 208 209 /** 210 * Returns the component state for the specified component. This should 211 * only be used for Components that don't have any special state beyond 212 * that of ENABLED, DISABLED or FOCUSED. For example, buttons shouldn't 213 * call into this method. 214 */ 215 static int getComponentState(Component c) { 216 if (c.isEnabled()) { 217 if (c.isFocusOwner()) { 218 return SynthUI.ENABLED | SynthUI.FOCUSED; 219 } 220 return SynthUI.ENABLED; 221 } 222 return SynthUI.DISABLED; 223 } 224 225 /** 226 * Gets a SynthStyle for the specified region of the specified component. 227 * This is not for general consumption, only custom UIs should call this 228 * method. 229 * 230 * @param c JComponent to get the SynthStyle for 231 * @param region Identifies the region of the specified component 232 * @return SynthStyle to use. 233 */ 234 public static SynthStyle getStyle(JComponent c, Region region) { 235 return getStyleFactory().getStyle(c, region); 236 } 237 238 /** 239 * Returns true if the Style should be updated in response to the 240 * specified PropertyChangeEvent. This forwards to 241 * <code>shouldUpdateStyleOnAncestorChanged</code> as necessary. 242 */ 243 static boolean shouldUpdateStyle(PropertyChangeEvent event) { 244 LookAndFeel laf = UIManager.getLookAndFeel(); 245 return (laf instanceof SynthLookAndFeel && 246 ((SynthLookAndFeel) laf).shouldUpdateStyleOnEvent(event)); 247 } 248 249 /** 250 * A convience method that will reset the Style of StyleContext if 251 * necessary. 252 * 253 * @return newStyle 254 */ 255 static SynthStyle updateStyle(SynthContext context, SynthUI ui) { 256 SynthStyle newStyle = getStyle(context.getComponent(), 257 context.getRegion()); 258 SynthStyle oldStyle = context.getStyle(); 259 260 if (newStyle != oldStyle) { 261 if (oldStyle != null) { 262 oldStyle.uninstallDefaults(context); 263 } 264 context.setStyle(newStyle); 265 newStyle.installDefaults(context, ui); 266 } 267 return newStyle; 268 } 269 270 /** 271 * Updates the style associated with <code>c</code>, and all its children. 272 * This is a lighter version of 273 * <code>SwingUtilities.updateComponentTreeUI</code>. 274 * 275 * @param c Component to update style for. 276 */ 277 public static void updateStyles(Component c) { 278 if (c instanceof JComponent) { 279 // Yes, this is hacky. A better solution is to get the UI 280 // and cast, but JComponent doesn't expose a getter for the UI 281 // (each of the UIs do), making that approach impractical. 282 String name = c.getName(); 283 c.setName(null); 284 if (name != null) { 285 c.setName(name); 286 } 287 ((JComponent)c).revalidate(); 288 } 289 Component[] children = null; 290 if (c instanceof JMenu) { 291 children = ((JMenu)c).getMenuComponents(); 292 } 293 else if (c instanceof Container) { 294 children = ((Container)c).getComponents(); 295 } 296 if (children != null) { 297 for (Component child : children) { 298 updateStyles(child); 299 } 300 } 301 c.repaint(); 302 } 303 304 /** 305 * Returns the Region for the JComponent <code>c</code>. 306 * 307 * @param c JComponent to fetch the Region for 308 * @return Region corresponding to <code>c</code> 309 */ 310 public static Region getRegion(JComponent c) { 311 return Region.getRegion(c); 312 } 313 314 /** 315 * A convenience method to return where the foreground should be 316 * painted for the Component identified by the passed in 317 * AbstractSynthContext. 318 */ 319 static Insets getPaintingInsets(SynthContext state, Insets insets) { 320 if (state.isSubregion()) { 321 insets = state.getStyle().getInsets(state, insets); 322 } 323 else { 324 insets = state.getComponent().getInsets(insets); 325 } 326 return insets; 327 } 328 329 /** 330 * A convenience method that handles painting of the background. 331 * All SynthUI implementations should override update and invoke 332 * this method. 333 */ 334 static void update(SynthContext state, Graphics g) { 335 paintRegion(state, g, null); 336 } 337 338 /** 339 * A convenience method that handles painting of the background for 340 * subregions. All SynthUI's that have subregions should invoke 341 * this method, than paint the foreground. 342 */ 343 static void updateSubregion(SynthContext state, Graphics g, 344 Rectangle bounds) { 345 paintRegion(state, g, bounds); 346 } 347 348 private static void paintRegion(SynthContext state, Graphics g, 349 Rectangle bounds) { 350 JComponent c = state.getComponent(); 351 SynthStyle style = state.getStyle(); 352 int x, y, width, height; 353 354 if (bounds == null) { 355 x = 0; 356 y = 0; 357 width = c.getWidth(); 358 height = c.getHeight(); 359 } 360 else { 361 x = bounds.x; 362 y = bounds.y; 363 width = bounds.width; 364 height = bounds.height; 365 } 366 367 // Fill in the background, if necessary. 368 boolean subregion = state.isSubregion(); 369 if ((subregion && style.isOpaque(state)) || 370 (!subregion && c.isOpaque())) { 371 g.setColor(style.getColor(state, ColorType.BACKGROUND)); 372 g.fillRect(x, y, width, height); 373 } 374 } 375 376 static boolean isLeftToRight(Component c) { 377 return c.getComponentOrientation().isLeftToRight(); 378 } 379 380 /** 381 * Returns the ui that is of type <code>klass</code>, or null if 382 * one can not be found. 383 */ 384 static Object getUIOfType(ComponentUI ui, Class klass) { 385 if (klass.isInstance(ui)) { 386 return ui; 387 } 388 return null; 389 } 390 391 /** 392 * Creates the Synth look and feel <code>ComponentUI</code> for 393 * the passed in <code>JComponent</code>. 394 * 395 * @param c JComponent to create the <code>ComponentUI</code> for 396 * @return ComponentUI to use for <code>c</code> 397 */ 398 public static ComponentUI createUI(JComponent c) { 399 String key = c.getUIClassID().intern(); 400 401 if (key == "ButtonUI") { 402 return SynthButtonUI.createUI(c); 403 } 404 else if (key == "CheckBoxUI") { 405 return SynthCheckBoxUI.createUI(c); 406 } 407 else if (key == "CheckBoxMenuItemUI") { 408 return SynthCheckBoxMenuItemUI.createUI(c); 409 } 410 else if (key == "ColorChooserUI") { 411 return SynthColorChooserUI.createUI(c); 412 } 413 else if (key == "ComboBoxUI") { 414 return SynthComboBoxUI.createUI(c); 415 } 416 else if (key == "DesktopPaneUI") { 417 return SynthDesktopPaneUI.createUI(c); 418 } 419 else if (key == "DesktopIconUI") { 420 return SynthDesktopIconUI.createUI(c); 421 } 422 else if (key == "EditorPaneUI") { 423 return SynthEditorPaneUI.createUI(c); 424 } 425 else if (key == "FileChooserUI") { 426 return SynthFileChooserUI.createUI(c); 427 } 428 else if (key == "FormattedTextFieldUI") { 429 return SynthFormattedTextFieldUI.createUI(c); 430 } 431 else if (key == "InternalFrameUI") { 432 return SynthInternalFrameUI.createUI(c); 433 } 434 else if (key == "LabelUI") { 435 return SynthLabelUI.createUI(c); 436 } 437 else if (key == "ListUI") { 438 return SynthListUI.createUI(c); 439 } 440 else if (key == "MenuBarUI") { 441 return SynthMenuBarUI.createUI(c); 442 } 443 else if (key == "MenuUI") { 444 return SynthMenuUI.createUI(c); 445 } 446 else if (key == "MenuItemUI") { 447 return SynthMenuItemUI.createUI(c); 448 } 449 else if (key == "OptionPaneUI") { 450 return SynthOptionPaneUI.createUI(c); 451 } 452 else if (key == "PanelUI") { 453 return SynthPanelUI.createUI(c); 454 } 455 else if (key == "PasswordFieldUI") { 456 return SynthPasswordFieldUI.createUI(c); 457 } 458 else if (key == "PopupMenuSeparatorUI") { 459 return SynthSeparatorUI.createUI(c); 460 } 461 else if (key == "PopupMenuUI") { 462 return SynthPopupMenuUI.createUI(c); 463 } 464 else if (key == "ProgressBarUI") { 465 return SynthProgressBarUI.createUI(c); 466 } 467 else if (key == "RadioButtonUI") { 468 return SynthRadioButtonUI.createUI(c); 469 } 470 else if (key == "RadioButtonMenuItemUI") { 471 return SynthRadioButtonMenuItemUI.createUI(c); 472 } 473 else if (key == "RootPaneUI") { 474 return SynthRootPaneUI.createUI(c); 475 } 476 else if (key == "ScrollBarUI") { 477 return SynthScrollBarUI.createUI(c); 478 } 479 else if (key == "ScrollPaneUI") { 480 return SynthScrollPaneUI.createUI(c); 481 } 482 else if (key == "SeparatorUI") { 483 return SynthSeparatorUI.createUI(c); 484 } 485 else if (key == "SliderUI") { 486 return SynthSliderUI.createUI(c); 487 } 488 else if (key == "SpinnerUI") { 489 return SynthSpinnerUI.createUI(c); 490 } 491 else if (key == "SplitPaneUI") { 492 return SynthSplitPaneUI.createUI(c); 493 } 494 else if (key == "TabbedPaneUI") { 495 return SynthTabbedPaneUI.createUI(c); 496 } 497 else if (key == "TableUI") { 498 return SynthTableUI.createUI(c); 499 } 500 else if (key == "TableHeaderUI") { 501 return SynthTableHeaderUI.createUI(c); 502 } 503 else if (key == "TextAreaUI") { 504 return SynthTextAreaUI.createUI(c); 505 } 506 else if (key == "TextFieldUI") { 507 return SynthTextFieldUI.createUI(c); 508 } 509 else if (key == "TextPaneUI") { 510 return SynthTextPaneUI.createUI(c); 511 } 512 else if (key == "ToggleButtonUI") { 513 return SynthToggleButtonUI.createUI(c); 514 } 515 else if (key == "ToolBarSeparatorUI") { 516 return SynthSeparatorUI.createUI(c); 517 } 518 else if (key == "ToolBarUI") { 519 return SynthToolBarUI.createUI(c); 520 } 521 else if (key == "ToolTipUI") { 522 return SynthToolTipUI.createUI(c); 523 } 524 else if (key == "TreeUI") { 525 return SynthTreeUI.createUI(c); 526 } 527 else if (key == "ViewportUI") { 528 return SynthViewportUI.createUI(c); 529 } 530 return null; 531 } 532 533 534 /** 535 * Creates a SynthLookAndFeel. 536 * <p> 537 * For the returned <code>SynthLookAndFeel</code> to be useful you need to 538 * invoke <code>load</code> to specify the set of 539 * <code>SynthStyle</code>s, or invoke <code>setStyleFactory</code>. 540 * 541 * @see #load 542 * @see #setStyleFactory 543 */ 544 public SynthLookAndFeel() { 545 factory = new DefaultSynthStyleFactory(); 546 _handler = new Handler(); 547 } 548 549 /** 550 * Loads the set of <code>SynthStyle</code>s that will be used by 551 * this <code>SynthLookAndFeel</code>. <code>resourceBase</code> is 552 * used to resolve any path based resources, for example an 553 * <code>Image</code> would be resolved by 554 * <code>resourceBase.getResource(path)</code>. Refer to 555 * <a href="doc-files/synthFileFormat.html">Synth File Format</a> 556 * for more information. 557 * 558 * @param input InputStream to load from 559 * @param resourceBase used to resolve any images or other resources 560 * @throws ParseException if there is an error in parsing 561 * @throws IllegalArgumentException if input or resourceBase is <code>null</code> 562 */ 563 public void load(InputStream input, Class<?> resourceBase) throws 564 ParseException { 565 if (resourceBase == null) { 566 throw new IllegalArgumentException( 567 "You must supply a valid resource base Class"); 568 } 569 570 if (defaultsMap == null) { 571 defaultsMap = new HashMap<String, Object>(); 572 } 573 574 new SynthParser().parse(input, (DefaultSynthStyleFactory) factory, 575 null, resourceBase, defaultsMap); 576 } 577 578 /** 579 * Loads the set of <code>SynthStyle</code>s that will be used by 580 * this <code>SynthLookAndFeel</code>. Path based resources are resolved 581 * relatively to the specified <code>URL</code> of the style. For example 582 * an <code>Image</code> would be resolved by 583 * <code>new URL(synthFile, path)</code>. Refer to 584 * <a href="doc-files/synthFileFormat.html">Synth File Format</a> for more 585 * information. 586 * 587 * @param url the <code>URL</code> to load the set of 588 * <code>SynthStyle</code> from 589 * @throws ParseException if there is an error in parsing 590 * @throws IllegalArgumentException if synthSet is <code>null</code> 591 * @throws IOException if synthSet cannot be opened as an <code>InputStream</code> 592 * @since 1.6 593 */ 594 public void load(URL url) throws ParseException, IOException { 595 if (url == null) { 596 throw new IllegalArgumentException( 597 "You must supply a valid Synth set URL"); 598 } 599 600 if (defaultsMap == null) { 601 defaultsMap = new HashMap<String, Object>(); 602 } 603 604 InputStream input = url.openStream(); 605 new SynthParser().parse(input, (DefaultSynthStyleFactory) factory, 606 url, null, defaultsMap); 607 } 608 609 /** 610 * Called by UIManager when this look and feel is installed. 611 */ 612 @Override 613 public void initialize() { 614 super.initialize(); 615 DefaultLookup.setDefaultLookup(new SynthDefaultLookup()); 616 setStyleFactory(factory); 617 KeyboardFocusManager.getCurrentKeyboardFocusManager(). 618 addPropertyChangeListener(_handler); 619 } 620 621 /** 622 * Called by UIManager when this look and feel is uninstalled. 623 */ 624 @Override 625 public void uninitialize() { 626 KeyboardFocusManager.getCurrentKeyboardFocusManager(). 627 removePropertyChangeListener(_handler); 628 // We should uninstall the StyleFactory here, but unfortunately 629 // there are a handful of things that retain references to the 630 // LookAndFeel and expect things to work 631 super.uninitialize(); 632 } 633 634 /** 635 * Returns the defaults for this SynthLookAndFeel. 636 * 637 * @return Defaults table. 638 */ 639 @Override 640 public UIDefaults getDefaults() { 641 UIDefaults table = new UIDefaults(60, 0.75f); 642 643 Region.registerUIs(table); 644 table.setDefaultLocale(Locale.getDefault()); 645 table.addResourceBundle( 646 "com.sun.swing.internal.plaf.basic.resources.basic" ); 647 table.addResourceBundle("com.sun.swing.internal.plaf.synth.resources.synth"); 648 649 // SynthTabbedPaneUI supports rollover on tabs, GTK does not 650 table.put("TabbedPane.isTabRollover", Boolean.TRUE); 651 652 // These need to be defined for JColorChooser to work. 653 table.put("ColorChooser.swatchesRecentSwatchSize", 654 new Dimension(10, 10)); 655 table.put("ColorChooser.swatchesDefaultRecentColor", Color.RED); 656 table.put("ColorChooser.swatchesSwatchSize", new Dimension(10, 10)); 657 658 // These need to be defined for ImageView. 659 table.put("html.pendingImage", SwingUtilities2.makeIcon(getClass(), 660 BasicLookAndFeel.class, 661 "icons/image-delayed.png")); 662 table.put("html.missingImage", SwingUtilities2.makeIcon(getClass(), 663 BasicLookAndFeel.class, 664 "icons/image-failed.png")); 665 666 // These are needed for PopupMenu. 667 table.put("PopupMenu.selectedWindowInputMapBindings", new Object[] { 668 "ESCAPE", "cancel", 669 "DOWN", "selectNext", 670 "KP_DOWN", "selectNext", 671 "UP", "selectPrevious", 672 "KP_UP", "selectPrevious", 673 "LEFT", "selectParent", 674 "KP_LEFT", "selectParent", 675 "RIGHT", "selectChild", 676 "KP_RIGHT", "selectChild", 677 "ENTER", "return", 678 "SPACE", "return" 679 }); 680 table.put("PopupMenu.selectedWindowInputMapBindings.RightToLeft", 681 new Object[] { 682 "LEFT", "selectChild", 683 "KP_LEFT", "selectChild", 684 "RIGHT", "selectParent", 685 "KP_RIGHT", "selectParent", 686 }); 687 688 // enabled antialiasing depending on desktop settings 689 flushUnreferenced(); 690 Object aaTextInfo = getAATextInfo(); 691 table.put(SwingUtilities2.AA_TEXT_PROPERTY_KEY, aaTextInfo); 692 new AATextListener(this); 693 694 if (defaultsMap != null) { 695 table.putAll(defaultsMap); 696 } 697 return table; 698 } 699 700 /** 701 * Returns true, SynthLookAndFeel is always supported. 702 * 703 * @return true. 704 */ 705 @Override 706 public boolean isSupportedLookAndFeel() { 707 return true; 708 } 709 710 /** 711 * Returns false, SynthLookAndFeel is not a native look and feel. 712 * 713 * @return false 714 */ 715 @Override 716 public boolean isNativeLookAndFeel() { 717 return false; 718 } 719 720 /** 721 * Returns a textual description of SynthLookAndFeel. 722 * 723 * @return textual description of synth. 724 */ 725 @Override 726 public String getDescription() { 727 return "Synth look and feel"; 728 } 729 730 /** 731 * Return a short string that identifies this look and feel. 732 * 733 * @return a short string identifying this look and feel. 734 */ 735 @Override 736 public String getName() { 737 return "Synth look and feel"; 738 } 739 740 /** 741 * Return a string that identifies this look and feel. 742 * 743 * @return a short string identifying this look and feel. 744 */ 745 @Override 746 public String getID() { 747 return "Synth"; 748 } 749 750 /** 751 * Returns whether or not the UIs should update their 752 * <code>SynthStyles</code> from the <code>SynthStyleFactory</code> 753 * when the ancestor of the <code>JComponent</code> changes. A subclass 754 * that provided a <code>SynthStyleFactory</code> that based the 755 * return value from <code>getStyle</code> off the containment hierarchy 756 * would override this method to return true. 757 * 758 * @return whether or not the UIs should update their 759 * <code>SynthStyles</code> from the <code>SynthStyleFactory</code> 760 * when the ancestor changed. 761 */ 762 public boolean shouldUpdateStyleOnAncestorChanged() { 763 return false; 764 } 765 766 /** 767 * Returns whether or not the UIs should update their styles when a 768 * particular event occurs. 769 * 770 * @param ev a {@code PropertyChangeEvent} 771 * @return whether or not the UIs should update their styles 772 * @since 1.7 773 */ 774 protected boolean shouldUpdateStyleOnEvent(PropertyChangeEvent ev) { 775 String eName = ev.getPropertyName(); 776 if ("name" == eName || "componentOrientation" == eName) { 777 return true; 778 } 779 if ("ancestor" == eName && ev.getNewValue() != null) { 780 // Only update on an ancestor change when getting a valid 781 // parent and the LookAndFeel wants this. 782 return shouldUpdateStyleOnAncestorChanged(); 783 } 784 return false; 785 } 786 787 /** 788 * Returns the antialiasing information as specified by the host desktop. 789 * Antialiasing might be forced off if the desktop is GNOME and the user 790 * has set his locale to Chinese, Japanese or Korean. This is consistent 791 * with what GTK does. See com.sun.java.swing.plaf.gtk.GtkLookAndFeel 792 * for more information about CJK and antialiased fonts. 793 * 794 * @return the text antialiasing information associated to the desktop 795 */ 796 private static Object getAATextInfo() { 797 String language = Locale.getDefault().getLanguage(); 798 String desktop = 799 AccessController.doPrivileged(new GetPropertyAction("sun.desktop")); 800 801 boolean isCjkLocale = (Locale.CHINESE.getLanguage().equals(language) || 802 Locale.JAPANESE.getLanguage().equals(language) || 803 Locale.KOREAN.getLanguage().equals(language)); 804 boolean isGnome = "gnome".equals(desktop); 805 boolean isLocal = SwingUtilities2.isLocalDisplay(); 806 807 boolean setAA = isLocal && (!isGnome || !isCjkLocale); 808 809 Object aaTextInfo = SwingUtilities2.AATextInfo.getAATextInfo(setAA); 810 return aaTextInfo; 811 } 812 813 private static ReferenceQueue<LookAndFeel> queue = new ReferenceQueue<LookAndFeel>(); 814 815 private static void flushUnreferenced() { 816 AATextListener aatl; 817 while ((aatl = (AATextListener) queue.poll()) != null) { 818 aatl.dispose(); 819 } 820 } 821 822 private static class AATextListener 823 extends WeakReference<LookAndFeel> implements PropertyChangeListener { 824 private String key = SunToolkit.DESKTOPFONTHINTS; 825 826 AATextListener(LookAndFeel laf) { 827 super(laf, queue); 828 Toolkit tk = Toolkit.getDefaultToolkit(); 829 tk.addPropertyChangeListener(key, this); 830 } 831 832 @Override 833 public void propertyChange(PropertyChangeEvent pce) { 834 UIDefaults defaults = UIManager.getLookAndFeelDefaults(); 835 if (defaults.getBoolean("Synth.doNotSetTextAA")) { 836 dispose(); 837 return; 838 } 839 840 LookAndFeel laf = get(); 841 if (laf == null || laf != UIManager.getLookAndFeel()) { 842 dispose(); 843 return; 844 } 845 846 Object aaTextInfo = getAATextInfo(); 847 defaults.put(SwingUtilities2.AA_TEXT_PROPERTY_KEY, aaTextInfo); 848 849 updateUI(); 850 } 851 852 void dispose() { 853 Toolkit tk = Toolkit.getDefaultToolkit(); 854 tk.removePropertyChangeListener(key, this); 855 } 856 857 /** 858 * Updates the UI of the passed in window and all its children. 859 */ 860 private static void updateWindowUI(Window window) { 861 updateStyles(window); 862 Window ownedWins[] = window.getOwnedWindows(); 863 for (Window w : ownedWins) { 864 updateWindowUI(w); 865 } 866 } 867 868 /** 869 * Updates the UIs of all the known Frames. 870 */ 871 private static void updateAllUIs() { 872 Frame appFrames[] = Frame.getFrames(); 873 for (Frame frame : appFrames) { 874 updateWindowUI(frame); 875 } 876 } 877 878 /** 879 * Indicates if an updateUI call is pending. 880 */ 881 private static boolean updatePending; 882 883 /** 884 * Sets whether or not an updateUI call is pending. 885 */ 886 private static synchronized void setUpdatePending(boolean update) { 887 updatePending = update; 888 } 889 890 /** 891 * Returns true if a UI update is pending. 892 */ 893 private static synchronized boolean isUpdatePending() { 894 return updatePending; 895 } 896 897 protected void updateUI() { 898 if (!isUpdatePending()) { 899 setUpdatePending(true); 900 Runnable uiUpdater = new Runnable() { 901 @Override 902 public void run() { 903 updateAllUIs(); 904 setUpdatePending(false); 905 } 906 }; 907 SwingUtilities.invokeLater(uiUpdater); 908 } 909 } 910 } 911 912 private void writeObject(java.io.ObjectOutputStream out) 913 throws IOException { 914 throw new NotSerializableException(this.getClass().getName()); 915 } 916 917 private class Handler implements PropertyChangeListener { 918 @Override 919 public void propertyChange(PropertyChangeEvent evt) { 920 String propertyName = evt.getPropertyName(); 921 Object newValue = evt.getNewValue(); 922 Object oldValue = evt.getOldValue(); 923 924 if ("focusOwner" == propertyName) { 925 if (oldValue instanceof JComponent) { 926 repaintIfBackgroundsDiffer((JComponent)oldValue); 927 928 } 929 930 if (newValue instanceof JComponent) { 931 repaintIfBackgroundsDiffer((JComponent)newValue); 932 } 933 } 934 else if ("managingFocus" == propertyName) { 935 // De-register listener on old keyboard focus manager and 936 // register it on the new one. 937 KeyboardFocusManager manager = 938 (KeyboardFocusManager)evt.getSource(); 939 if (newValue.equals(Boolean.FALSE)) { 940 manager.removePropertyChangeListener(_handler); 941 } 942 else { 943 manager.addPropertyChangeListener(_handler); 944 } 945 } 946 } 947 948 /** 949 * This is a support method that will check if the background colors of 950 * the specified component differ between focused and unfocused states. 951 * If the color differ the component will then repaint itself. 952 * 953 * @comp the component to check 954 */ 955 private void repaintIfBackgroundsDiffer(JComponent comp) { 956 ComponentUI ui = (ComponentUI)comp.getClientProperty( 957 SwingUtilities2.COMPONENT_UI_PROPERTY_KEY); 958 if (ui instanceof SynthUI) { 959 SynthUI synthUI = (SynthUI)ui; 960 SynthContext context = synthUI.getContext(comp); 961 SynthStyle style = context.getStyle(); 962 int state = context.getComponentState(); 963 964 // Get the current background color. 965 Color currBG = style.getColor(context, ColorType.BACKGROUND); 966 967 // Get the last background color. 968 state ^= SynthConstants.FOCUSED; 969 context.setComponentState(state); 970 Color lastBG = style.getColor(context, ColorType.BACKGROUND); 971 972 // Reset the component state back to original. 973 state ^= SynthConstants.FOCUSED; 974 context.setComponentState(state); 975 976 // Repaint the component if the backgrounds differed. 977 if (currBG != null && !currBG.equals(lastBG)) { 978 comp.repaint(); 979 } 980 context.dispose(); 981 } 982 } 983 } 984 }