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