1118 lines · 45.0 KB · Java
RawDownload
/* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */package javax.swing.plaf.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)){

Lines 1–500 of 1118.