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