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