blob: a0f5afc22c878c58ab07ce5d626a1d16e6817f1c [file] [log] [blame]
/*
* Copyright 2005-2006 Sun Microsystems, Inc. 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. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package ${PACKAGE};
import javax.swing.Painter;
import java.awt.Graphics;
import sun.font.FontManager;
import sun.swing.plaf.synth.DefaultSynthStyle;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.plaf.BorderUIResource;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.DimensionUIResource;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.InsetsUIResource;
import javax.swing.plaf.synth.Region;
import javax.swing.plaf.synth.SynthStyle;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import static java.awt.image.BufferedImage.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
/**
* This class contains all the implementation details related to
* ${LAF_NAME}. It contains all the code for initializing the UIDefaults table,
* as well as for selecting
* a SynthStyle based on a JComponent/Region pair.
*
* @author Richard Bair
*/
final class ${LAF_NAME}Defaults {
/**
* The map of SynthStyles. This map is keyed by Region. Each Region maps
* to a List of LazyStyles. Each LazyStyle has a reference to the prefix
* that was registered with it. This reference can then be inspected to see
* if it is the proper lazy style.
* <p/>
* There can be more than one LazyStyle for a single Region if there is more
* than one prefix defined for a given region. For example, both Button and
* "MyButton" might be prefixes assigned to the Region.Button region.
*/
private Map<Region, List<LazyStyle>> m;
/**
* A map of regions which have been registered.
* This mapping is maintained so that the Region can be found based on
* prefix in a very fast manner. This is used in the "matches" method of
* LazyStyle.
*/
private Map<String, Region> registeredRegions =
new HashMap<String, Region>();
/**
* Our fallback style to avoid NPEs if the proper style cannot be found in
* this class. Not sure if relying on DefaultSynthStyle is the best choice.
*/
private DefaultSynthStyle defaultStyle;
/**
* The default font that will be used. I store this value so that it can be
* set in the UIDefaults when requested.
*/
private FontUIResource defaultFont;
/**
* Map of lists of derived colors keyed by the DerivedColorKeys
*/
private Map<DerivedColorKey, DerivedColor> derivedColorsMap =
new HashMap<DerivedColorKey, DerivedColor>();
/** Tempory key used for fetching from the derivedColorsMap */
private final DerivedColorKey tmpDCKey = new DerivedColorKey();
/** Listener for changes to user defaults table */
private DefaultsListener defaultsListener = new DefaultsListener();
/** Called by UIManager when this look and feel is installed. */
void initialize() {
// add listener for derived colors
UIManager.addPropertyChangeListener(defaultsListener);
UIManager.getDefaults().addPropertyChangeListener(defaultsListener);
}
/** Called by UIManager when this look and feel is uninstalled. */
void uninitialize() {
// remove listener for derived colors
UIManager.getDefaults().removePropertyChangeListener(defaultsListener);
UIManager.removePropertyChangeListener(defaultsListener);
}
/**
* Create a new ${LAF_NAME}Defaults. This constructor is only called from
* within ${LAF_NAME}LookAndFeel.
*/
${LAF_NAME}Defaults() {
m = new HashMap<Region, List<LazyStyle>>();
//Create the default font and default style. Also register all of the
//regions and their states that this class will use for later lookup.
//Additional regions can be registered later by 3rd party components.
//These are simply the default registrations.
defaultFont = FontManager.getFontConfigFUIR("sans", Font.PLAIN, 12);
defaultStyle = new DefaultSynthStyle();
defaultStyle.setFont(defaultFont);
//initialize the map of styles
${STYLE_INIT}
}
//--------------- Methods called by ${LAF_NAME}LookAndFeel
/**
* Called from ${LAF_NAME}LookAndFeel to initialize the UIDefaults.
*
* @param d UIDefaults table to initialize. This will never be null.
* If listeners are attached to <code>d</code>, then you will
* only receive notification of LookAndFeel level defaults, not
* all defaults on the UIManager.
*/
void initializeDefaults(UIDefaults d) {
${UI_DEFAULT_INIT}
}
/**
* <p>Registers the given region and prefix. The prefix, if it contains
* quoted sections, refers to certain named components. If there are not
* quoted sections, then the prefix refers to a generic component type.</p>
*
* <p>If the given region/prefix combo has already been registered, then
* it will not be registered twice. The second registration attempt will
* fail silently.</p>
*
* @param region The Synth Region that is being registered. Such as Button,
* or ScrollBarThumb.
* @param prefix The UIDefault prefix. For example, could be ComboBox, or if
* a named components, "MyComboBox", or even something like
* ToolBar:"MyComboBox":"ComboBox.arrowButton"
*/
void register(Region region, String prefix) {
//validate the method arguments
if (region == null || prefix == null) {
throw new IllegalArgumentException(
"Neither Region nor Prefix may be null");
}
//Add a LazyStyle for this region/prefix to m.
List<LazyStyle> styles = m.get(region);
if (styles == null) {
styles = new LinkedList<LazyStyle>();
styles.add(new LazyStyle(prefix));
m.put(region, styles);
} else {
//iterate over all the current styles and see if this prefix has
//already been registered. If not, then register it.
for (LazyStyle s : styles) {
if (prefix.equals(s.prefix)) {
return;
}
}
styles.add(new LazyStyle(prefix));
}
//add this region to the map of registered regions
registeredRegions.put(region.getName(), region);
}
/**
* <p>Locate the style associated with the given region, and component.
* This is called from ${LAF_NAME}LookAndFeel in the SynthStyleFactory
* implementation.</p>
*
* <p>Lookup occurs as follows:<br/>
* Check the map of styles <code>m</code>. If the map contains no styles at
* all, then simply return the defaultStyle. If the map contains styles,
* then iterate over all of the styles for the Region <code>r</code> looking
* for the best match, based on prefix. If a match was made, then return
* that SynthStyle. Otherwise, return the defaultStyle.</p>
*
* @param comp The component associated with this region. For example, if
* the Region is Region.Button then the component will be a JButton.
* If the Region is a subregion, such as ScrollBarThumb, then the
* associated component will be the component that subregion belongs
* to, such as JScrollBar. The JComponent may be named. It may not be
* null.
* @param r The region we are looking for a style for. May not be null.
*/
SynthStyle getStyle(JComponent comp, Region r) {
//validate method arguments
if (comp == null || r == null) {
throw new IllegalArgumentException(
"Neither comp nor r may be null");
}
//if there are no lazy styles registered for the region r, then return
//the default style
List<LazyStyle> styles = m.get(r);
if (styles == null || styles.size() == 0) {
return defaultStyle;
}
//Look for the best SynthStyle for this component/region pair.
LazyStyle foundStyle = null;
for (LazyStyle s : styles) {
if (s.matches(comp)) {
//replace the foundStyle if foundStyle is null, or
//if the new style "s" is more specific (ie, its path was
//longer), or if the foundStyle was "simple" and the new style
//was not (ie: the foundStyle was for something like Button and
//the new style was for something like "MyButton", hence, being
//more specific.) In all cases, favor the most specific style
//found.
if (foundStyle == null ||
(foundStyle.parts.length < s.parts.length) ||
(foundStyle.parts.length == s.parts.length
&& foundStyle.simple && !s.simple)) {
foundStyle = s;
}
}
}
//return the style, if found, or the default style if not found
return foundStyle == null ? defaultStyle : foundStyle.getStyle(comp);
}
/*
Various public helper classes.
These may be used to register 3rd party values into UIDefaults
*/
/**
* <p>Derives its font value based on a parent font and a set of offsets and
* attributes. This class is an ActiveValue, meaning that it will recompute
* its value each time it is requested from UIDefaults. It is therefore
* recommended to read this value once and cache it in the UI delegate class
* until asked to reinitialize.</p>
*
* <p>To use this class, create an instance with the key of the font in the
* UI defaults table from which to derive this font, along with a size
* offset (if any), and whether it is to be bold, italic, or left in its
* default form.</p>
*/
public static final class DerivedFont implements UIDefaults.ActiveValue {
private float sizeOffset;
private Boolean bold;
private Boolean italic;
private String parentKey;
/**
* Create a new DerivedFont.
*
* @param key The UIDefault key associated with this derived font's
* parent or source. If this key leads to a null value, or a
* value that is not a font, then null will be returned as
* the derived font. The key must not be null.
* @param sizeOffset The size offset, as a percentage, to use. For
* example, if the source font was a 12pt font and the
* sizeOffset were specified as .9, then the new font
* will be 90% of what the source font was, or, 10.8
* pts which is rounded to 11pts. This fractional
* based offset allows for proper font scaling in high
* DPI or large system font scenarios.
* @param bold Whether the new font should be bold. If null, then this
* new font will inherit the bold setting of the source
* font.
* @param italic Whether the new font should be italicized. If null,
* then this new font will inherit the italic setting of
* the source font.
*/
public DerivedFont(String key, float sizeOffset, Boolean bold,
Boolean italic) {
//validate the constructor arguments
if (key == null) {
throw new IllegalArgumentException("You must specify a key");
}
//set the values
this.parentKey = key;
this.sizeOffset = sizeOffset;
this.bold = bold;
this.italic = italic;
}
/**
* @inheritDoc
*/
@Override
public Object createValue(UIDefaults defaults) {
Font f = defaults.getFont(parentKey);
if (f != null) {
// always round size for now so we have exact int font size
// (or we may have lame looking fonts)
float size = Math.round(f.getSize2D() * sizeOffset);
int style = f.getStyle();
if (bold != null) {
if (bold.booleanValue()) {
style = style | Font.BOLD;
} else {
style = style & ~Font.BOLD;
}
}
if (italic != null) {
if (italic.booleanValue()) {
style = style | Font.ITALIC;
} else {
style = style & ~Font.ITALIC;
}
}
return f.deriveFont(style, size);
} else {
return null;
}
}
}
/**
* This class is private because it relies on the constructor of the
* auto-generated AbstractRegionPainter subclasses. Hence, it is not
* generally useful, and is private.
* <p/>
* LazyPainter is a LazyValue class. It will create the
* AbstractRegionPainter lazily, when asked. It uses reflection to load the
* proper class and invoke its constructor.
*/
private static final class LazyPainter implements UIDefaults.LazyValue {
private int which;
private AbstractRegionPainter.PaintContext ctx;
private String className;
LazyPainter(String className, int which, Insets insets,
Dimension canvasSize, boolean inverted) {
if (className == null) {
throw new IllegalArgumentException(
"The className must be specified");
}
this.className = className;
this.which = which;
this.ctx = new AbstractRegionPainter.PaintContext(
insets, canvasSize, inverted);
}
LazyPainter(String className, int which, Insets insets,
Dimension canvasSize, boolean inverted,
AbstractRegionPainter.PaintContext.CacheMode cacheMode,
double maxH, double maxV) {
if (className == null) {
throw new IllegalArgumentException(
"The className must be specified");
}
this.className = className;
this.which = which;
this.ctx = new AbstractRegionPainter.PaintContext(
insets, canvasSize, inverted, cacheMode, maxH, maxV);
}
@Override
public Object createValue(UIDefaults table) {
try {
Class c;
Object cl;
// See if we should use a separate ClassLoader
if (table == null || !((cl = table.get("ClassLoader"))
instanceof ClassLoader)) {
cl = Thread.currentThread().
getContextClassLoader();
if (cl == null) {
// Fallback to the system class loader.
cl = ClassLoader.getSystemClassLoader();
}
}
c = Class.forName(className, true, (ClassLoader)cl);
Constructor constructor = c.getConstructor(
AbstractRegionPainter.PaintContext.class, int.class);
if (constructor == null) {
throw new NullPointerException(
"Failed to find the constructor for the class: " +
className);
}
return constructor.newInstance(ctx, which);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
/**
* A class which creates the NimbusStyle associated with it lazily, but also
* manages a lot more information about the style. It is less of a LazyValue
* type of class, and more of an Entry or Item type of class, as it
* represents an entry in the list of LazyStyles in the map m.
*
* The primary responsibilities of this class include:
* <ul>
* <li>Determining whether a given component/region pair matches this
* style</li>
* <li>Splitting the prefix specified in the constructor into its
* constituent parts to facilitate quicker matching</li>
* <li>Creating and vending a NimbusStyle lazily.</li>
* </ul>
*/
private final class LazyStyle {
/**
* The prefix this LazyStyle was registered with. Something like
* Button or ComboBox:"ComboBox.arrowButton"
*/
private String prefix;
/**
* Whether or not this LazyStyle represents an unnamed component
*/
private boolean simple = true;
/**
* The various parts, or sections, of the prefix. For example,
* the prefix:
* ComboBox:"ComboBox.arrowButton"
*
* will be broken into two parts,
* ComboBox and "ComboBox.arrowButton"
*/
private Part[] parts;
/**
* Cached shared style.
*/
private NimbusStyle style;
/**
* A weakly referenced hash map such that if the reference JComponent
* key is garbage collected then the entry is removed from the map.
* This cache exists so that when a JComponent has nimbus overrides
* in its client map, a unique style will be created and returned
* for that JComponent instance, always. In such a situation each
* JComponent instance must have its own instance of NimbusStyle.
*/
private WeakHashMap<JComponent, WeakReference<NimbusStyle>> overridesCache;
/**
* Create a new LazyStyle.
*
* @param prefix The prefix associated with this style. Cannot be null.
*/
private LazyStyle(String prefix) {
if (prefix == null) {
throw new IllegalArgumentException(
"The prefix must not be null");
}
this.prefix = prefix;
//there is one odd case that needs to be supported here: cell
//renderers. A cell renderer is defined as a named internal
//component, so for example:
// List."List.cellRenderer"
//The problem is that the component named List.cellRenderer is not a
//child of a JList. Rather, it is treated more as a direct component
//Thus, if the prefix ends with "cellRenderer", then remove all the
//previous dotted parts of the prefix name so that it becomes, for
//example: "List.cellRenderer"
//Likewise, we have a hacked work around for cellRenderer, renderer,
//and listRenderer.
String temp = prefix;
if (temp.endsWith("cellRenderer\"")
|| temp.endsWith("renderer\"")
|| temp.endsWith("listRenderer\"")) {
temp = temp.substring(temp.lastIndexOf(":\"") + 1);
}
//otherwise, normal code path
List<String> sparts = split(temp);
parts = new Part[sparts.size()];
for (int i = 0; i < parts.length; i++) {
parts[i] = new Part(sparts.get(i));
if (parts[i].named) {
simple = false;
}
}
}
/**
* Gets the style. Creates it if necessary.
* @return the style
*/
SynthStyle getStyle(JComponent c) {
// if the component has overrides, it gets its own unique style
// instead of the shared style.
if (c.getClientProperty("Nimbus.Overrides") != null) {
if (overridesCache == null)
overridesCache = new WeakHashMap<JComponent, WeakReference<NimbusStyle>>();
WeakReference<NimbusStyle> ref = overridesCache.get(c);
NimbusStyle s = ref == null ? null : ref.get();
if (s == null) {
s = new NimbusStyle(prefix, c);
overridesCache.put(c, new WeakReference<NimbusStyle>(s));
}
return s;
}
// lazily create the style if necessary
if (style == null)
style = new NimbusStyle(prefix, null);
// return the style
return style;
}
/**
* This LazyStyle is a match for the given component if, and only if,
* for each part of the prefix the component hierarchy matches exactly.
* That is, if given "a":something:"b", then:
* c.getName() must equals "b"
* c.getParent() can be anything
* c.getParent().getParent().getName() must equal "a".
*/
boolean matches(JComponent c) {
return matches(c, parts.length - 1);
}
private boolean matches(Component c, int partIndex) {
if (partIndex < 0) return true;
if (c == null) return false;
//only get here if partIndex > 0 and c == null
String name = c.getName();
if (parts[partIndex].named && parts[partIndex].s.equals(name)) {
//so far so good, recurse
return matches(c.getParent(), partIndex - 1);
} else if (!parts[partIndex].named) {
//if c is not named, and parts[partIndex] has an expected class
//type registered, then check to make sure c is of the
//right type;
Class clazz = parts[partIndex].c;
if (clazz != null && clazz.isAssignableFrom(c.getClass())) {
//so far so good, recurse
return matches(c.getParent(), partIndex - 1);
} else if (clazz == null &&
registeredRegions.containsKey(parts[partIndex].s)) {
Region r = registeredRegions.get(parts[partIndex].s);
Component parent = r.isSubregion() ? c : c.getParent();
//special case the JInternalFrameTitlePane, because it
//doesn't fit the mold. very, very funky.
if (r == Region.INTERNAL_FRAME_TITLE_PANE && parent != null
&& parent instanceof JInternalFrame.JDesktopIcon) {
JInternalFrame.JDesktopIcon icon =
(JInternalFrame.JDesktopIcon) parent;
parent = icon.getInternalFrame();
}
//it was the name of a region. So far, so good. Recurse.
return matches(parent, partIndex - 1);
}
}
return false;
}
/**
* Given some dot separated prefix, split on the colons that are
* not within quotes, and not within brackets.
*
* @param prefix
* @return
*/
private List<String> split(String prefix) {
List<String> parts = new ArrayList<String>();
int bracketCount = 0;
boolean inquotes = false;
int lastIndex = 0;
for (int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
if (c == '[') {
bracketCount++;
continue;
} else if (c == '"') {
inquotes = !inquotes;
continue;
} else if (c == ']') {
bracketCount--;
if (bracketCount < 0) {
throw new RuntimeException(
"Malformed prefix: " + prefix);
}
continue;
}
if (c == ':' && !inquotes && bracketCount == 0) {
//found a character to split on.
parts.add(prefix.substring(lastIndex, i));
lastIndex = i + 1;
}
}
if (lastIndex < prefix.length() - 1 && !inquotes
&& bracketCount == 0) {
parts.add(prefix.substring(lastIndex));
}
return parts;
}
private final class Part {
private String s;
//true if this part represents a component name
private boolean named;
private Class c;
Part(String s) {
named = s.charAt(0) == '"' && s.charAt(s.length() - 1) == '"';
if (named) {
this.s = s.substring(1, s.length() - 1);
} else {
this.s = s;
//TODO use a map of known regions for Synth and Swing, and
//then use [classname] instead of org_class_name style
try {
c = Class.forName("javax.swing.J" + s);
} catch (Exception e) {
}
try {
c = Class.forName(s.replace("_", "."));
} catch (Exception e) {
}
}
}
}
}
/**
* Get a derived color, derived colors are shared instances and will be
* updated when its parent UIDefault color changes.
*
* @param uiDefaultParentName The parent UIDefault key
* @param hOffset The hue offset
* @param sOffset The saturation offset
* @param bOffset The brightness offset
* @param aOffset The alpha offset
* @return The stored derived color
*/
public DerivedColor getDerivedColor(String uiDefaultParentName,
float hOffset, float sOffset,
float bOffset, int aOffset){
return getDerivedColor(uiDefaultParentName, hOffset, sOffset,
bOffset, aOffset, true);
}
/**
* Get a derived color, derived colors are shared instances and will be
* updated when its parent UIDefault color changes.
*
* @param uiDefaultParentName The parent UIDefault key
* @param hOffset The hue offset
* @param sOffset The saturation offset
* @param bOffset The brightness offset
* @param aOffset The alpha offset
* @param uiResource True if the derived color should be a UIResource,
* false if it should not be a UIResource
* @return The stored derived color
*/
public DerivedColor getDerivedColor(String uiDefaultParentName,
float hOffset, float sOffset,
float bOffset, int aOffset,
boolean uiResource){
tmpDCKey.set(uiDefaultParentName, hOffset, sOffset, bOffset, aOffset,
uiResource);
DerivedColor color = derivedColorsMap.get(tmpDCKey);
if (color == null){
if (uiResource) {
color = new DerivedColor.UIResource(uiDefaultParentName,
hOffset, sOffset, bOffset, aOffset);
} else {
color = new DerivedColor(uiDefaultParentName, hOffset, sOffset,
bOffset, aOffset);
}
// calculate the initial value
color.rederiveColor();
// add the listener so that if the color changes we'll propogate it
color.addPropertyChangeListener(defaultsListener);
// add to the derived colors table
derivedColorsMap.put(new DerivedColorKey(uiDefaultParentName,
hOffset, sOffset, bOffset, aOffset, uiResource),color);
}
return color;
}
/**
* Key class for derived colors
*/
private class DerivedColorKey {
private String uiDefaultParentName;
private float hOffset, sOffset, bOffset;
private int aOffset;
private boolean uiResource;
DerivedColorKey(){}
DerivedColorKey(String uiDefaultParentName, float hOffset,
float sOffset, float bOffset, int aOffset,
boolean uiResource) {
set(uiDefaultParentName, hOffset, sOffset, bOffset, aOffset, uiResource);
}
void set (String uiDefaultParentName, float hOffset,
float sOffset, float bOffset, int aOffset,
boolean uiResource) {
this.uiDefaultParentName = uiDefaultParentName;
this.hOffset = hOffset;
this.sOffset = sOffset;
this.bOffset = bOffset;
this.aOffset = aOffset;
this.uiResource = uiResource;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DerivedColorKey)) return false;
DerivedColorKey that = (DerivedColorKey) o;
if (aOffset != that.aOffset) return false;
if (Float.compare(that.bOffset, bOffset) != 0) return false;
if (Float.compare(that.hOffset, hOffset) != 0) return false;
if (Float.compare(that.sOffset, sOffset) != 0) return false;
if (uiDefaultParentName != null ?
!uiDefaultParentName.equals(that.uiDefaultParentName) :
that.uiDefaultParentName != null) return false;
if (this.uiResource != that.uiResource) return false;
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + uiDefaultParentName.hashCode();
result = 31 * result + hOffset != +0.0f ?
Float.floatToIntBits(hOffset) : 0;
result = 31 * result + sOffset != +0.0f ?
Float.floatToIntBits(sOffset) : 0;
result = 31 * result + bOffset != +0.0f ?
Float.floatToIntBits(bOffset) : 0;
result = 31 * result + aOffset;
result = 31 * result + (uiResource ? 1 : 0);
return result;
}
}
/**
* Listener to update derived colors on UIManager Defaults changes
*/
private class DefaultsListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
Object src = evt.getSource();
String key = evt.getPropertyName();
if (key.equals("lookAndFeel")){
// LAF has been installed, this is the first point at which we
// can access our defaults table via UIManager so before now
// all derived colors will be incorrect.
// First we need to update
for (DerivedColor color : derivedColorsMap.values()) {
color.rederiveColor();
}
} else if (src instanceof DerivedColor && key.equals("rgb")) {
// derived color that is in UIManager defaults has changed
// update all its dependent colors. Don't worry about doing
// this recursively since calling rederiveColor will cause
// another PCE to be fired, ending up here and essentially
// recursing
DerivedColor parentColor = (DerivedColor)src;
String parentKey = null;
Set<Map.Entry<Object,Object>> entries =
UIManager.getDefaults().entrySet();
for (Map.Entry entry : entries) {
Object value = entry.getValue();
if (value == parentColor) {
parentKey = entry.getKey().toString();
}
}
if (parentKey == null) {
//couldn't find the DerivedColor in the UIDefaults map,
//so we just bail.
return;
}
for (Map.Entry entry : entries) {
Object value = entry.getValue();
if (value instanceof DerivedColor) {
DerivedColor color = (DerivedColor)entry.getValue();
if (parentKey.equals(color.getUiDefaultParentName())) {
color.rederiveColor();
}
}
}
}
}
}
private static final class PainterBorder implements Border, UIResource {
private Insets insets;
private Painter painter;
private String painterKey;
PainterBorder(String painterKey, Insets insets) {
this.insets = insets;
this.painterKey = painterKey;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) {
if (painter == null) {
painter = (Painter)UIManager.get(painterKey);
if (painter == null) return;
}
g.translate(x, y);
if (g instanceof Graphics2D)
painter.paint((Graphics2D)g, c, w, h);
else {
BufferedImage img = new BufferedImage(w, h, TYPE_INT_ARGB);
Graphics2D gfx = img.createGraphics();
painter.paint(gfx, c, w, h);
gfx.dispose();
g.drawImage(img, x, y, null);
img = null;
}
g.translate(-x, -y);
}
@Override
public Insets getBorderInsets(Component c) {
return (Insets)insets.clone();
}
@Override
public boolean isBorderOpaque() {
return false;
}
}
}