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