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} to
  54  * {@code setStyleFactory}.
  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}.
  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} (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} 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}, and all its children.
 273      * This is a lighter version of
 274      * {@code SwingUtilities.updateComponentTreeUI}.
 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}.
 307      *
 308      * @param c JComponent to fetch the Region for
 309      * @return Region corresponding to {@code c}
 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}, 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} for
 394      * the passed in {@code JComponent}.
 395      *
 396      * @param c JComponent to create the {@code ComponentUI} for
 397      * @return ComponentUI to use for {@code c}
 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} to be useful you need to
 539      * invoke {@code load} to specify the set of
 540      * {@code SynthStyle}s, or invoke {@code setStyleFactory}.
 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}s that will be used by
 552      * this {@code SynthLookAndFeel}. {@code resourceBase} is
 553      * used to resolve any path based resources, for example an
 554      * {@code Image} would be resolved by
 555      * {@code resourceBase.getResource(path)}. 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}
 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}s that will be used by
 581      * this {@code SynthLookAndFeel}. Path based resources are resolved
 582      * relatively to the specified {@code URL} of the style. For example
 583      * an {@code Image} would be resolved by
 584      * {@code new URL(synthFile, path)}. Refer to
 585      * <a href="doc-files/synthFileFormat.html">Synth File Format</a> for more
 586      * information.
 587      *
 588      * @param url the {@code URL} to load the set of
 589      *     {@code SynthStyle} from
 590      * @throws ParseException if there is an error in parsing
 591      * @throws IllegalArgumentException if synthSet is {@code null}
 592      * @throws IOException if synthSet cannot be opened as an {@code InputStream}
 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         SwingUtilities2.putAATextInfo(useLAFConditions(), table);
 692         new AATextListener(this);
 693 
 694         if (defaultsMap != null) {
 695             table.putAll(defaultsMap);
 696         }
 697         return table;
 698     }
 699 
 700     /**
 701      * Returns true, SynthLookAndFeel is always supported.
 702      *
 703      * @return true.
 704      */
 705     @Override
 706     public boolean isSupportedLookAndFeel() {
 707         return true;
 708     }
 709 
 710     /**
 711      * Returns false, SynthLookAndFeel is not a native look and feel.
 712      *
 713      * @return false
 714      */
 715     @Override
 716     public boolean isNativeLookAndFeel() {
 717         return false;
 718     }
 719 
 720     /**
 721      * Returns a textual description of SynthLookAndFeel.
 722      *
 723      * @return textual description of synth.
 724      */
 725     @Override
 726     public String getDescription() {
 727         return "Synth look and feel";
 728     }
 729 
 730     /**
 731      * Return a short string that identifies this look and feel.
 732      *
 733      * @return a short string identifying this look and feel.
 734      */
 735     @Override
 736     public String getName() {
 737         return "Synth look and feel";
 738     }
 739 
 740     /**
 741      * Return a string that identifies this look and feel.
 742      *
 743      * @return a short string identifying this look and feel.
 744      */
 745     @Override
 746     public String getID() {
 747         return "Synth";
 748     }
 749 
 750     /**
 751      * Returns whether or not the UIs should update their
 752      * {@code SynthStyles} from the {@code SynthStyleFactory}
 753      * when the ancestor of the {@code JComponent} changes. A subclass
 754      * that provided a {@code SynthStyleFactory} that based the
 755      * return value from {@code getStyle} off the containment hierarchy
 756      * would override this method to return true.
 757      *
 758      * @return whether or not the UIs should update their
 759      * {@code SynthStyles} from the {@code SynthStyleFactory}
 760      * when the ancestor changed.
 761      */
 762     public boolean shouldUpdateStyleOnAncestorChanged() {
 763         return false;
 764     }
 765 
 766     /**
 767      * Returns whether or not the UIs should update their styles when a
 768      * particular event occurs.
 769      *
 770      * @param ev a {@code PropertyChangeEvent}
 771      * @return whether or not the UIs should update their styles
 772      * @since 1.7
 773      */
 774     protected boolean shouldUpdateStyleOnEvent(PropertyChangeEvent ev) {
 775         String eName = ev.getPropertyName();
 776         if ("name" == eName || "componentOrientation" == eName) {
 777             return true;
 778         }
 779         if ("ancestor" == eName && ev.getNewValue() != null) {
 780             // Only update on an ancestor change when getting a valid
 781             // parent and the LookAndFeel wants this.
 782             return shouldUpdateStyleOnAncestorChanged();
 783         }
 784         return false;
 785     }
 786 
 787     /**
 788      * Returns the antialiasing information as specified by the host desktop.
 789      * Antialiasing might be forced off if the desktop is GNOME and the user
 790      * has set his locale to Chinese, Japanese or Korean. This is consistent
 791      * with what GTK does. See com.sun.java.swing.plaf.gtk.GtkLookAndFeel
 792      * for more information about CJK and antialiased fonts.
 793      *
 794      * @return the text antialiasing information associated to the desktop
 795      */
 796     private static boolean useLAFConditions() {
 797         String language = Locale.getDefault().getLanguage();
 798         String desktop =
 799             AccessController.doPrivileged(new GetPropertyAction("sun.desktop"));
 800 
 801         boolean isCjkLocale = (Locale.CHINESE.getLanguage().equals(language) ||
 802                 Locale.JAPANESE.getLanguage().equals(language) ||
 803                 Locale.KOREAN.getLanguage().equals(language));
 804         boolean isGnome = "gnome".equals(desktop);
 805         boolean isLocal = SwingUtilities2.isLocalDisplay();
 806 
 807         return isLocal && (!isGnome || !isCjkLocale);
 808     }
 809 
 810     private static ReferenceQueue<LookAndFeel> queue = new ReferenceQueue<LookAndFeel>();
 811 
 812     private static void flushUnreferenced() {
 813         AATextListener aatl;
 814         while ((aatl = (AATextListener) queue.poll()) != null) {
 815             aatl.dispose();
 816         }
 817     }
 818 
 819     private static class AATextListener
 820         extends WeakReference<LookAndFeel> implements PropertyChangeListener {
 821         private String key = SunToolkit.DESKTOPFONTHINTS;
 822 
 823         AATextListener(LookAndFeel laf) {
 824             super(laf, queue);
 825             Toolkit tk = Toolkit.getDefaultToolkit();
 826             tk.addPropertyChangeListener(key, this);
 827         }
 828 
 829         @Override
 830         public void propertyChange(PropertyChangeEvent pce) {
 831             UIDefaults defaults = UIManager.getLookAndFeelDefaults();
 832             if (defaults.getBoolean("Synth.doNotSetTextAA")) {
 833                 dispose();
 834                 return;
 835             }
 836 
 837             LookAndFeel laf = get();
 838             if (laf == null || laf != UIManager.getLookAndFeel()) {
 839                 dispose();
 840                 return;
 841             }
 842 
 843             SwingUtilities2.putAATextInfo(useLAFConditions(), defaults);
 844 
 845             updateUI();
 846         }
 847 
 848         void dispose() {
 849             Toolkit tk = Toolkit.getDefaultToolkit();
 850             tk.removePropertyChangeListener(key, this);
 851         }
 852 
 853         /**
 854          * Updates the UI of the passed in window and all its children.
 855          */
 856         private static void updateWindowUI(Window window) {
 857             updateStyles(window);
 858             Window ownedWins[] = window.getOwnedWindows();
 859             for (Window w : ownedWins) {
 860                 updateWindowUI(w);
 861             }
 862         }
 863 
 864         /**
 865          * Updates the UIs of all the known Frames.
 866          */
 867         private static void updateAllUIs() {
 868             Frame appFrames[] = Frame.getFrames();
 869             for (Frame frame : appFrames) {
 870                 updateWindowUI(frame);
 871             }
 872         }
 873 
 874         /**
 875          * Indicates if an updateUI call is pending.
 876          */
 877         private static boolean updatePending;
 878 
 879         /**
 880          * Sets whether or not an updateUI call is pending.
 881          */
 882         private static synchronized void setUpdatePending(boolean update) {
 883             updatePending = update;
 884         }
 885 
 886         /**
 887          * Returns true if a UI update is pending.
 888          */
 889         private static synchronized boolean isUpdatePending() {
 890             return updatePending;
 891         }
 892 
 893         protected void updateUI() {
 894             if (!isUpdatePending()) {
 895                 setUpdatePending(true);
 896                 Runnable uiUpdater = new Runnable() {
 897                     @Override
 898                     public void run() {
 899                         updateAllUIs();
 900                         setUpdatePending(false);
 901                     }
 902                 };
 903                 SwingUtilities.invokeLater(uiUpdater);
 904             }
 905         }
 906     }
 907 
 908     private void writeObject(java.io.ObjectOutputStream out)
 909             throws IOException {
 910         throw new NotSerializableException(this.getClass().getName());
 911     }
 912 
 913     private class Handler implements PropertyChangeListener {
 914         @Override
 915         public void propertyChange(PropertyChangeEvent evt) {
 916             String propertyName = evt.getPropertyName();
 917             Object newValue = evt.getNewValue();
 918             Object oldValue = evt.getOldValue();
 919 
 920             if ("focusOwner" == propertyName) {
 921                 if (oldValue instanceof JComponent) {
 922                     repaintIfBackgroundsDiffer((JComponent)oldValue);
 923 
 924                 }
 925 
 926                 if (newValue instanceof JComponent) {
 927                     repaintIfBackgroundsDiffer((JComponent)newValue);
 928                 }
 929             }
 930             else if ("managingFocus" == propertyName) {
 931                 // De-register listener on old keyboard focus manager and
 932                 // register it on the new one.
 933                 KeyboardFocusManager manager =
 934                     (KeyboardFocusManager)evt.getSource();
 935                 if (newValue.equals(Boolean.FALSE)) {
 936                     manager.removePropertyChangeListener(_handler);
 937                 }
 938                 else {
 939                     manager.addPropertyChangeListener(_handler);
 940                 }
 941             }
 942         }
 943 
 944         /**
 945          * This is a support method that will check if the background colors of
 946          * the specified component differ between focused and unfocused states.
 947          * If the color differ the component will then repaint itself.
 948          *
 949          * @comp the component to check
 950          */
 951         private void repaintIfBackgroundsDiffer(JComponent comp) {
 952             ComponentUI ui = comp.getUI();
 953             if (ui instanceof SynthUI) {
 954                 SynthUI synthUI = (SynthUI)ui;
 955                 SynthContext context = synthUI.getContext(comp);
 956                 SynthStyle style = context.getStyle();
 957                 int state = context.getComponentState();
 958 
 959                 // Get the current background color.
 960                 Color currBG = style.getColor(context, ColorType.BACKGROUND);
 961 
 962                 // Get the last background color.
 963                 state ^= SynthConstants.FOCUSED;
 964                 context.setComponentState(state);
 965                 Color lastBG = style.getColor(context, ColorType.BACKGROUND);
 966 
 967                 // Reset the component state back to original.
 968                 state ^= SynthConstants.FOCUSED;
 969                 context.setComponentState(state);
 970 
 971                 // Repaint the component if the backgrounds differed.
 972                 if (currBG != null && !currBG.equals(lastBG)) {
 973                     comp.repaint();
 974                 }
 975                 context.dispose();
 976             }
 977         }
 978     }
 979 }