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 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
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(int i = 0; i < children.length; i++) {
330 updateStyles(children[i]);
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) {
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();
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();
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 /**
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 = (String)
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 queue = new ReferenceQueue();
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 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 = (LookAndFeel) 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 (int i = 0; i < ownedWins.length; i++) {
874 updateWindowUI(ownedWins[i]);
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 (int i = 0; i < appFrames.length; i++) {
884 updateWindowUI(appFrames[i]);
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;
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 (((Boolean)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;
|
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
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) {
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 /**
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;
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;
|