1 /*
   2  * Copyright (c) 2002, 2015, 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 
 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      */
 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 
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 348     static void updateSubregion(SynthContext state, Graphics g,
 349                                 Rectangle bounds) {
 350         paintRegion(state, g, bounds);
 351     }
 352 
 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 
 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      */
 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      */
 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      */
 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      */
 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      */
 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
 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
 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
 645     public UIDefaults getDefaults() {
 646         UIDefaults table = new UIDefaults(60, 0.75f);
 647 
 648         Region.registerUIs(table);
 649         table.setDefaultLocale(Locale.getDefault());
 650         table.addResourceBundle(
 651               "com.sun.swing.internal.plaf.basic.resources.basic" );
 652         table.addResourceBundle("com.sun.swing.internal.plaf.synth.resources.synth");
 653 
 654         // SynthTabbedPaneUI supports rollover on tabs, GTK does not
 655         table.put("TabbedPane.isTabRollover", Boolean.TRUE);
 656 
 657         // These need to be defined for JColorChooser to work.
 658         table.put("ColorChooser.swatchesRecentSwatchSize",
 659                   new Dimension(10, 10));
 660         table.put("ColorChooser.swatchesDefaultRecentColor", Color.RED);
 661         table.put("ColorChooser.swatchesSwatchSize", new Dimension(10, 10));
 662 
 663         // These need to be defined for ImageView.
 664         table.put("html.pendingImage", SwingUtilities2.makeIcon(getClass(),
 665                                 BasicLookAndFeel.class,
 666                                 "icons/image-delayed.png"));
 667         table.put("html.missingImage", SwingUtilities2.makeIcon(getClass(),
 668                                 BasicLookAndFeel.class,
 669                                 "icons/image-failed.png"));
 670 
 671         // These are needed for PopupMenu.
 672         table.put("PopupMenu.selectedWindowInputMapBindings", new Object[] {
 673                   "ESCAPE", "cancel",
 674                     "DOWN", "selectNext",
 675                  "KP_DOWN", "selectNext",
 676                       "UP", "selectPrevious",
 677                    "KP_UP", "selectPrevious",
 678                     "LEFT", "selectParent",
 679                  "KP_LEFT", "selectParent",
 680                    "RIGHT", "selectChild",
 681                 "KP_RIGHT", "selectChild",
 682                    "ENTER", "return",
 683                    "SPACE", "return"
 684         });
 685         table.put("PopupMenu.selectedWindowInputMapBindings.RightToLeft",
 686                   new Object[] {
 687                     "LEFT", "selectChild",
 688                  "KP_LEFT", "selectChild",
 689                    "RIGHT", "selectParent",
 690                 "KP_RIGHT", "selectParent",
 691                   });
 692 
 693         table.put("uiDrawing.text", SwingUtilities2.DEFAULT_UI_TEXT_DRAWING);
 694 
 695         // enabled antialiasing depending on desktop settings
 696         flushUnreferenced();
 697         SwingUtilities2.putAATextInfo(useLAFConditions(), table);
 698         new AATextListener(this);
 699 
 700         if (defaultsMap != null) {
 701             table.putAll(defaultsMap);
 702         }
 703         return table;
 704     }
 705 
 706     /**
 707      * Returns true, SynthLookAndFeel is always supported.
 708      *
 709      * @return true.
 710      */
 711     @Override
 712     public boolean isSupportedLookAndFeel() {
 713         return true;
 714     }
 715 
 716     /**
 717      * Returns false, SynthLookAndFeel is not a native look and feel.
 718      *
 719      * @return false
 720      */
 721     @Override
 722     public boolean isNativeLookAndFeel() {
 723         return false;
 724     }
 725 
 726     /**
 727      * Returns a textual description of SynthLookAndFeel.
 728      *
 729      * @return textual description of synth.
 730      */
 731     @Override
 732     public String getDescription() {
 733         return "Synth look and feel";
 734     }
 735 
 736     /**
 737      * Return a short string that identifies this look and feel.
 738      *
 739      * @return a short string identifying this look and feel.
 740      */
 741     @Override
 742     public String getName() {
 743         return "Synth look and feel";
 744     }
 745 
 746     /**
 747      * Return a string that identifies this look and feel.
 748      *
 749      * @return a short string identifying this look and feel.
 750      */
 751     @Override
 752     public String getID() {
 753         return "Synth";
 754     }
 755 
 756     /**
 757      * Returns whether or not the UIs should update their
 758      * <code>SynthStyles</code> from the <code>SynthStyleFactory</code>
 759      * when the ancestor of the <code>JComponent</code> changes. A subclass
 760      * that provided a <code>SynthStyleFactory</code> that based the
 761      * return value from <code>getStyle</code> off the containment hierarchy
 762      * would override this method to return true.
 763      *
 764      * @return whether or not the UIs should update their
 765      * <code>SynthStyles</code> from the <code>SynthStyleFactory</code>
 766      * when the ancestor changed.
 767      */
 768     public boolean shouldUpdateStyleOnAncestorChanged() {
 769         return false;
 770     }
 771 
 772     /**
 773      * Returns whether or not the UIs should update their styles when a
 774      * particular event occurs.
 775      *
 776      * @param ev a {@code PropertyChangeEvent}
 777      * @return whether or not the UIs should update their styles
 778      * @since 1.7
 779      */
 780     protected boolean shouldUpdateStyleOnEvent(PropertyChangeEvent ev) {
 781         String eName = ev.getPropertyName();
 782         if ("name" == eName || "componentOrientation" == eName) {
 783             return true;
 784         }
 785         if ("ancestor" == eName && ev.getNewValue() != null) {
 786             // Only update on an ancestor change when getting a valid
 787             // parent and the LookAndFeel wants this.
 788             return shouldUpdateStyleOnAncestorChanged();
 789         }
 790         return false;
 791     }
 792 
 793     /**
 794      * Returns the antialiasing information as specified by the host desktop.
 795      * Antialiasing might be forced off if the desktop is GNOME and the user
 796      * has set his locale to Chinese, Japanese or Korean. This is consistent
 797      * with what GTK does. See com.sun.java.swing.plaf.gtk.GtkLookAndFeel
 798      * for more information about CJK and antialiased fonts.
 799      *
 800      * @return the text antialiasing information associated to the desktop
 801      */
 802     private static boolean useLAFConditions() {
 803         String language = Locale.getDefault().getLanguage();
 804         String desktop =
 805             AccessController.doPrivileged(new GetPropertyAction("sun.desktop"));
 806 
 807         boolean isCjkLocale = (Locale.CHINESE.getLanguage().equals(language) ||
 808                 Locale.JAPANESE.getLanguage().equals(language) ||
 809                 Locale.KOREAN.getLanguage().equals(language));
 810         boolean isGnome = "gnome".equals(desktop);
 811         boolean isLocal = SwingUtilities2.isLocalDisplay();
 812 
 813         return isLocal && (!isGnome || !isCjkLocale);
 814     }
 815 
 816     private static ReferenceQueue<LookAndFeel> queue = new ReferenceQueue<LookAndFeel>();
 817 
 818     private static void flushUnreferenced() {
 819         AATextListener aatl;
 820         while ((aatl = (AATextListener) queue.poll()) != null) {
 821             aatl.dispose();
 822         }
 823     }
 824 
 825     private static class AATextListener
 826         extends WeakReference<LookAndFeel> implements PropertyChangeListener {
 827         private String key = SunToolkit.DESKTOPFONTHINTS;
 828 
 829         AATextListener(LookAndFeel laf) {
 830             super(laf, queue);
 831             Toolkit tk = Toolkit.getDefaultToolkit();
 832             tk.addPropertyChangeListener(key, this);
 833         }
 834 
 835         @Override
 836         public void propertyChange(PropertyChangeEvent pce) {
 837             UIDefaults defaults = UIManager.getLookAndFeelDefaults();
 838             if (defaults.getBoolean("Synth.doNotSetTextAA")) {
 839                 dispose();
 840                 return;
 841             }
 842 
 843             LookAndFeel laf = get();
 844             if (laf == null || laf != UIManager.getLookAndFeel()) {
 845                 dispose();
 846                 return;
 847             }
 848 
 849             SwingUtilities2.putAATextInfo(useLAFConditions(), defaults);
 850 
 851             updateUI();
 852         }
 853 
 854         void dispose() {
 855             Toolkit tk = Toolkit.getDefaultToolkit();
 856             tk.removePropertyChangeListener(key, this);
 857         }
 858 
 859         /**
 860          * Updates the UI of the passed in window and all its children.
 861          */
 862         private static void updateWindowUI(Window window) {
 863             updateStyles(window);
 864             Window ownedWins[] = window.getOwnedWindows();
 865             for (Window w : ownedWins) {
 866                 updateWindowUI(w);
 867             }
 868         }
 869 
 870         /**
 871          * Updates the UIs of all the known Frames.
 872          */
 873         private static void updateAllUIs() {
 874             Frame appFrames[] = Frame.getFrames();
 875             for (Frame frame : appFrames) {
 876                 updateWindowUI(frame);
 877             }
 878         }
 879 
 880         /**
 881          * Indicates if an updateUI call is pending.
 882          */
 883         private static boolean updatePending;
 884 
 885         /**
 886          * Sets whether or not an updateUI call is pending.
 887          */
 888         private static synchronized void setUpdatePending(boolean update) {
 889             updatePending = update;
 890         }
 891 
 892         /**
 893          * Returns true if a UI update is pending.
 894          */
 895         private static synchronized boolean isUpdatePending() {
 896             return updatePending;
 897         }
 898 
 899         protected void updateUI() {
 900             if (!isUpdatePending()) {
 901                 setUpdatePending(true);
 902                 Runnable uiUpdater = new Runnable() {
 903                     @Override
 904                     public void run() {
 905                         updateAllUIs();
 906                         setUpdatePending(false);
 907                     }
 908                 };
 909                 SwingUtilities.invokeLater(uiUpdater);
 910             }
 911         }
 912     }
 913 
 914     private void writeObject(java.io.ObjectOutputStream out)
 915             throws IOException {
 916         throw new NotSerializableException(this.getClass().getName());
 917     }
 918 
 919     private class Handler implements PropertyChangeListener {
 920         @Override
 921         public void propertyChange(PropertyChangeEvent evt) {
 922             String propertyName = evt.getPropertyName();
 923             Object newValue = evt.getNewValue();
 924             Object oldValue = evt.getOldValue();
 925 
 926             if ("focusOwner" == propertyName) {
 927                 if (oldValue instanceof JComponent) {
 928                     repaintIfBackgroundsDiffer((JComponent)oldValue);
 929 
 930                 }
 931 
 932                 if (newValue instanceof JComponent) {
 933                     repaintIfBackgroundsDiffer((JComponent)newValue);
 934                 }
 935             }
 936             else if ("managingFocus" == propertyName) {
 937                 // De-register listener on old keyboard focus manager and
 938                 // register it on the new one.
 939                 KeyboardFocusManager manager =
 940                     (KeyboardFocusManager)evt.getSource();
 941                 if (newValue.equals(Boolean.FALSE)) {
 942                     manager.removePropertyChangeListener(_handler);
 943                 }
 944                 else {
 945                     manager.addPropertyChangeListener(_handler);
 946                 }
 947             }
 948         }
 949 
 950         /**
 951          * This is a support method that will check if the background colors of
 952          * the specified component differ between focused and unfocused states.
 953          * If the color differ the component will then repaint itself.
 954          *
 955          * @comp the component to check
 956          */
 957         private void repaintIfBackgroundsDiffer(JComponent comp) {
 958             ComponentUI ui = comp.getUI();
 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 }