You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1119 lines
44 KiB
1119 lines
44 KiB
/*
|
|
* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
package javax.swing.plaf.nimbus;
|
|
|
|
import javax.swing.Painter;
|
|
|
|
import javax.swing.JComponent;
|
|
import javax.swing.UIDefaults;
|
|
import javax.swing.UIManager;
|
|
import javax.swing.plaf.ColorUIResource;
|
|
import javax.swing.plaf.synth.ColorType;
|
|
import static javax.swing.plaf.synth.SynthConstants.*;
|
|
import javax.swing.plaf.synth.SynthContext;
|
|
import javax.swing.plaf.synth.SynthPainter;
|
|
import javax.swing.plaf.synth.SynthStyle;
|
|
import java.awt.Color;
|
|
import java.awt.Font;
|
|
import java.awt.Insets;
|
|
import java.lang.ref.WeakReference;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.TreeMap;
|
|
|
|
/**
|
|
* <p>A SynthStyle implementation used by Nimbus. Each Region that has been
|
|
* registered with the NimbusLookAndFeel will have an associated NimbusStyle.
|
|
* Third party components that are registered with the NimbusLookAndFeel will
|
|
* therefore be handed a NimbusStyle from the look and feel from the
|
|
* #getStyle(JComponent, Region) method.</p>
|
|
*
|
|
* <p>This class properly reads and retrieves values placed in the UIDefaults
|
|
* according to the standard Nimbus naming conventions. It will create and
|
|
* retrieve painters, fonts, colors, and other data stored there.</p>
|
|
*
|
|
* <p>NimbusStyle also supports the ability to override settings on a per
|
|
* component basis. NimbusStyle checks the component's client property map for
|
|
* "Nimbus.Overrides". If the value associated with this key is an instance of
|
|
* UIDefaults, then the values in that defaults table will override the standard
|
|
* Nimbus defaults in UIManager, but for that component instance only.</p>
|
|
*
|
|
* <p>Optionally, you may specify the client property
|
|
* "Nimbus.Overrides.InheritDefaults". If true, this client property indicates
|
|
* that the defaults located in UIManager should first be read, and then
|
|
* replaced with defaults located in the component client properties. If false,
|
|
* then only the defaults located in the component client property map will
|
|
* be used. If not specified, it is assumed to be true.</p>
|
|
*
|
|
* <p>You must specify "Nimbus.Overrides" for "Nimbus.Overrides.InheritDefaults"
|
|
* to have any effect. "Nimbus.Overrides" indicates whether there are any
|
|
* overrides, while "Nimbus.Overrides.InheritDefaults" indicates whether those
|
|
* overrides should first be initialized with the defaults from UIManager.</p>
|
|
*
|
|
* <p>The NimbusStyle is reloaded whenever a property change event is fired
|
|
* for a component for "Nimbus.Overrides" or "Nimbus.Overrides.InheritDefaults".
|
|
* So for example, setting a new UIDefaults on a component would cause the
|
|
* style to be reloaded.</p>
|
|
*
|
|
* <p>The values are only read out of UIManager once, and then cached. If
|
|
* you need to read the values again (for example, if the UI is being reloaded),
|
|
* then discard this NimbusStyle and read a new one from NimbusLookAndFeel
|
|
* using NimbusLookAndFeel.getStyle.</p>
|
|
*
|
|
* <p>The primary API of interest in this class for 3rd party component authors
|
|
* are the three methods which retrieve painters: #getBackgroundPainter,
|
|
* #getForegroundPainter, and #getBorderPainter.</p>
|
|
*
|
|
* <p>NimbusStyle allows you to specify custom states, or modify the order of
|
|
* states. Synth (and thus Nimbus) has the concept of a "state". For example,
|
|
* a JButton might be in the "MOUSE_OVER" state, or the "ENABLED" state, or the
|
|
* "DISABLED" state. These are all "standard" states which are defined in synth,
|
|
* and which apply to all synth Regions.</p>
|
|
*
|
|
* <p>Sometimes, however, you need to have a custom state. For example, you
|
|
* want JButton to render differently if it's parent is a JToolbar. In Nimbus,
|
|
* you specify these custom states by including a special key in UIDefaults.
|
|
* The following UIDefaults entries define three states for this button:</p>
|
|
*
|
|
* <pre><code>
|
|
* JButton.States = Enabled, Disabled, Toolbar
|
|
* JButton[Enabled].backgroundPainter = somePainter
|
|
* JButton[Disabled].background = BLUE
|
|
* JButton[Toolbar].backgroundPainter = someOtherPaint
|
|
* </code></pre>
|
|
*
|
|
* <p>As you can see, the <code>JButton.States</code> entry lists the states
|
|
* that the JButton style will support. You then specify the settings for
|
|
* each state. If you do not specify the <code>JButton.States</code> entry,
|
|
* then the standard Synth states will be assumed. If you specify the entry
|
|
* but the list of states is empty or null, then the standard synth states
|
|
* will be assumed.</p>
|
|
*
|
|
* @author Richard Bair
|
|
* @author Jasper Potts
|
|
*/
|
|
public final class NimbusStyle extends SynthStyle {
|
|
/* Keys and scales for large/small/mini components, based on Apples sizes */
|
|
public static final String LARGE_KEY = "large";
|
|
public static final String SMALL_KEY = "small";
|
|
public static final String MINI_KEY = "mini";
|
|
public static final double LARGE_SCALE = 1.15;
|
|
public static final double SMALL_SCALE = 0.857;
|
|
public static final double MINI_SCALE = 0.714;
|
|
|
|
/**
|
|
* Special constant used for performance reasons during the get() method.
|
|
* If get() runs through all of the search locations and determines that
|
|
* there is no value, then NULL will be placed into the values map. This way
|
|
* on subsequent lookups it will simply extract NULL, see it, and return
|
|
* null rather than continuing the lookup procedure.
|
|
*/
|
|
private static final Object NULL = '\0';
|
|
/**
|
|
* <p>The Color to return from getColorForState if it would otherwise have
|
|
* returned null.</p>
|
|
*
|
|
* <p>Returning null from getColorForState is a very bad thing, as it causes
|
|
* the AWT peer for the component to install a SystemColor, which is not a
|
|
* UIResource. As a result, if <code>null</code> is returned from
|
|
* getColorForState, then thereafter the color is not updated for other
|
|
* states or on LAF changes or updates. This DEFAULT_COLOR is used to
|
|
* ensure that a ColorUIResource is always returned from
|
|
* getColorForState.</p>
|
|
*/
|
|
private static final Color DEFAULT_COLOR = new ColorUIResource(Color.BLACK);
|
|
/**
|
|
* Simple Comparator for ordering the RuntimeStates according to their
|
|
* rank.
|
|
*/
|
|
private static final Comparator<RuntimeState> STATE_COMPARATOR =
|
|
new Comparator<RuntimeState>() {
|
|
@Override
|
|
public int compare(RuntimeState a, RuntimeState b) {
|
|
return a.state - b.state;
|
|
}
|
|
};
|
|
/**
|
|
* The prefix for the component or region that this NimbusStyle
|
|
* represents. This prefix is used to lookup state in the UIManager.
|
|
* It should be something like Button or Slider.Thumb or "MyButton" or
|
|
* ComboBox."ComboBox.arrowButton" or "MyComboBox"."ComboBox.arrowButton"
|
|
*/
|
|
private String prefix;
|
|
/**
|
|
* The SynthPainter that will be returned from this NimbusStyle. The
|
|
* SynthPainter returned will be a SynthPainterImpl, which will in turn
|
|
* delegate back to this NimbusStyle for the proper Painter (not
|
|
* SynthPainter) to use for painting the foreground, background, or border.
|
|
*/
|
|
private SynthPainter painter;
|
|
/**
|
|
* Data structure containing all of the defaults, insets, states, and other
|
|
* values associated with this style. This instance refers to default
|
|
* values, and are used when no overrides are discovered in the client
|
|
* properties of a component. These values are lazily created on first
|
|
* access.
|
|
*/
|
|
private Values values;
|
|
|
|
/**
|
|
* A temporary CacheKey used to perform lookups. This pattern avoids
|
|
* creating useless garbage keys, or concatenating strings, etc.
|
|
*/
|
|
private CacheKey tmpKey = new CacheKey("", 0);
|
|
|
|
/**
|
|
* Some NimbusStyles are created for a specific component only. In Nimbus,
|
|
* this happens whenever the component has as a client property a
|
|
* UIDefaults which overrides (or supplements) those defaults found in
|
|
* UIManager.
|
|
*/
|
|
private WeakReference<JComponent> component;
|
|
|
|
/**
|
|
* Create a new NimbusStyle. Only the prefix must be supplied. At the
|
|
* appropriate time, installDefaults will be called. At that point, all of
|
|
* the state information will be pulled from UIManager and stored locally
|
|
* within this style.
|
|
*
|
|
* @param prefix Something like Button or Slider.Thumb or
|
|
* org.jdesktop.swingx.JXStatusBar or ComboBox."ComboBox.arrowButton"
|
|
* @param c an optional reference to a component that this NimbusStyle
|
|
* should be associated with. This is only used when the component
|
|
* has Nimbus overrides registered in its client properties and
|
|
* should be null otherwise.
|
|
*/
|
|
NimbusStyle(String prefix, JComponent c) {
|
|
if (c != null) {
|
|
this.component = new WeakReference<JComponent>(c);
|
|
}
|
|
this.prefix = prefix;
|
|
this.painter = new SynthPainterImpl(this);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* Overridden to cause this style to populate itself with data from
|
|
* UIDefaults, if necessary.
|
|
*/
|
|
@Override public void installDefaults(SynthContext ctx) {
|
|
validate();
|
|
|
|
//delegate to the superclass to install defaults such as background,
|
|
//foreground, font, and opaque onto the swing component.
|
|
super.installDefaults(ctx);
|
|
}
|
|
|
|
/**
|
|
* Pulls data out of UIDefaults, if it has not done so already, and sets
|
|
* up the internal state.
|
|
*/
|
|
private void validate() {
|
|
// a non-null values object is the flag we use to determine whether
|
|
// to reparse from UIManager.
|
|
if (values != null) return;
|
|
|
|
// reconstruct this NimbusStyle based on the entries in the UIManager
|
|
// and possibly based on any overrides within the component's
|
|
// client properties (assuming such a component exists and contains
|
|
// any Nimbus.Overrides)
|
|
values = new Values();
|
|
|
|
Map<String, Object> defaults =
|
|
((NimbusLookAndFeel) UIManager.getLookAndFeel()).
|
|
getDefaultsForPrefix(prefix);
|
|
|
|
// inspect the client properties for the key "Nimbus.Overrides". If the
|
|
// value is an instance of UIDefaults, then these defaults are used
|
|
// in place of, or in addition to, the defaults in UIManager.
|
|
if (component != null) {
|
|
// We know component.get() is non-null here, as if the component
|
|
// were GC'ed, we wouldn't be processing its style.
|
|
Object o = component.get().getClientProperty("Nimbus.Overrides");
|
|
if (o instanceof UIDefaults) {
|
|
Object i = component.get().getClientProperty(
|
|
"Nimbus.Overrides.InheritDefaults");
|
|
boolean inherit = i instanceof Boolean ? (Boolean)i : true;
|
|
UIDefaults d = (UIDefaults)o;
|
|
TreeMap<String, Object> map = new TreeMap<String, Object>();
|
|
for (Object obj : d.keySet()) {
|
|
if (obj instanceof String) {
|
|
String key = (String)obj;
|
|
if (key.startsWith(prefix)) {
|
|
map.put(key, d.get(key));
|
|
}
|
|
}
|
|
}
|
|
if (inherit) {
|
|
defaults.putAll(map);
|
|
} else {
|
|
defaults = map;
|
|
}
|
|
}
|
|
}
|
|
|
|
//a list of the different types of states used by this style. This
|
|
//list may contain only "standard" states (those defined by Synth),
|
|
//or it may contain custom states, or it may contain only "standard"
|
|
//states but list them in a non-standard order.
|
|
List<State> states = new ArrayList<State>();
|
|
//a map of state name to code
|
|
Map<String,Integer> stateCodes = new HashMap<String,Integer>();
|
|
//This is a list of runtime "state" context objects. These contain
|
|
//the values associated with each state.
|
|
List<RuntimeState> runtimeStates = new ArrayList<RuntimeState>();
|
|
|
|
//determine whether there are any custom states, or custom state
|
|
//order. If so, then read all those custom states and define the
|
|
//"values" stateTypes to be a non-null array.
|
|
//Otherwise, let the "values" stateTypes be null to indicate that
|
|
//there are no custom states or custom state ordering
|
|
String statesString = (String)defaults.get(prefix + ".States");
|
|
if (statesString != null) {
|
|
String s[] = statesString.split(",");
|
|
for (int i=0; i<s.length; i++) {
|
|
s[i] = s[i].trim();
|
|
if (!State.isStandardStateName(s[i])) {
|
|
//this is a non-standard state name, so look for the
|
|
//custom state associated with it
|
|
String stateName = prefix + "." + s[i];
|
|
State customState = (State)defaults.get(stateName);
|
|
if (customState != null) {
|
|
states.add(customState);
|
|
}
|
|
} else {
|
|
states.add(State.getStandardState(s[i]));
|
|
}
|
|
}
|
|
|
|
//if there were any states defined, then set the stateTypes array
|
|
//to be non-null. Otherwise, leave it null (meaning, use the
|
|
//standard synth states).
|
|
if (states.size() > 0) {
|
|
values.stateTypes = states.toArray(new State[states.size()]);
|
|
}
|
|
|
|
//assign codes for each of the state types
|
|
int code = 1;
|
|
for (State state : states) {
|
|
stateCodes.put(state.getName(), code);
|
|
code <<= 1;
|
|
}
|
|
} else {
|
|
//since there were no custom states defined, setup the list of
|
|
//standard synth states. Note that the "v.stateTypes" is not
|
|
//being set here, indicating that at runtime the state selection
|
|
//routines should use standard synth states instead of custom
|
|
//states. I do need to popuplate this temp list now though, so that
|
|
//the remainder of this method will function as expected.
|
|
states.add(State.Enabled);
|
|
states.add(State.MouseOver);
|
|
states.add(State.Pressed);
|
|
states.add(State.Disabled);
|
|
states.add(State.Focused);
|
|
states.add(State.Selected);
|
|
states.add(State.Default);
|
|
|
|
//assign codes for the states
|
|
stateCodes.put("Enabled", ENABLED);
|
|
stateCodes.put("MouseOver", MOUSE_OVER);
|
|
stateCodes.put("Pressed", PRESSED);
|
|
stateCodes.put("Disabled", DISABLED);
|
|
stateCodes.put("Focused", FOCUSED);
|
|
stateCodes.put("Selected", SELECTED);
|
|
stateCodes.put("Default", DEFAULT);
|
|
}
|
|
|
|
//Now iterate over all the keys in the defaults table
|
|
for (String key : defaults.keySet()) {
|
|
//The key is something like JButton.Enabled.backgroundPainter,
|
|
//or JButton.States, or JButton.background.
|
|
//Remove the "JButton." portion of the key
|
|
String temp = key.substring(prefix.length());
|
|
//if there is a " or : then we skip it because it is a subregion
|
|
//of some kind
|
|
if (temp.indexOf('"') != -1 || temp.indexOf(':') != -1) continue;
|
|
//remove the separator
|
|
temp = temp.substring(1);
|
|
//At this point, temp may be any of the following:
|
|
//background
|
|
//[Enabled].background
|
|
//[Enabled+MouseOver].background
|
|
//property.foo
|
|
|
|
//parse out the states and the property
|
|
String stateString = null;
|
|
String property = null;
|
|
int bracketIndex = temp.indexOf(']');
|
|
if (bracketIndex < 0) {
|
|
//there is not a state string, so property = temp
|
|
property = temp;
|
|
} else {
|
|
stateString = temp.substring(0, bracketIndex);
|
|
property = temp.substring(bracketIndex + 2);
|
|
}
|
|
|
|
//now that I have the state (if any) and the property, get the
|
|
//value for this property and install it where it belongs
|
|
if (stateString == null) {
|
|
//there was no state, just a property. Check for the custom
|
|
//"contentMargins" property (which is handled specially by
|
|
//Synth/Nimbus). Also check for the property being "States",
|
|
//in which case it is not a real property and should be ignored.
|
|
//otherwise, assume it is a property and install it on the
|
|
//values object
|
|
if ("contentMargins".equals(property)) {
|
|
values.contentMargins = (Insets)defaults.get(key);
|
|
} else if ("States".equals(property)) {
|
|
//ignore
|
|
} else {
|
|
values.defaults.put(property, defaults.get(key));
|
|
}
|
|
} else {
|
|
//it is possible that the developer has a malformed UIDefaults
|
|
//entry, such that something was specified in the place of
|
|
//the State portion of the key but it wasn't a state. In this
|
|
//case, skip will be set to true
|
|
boolean skip = false;
|
|
//this variable keeps track of the int value associated with
|
|
//the state. See SynthState for details.
|
|
int componentState = 0;
|
|
//Multiple states may be specified in the string, such as
|
|
//Enabled+MouseOver
|
|
String[] stateParts = stateString.split("\\+");
|
|
//For each state, we need to find the State object associated
|
|
//with it, or skip it if it cannot be found.
|
|
for (String s : stateParts) {
|
|
if (stateCodes.containsKey(s)) {
|
|
componentState |= stateCodes.get(s);
|
|
} else {
|
|
//Was not a state. Maybe it was a subregion or something
|
|
//skip it.
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (skip) continue;
|
|
|
|
//find the RuntimeState for this State
|
|
RuntimeState rs = null;
|
|
for (RuntimeState s : runtimeStates) {
|
|
if (s.state == componentState) {
|
|
rs = s;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//couldn't find the runtime state, so create a new one
|
|
if (rs == null) {
|
|
rs = new RuntimeState(componentState, stateString);
|
|
runtimeStates.add(rs);
|
|
}
|
|
|
|
//check for a couple special properties, such as for the
|
|
//painters. If these are found, then set the specially on
|
|
//the runtime state. Else, it is just a normal property,
|
|
//so put it in the UIDefaults associated with that runtime
|
|
//state
|
|
if ("backgroundPainter".equals(property)) {
|
|
rs.backgroundPainter = getPainter(defaults, key);
|
|
} else if ("foregroundPainter".equals(property)) {
|
|
rs.foregroundPainter = getPainter(defaults, key);
|
|
} else if ("borderPainter".equals(property)) {
|
|
rs.borderPainter = getPainter(defaults, key);
|
|
} else {
|
|
rs.defaults.put(property, defaults.get(key));
|
|
}
|
|
}
|
|
}
|
|
|
|
//now that I've collected all the runtime states, I'll sort them based
|
|
//on their integer "state" (see SynthState for how this works).
|
|
Collections.sort(runtimeStates, STATE_COMPARATOR);
|
|
|
|
//finally, set the array of runtime states on the values object
|
|
values.states = runtimeStates.toArray(new RuntimeState[runtimeStates.size()]);
|
|
}
|
|
|
|
private Painter getPainter(Map<String, Object> defaults, String key) {
|
|
Object p = defaults.get(key);
|
|
if (p instanceof UIDefaults.LazyValue) {
|
|
p = ((UIDefaults.LazyValue)p).createValue(UIManager.getDefaults());
|
|
}
|
|
return (p instanceof Painter ? (Painter)p : null);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* Overridden to cause this style to populate itself with data from
|
|
* UIDefaults, if necessary.
|
|
*/
|
|
@Override public Insets getInsets(SynthContext ctx, Insets in) {
|
|
if (in == null) {
|
|
in = new Insets(0, 0, 0, 0);
|
|
}
|
|
|
|
Values v = getValues(ctx);
|
|
|
|
if (v.contentMargins == null) {
|
|
in.bottom = in.top = in.left = in.right = 0;
|
|
return in;
|
|
} else {
|
|
in.bottom = v.contentMargins.bottom;
|
|
in.top = v.contentMargins.top;
|
|
in.left = v.contentMargins.left;
|
|
in.right = v.contentMargins.right;
|
|
// Account for scale
|
|
// The key "JComponent.sizeVariant" is used to match Apple's LAF
|
|
String scaleKey = (String)ctx.getComponent().getClientProperty(
|
|
"JComponent.sizeVariant");
|
|
if (scaleKey != null){
|
|
if (LARGE_KEY.equals(scaleKey)){
|
|
in.bottom *= LARGE_SCALE;
|
|
in.top *= LARGE_SCALE;
|
|
in.left *= LARGE_SCALE;
|
|
in.right *= LARGE_SCALE;
|
|
} else if (SMALL_KEY.equals(scaleKey)){
|
|
in.bottom *= SMALL_SCALE;
|
|
in.top *= SMALL_SCALE;
|
|
in.left *= SMALL_SCALE;
|
|
in.right *= SMALL_SCALE;
|
|
} else if (MINI_KEY.equals(scaleKey)){
|
|
in.bottom *= MINI_SCALE;
|
|
in.top *= MINI_SCALE;
|
|
in.left *= MINI_SCALE;
|
|
in.right *= MINI_SCALE;
|
|
}
|
|
}
|
|
return in;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* <p>Overridden to cause this style to populate itself with data from
|
|
* UIDefaults, if necessary.</p>
|
|
*
|
|
* <p>In addition, NimbusStyle handles ColorTypes slightly differently from
|
|
* Synth.</p>
|
|
* <ul>
|
|
* <li>ColorType.BACKGROUND will equate to the color stored in UIDefaults
|
|
* named "background".</li>
|
|
* <li>ColorType.TEXT_BACKGROUND will equate to the color stored in
|
|
* UIDefaults named "textBackground".</li>
|
|
* <li>ColorType.FOREGROUND will equate to the color stored in UIDefaults
|
|
* named "textForeground".</li>
|
|
* <li>ColorType.TEXT_FOREGROUND will equate to the color stored in
|
|
* UIDefaults named "textForeground".</li>
|
|
* </ul>
|
|
*/
|
|
@Override protected Color getColorForState(SynthContext ctx, ColorType type) {
|
|
String key = null;
|
|
if (type == ColorType.BACKGROUND) {
|
|
key = "background";
|
|
} else if (type == ColorType.FOREGROUND) {
|
|
//map FOREGROUND as TEXT_FOREGROUND
|
|
key = "textForeground";
|
|
} else if (type == ColorType.TEXT_BACKGROUND) {
|
|
key = "textBackground";
|
|
} else if (type == ColorType.TEXT_FOREGROUND) {
|
|
key = "textForeground";
|
|
} else if (type == ColorType.FOCUS) {
|
|
key = "focus";
|
|
} else if (type != null) {
|
|
key = type.toString();
|
|
} else {
|
|
return DEFAULT_COLOR;
|
|
}
|
|
Color c = (Color) get(ctx, key);
|
|
//if all else fails, return a default color (which is a ColorUIResource)
|
|
if (c == null) c = DEFAULT_COLOR;
|
|
return c;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* Overridden to cause this style to populate itself with data from
|
|
* UIDefaults, if necessary. If a value named "font" is not found in
|
|
* UIDefaults, then the "defaultFont" font in UIDefaults will be returned
|
|
* instead.
|
|
*/
|
|
@Override protected Font getFontForState(SynthContext ctx) {
|
|
Font f = (Font)get(ctx, "font");
|
|
if (f == null) f = UIManager.getFont("defaultFont");
|
|
|
|
// Account for scale
|
|
// The key "JComponent.sizeVariant" is used to match Apple's LAF
|
|
String scaleKey = (String)ctx.getComponent().getClientProperty(
|
|
"JComponent.sizeVariant");
|
|
if (scaleKey != null){
|
|
if (LARGE_KEY.equals(scaleKey)){
|
|
f = f.deriveFont(Math.round(f.getSize2D()*LARGE_SCALE));
|
|
} else if (SMALL_KEY.equals(scaleKey)){
|
|
f = f.deriveFont(Math.round(f.getSize2D()*SMALL_SCALE));
|
|
} else if (MINI_KEY.equals(scaleKey)){
|
|
f = f.deriveFont(Math.round(f.getSize2D()*MINI_SCALE));
|
|
}
|
|
}
|
|
return f;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* Returns the SynthPainter for this style, which ends up delegating to
|
|
* the Painters installed in this style.
|
|
*/
|
|
@Override public SynthPainter getPainter(SynthContext ctx) {
|
|
return painter;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* Overridden to cause this style to populate itself with data from
|
|
* UIDefaults, if necessary. If opacity is not specified in UI defaults,
|
|
* then it defaults to being non-opaque.
|
|
*/
|
|
@Override public boolean isOpaque(SynthContext ctx) {
|
|
// Force Table CellRenderers to be opaque
|
|
if ("Table.cellRenderer".equals(ctx.getComponent().getName())) {
|
|
return true;
|
|
}
|
|
Boolean opaque = (Boolean)get(ctx, "opaque");
|
|
return opaque == null ? false : opaque;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* <p>Overridden to cause this style to populate itself with data from
|
|
* UIDefaults, if necessary.</p>
|
|
*
|
|
* <p>Properties in UIDefaults may be specified in a chained manner. For
|
|
* example:
|
|
* <pre>
|
|
* background
|
|
* Button.opacity
|
|
* Button.Enabled.foreground
|
|
* Button.Enabled+Selected.background
|
|
* </pre>
|
|
*
|
|
* <p>In this example, suppose you were in the Enabled+Selected state and
|
|
* searched for "foreground". In this case, we first check for
|
|
* Button.Enabled+Selected.foreground, but no such color exists. We then
|
|
* fall back to the next valid state, in this case,
|
|
* Button.Enabled.foreground, and have a match. So we return it.</p>
|
|
*
|
|
* <p>Again, if we were in the state Enabled and looked for "background", we
|
|
* wouldn't find it in Button.Enabled, or in Button, but would at the top
|
|
* level in UIManager. So we return that value.</p>
|
|
*
|
|
* <p>One special note: the "key" passed to this method could be of the form
|
|
* "background" or "Button.background" where "Button" equals the prefix
|
|
* passed to the NimbusStyle constructor. In either case, it looks for
|
|
* "background".</p>
|
|
*
|
|
* @param ctx
|
|
* @param key must not be null
|
|
*/
|
|
@Override public Object get(SynthContext ctx, Object key) {
|
|
Values v = getValues(ctx);
|
|
|
|
// strip off the prefix, if there is one.
|
|
String fullKey = key.toString();
|
|
String partialKey = fullKey.substring(fullKey.indexOf(".") + 1);
|
|
|
|
Object obj = null;
|
|
int xstate = getExtendedState(ctx, v);
|
|
|
|
// check the cache
|
|
tmpKey.init(partialKey, xstate);
|
|
obj = v.cache.get(tmpKey);
|
|
boolean wasInCache = obj != null;
|
|
if (!wasInCache){
|
|
// Search exact matching states and then lesser matching states
|
|
RuntimeState s = null;
|
|
int[] lastIndex = new int[] {-1};
|
|
while (obj == null &&
|
|
(s = getNextState(v.states, lastIndex, xstate)) != null) {
|
|
obj = s.defaults.get(partialKey);
|
|
}
|
|
// Search Region Defaults
|
|
if (obj == null && v.defaults != null) {
|
|
obj = v.defaults.get(partialKey);
|
|
}
|
|
// return found object
|
|
// Search UIManager Defaults
|
|
if (obj == null) obj = UIManager.get(fullKey);
|
|
// Search Synth Defaults for InputMaps
|
|
if (obj == null && partialKey.equals("focusInputMap")) {
|
|
obj = super.get(ctx, fullKey);
|
|
}
|
|
// if all we got was a null, store this fact for later use
|
|
v.cache.put(new CacheKey(partialKey, xstate),
|
|
obj == null ? NULL : obj);
|
|
}
|
|
// return found object
|
|
return obj == NULL ? null : obj;
|
|
}
|
|
|
|
/**
|
|
* Gets the appropriate background Painter, if there is one, for the state
|
|
* specified in the given SynthContext. This method does appropriate
|
|
* fallback searching, as described in #get.
|
|
*
|
|
* @param ctx The SynthContext. Must not be null.
|
|
* @return The background painter associated for the given state, or null if
|
|
* none could be found.
|
|
*/
|
|
public Painter getBackgroundPainter(SynthContext ctx) {
|
|
Values v = getValues(ctx);
|
|
int xstate = getExtendedState(ctx, v);
|
|
Painter p = null;
|
|
|
|
// check the cache
|
|
tmpKey.init("backgroundPainter$$instance", xstate);
|
|
p = (Painter)v.cache.get(tmpKey);
|
|
if (p != null) return p;
|
|
|
|
// not in cache, so lookup and store in cache
|
|
RuntimeState s = null;
|
|
int[] lastIndex = new int[] {-1};
|
|
while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
|
|
if (s.backgroundPainter != null) {
|
|
p = s.backgroundPainter;
|
|
break;
|
|
}
|
|
}
|
|
if (p == null) p = (Painter)get(ctx, "backgroundPainter");
|
|
if (p != null) {
|
|
v.cache.put(new CacheKey("backgroundPainter$$instance", xstate), p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets the appropriate foreground Painter, if there is one, for the state
|
|
* specified in the given SynthContext. This method does appropriate
|
|
* fallback searching, as described in #get.
|
|
*
|
|
* @param ctx The SynthContext. Must not be null.
|
|
* @return The foreground painter associated for the given state, or null if
|
|
* none could be found.
|
|
*/
|
|
public Painter getForegroundPainter(SynthContext ctx) {
|
|
Values v = getValues(ctx);
|
|
int xstate = getExtendedState(ctx, v);
|
|
Painter p = null;
|
|
|
|
// check the cache
|
|
tmpKey.init("foregroundPainter$$instance", xstate);
|
|
p = (Painter)v.cache.get(tmpKey);
|
|
if (p != null) return p;
|
|
|
|
// not in cache, so lookup and store in cache
|
|
RuntimeState s = null;
|
|
int[] lastIndex = new int[] {-1};
|
|
while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
|
|
if (s.foregroundPainter != null) {
|
|
p = s.foregroundPainter;
|
|
break;
|
|
}
|
|
}
|
|
if (p == null) p = (Painter)get(ctx, "foregroundPainter");
|
|
if (p != null) {
|
|
v.cache.put(new CacheKey("foregroundPainter$$instance", xstate), p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Gets the appropriate border Painter, if there is one, for the state
|
|
* specified in the given SynthContext. This method does appropriate
|
|
* fallback searching, as described in #get.
|
|
*
|
|
* @param ctx The SynthContext. Must not be null.
|
|
* @return The border painter associated for the given state, or null if
|
|
* none could be found.
|
|
*/
|
|
public Painter getBorderPainter(SynthContext ctx) {
|
|
Values v = getValues(ctx);
|
|
int xstate = getExtendedState(ctx, v);
|
|
Painter p = null;
|
|
|
|
// check the cache
|
|
tmpKey.init("borderPainter$$instance", xstate);
|
|
p = (Painter)v.cache.get(tmpKey);
|
|
if (p != null) return p;
|
|
|
|
// not in cache, so lookup and store in cache
|
|
RuntimeState s = null;
|
|
int[] lastIndex = new int[] {-1};
|
|
while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
|
|
if (s.borderPainter != null) {
|
|
p = s.borderPainter;
|
|
break;
|
|
}
|
|
}
|
|
if (p == null) p = (Painter)get(ctx, "borderPainter");
|
|
if (p != null) {
|
|
v.cache.put(new CacheKey("borderPainter$$instance", xstate), p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Utility method which returns the proper Values based on the given
|
|
* SynthContext. Ensures that parsing of the values has occurred, or
|
|
* reoccurs as necessary.
|
|
*
|
|
* @param ctx The SynthContext
|
|
* @return a non-null values reference
|
|
*/
|
|
private Values getValues(SynthContext ctx) {
|
|
validate();
|
|
return values;
|
|
}
|
|
|
|
/**
|
|
* Simple utility method that searches the given array of Strings for the
|
|
* given string. This method is only called from getExtendedState if
|
|
* the developer has specified a specific state for the component to be
|
|
* in (ie, has "wedged" the component in that state) by specifying
|
|
* they client property "Nimbus.State".
|
|
*
|
|
* @param names a non-null array of strings
|
|
* @param name the name to look for in the array
|
|
* @return true or false based on whether the given name is in the array
|
|
*/
|
|
private boolean contains(String[] names, String name) {
|
|
assert name != null;
|
|
for (int i=0; i<names.length; i++) {
|
|
if (name.equals(names[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* <p>Gets the extended state for a given synth context. Nimbus supports the
|
|
* ability to define custom states. The algorithm used for choosing what
|
|
* style information to use for a given state requires a single integer
|
|
* bit string where each bit in the integer represents a different state
|
|
* that the component is in. This method uses the componentState as
|
|
* reported in the SynthContext, in addition to custom states, to determine
|
|
* what this extended state is.</p>
|
|
*
|
|
* <p>In addition, this method checks the component in the given context
|
|
* for a client property called "Nimbus.State". If one exists, then it will
|
|
* decompose the String associated with that property to determine what
|
|
* state to return. In this way, the developer can force a component to be
|
|
* in a specific state, regardless of what the "real" state of the component
|
|
* is.</p>
|
|
*
|
|
* <p>The string associated with "Nimbus.State" would be of the form:
|
|
* <pre>Enabled+CustomState+MouseOver</pre></p>
|
|
*
|
|
* @param ctx
|
|
* @param v
|
|
* @return
|
|
*/
|
|
private int getExtendedState(SynthContext ctx, Values v) {
|
|
JComponent c = ctx.getComponent();
|
|
int xstate = 0;
|
|
int mask = 1;
|
|
//check for the Nimbus.State client property
|
|
//Performance NOTE: getClientProperty ends up inside a synchronized
|
|
//block, so there is some potential for performance issues here, however
|
|
//I'm not certain that there is one on a modern VM.
|
|
Object property = c.getClientProperty("Nimbus.State");
|
|
if (property != null) {
|
|
String stateNames = property.toString();
|
|
String[] states = stateNames.split("\\+");
|
|
if (v.stateTypes == null){
|
|
// standard states only
|
|
for (String stateStr : states) {
|
|
State.StandardState s = State.getStandardState(stateStr);
|
|
if (s != null) xstate |= s.getState();
|
|
}
|
|
} else {
|
|
// custom states
|
|
for (State s : v.stateTypes) {
|
|
if (contains(states, s.getName())) {
|
|
xstate |= mask;
|
|
}
|
|
mask <<= 1;
|
|
}
|
|
}
|
|
} else {
|
|
//if there are no custom states defined, then simply return the
|
|
//state that Synth reported
|
|
if (v.stateTypes == null) return ctx.getComponentState();
|
|
|
|
//there are custom states on this values, so I'll have to iterate
|
|
//over them all and return a custom extended state
|
|
int state = ctx.getComponentState();
|
|
for (State s : v.stateTypes) {
|
|
if (s.isInState(c, state)) {
|
|
xstate |= mask;
|
|
}
|
|
mask <<= 1;
|
|
}
|
|
}
|
|
return xstate;
|
|
}
|
|
|
|
/**
|
|
* <p>Gets the RuntimeState that most closely matches the state in the given
|
|
* context, but is less specific than the given "lastState". Essentially,
|
|
* this allows you to search for the next best state.</p>
|
|
*
|
|
* <p>For example, if you had the following three states:
|
|
* <pre>
|
|
* Enabled
|
|
* Enabled+Pressed
|
|
* Disabled
|
|
* </pre>
|
|
* And you wanted to find the state that best represented
|
|
* ENABLED+PRESSED+FOCUSED and <code>lastState</code> was null (or an
|
|
* empty array, or an array with a single int with index == -1), then
|
|
* Enabled+Pressed would be returned. If you then call this method again but
|
|
* pass the index of Enabled+Pressed as the "lastState", then
|
|
* Enabled would be returned. If you call this method a third time and pass
|
|
* the index of Enabled in as the <code>lastState</code>, then null would be
|
|
* returned.</p>
|
|
*
|
|
* <p>The actual code path for determining the proper state is the same as
|
|
* in Synth.</p>
|
|
*
|
|
* @param ctx
|
|
* @param lastState a 1 element array, allowing me to do pass-by-reference.
|
|
* @return
|
|
*/
|
|
private RuntimeState getNextState(RuntimeState[] states,
|
|
int[] lastState,
|
|
int xstate) {
|
|
// Use the StateInfo with the most bits that matches that of state.
|
|
// If there are none, then fallback to
|
|
// the StateInfo with a state of 0, indicating it'll match anything.
|
|
|
|
// Consider if we have 3 StateInfos a, b and c with states:
|
|
// SELECTED, SELECTED | ENABLED, 0
|
|
//
|
|
// Input Return Value
|
|
// ----- ------------
|
|
// SELECTED a
|
|
// SELECTED | ENABLED b
|
|
// MOUSE_OVER c
|
|
// SELECTED | ENABLED | FOCUSED b
|
|
// ENABLED c
|
|
|
|
if (states != null && states.length > 0) {
|
|
int bestCount = 0;
|
|
int bestIndex = -1;
|
|
int wildIndex = -1;
|
|
|
|
//if xstate is 0, then search for the runtime state with component
|
|
//state of 0. That is, find the exact match and return it.
|
|
if (xstate == 0) {
|
|
for (int counter = states.length - 1; counter >= 0; counter--) {
|
|
if (states[counter].state == 0) {
|
|
lastState[0] = counter;
|
|
return states[counter];
|
|
}
|
|
}
|
|
//an exact match couldn't be found, so there was no match.
|
|
lastState[0] = -1;
|
|
return null;
|
|
}
|
|
|
|
//xstate is some value != 0
|
|
|
|
//determine from which index to start looking. If lastState[0] is -1
|
|
//then we know to start from the end of the state array. Otherwise,
|
|
//we start at the lastIndex - 1.
|
|
int lastStateIndex = lastState == null || lastState[0] == -1 ?
|
|
states.length : lastState[0];
|
|
|
|
for (int counter = lastStateIndex - 1; counter >= 0; counter--) {
|
|
int oState = states[counter].state;
|
|
|
|
if (oState == 0) {
|
|
if (wildIndex == -1) {
|
|
wildIndex = counter;
|
|
}
|
|
} else if ((xstate & oState) == oState) {
|
|
// This is key, we need to make sure all bits of the
|
|
// StateInfo match, otherwise a StateInfo with
|
|
// SELECTED | ENABLED would match ENABLED, which we
|
|
// don't want.
|
|
|
|
// This comes from BigInteger.bitCnt
|
|
int bitCount = oState;
|
|
bitCount -= (0xaaaaaaaa & bitCount) >>> 1;
|
|
bitCount = (bitCount & 0x33333333) + ((bitCount >>> 2) &
|
|
0x33333333);
|
|
bitCount = bitCount + (bitCount >>> 4) & 0x0f0f0f0f;
|
|
bitCount += bitCount >>> 8;
|
|
bitCount += bitCount >>> 16;
|
|
bitCount = bitCount & 0xff;
|
|
if (bitCount > bestCount) {
|
|
bestIndex = counter;
|
|
bestCount = bitCount;
|
|
}
|
|
}
|
|
}
|
|
if (bestIndex != -1) {
|
|
lastState[0] = bestIndex;
|
|
return states[bestIndex];
|
|
}
|
|
if (wildIndex != -1) {
|
|
lastState[0] = wildIndex;
|
|
return states[wildIndex];
|
|
}
|
|
}
|
|
lastState[0] = -1;
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Contains values such as the UIDefaults and painters associated with
|
|
* a state. Whereas <code>State</code> represents a distinct state that a
|
|
* component can be in (such as Enabled), this class represents the colors,
|
|
* fonts, painters, etc associated with some state for this
|
|
* style.
|
|
*/
|
|
private final class RuntimeState implements Cloneable {
|
|
int state;
|
|
Painter backgroundPainter;
|
|
Painter foregroundPainter;
|
|
Painter borderPainter;
|
|
String stateName;
|
|
UIDefaults defaults = new UIDefaults(10, .7f);
|
|
|
|
private RuntimeState(int state, String stateName) {
|
|
this.state = state;
|
|
this.stateName = stateName;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return stateName;
|
|
}
|
|
|
|
@Override
|
|
public RuntimeState clone() {
|
|
RuntimeState clone = new RuntimeState(state, stateName);
|
|
clone.backgroundPainter = backgroundPainter;
|
|
clone.foregroundPainter = foregroundPainter;
|
|
clone.borderPainter = borderPainter;
|
|
clone.defaults.putAll(defaults);
|
|
return clone;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Essentially a struct of data for a style. A default instance of this
|
|
* class is used by NimbusStyle. Additional instances exist for each
|
|
* component that has overrides.
|
|
*/
|
|
private static final class Values {
|
|
/**
|
|
* The list of State types. A State represents a type of state, such
|
|
* as Enabled, Default, WindowFocused, etc. These can be custom states.
|
|
*/
|
|
State[] stateTypes = null;
|
|
/**
|
|
* The list of actual runtime state representations. These can represent things such
|
|
* as Enabled + Focused. Thus, they differ from States in that they contain
|
|
* several states together, and have associated properties, data, etc.
|
|
*/
|
|
RuntimeState[] states = null;
|
|
/**
|
|
* The content margins for this region.
|
|
*/
|
|
Insets contentMargins;
|
|
/**
|
|
* Defaults on the region/component level.
|
|
*/
|
|
UIDefaults defaults = new UIDefaults(10, .7f);
|
|
/**
|
|
* Simple cache. After a value has been looked up, it is stored
|
|
* in this cache for later retrieval. The key is a concatenation of
|
|
* the property being looked up, two dollar signs, and the extended
|
|
* state. So for example:
|
|
*
|
|
* foo.bar$$2353
|
|
*/
|
|
Map<CacheKey,Object> cache = new HashMap<CacheKey,Object>();
|
|
}
|
|
|
|
/**
|
|
* This implementation presupposes that key is never null and that
|
|
* the two keys being checked for equality are never null
|
|
*/
|
|
private static final class CacheKey {
|
|
private String key;
|
|
private int xstate;
|
|
|
|
CacheKey(Object key, int xstate) {
|
|
init(key, xstate);
|
|
}
|
|
|
|
void init(Object key, int xstate) {
|
|
this.key = key.toString();
|
|
this.xstate = xstate;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
final CacheKey other = (CacheKey) obj;
|
|
if (obj == null) return false;
|
|
if (this.xstate != other.xstate) return false;
|
|
if (!this.key.equals(other.key)) return false;
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int hash = 3;
|
|
hash = 29 * hash + this.key.hashCode();
|
|
hash = 29 * hash + this.xstate;
|
|
return hash;
|
|
}
|
|
}
|
|
}
|