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