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 com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel;
  28 
  29 import java.awt.*;
  30 import java.beans.*;
  31 import java.io.*;
  32 import java.lang.ref.*;
  33 import java.net.*;
  34 import java.security.*;
  35 import java.text.*;
  36 import java.util.*;
  37 import javax.swing.*;
  38 import javax.swing.plaf.*;
  39 import javax.swing.plaf.basic.*;
  40 
  41 import sun.awt.*;
  42 import sun.security.action.*;
  43 import sun.swing.*;
  44 import sun.swing.plaf.synth.*;
  45 
  46 /**
  47  * SynthLookAndFeel provides the basis for creating a customized look and
  48  * feel. SynthLookAndFeel does not directly provide a look, all painting is
  49  * delegated.
  50  * You need to either provide a configuration file, by way of the
  51  * {@link #load} method, or provide your own {@link SynthStyleFactory}
  52  * to {@link #setStyleFactory}. Refer to the
  53  * <a href="package-summary.html">package summary</a> for an example of
  54  * loading a file, and {@link javax.swing.plaf.synth.SynthStyleFactory} for
  55  * an example of providing your own <code>SynthStyleFactory</code> to
  56  * <code>setStyleFactory</code>.
  57  * <p>
  58  * <strong>Warning:</strong>
  59  * This class implements {@link Serializable} as a side effect of it
  60  * extending {@link BasicLookAndFeel}. It is not intended to be serialized.
  61  * An attempt to serialize it will
  62  * result in {@link NotSerializableException}.
  63  *
  64  * @serial exclude
  65  * @since 1.5
  66  * @author Scott Violet
  67  */
  68 public class SynthLookAndFeel extends BasicLookAndFeel {
  69     /**
  70      * Used in a handful of places where we need an empty Insets.
  71      */
  72     static final Insets EMPTY_UIRESOURCE_INSETS = new InsetsUIResource(
  73                                                             0, 0, 0, 0);
  74 
  75     /**
  76      * AppContext key to get the current SynthStyleFactory.
  77      */
  78     private static final Object STYLE_FACTORY_KEY = new Object(); // 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 accomodate 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         // Note: The following Nimbus-specific call should be refactored
 246         // to be in the Nimbus LAF. Due to constraints in an update release,
 247         // we couldn't actually provide the public API necessary to allow
 248         // NimbusLookAndFeel (a subclass of SynthLookAndFeel) to provide its
 249         // own rules for shouldUpdateStyle.
 250         LookAndFeel laf = UIManager.getLookAndFeel();
 251         if (laf instanceof NimbusLookAndFeel &&
 252             ((NimbusLookAndFeel) laf).shouldUpdateStyleOnEvent(event)) {
 253             return true;
 254         }
 255 
 256         String eName = event.getPropertyName();
 257         if ("name" == eName) {
 258             // Always update on a name change
 259             return true;
 260         }
 261         else if ("componentOrientation" == eName) {
 262             // Always update on a component orientation change
 263             return true;
 264         }
 265         else if ("ancestor" == eName && event.getNewValue() != null) {
 266             // Only update on an ancestor change when getting a valid
 267             // parent and the LookAndFeel wants this.
 268             return (laf instanceof SynthLookAndFeel &&
 269                     ((SynthLookAndFeel)laf).
 270                      shouldUpdateStyleOnAncestorChanged());
 271         }
 272         return false;
 273     }
 274 
 275     /**
 276      * A convience method that will reset the Style of StyleContext if
 277      * necessary.
 278      *
 279      * @return newStyle
 280      */
 281     static SynthStyle updateStyle(SynthContext context, SynthUI ui) {
 282         SynthStyle newStyle = getStyle(context.getComponent(),
 283                                        context.getRegion());
 284         SynthStyle oldStyle = context.getStyle();
 285 
 286         if (newStyle != oldStyle) {
 287             if (oldStyle != null) {
 288                 oldStyle.uninstallDefaults(context);
 289             }
 290             context.setStyle(newStyle);
 291             newStyle.installDefaults(context, ui);
 292         }
 293         return newStyle;
 294     }
 295 
 296     /**
 297      * Updates the style associated with <code>c</code>, and all its children.
 298      * This is a lighter version of
 299      * <code>SwingUtilities.updateComponentTreeUI</code>.
 300      *
 301      * @param c Component to update style for.
 302      */
 303     public static void updateStyles(Component c) {
 304         _updateStyles(c);
 305         c.repaint();
 306     }
 307 
 308     // Implementation for updateStyles
 309     private static void _updateStyles(Component c) {
 310         if (c instanceof JComponent) {
 311             // Yes, this is hacky. A better solution is to get the UI
 312             // and cast, but JComponent doesn't expose a getter for the UI
 313             // (each of the UIs do), making that approach impractical.
 314             String name = c.getName();
 315             c.setName(null);
 316             if (name != null) {
 317                 c.setName(name);
 318             }
 319             ((JComponent)c).revalidate();
 320         }
 321         Component[] children = null;
 322         if (c instanceof JMenu) {
 323             children = ((JMenu)c).getMenuComponents();
 324         }
 325         else if (c instanceof Container) {
 326             children = ((Container)c).getComponents();
 327         }
 328         if (children != null) {
 329             for (Component child : children) {
 330                 updateStyles(child);
 331             }
 332         }
 333     }
 334 
 335     /**
 336      * Returns the Region for the JComponent <code>c</code>.
 337      *
 338      * @param c JComponent to fetch the Region for
 339      * @return Region corresponding to <code>c</code>
 340      */
 341     public static Region getRegion(JComponent c) {
 342         return Region.getRegion(c);
 343     }
 344 
 345     /**
 346      * A convenience method to return where the foreground should be
 347      * painted for the Component identified by the passed in
 348      * AbstractSynthContext.
 349      */
 350     static Insets getPaintingInsets(SynthContext state, Insets insets) {
 351         if (state.isSubregion()) {
 352             insets = state.getStyle().getInsets(state, insets);
 353         }
 354         else {
 355             insets = state.getComponent().getInsets(insets);
 356         }
 357         return insets;
 358     }
 359 
 360     /**
 361      * A convenience method that handles painting of the background.
 362      * All SynthUI implementations should override update and invoke
 363      * this method.
 364      */
 365     static void update(SynthContext state, Graphics g) {
 366         paintRegion(state, g, null);
 367     }
 368 
 369     /**
 370      * A convenience method that handles painting of the background for
 371      * subregions. All SynthUI's that have subregions should invoke
 372      * this method, than paint the foreground.
 373      */
 374     static void updateSubregion(SynthContext state, Graphics g,
 375                                 Rectangle bounds) {
 376         paintRegion(state, g, bounds);
 377     }
 378 
 379     private static void paintRegion(SynthContext state, Graphics g,
 380                                     Rectangle bounds) {
 381         JComponent c = state.getComponent();
 382         SynthStyle style = state.getStyle();
 383         int x, y, width, height;
 384 
 385         if (bounds == null) {
 386             x = 0;
 387             y = 0;
 388             width = c.getWidth();
 389             height = c.getHeight();
 390         }
 391         else {
 392             x = bounds.x;
 393             y = bounds.y;
 394             width = bounds.width;
 395             height = bounds.height;
 396         }
 397 
 398         // Fill in the background, if necessary.
 399         boolean subregion = state.isSubregion();
 400         if ((subregion && style.isOpaque(state)) ||
 401                           (!subregion && c.isOpaque())) {
 402             g.setColor(style.getColor(state, ColorType.BACKGROUND));
 403             g.fillRect(x, y, width, height);
 404         }
 405     }
 406 
 407     static boolean isLeftToRight(Component c) {
 408         return c.getComponentOrientation().isLeftToRight();
 409     }
 410 
 411     /**
 412      * Returns the ui that is of type <code>klass</code>, or null if
 413      * one can not be found.
 414      */
 415     static Object getUIOfType(ComponentUI ui, Class klass) {
 416         if (klass.isInstance(ui)) {
 417             return ui;
 418         }
 419         return null;
 420     }
 421 
 422     /**
 423      * Creates the Synth look and feel <code>ComponentUI</code> for
 424      * the passed in <code>JComponent</code>.
 425      *
 426      * @param c JComponent to create the <code>ComponentUI</code> for
 427      * @return ComponentUI to use for <code>c</code>
 428      */
 429     public static ComponentUI createUI(JComponent c) {
 430         String key = c.getUIClassID().intern();
 431 
 432         if (key == "ButtonUI") {
 433             return SynthButtonUI.createUI(c);
 434         }
 435         else if (key == "CheckBoxUI") {
 436             return SynthCheckBoxUI.createUI(c);
 437         }
 438         else if (key == "CheckBoxMenuItemUI") {
 439             return SynthCheckBoxMenuItemUI.createUI(c);
 440         }
 441         else if (key == "ColorChooserUI") {
 442             return SynthColorChooserUI.createUI(c);
 443         }
 444         else if (key == "ComboBoxUI") {
 445             return SynthComboBoxUI.createUI(c);
 446         }
 447         else if (key == "DesktopPaneUI") {
 448             return SynthDesktopPaneUI.createUI(c);
 449         }
 450         else if (key == "DesktopIconUI") {
 451             return SynthDesktopIconUI.createUI(c);
 452         }
 453         else if (key == "EditorPaneUI") {
 454             return SynthEditorPaneUI.createUI(c);
 455         }
 456         else if (key == "FileChooserUI") {
 457             return SynthFileChooserUI.createUI(c);
 458         }
 459         else if (key == "FormattedTextFieldUI") {
 460             return SynthFormattedTextFieldUI.createUI(c);
 461         }
 462         else if (key == "InternalFrameUI") {
 463             return SynthInternalFrameUI.createUI(c);
 464         }
 465         else if (key == "LabelUI") {
 466             return SynthLabelUI.createUI(c);
 467         }
 468         else if (key == "ListUI") {
 469             return SynthListUI.createUI(c);
 470         }
 471         else if (key == "MenuBarUI") {
 472             return SynthMenuBarUI.createUI(c);
 473         }
 474         else if (key == "MenuUI") {
 475             return SynthMenuUI.createUI(c);
 476         }
 477         else if (key == "MenuItemUI") {
 478             return SynthMenuItemUI.createUI(c);
 479         }
 480         else if (key == "OptionPaneUI") {
 481             return SynthOptionPaneUI.createUI(c);
 482         }
 483         else if (key == "PanelUI") {
 484             return SynthPanelUI.createUI(c);
 485         }
 486         else if (key == "PasswordFieldUI") {
 487             return SynthPasswordFieldUI.createUI(c);
 488         }
 489         else if (key == "PopupMenuSeparatorUI") {
 490             return SynthSeparatorUI.createUI(c);
 491         }
 492         else if (key == "PopupMenuUI") {
 493             return SynthPopupMenuUI.createUI(c);
 494         }
 495         else if (key == "ProgressBarUI") {
 496             return SynthProgressBarUI.createUI(c);
 497         }
 498         else if (key == "RadioButtonUI") {
 499             return SynthRadioButtonUI.createUI(c);
 500         }
 501         else if (key == "RadioButtonMenuItemUI") {
 502             return SynthRadioButtonMenuItemUI.createUI(c);
 503         }
 504         else if (key == "RootPaneUI") {
 505             return SynthRootPaneUI.createUI(c);
 506         }
 507         else if (key == "ScrollBarUI") {
 508             return SynthScrollBarUI.createUI(c);
 509         }
 510         else if (key == "ScrollPaneUI") {
 511             return SynthScrollPaneUI.createUI(c);
 512         }
 513         else if (key == "SeparatorUI") {
 514             return SynthSeparatorUI.createUI(c);
 515         }
 516         else if (key == "SliderUI") {
 517             return SynthSliderUI.createUI(c);
 518         }
 519         else if (key == "SpinnerUI") {
 520             return SynthSpinnerUI.createUI(c);
 521         }
 522         else if (key == "SplitPaneUI") {
 523             return SynthSplitPaneUI.createUI(c);
 524         }
 525         else if (key == "TabbedPaneUI") {
 526             return SynthTabbedPaneUI.createUI(c);
 527         }
 528         else if (key == "TableUI") {
 529             return SynthTableUI.createUI(c);
 530         }
 531         else if (key == "TableHeaderUI") {
 532             return SynthTableHeaderUI.createUI(c);
 533         }
 534         else if (key == "TextAreaUI") {
 535             return SynthTextAreaUI.createUI(c);
 536         }
 537         else if (key == "TextFieldUI") {
 538             return SynthTextFieldUI.createUI(c);
 539         }
 540         else if (key == "TextPaneUI") {
 541             return SynthTextPaneUI.createUI(c);
 542         }
 543         else if (key == "ToggleButtonUI") {
 544             return SynthToggleButtonUI.createUI(c);
 545         }
 546         else if (key == "ToolBarSeparatorUI") {
 547             return SynthSeparatorUI.createUI(c);
 548         }
 549         else if (key == "ToolBarUI") {
 550             return SynthToolBarUI.createUI(c);
 551         }
 552         else if (key == "ToolTipUI") {
 553             return SynthToolTipUI.createUI(c);
 554         }
 555         else if (key == "TreeUI") {
 556             return SynthTreeUI.createUI(c);
 557         }
 558         else if (key == "ViewportUI") {
 559             return SynthViewportUI.createUI(c);
 560         }
 561         return null;
 562     }
 563 
 564 
 565     /**
 566      * Creates a SynthLookAndFeel.
 567      * <p>
 568      * For the returned <code>SynthLookAndFeel</code> to be useful you need to
 569      * invoke <code>load</code> to specify the set of
 570      * <code>SynthStyle</code>s, or invoke <code>setStyleFactory</code>.
 571      *
 572      * @see #load
 573      * @see #setStyleFactory
 574      */
 575     public SynthLookAndFeel() {
 576         factory = new DefaultSynthStyleFactory();
 577         _handler = new Handler();
 578     }
 579 
 580     /**
 581      * Loads the set of <code>SynthStyle</code>s that will be used by
 582      * this <code>SynthLookAndFeel</code>. <code>resourceBase</code> is
 583      * used to resolve any path based resources, for example an
 584      * <code>Image</code> would be resolved by
 585      * <code>resourceBase.getResource(path)</code>. Refer to
 586      * <a href="doc-files/synthFileFormat.html">Synth File Format</a>
 587      * for more information.
 588      *
 589      * @param input InputStream to load from
 590      * @param resourceBase used to resolve any images or other resources
 591      * @throws ParseException if there is an error in parsing
 592      * @throws IllegalArgumentException if input or resourceBase is <code>null</code>
 593      */
 594     public void load(InputStream input, Class<?> resourceBase) throws
 595                        ParseException {
 596         if (resourceBase == null) {
 597             throw new IllegalArgumentException(
 598                 "You must supply a valid resource base Class");
 599         }
 600 
 601         if (defaultsMap == null) {
 602             defaultsMap = new HashMap<String, Object>();
 603         }
 604 
 605         new SynthParser().parse(input, (DefaultSynthStyleFactory) factory,
 606                                 null, resourceBase, defaultsMap);
 607     }
 608 
 609     /**
 610      * Loads the set of <code>SynthStyle</code>s that will be used by
 611      * this <code>SynthLookAndFeel</code>. Path based resources are resolved
 612      * relatively to the specified <code>URL</code> of the style. For example
 613      * an <code>Image</code> would be resolved by
 614      * <code>new URL(synthFile, path)</code>. Refer to
 615      * <a href="doc-files/synthFileFormat.html">Synth File Format</a> for more
 616      * information.
 617      *
 618      * @param url the <code>URL</code> to load the set of
 619      *     <code>SynthStyle</code> from
 620      * @throws ParseException if there is an error in parsing
 621      * @throws IllegalArgumentException if synthSet is <code>null</code>
 622      * @throws IOException if synthSet cannot be opened as an <code>InputStream</code>
 623      * @since 1.6
 624      */
 625     public void load(URL url) throws ParseException, IOException {
 626         if (url == null) {
 627             throw new IllegalArgumentException(
 628                 "You must supply a valid Synth set URL");
 629         }
 630 
 631         if (defaultsMap == null) {
 632             defaultsMap = new HashMap<String, Object>();
 633         }
 634 
 635         InputStream input = url.openStream();
 636         new SynthParser().parse(input, (DefaultSynthStyleFactory) factory,
 637                                 url, null, defaultsMap);
 638     }
 639 
 640     /**
 641      * Called by UIManager when this look and feel is installed.
 642      */
 643     @Override
 644     public void initialize() {
 645         super.initialize();
 646         DefaultLookup.setDefaultLookup(new SynthDefaultLookup());
 647         setStyleFactory(factory);
 648         KeyboardFocusManager.getCurrentKeyboardFocusManager().
 649             addPropertyChangeListener(_handler);
 650     }
 651 
 652     /**
 653      * Called by UIManager when this look and feel is uninstalled.
 654      */
 655     @Override
 656     public void uninitialize() {
 657         KeyboardFocusManager.getCurrentKeyboardFocusManager().
 658             removePropertyChangeListener(_handler);
 659         // We should uninstall the StyleFactory here, but unfortunately
 660         // there are a handful of things that retain references to the
 661         // LookAndFeel and expect things to work
 662         super.uninitialize();
 663     }
 664 
 665     /**
 666      * Returns the defaults for this SynthLookAndFeel.
 667      *
 668      * @return Defaults table.
 669      */
 670     @Override
 671     public UIDefaults getDefaults() {
 672         UIDefaults table = new UIDefaults(60, 0.75f);
 673 
 674         Region.registerUIs(table);
 675         table.setDefaultLocale(Locale.getDefault());
 676         table.addResourceBundle(
 677               "com.sun.swing.internal.plaf.basic.resources.basic" );
 678         table.addResourceBundle("com.sun.swing.internal.plaf.synth.resources.synth");
 679 
 680         // SynthTabbedPaneUI supports rollover on tabs, GTK does not
 681         table.put("TabbedPane.isTabRollover", Boolean.TRUE);
 682 
 683         // These need to be defined for JColorChooser to work.
 684         table.put("ColorChooser.swatchesRecentSwatchSize",
 685                   new Dimension(10, 10));
 686         table.put("ColorChooser.swatchesDefaultRecentColor", Color.RED);
 687         table.put("ColorChooser.swatchesSwatchSize", new Dimension(10, 10));
 688 
 689         // These need to be defined for ImageView.
 690         table.put("html.pendingImage", SwingUtilities2.makeIcon(getClass(),
 691                                 BasicLookAndFeel.class,
 692                                 "icons/image-delayed.png"));
 693         table.put("html.missingImage", SwingUtilities2.makeIcon(getClass(),
 694                                 BasicLookAndFeel.class,
 695                                 "icons/image-failed.png"));
 696 
 697         // These are needed for PopupMenu.
 698         table.put("PopupMenu.selectedWindowInputMapBindings", new Object[] {
 699                   "ESCAPE", "cancel",
 700                     "DOWN", "selectNext",
 701                  "KP_DOWN", "selectNext",
 702                       "UP", "selectPrevious",
 703                    "KP_UP", "selectPrevious",
 704                     "LEFT", "selectParent",
 705                  "KP_LEFT", "selectParent",
 706                    "RIGHT", "selectChild",
 707                 "KP_RIGHT", "selectChild",
 708                    "ENTER", "return",
 709                    "SPACE", "return"
 710         });
 711         table.put("PopupMenu.selectedWindowInputMapBindings.RightToLeft",
 712                   new Object[] {
 713                     "LEFT", "selectChild",
 714                  "KP_LEFT", "selectChild",
 715                    "RIGHT", "selectParent",
 716                 "KP_RIGHT", "selectParent",
 717                   });
 718 
 719         // enabled antialiasing depending on desktop settings
 720         flushUnreferenced();
 721         Object aaTextInfo = getAATextInfo();
 722         table.put(SwingUtilities2.AA_TEXT_PROPERTY_KEY, aaTextInfo);
 723         new AATextListener(this);
 724 
 725         if (defaultsMap != null) {
 726             table.putAll(defaultsMap);
 727         }
 728         return table;
 729     }
 730 
 731     /**
 732      * Returns true, SynthLookAndFeel is always supported.
 733      *
 734      * @return true.
 735      */
 736     @Override
 737     public boolean isSupportedLookAndFeel() {
 738         return true;
 739     }
 740 
 741     /**
 742      * Returns false, SynthLookAndFeel is not a native look and feel.
 743      *
 744      * @return false
 745      */
 746     @Override
 747     public boolean isNativeLookAndFeel() {
 748         return false;
 749     }
 750 
 751     /**
 752      * Returns a textual description of SynthLookAndFeel.
 753      *
 754      * @return textual description of synth.
 755      */
 756     @Override
 757     public String getDescription() {
 758         return "Synth look and feel";
 759     }
 760 
 761     /**
 762      * Return a short string that identifies this look and feel.
 763      *
 764      * @return a short string identifying this look and feel.
 765      */
 766     @Override
 767     public String getName() {
 768         return "Synth look and feel";
 769     }
 770 
 771     /**
 772      * Return a string that identifies this look and feel.
 773      *
 774      * @return a short string identifying this look and feel.
 775      */
 776     @Override
 777     public String getID() {
 778         return "Synth";
 779     }
 780 
 781     /**
 782      * Returns whether or not the UIs should update their
 783      * <code>SynthStyles</code> from the <code>SynthStyleFactory</code>
 784      * when the ancestor of the <code>JComponent</code> changes. A subclass
 785      * that provided a <code>SynthStyleFactory</code> that based the
 786      * return value from <code>getStyle</code> off the containment hierarchy
 787      * would override this method to return true.
 788      *
 789      * @return whether or not the UIs should update their
 790      * <code>SynthStyles</code> from the <code>SynthStyleFactory</code>
 791      * when the ancestor changed.
 792      */
 793     public boolean shouldUpdateStyleOnAncestorChanged() {
 794         return false;
 795     }
 796 
 797     /**
 798      * Returns the antialiasing information as specified by the host desktop.
 799      * Antialiasing might be forced off if the desktop is GNOME and the user
 800      * has set his locale to Chinese, Japanese or Korean. This is consistent
 801      * with what GTK does. See com.sun.java.swing.plaf.gtk.GtkLookAndFeel
 802      * for more information about CJK and antialiased fonts.
 803      *
 804      * @return the text antialiasing information associated to the desktop
 805      */
 806     private static Object getAATextInfo() {
 807         String language = Locale.getDefault().getLanguage();
 808         String desktop =
 809             AccessController.doPrivileged(new GetPropertyAction("sun.desktop"));
 810 
 811         boolean isCjkLocale = (Locale.CHINESE.getLanguage().equals(language) ||
 812                 Locale.JAPANESE.getLanguage().equals(language) ||
 813                 Locale.KOREAN.getLanguage().equals(language));
 814         boolean isGnome = "gnome".equals(desktop);
 815         boolean isLocal = SwingUtilities2.isLocalDisplay();
 816 
 817         boolean setAA = isLocal && (!isGnome || !isCjkLocale);
 818 
 819         Object aaTextInfo = SwingUtilities2.AATextInfo.getAATextInfo(setAA);
 820         return aaTextInfo;
 821     }
 822 
 823     private static ReferenceQueue<LookAndFeel> queue = new ReferenceQueue<LookAndFeel>();
 824 
 825     private static void flushUnreferenced() {
 826         AATextListener aatl;
 827         while ((aatl = (AATextListener) queue.poll()) != null) {
 828             aatl.dispose();
 829         }
 830     }
 831 
 832     private static class AATextListener
 833         extends WeakReference<LookAndFeel> implements PropertyChangeListener {
 834         private String key = SunToolkit.DESKTOPFONTHINTS;
 835 
 836         AATextListener(LookAndFeel laf) {
 837             super(laf, queue);
 838             Toolkit tk = Toolkit.getDefaultToolkit();
 839             tk.addPropertyChangeListener(key, this);
 840         }
 841 
 842         @Override
 843         public void propertyChange(PropertyChangeEvent pce) {
 844             UIDefaults defaults = UIManager.getLookAndFeelDefaults();
 845             if (defaults.getBoolean("Synth.doNotSetTextAA")) {
 846                 dispose();
 847                 return;
 848             }
 849 
 850             LookAndFeel laf = get();
 851             if (laf == null || laf != UIManager.getLookAndFeel()) {
 852                 dispose();
 853                 return;
 854             }
 855 
 856             Object aaTextInfo = getAATextInfo();
 857             defaults.put(SwingUtilities2.AA_TEXT_PROPERTY_KEY, aaTextInfo);
 858 
 859             updateUI();
 860         }
 861 
 862         void dispose() {
 863             Toolkit tk = Toolkit.getDefaultToolkit();
 864             tk.removePropertyChangeListener(key, this);
 865         }
 866 
 867         /**
 868          * Updates the UI of the passed in window and all its children.
 869          */
 870         private static void updateWindowUI(Window window) {
 871             updateStyles(window);
 872             Window ownedWins[] = window.getOwnedWindows();
 873             for (Window w : ownedWins) {
 874                 updateWindowUI(w);
 875             }
 876         }
 877 
 878         /**
 879          * Updates the UIs of all the known Frames.
 880          */
 881         private static void updateAllUIs() {
 882             Frame appFrames[] = Frame.getFrames();
 883             for (Frame frame : appFrames) {
 884                 updateWindowUI(frame);
 885             }
 886         }
 887 
 888         /**
 889          * Indicates if an updateUI call is pending.
 890          */
 891         private static boolean updatePending;
 892 
 893         /**
 894          * Sets whether or not an updateUI call is pending.
 895          */
 896         private static synchronized void setUpdatePending(boolean update) {
 897             updatePending = update;
 898         }
 899 
 900         /**
 901          * Returns true if a UI update is pending.
 902          */
 903         private static synchronized boolean isUpdatePending() {
 904             return updatePending;
 905         }
 906 
 907         protected void updateUI() {
 908             if (!isUpdatePending()) {
 909                 setUpdatePending(true);
 910                 Runnable uiUpdater = new Runnable() {
 911                     @Override
 912                     public void run() {
 913                         updateAllUIs();
 914                         setUpdatePending(false);
 915                     }
 916                 };
 917                 SwingUtilities.invokeLater(uiUpdater);
 918             }
 919         }
 920     }
 921 
 922     private void writeObject(java.io.ObjectOutputStream out)
 923             throws IOException {
 924         throw new NotSerializableException(this.getClass().getName());
 925     }
 926 
 927     private class Handler implements PropertyChangeListener {
 928         @Override
 929         public void propertyChange(PropertyChangeEvent evt) {
 930             String propertyName = evt.getPropertyName();
 931             Object newValue = evt.getNewValue();
 932             Object oldValue = evt.getOldValue();
 933 
 934             if ("focusOwner" == propertyName) {
 935                 if (oldValue instanceof JComponent) {
 936                     repaintIfBackgroundsDiffer((JComponent)oldValue);
 937 
 938                 }
 939 
 940                 if (newValue instanceof JComponent) {
 941                     repaintIfBackgroundsDiffer((JComponent)newValue);
 942                 }
 943             }
 944             else if ("managingFocus" == propertyName) {
 945                 // De-register listener on old keyboard focus manager and
 946                 // register it on the new one.
 947                 KeyboardFocusManager manager =
 948                     (KeyboardFocusManager)evt.getSource();
 949                 if (newValue.equals(Boolean.FALSE)) {
 950                     manager.removePropertyChangeListener(_handler);
 951                 }
 952                 else {
 953                     manager.addPropertyChangeListener(_handler);
 954                 }
 955             }
 956         }
 957 
 958         /**
 959          * This is a support method that will check if the background colors of
 960          * the specified component differ between focused and unfocused states.
 961          * If the color differ the component will then repaint itself.
 962          *
 963          * @comp the component to check
 964          */
 965         private void repaintIfBackgroundsDiffer(JComponent comp) {
 966             ComponentUI ui = (ComponentUI)comp.getClientProperty(
 967                     SwingUtilities2.COMPONENT_UI_PROPERTY_KEY);
 968             if (ui instanceof SynthUI) {
 969                 SynthUI synthUI = (SynthUI)ui;
 970                 SynthContext context = synthUI.getContext(comp);
 971                 SynthStyle style = context.getStyle();
 972                 int state = context.getComponentState();
 973 
 974                 // Get the current background color.
 975                 Color currBG = style.getColor(context, ColorType.BACKGROUND);
 976 
 977                 // Get the last background color.
 978                 state ^= SynthConstants.FOCUSED;
 979                 context.setComponentState(state);
 980                 Color lastBG = style.getColor(context, ColorType.BACKGROUND);
 981 
 982                 // Reset the component state back to original.
 983                 state ^= SynthConstants.FOCUSED;
 984                 context.setComponentState(state);
 985 
 986                 // Repaint the component if the backgrounds differed.
 987                 if (currBG != null && !currBG.equals(lastBG)) {
 988                     comp.repaint();
 989                 }
 990                 context.dispose();
 991             }
 992         }
 993     }
 994 }