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