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