blob: bece289030f170239fdd2ca94896d1a951ffa609 [file] [log] [blame]
/*
* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing.plaf.synth;
import java.awt.*;
import java.beans.*;
import java.io.*;
import java.lang.ref.*;
import java.net.*;
import java.security.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;
import sun.awt.*;
import sun.security.action.*;
import sun.swing.*;
import sun.swing.plaf.synth.*;
/**
* SynthLookAndFeel provides the basis for creating a customized look and
* feel. SynthLookAndFeel does not directly provide a look, all painting is
* delegated.
* You need to either provide a configuration file, by way of the
* {@link #load} method, or provide your own {@link SynthStyleFactory}
* to {@link #setStyleFactory}. Refer to the
* <a href="package-summary.html">package summary</a> for an example of
* loading a file, and {@link javax.swing.plaf.synth.SynthStyleFactory} for
* an example of providing your own <code>SynthStyleFactory</code> to
* <code>setStyleFactory</code>.
* <p>
* <strong>Warning:</strong>
* This class implements {@link Serializable} as a side effect of it
* extending {@link BasicLookAndFeel}. It is not intended to be serialized.
* An attempt to serialize it will
* result in {@link NotSerializableException}.
*
* @serial exclude
* @since 1.5
* @author Scott Violet
*/
public class SynthLookAndFeel extends BasicLookAndFeel {
/**
* Used in a handful of places where we need an empty Insets.
*/
static final Insets EMPTY_UIRESOURCE_INSETS = new InsetsUIResource(
0, 0, 0, 0);
/**
* AppContext key to get the current SynthStyleFactory.
*/
private static final Object STYLE_FACTORY_KEY =
new StringBuffer("com.sun.java.swing.plaf.gtk.StyleCache");
/**
* AppContext key to get selectedUI.
*/
private static final Object SELECTED_UI_KEY = new StringBuilder("selectedUI");
/**
* AppContext key to get selectedUIState.
*/
private static final Object SELECTED_UI_STATE_KEY = new StringBuilder("selectedUIState");
/**
* The last SynthStyleFactory that was asked for from AppContext
* <code>lastContext</code>.
*/
private static SynthStyleFactory lastFactory;
/**
* AppContext lastLAF came from.
*/
private static AppContext lastContext;
/**
* SynthStyleFactory for the this SynthLookAndFeel.
*/
private SynthStyleFactory factory;
/**
* Map of defaults table entries. This is populated via the load
* method.
*/
private Map<String, Object> defaultsMap;
private Handler _handler;
static ComponentUI getSelectedUI() {
return (ComponentUI) AppContext.getAppContext().get(SELECTED_UI_KEY);
}
/**
* Used by the renderers. For the most part the renderers are implemented
* as Labels, which is problematic in so far as they are never selected.
* To accomodate this SynthLabelUI checks if the current
* UI matches that of <code>selectedUI</code> (which this methods sets), if
* it does, then a state as set by this method is returned. This provides
* a way for labels to have a state other than selected.
*/
static void setSelectedUI(ComponentUI uix, boolean selected,
boolean focused, boolean enabled,
boolean rollover) {
int selectedUIState = 0;
if (selected) {
selectedUIState = SynthConstants.SELECTED;
if (focused) {
selectedUIState |= SynthConstants.FOCUSED;
}
}
else if (rollover && enabled) {
selectedUIState |=
SynthConstants.MOUSE_OVER | SynthConstants.ENABLED;
if (focused) {
selectedUIState |= SynthConstants.FOCUSED;
}
}
else {
if (enabled) {
selectedUIState |= SynthConstants.ENABLED;
if (focused) {
selectedUIState |= SynthConstants.FOCUSED;
}
}
else {
selectedUIState |= SynthConstants.DISABLED;
}
}
AppContext context = AppContext.getAppContext();
context.put(SELECTED_UI_KEY, uix);
context.put(SELECTED_UI_STATE_KEY, Integer.valueOf(selectedUIState));
}
static int getSelectedUIState() {
Integer result = (Integer) AppContext.getAppContext().get(SELECTED_UI_STATE_KEY);
return result == null ? 0 : result.intValue();
}
/**
* Clears out the selected UI that was last set in setSelectedUI.
*/
static void resetSelectedUI() {
AppContext.getAppContext().remove(SELECTED_UI_KEY);
}
/**
* Sets the SynthStyleFactory that the UI classes provided by
* synth will use to obtain a SynthStyle.
*
* @param cache SynthStyleFactory the UIs should use.
*/
public static void setStyleFactory(SynthStyleFactory cache) {
// We assume the setter is called BEFORE the getter has been invoked
// for a particular AppContext.
synchronized(SynthLookAndFeel.class) {
AppContext context = AppContext.getAppContext();
lastFactory = cache;
lastContext = context;
context.put(STYLE_FACTORY_KEY, cache);
}
}
/**
* Returns the current SynthStyleFactory.
*
* @return SynthStyleFactory
*/
public static SynthStyleFactory getStyleFactory() {
synchronized(SynthLookAndFeel.class) {
AppContext context = AppContext.getAppContext();
if (lastContext == context) {
return lastFactory;
}
lastContext = context;
lastFactory = (SynthStyleFactory) context.get(STYLE_FACTORY_KEY);
return lastFactory;
}
}
/**
* Returns the component state for the specified component. This should
* only be used for Components that don't have any special state beyond
* that of ENABLED, DISABLED or FOCUSED. For example, buttons shouldn't
* call into this method.
*/
static int getComponentState(Component c) {
if (c.isEnabled()) {
if (c.isFocusOwner()) {
return SynthUI.ENABLED | SynthUI.FOCUSED;
}
return SynthUI.ENABLED;
}
return SynthUI.DISABLED;
}
/**
* Gets a SynthStyle for the specified region of the specified component.
* This is not for general consumption, only custom UIs should call this
* method.
*
* @param c JComponent to get the SynthStyle for
* @param region Identifies the region of the specified component
* @return SynthStyle to use.
*/
public static SynthStyle getStyle(JComponent c, Region region) {
return getStyleFactory().getStyle(c, region);
}
/**
* Returns true if the Style should be updated in response to the
* specified PropertyChangeEvent. This forwards to
* <code>shouldUpdateStyleOnAncestorChanged</code> as necessary.
*/
static boolean shouldUpdateStyle(PropertyChangeEvent event) {
LookAndFeel laf = UIManager.getLookAndFeel();
return (laf instanceof SynthLookAndFeel &&
((SynthLookAndFeel) laf).shouldUpdateStyleOnEvent(event));
}
/**
* A convience method that will reset the Style of StyleContext if
* necessary.
*
* @return newStyle
*/
static SynthStyle updateStyle(SynthContext context, SynthUI ui) {
SynthStyle newStyle = getStyle(context.getComponent(),
context.getRegion());
SynthStyle oldStyle = context.getStyle();
if (newStyle != oldStyle) {
if (oldStyle != null) {
oldStyle.uninstallDefaults(context);
}
context.setStyle(newStyle);
newStyle.installDefaults(context, ui);
}
return newStyle;
}
/**
* Updates the style associated with <code>c</code>, and all its children.
* This is a lighter version of
* <code>SwingUtilities.updateComponentTreeUI</code>.
*
* @param c Component to update style for.
*/
public static void updateStyles(Component c) {
if (c instanceof JComponent) {
// Yes, this is hacky. A better solution is to get the UI
// and cast, but JComponent doesn't expose a getter for the UI
// (each of the UIs do), making that approach impractical.
String name = c.getName();
c.setName(null);
if (name != null) {
c.setName(name);
}
((JComponent)c).revalidate();
}
Component[] children = null;
if (c instanceof JMenu) {
children = ((JMenu)c).getMenuComponents();
}
else if (c instanceof Container) {
children = ((Container)c).getComponents();
}
if (children != null) {
for (Component child : children) {
updateStyles(child);
}
}
c.repaint();
}
/**
* Returns the Region for the JComponent <code>c</code>.
*
* @param c JComponent to fetch the Region for
* @return Region corresponding to <code>c</code>
*/
public static Region getRegion(JComponent c) {
return Region.getRegion(c);
}
/**
* A convenience method to return where the foreground should be
* painted for the Component identified by the passed in
* AbstractSynthContext.
*/
static Insets getPaintingInsets(SynthContext state, Insets insets) {
if (state.isSubregion()) {
insets = state.getStyle().getInsets(state, insets);
}
else {
insets = state.getComponent().getInsets(insets);
}
return insets;
}
/**
* A convenience method that handles painting of the background.
* All SynthUI implementations should override update and invoke
* this method.
*/
static void update(SynthContext state, Graphics g) {
paintRegion(state, g, null);
}
/**
* A convenience method that handles painting of the background for
* subregions. All SynthUI's that have subregions should invoke
* this method, than paint the foreground.
*/
static void updateSubregion(SynthContext state, Graphics g,
Rectangle bounds) {
paintRegion(state, g, bounds);
}
private static void paintRegion(SynthContext state, Graphics g,
Rectangle bounds) {
JComponent c = state.getComponent();
SynthStyle style = state.getStyle();
int x, y, width, height;
if (bounds == null) {
x = 0;
y = 0;
width = c.getWidth();
height = c.getHeight();
}
else {
x = bounds.x;
y = bounds.y;
width = bounds.width;
height = bounds.height;
}
// Fill in the background, if necessary.
boolean subregion = state.isSubregion();
if ((subregion && style.isOpaque(state)) ||
(!subregion && c.isOpaque())) {
g.setColor(style.getColor(state, ColorType.BACKGROUND));
g.fillRect(x, y, width, height);
}
}
static boolean isLeftToRight(Component c) {
return c.getComponentOrientation().isLeftToRight();
}
/**
* Returns the ui that is of type <code>klass</code>, or null if
* one can not be found.
*/
static Object getUIOfType(ComponentUI ui, Class klass) {
if (klass.isInstance(ui)) {
return ui;
}
return null;
}
/**
* Creates the Synth look and feel <code>ComponentUI</code> for
* the passed in <code>JComponent</code>.
*
* @param c JComponent to create the <code>ComponentUI</code> for
* @return ComponentUI to use for <code>c</code>
*/
public static ComponentUI createUI(JComponent c) {
String key = c.getUIClassID().intern();
if (key == "ButtonUI") {
return SynthButtonUI.createUI(c);
}
else if (key == "CheckBoxUI") {
return SynthCheckBoxUI.createUI(c);
}
else if (key == "CheckBoxMenuItemUI") {
return SynthCheckBoxMenuItemUI.createUI(c);
}
else if (key == "ColorChooserUI") {
return SynthColorChooserUI.createUI(c);
}
else if (key == "ComboBoxUI") {
return SynthComboBoxUI.createUI(c);
}
else if (key == "DesktopPaneUI") {
return SynthDesktopPaneUI.createUI(c);
}
else if (key == "DesktopIconUI") {
return SynthDesktopIconUI.createUI(c);
}
else if (key == "EditorPaneUI") {
return SynthEditorPaneUI.createUI(c);
}
else if (key == "FileChooserUI") {
return SynthFileChooserUI.createUI(c);
}
else if (key == "FormattedTextFieldUI") {
return SynthFormattedTextFieldUI.createUI(c);
}
else if (key == "InternalFrameUI") {
return SynthInternalFrameUI.createUI(c);
}
else if (key == "LabelUI") {
return SynthLabelUI.createUI(c);
}
else if (key == "ListUI") {
return SynthListUI.createUI(c);
}
else if (key == "MenuBarUI") {
return SynthMenuBarUI.createUI(c);
}
else if (key == "MenuUI") {
return SynthMenuUI.createUI(c);
}
else if (key == "MenuItemUI") {
return SynthMenuItemUI.createUI(c);
}
else if (key == "OptionPaneUI") {
return SynthOptionPaneUI.createUI(c);
}
else if (key == "PanelUI") {
return SynthPanelUI.createUI(c);
}
else if (key == "PasswordFieldUI") {
return SynthPasswordFieldUI.createUI(c);
}
else if (key == "PopupMenuSeparatorUI") {
return SynthSeparatorUI.createUI(c);
}
else if (key == "PopupMenuUI") {
return SynthPopupMenuUI.createUI(c);
}
else if (key == "ProgressBarUI") {
return SynthProgressBarUI.createUI(c);
}
else if (key == "RadioButtonUI") {
return SynthRadioButtonUI.createUI(c);
}
else if (key == "RadioButtonMenuItemUI") {
return SynthRadioButtonMenuItemUI.createUI(c);
}
else if (key == "RootPaneUI") {
return SynthRootPaneUI.createUI(c);
}
else if (key == "ScrollBarUI") {
return SynthScrollBarUI.createUI(c);
}
else if (key == "ScrollPaneUI") {
return SynthScrollPaneUI.createUI(c);
}
else if (key == "SeparatorUI") {
return SynthSeparatorUI.createUI(c);
}
else if (key == "SliderUI") {
return SynthSliderUI.createUI(c);
}
else if (key == "SpinnerUI") {
return SynthSpinnerUI.createUI(c);
}
else if (key == "SplitPaneUI") {
return SynthSplitPaneUI.createUI(c);
}
else if (key == "TabbedPaneUI") {
return SynthTabbedPaneUI.createUI(c);
}
else if (key == "TableUI") {
return SynthTableUI.createUI(c);
}
else if (key == "TableHeaderUI") {
return SynthTableHeaderUI.createUI(c);
}
else if (key == "TextAreaUI") {
return SynthTextAreaUI.createUI(c);
}
else if (key == "TextFieldUI") {
return SynthTextFieldUI.createUI(c);
}
else if (key == "TextPaneUI") {
return SynthTextPaneUI.createUI(c);
}
else if (key == "ToggleButtonUI") {
return SynthToggleButtonUI.createUI(c);
}
else if (key == "ToolBarSeparatorUI") {
return SynthSeparatorUI.createUI(c);
}
else if (key == "ToolBarUI") {
return SynthToolBarUI.createUI(c);
}
else if (key == "ToolTipUI") {
return SynthToolTipUI.createUI(c);
}
else if (key == "TreeUI") {
return SynthTreeUI.createUI(c);
}
else if (key == "ViewportUI") {
return SynthViewportUI.createUI(c);
}
return null;
}
/**
* Creates a SynthLookAndFeel.
* <p>
* For the returned <code>SynthLookAndFeel</code> to be useful you need to
* invoke <code>load</code> to specify the set of
* <code>SynthStyle</code>s, or invoke <code>setStyleFactory</code>.
*
* @see #load
* @see #setStyleFactory
*/
public SynthLookAndFeel() {
factory = new DefaultSynthStyleFactory();
_handler = new Handler();
}
/**
* Loads the set of <code>SynthStyle</code>s that will be used by
* this <code>SynthLookAndFeel</code>. <code>resourceBase</code> is
* used to resolve any path based resources, for example an
* <code>Image</code> would be resolved by
* <code>resourceBase.getResource(path)</code>. Refer to
* <a href="doc-files/synthFileFormat.html">Synth File Format</a>
* for more information.
*
* @param input InputStream to load from
* @param resourceBase used to resolve any images or other resources
* @throws ParseException if there is an error in parsing
* @throws IllegalArgumentException if input or resourceBase is <code>null</code>
*/
public void load(InputStream input, Class<?> resourceBase) throws
ParseException {
if (resourceBase == null) {
throw new IllegalArgumentException(
"You must supply a valid resource base Class");
}
if (defaultsMap == null) {
defaultsMap = new HashMap<String, Object>();
}
new SynthParser().parse(input, (DefaultSynthStyleFactory) factory,
null, resourceBase, defaultsMap);
}
/**
* Loads the set of <code>SynthStyle</code>s that will be used by
* this <code>SynthLookAndFeel</code>. Path based resources are resolved
* relatively to the specified <code>URL</code> of the style. For example
* an <code>Image</code> would be resolved by
* <code>new URL(synthFile, path)</code>. Refer to
* <a href="doc-files/synthFileFormat.html">Synth File Format</a> for more
* information.
*
* @param url the <code>URL</code> to load the set of
* <code>SynthStyle</code> from
* @throws ParseException if there is an error in parsing
* @throws IllegalArgumentException if synthSet is <code>null</code>
* @throws IOException if synthSet cannot be opened as an <code>InputStream</code>
* @since 1.6
*/
public void load(URL url) throws ParseException, IOException {
if (url == null) {
throw new IllegalArgumentException(
"You must supply a valid Synth set URL");
}
if (defaultsMap == null) {
defaultsMap = new HashMap<String, Object>();
}
InputStream input = url.openStream();
new SynthParser().parse(input, (DefaultSynthStyleFactory) factory,
url, null, defaultsMap);
}
/**
* Called by UIManager when this look and feel is installed.
*/
@Override
public void initialize() {
super.initialize();
DefaultLookup.setDefaultLookup(new SynthDefaultLookup());
setStyleFactory(factory);
KeyboardFocusManager.getCurrentKeyboardFocusManager().
addPropertyChangeListener(_handler);
}
/**
* Called by UIManager when this look and feel is uninstalled.
*/
@Override
public void uninitialize() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().
removePropertyChangeListener(_handler);
// We should uninstall the StyleFactory here, but unfortunately
// there are a handful of things that retain references to the
// LookAndFeel and expect things to work
super.uninitialize();
}
/**
* Returns the defaults for this SynthLookAndFeel.
*
* @return Defaults table.
*/
@Override
public UIDefaults getDefaults() {
UIDefaults table = new UIDefaults(60, 0.75f);
Region.registerUIs(table);
table.setDefaultLocale(Locale.getDefault());
table.addResourceBundle(
"com.sun.swing.internal.plaf.basic.resources.basic" );
table.addResourceBundle("com.sun.swing.internal.plaf.synth.resources.synth");
// SynthTabbedPaneUI supports rollover on tabs, GTK does not
table.put("TabbedPane.isTabRollover", Boolean.TRUE);
// These need to be defined for JColorChooser to work.
table.put("ColorChooser.swatchesRecentSwatchSize",
new Dimension(10, 10));
table.put("ColorChooser.swatchesDefaultRecentColor", Color.RED);
table.put("ColorChooser.swatchesSwatchSize", new Dimension(10, 10));
// These need to be defined for ImageView.
table.put("html.pendingImage", SwingUtilities2.makeIcon(getClass(),
BasicLookAndFeel.class,
"icons/image-delayed.png"));
table.put("html.missingImage", SwingUtilities2.makeIcon(getClass(),
BasicLookAndFeel.class,
"icons/image-failed.png"));
// These are needed for PopupMenu.
table.put("PopupMenu.selectedWindowInputMapBindings", new Object[] {
"ESCAPE", "cancel",
"DOWN", "selectNext",
"KP_DOWN", "selectNext",
"UP", "selectPrevious",
"KP_UP", "selectPrevious",
"LEFT", "selectParent",
"KP_LEFT", "selectParent",
"RIGHT", "selectChild",
"KP_RIGHT", "selectChild",
"ENTER", "return",
"SPACE", "return"
});
table.put("PopupMenu.selectedWindowInputMapBindings.RightToLeft",
new Object[] {
"LEFT", "selectChild",
"KP_LEFT", "selectChild",
"RIGHT", "selectParent",
"KP_RIGHT", "selectParent",
});
// enabled antialiasing depending on desktop settings
flushUnreferenced();
Object aaTextInfo = getAATextInfo();
table.put(SwingUtilities2.AA_TEXT_PROPERTY_KEY, aaTextInfo);
new AATextListener(this);
if (defaultsMap != null) {
table.putAll(defaultsMap);
}
return table;
}
/**
* Returns true, SynthLookAndFeel is always supported.
*
* @return true.
*/
@Override
public boolean isSupportedLookAndFeel() {
return true;
}
/**
* Returns false, SynthLookAndFeel is not a native look and feel.
*
* @return false
*/
@Override
public boolean isNativeLookAndFeel() {
return false;
}
/**
* Returns a textual description of SynthLookAndFeel.
*
* @return textual description of synth.
*/
@Override
public String getDescription() {
return "Synth look and feel";
}
/**
* Return a short string that identifies this look and feel.
*
* @return a short string identifying this look and feel.
*/
@Override
public String getName() {
return "Synth look and feel";
}
/**
* Return a string that identifies this look and feel.
*
* @return a short string identifying this look and feel.
*/
@Override
public String getID() {
return "Synth";
}
/**
* Returns whether or not the UIs should update their
* <code>SynthStyles</code> from the <code>SynthStyleFactory</code>
* when the ancestor of the <code>JComponent</code> changes. A subclass
* that provided a <code>SynthStyleFactory</code> that based the
* return value from <code>getStyle</code> off the containment hierarchy
* would override this method to return true.
*
* @return whether or not the UIs should update their
* <code>SynthStyles</code> from the <code>SynthStyleFactory</code>
* when the ancestor changed.
*/
public boolean shouldUpdateStyleOnAncestorChanged() {
return false;
}
/**
* Returns whether or not the UIs should update their styles when a
* particular event occurs.
*
* @param ev a {@code PropertyChangeEvent}
* @return whether or not the UIs should update their styles
* @since 1.7
*/
protected boolean shouldUpdateStyleOnEvent(PropertyChangeEvent ev) {
String eName = ev.getPropertyName();
if ("name" == eName || "componentOrientation" == eName) {
return true;
}
if ("ancestor" == eName && ev.getNewValue() != null) {
// Only update on an ancestor change when getting a valid
// parent and the LookAndFeel wants this.
return shouldUpdateStyleOnAncestorChanged();
}
return false;
}
/**
* Returns the antialiasing information as specified by the host desktop.
* Antialiasing might be forced off if the desktop is GNOME and the user
* has set his locale to Chinese, Japanese or Korean. This is consistent
* with what GTK does. See com.sun.java.swing.plaf.gtk.GtkLookAndFeel
* for more information about CJK and antialiased fonts.
*
* @return the text antialiasing information associated to the desktop
*/
private static Object getAATextInfo() {
String language = Locale.getDefault().getLanguage();
String desktop =
AccessController.doPrivileged(new GetPropertyAction("sun.desktop"));
boolean isCjkLocale = (Locale.CHINESE.getLanguage().equals(language) ||
Locale.JAPANESE.getLanguage().equals(language) ||
Locale.KOREAN.getLanguage().equals(language));
boolean isGnome = "gnome".equals(desktop);
boolean isLocal = SwingUtilities2.isLocalDisplay();
boolean setAA = isLocal && (!isGnome || !isCjkLocale);
Object aaTextInfo = SwingUtilities2.AATextInfo.getAATextInfo(setAA);
return aaTextInfo;
}
private static ReferenceQueue<LookAndFeel> queue = new ReferenceQueue<LookAndFeel>();
private static void flushUnreferenced() {
AATextListener aatl;
while ((aatl = (AATextListener) queue.poll()) != null) {
aatl.dispose();
}
}
private static class AATextListener
extends WeakReference<LookAndFeel> implements PropertyChangeListener {
private String key = SunToolkit.DESKTOPFONTHINTS;
AATextListener(LookAndFeel laf) {
super(laf, queue);
Toolkit tk = Toolkit.getDefaultToolkit();
tk.addPropertyChangeListener(key, this);
}
@Override
public void propertyChange(PropertyChangeEvent pce) {
UIDefaults defaults = UIManager.getLookAndFeelDefaults();
if (defaults.getBoolean("Synth.doNotSetTextAA")) {
dispose();
return;
}
LookAndFeel laf = get();
if (laf == null || laf != UIManager.getLookAndFeel()) {
dispose();
return;
}
Object aaTextInfo = getAATextInfo();
defaults.put(SwingUtilities2.AA_TEXT_PROPERTY_KEY, aaTextInfo);
updateUI();
}
void dispose() {
Toolkit tk = Toolkit.getDefaultToolkit();
tk.removePropertyChangeListener(key, this);
}
/**
* Updates the UI of the passed in window and all its children.
*/
private static void updateWindowUI(Window window) {
updateStyles(window);
Window ownedWins[] = window.getOwnedWindows();
for (Window w : ownedWins) {
updateWindowUI(w);
}
}
/**
* Updates the UIs of all the known Frames.
*/
private static void updateAllUIs() {
Frame appFrames[] = Frame.getFrames();
for (Frame frame : appFrames) {
updateWindowUI(frame);
}
}
/**
* Indicates if an updateUI call is pending.
*/
private static boolean updatePending;
/**
* Sets whether or not an updateUI call is pending.
*/
private static synchronized void setUpdatePending(boolean update) {
updatePending = update;
}
/**
* Returns true if a UI update is pending.
*/
private static synchronized boolean isUpdatePending() {
return updatePending;
}
protected void updateUI() {
if (!isUpdatePending()) {
setUpdatePending(true);
Runnable uiUpdater = new Runnable() {
@Override
public void run() {
updateAllUIs();
setUpdatePending(false);
}
};
SwingUtilities.invokeLater(uiUpdater);
}
}
}
private void writeObject(java.io.ObjectOutputStream out)
throws IOException {
throw new NotSerializableException(this.getClass().getName());
}
private class Handler implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
Object newValue = evt.getNewValue();
Object oldValue = evt.getOldValue();
if ("focusOwner" == propertyName) {
if (oldValue instanceof JComponent) {
repaintIfBackgroundsDiffer((JComponent)oldValue);
}
if (newValue instanceof JComponent) {
repaintIfBackgroundsDiffer((JComponent)newValue);
}
}
else if ("managingFocus" == propertyName) {
// De-register listener on old keyboard focus manager and
// register it on the new one.
KeyboardFocusManager manager =
(KeyboardFocusManager)evt.getSource();
if (newValue.equals(Boolean.FALSE)) {
manager.removePropertyChangeListener(_handler);
}
else {
manager.addPropertyChangeListener(_handler);
}
}
}
/**
* This is a support method that will check if the background colors of
* the specified component differ between focused and unfocused states.
* If the color differ the component will then repaint itself.
*
* @comp the component to check
*/
private void repaintIfBackgroundsDiffer(JComponent comp) {
ComponentUI ui = (ComponentUI)comp.getClientProperty(
SwingUtilities2.COMPONENT_UI_PROPERTY_KEY);
if (ui instanceof SynthUI) {
SynthUI synthUI = (SynthUI)ui;
SynthContext context = synthUI.getContext(comp);
SynthStyle style = context.getStyle();
int state = context.getComponentState();
// Get the current background color.
Color currBG = style.getColor(context, ColorType.BACKGROUND);
// Get the last background color.
state ^= SynthConstants.FOCUSED;
context.setComponentState(state);
Color lastBG = style.getColor(context, ColorType.BACKGROUND);
// Reset the component state back to original.
state ^= SynthConstants.FOCUSED;
context.setComponentState(state);
// Repaint the component if the backgrounds differed.
if (currBG != null && !currBG.equals(lastBG)) {
comp.repaint();
}
context.dispose();
}
}
}
}