1/*
2 * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package javax.swing.plaf.nimbus;
26
27import javax.swing.Painter;
28
29import javax.swing.JComponent;
30import javax.swing.UIDefaults;
31import javax.swing.UIManager;
32import javax.swing.plaf.ColorUIResource;
33import javax.swing.plaf.synth.ColorType;
34import static javax.swing.plaf.synth.SynthConstants.*;
35import javax.swing.plaf.synth.SynthContext;
36import javax.swing.plaf.synth.SynthPainter;
37import javax.swing.plaf.synth.SynthStyle;
38import java.awt.Color;
39import java.awt.Font;
40import java.awt.Insets;
41import java.lang.ref.WeakReference;
42import java.util.ArrayList;
43import java.util.Collections;
44import java.util.Comparator;
45import java.util.HashMap;
46import java.util.List;
47import java.util.Map;
48import java.util.TreeMap;
49
50/**
51 * <p>A SynthStyle implementation used by Nimbus. Each Region that has been
52 * registered with the NimbusLookAndFeel will have an associated NimbusStyle.
53 * Third party components that are registered with the NimbusLookAndFeel will
54 * therefore be handed a NimbusStyle from the look and feel from the
55 * #getStyle(JComponent, Region) method.</p>
56 *
57 * <p>This class properly reads and retrieves values placed in the UIDefaults
58 * according to the standard Nimbus naming conventions. It will create and
59 * retrieve painters, fonts, colors, and other data stored there.</p>
60 *
61 * <p>NimbusStyle also supports the ability to override settings on a per
62 * component basis. NimbusStyle checks the component's client property map for
63 * "Nimbus.Overrides". If the value associated with this key is an instance of
64 * UIDefaults, then the values in that defaults table will override the standard
65 * Nimbus defaults in UIManager, but for that component instance only.</p>
66 *
67 * <p>Optionally, you may specify the client property
68 * "Nimbus.Overrides.InheritDefaults". If true, this client property indicates
69 * that the defaults located in UIManager should first be read, and then
70 * replaced with defaults located in the component client properties. If false,
71 * then only the defaults located in the component client property map will
72 * be used. If not specified, it is assumed to be true.</p>
73 *
74 * <p>You must specify "Nimbus.Overrides" for "Nimbus.Overrides.InheritDefaults"
75 * to have any effect. "Nimbus.Overrides" indicates whether there are any
76 * overrides, while "Nimbus.Overrides.InheritDefaults" indicates whether those
77 * overrides should first be initialized with the defaults from UIManager.</p>
78 *
79 * <p>The NimbusStyle is reloaded whenever a property change event is fired
80 * for a component for "Nimbus.Overrides" or "Nimbus.Overrides.InheritDefaults".
81 * So for example, setting a new UIDefaults on a component would cause the
82 * style to be reloaded.</p>
83 *
84 * <p>The values are only read out of UIManager once, and then cached. If
85 * you need to read the values again (for example, if the UI is being reloaded),
86 * then discard this NimbusStyle and read a new one from NimbusLookAndFeel
87 * using NimbusLookAndFeel.getStyle.</p>
88 *
89 * <p>The primary API of interest in this class for 3rd party component authors
90 * are the three methods which retrieve painters: #getBackgroundPainter,
91 * #getForegroundPainter, and #getBorderPainter.</p>
92 *
93 * <p>NimbusStyle allows you to specify custom states, or modify the order of
94 * states. Synth (and thus Nimbus) has the concept of a "state". For example,
95 * a JButton might be in the "MOUSE_OVER" state, or the "ENABLED" state, or the
96 * "DISABLED" state. These are all "standard" states which are defined in synth,
97 * and which apply to all synth Regions.</p>
98 *
99 * <p>Sometimes, however, you need to have a custom state. For example, you
100 * want JButton to render differently if it's parent is a JToolbar. In Nimbus,
101 * you specify these custom states by including a special key in UIDefaults.
102 * The following UIDefaults entries define three states for this button:</p>
103 *
104 * <pre><code>
105 *     JButton.States = Enabled, Disabled, Toolbar
106 *     JButton[Enabled].backgroundPainter = somePainter
107 *     JButton[Disabled].background = BLUE
108 *     JButton[Toolbar].backgroundPainter = someOtherPaint
109 * </code></pre>
110 *
111 * <p>As you can see, the <code>JButton.States</code> entry lists the states
112 * that the JButton style will support. You then specify the settings for
113 * each state. If you do not specify the <code>JButton.States</code> entry,
114 * then the standard Synth states will be assumed. If you specify the entry
115 * but the list of states is empty or null, then the standard synth states
116 * will be assumed.</p>
117 *
118 * @author Richard Bair
119 * @author Jasper Potts
120 */
121public final class NimbusStyle extends SynthStyle {
122    /* Keys and scales for large/small/mini components, based on Apples sizes */
123    /** Large key */
124    public static final String LARGE_KEY = "large";
125    /** Small key */
126    public static final String SMALL_KEY = "small";
127    /** Mini key */
128    public static final String MINI_KEY = "mini";
129    /** Large scale */
130    public static final double LARGE_SCALE = 1.15;
131    /** Small scale */
132    public static final double SMALL_SCALE = 0.857;
133    /** Mini scale */
134    public static final double MINI_SCALE = 0.714;
135
136    /**
137     * Special constant used for performance reasons during the get() method.
138     * If get() runs through all of the search locations and determines that
139     * there is no value, then NULL will be placed into the values map. This way
140     * on subsequent lookups it will simply extract NULL, see it, and return
141     * null rather than continuing the lookup procedure.
142     */
143    private static final Object NULL = '\0';
144    /**
145     * <p>The Color to return from getColorForState if it would otherwise have
146     * returned null.</p>
147     *
148     * <p>Returning null from getColorForState is a very bad thing, as it causes
149     * the AWT peer for the component to install a SystemColor, which is not a
150     * UIResource. As a result, if <code>null</code> is returned from
151     * getColorForState, then thereafter the color is not updated for other
152     * states or on LAF changes or updates. This DEFAULT_COLOR is used to
153     * ensure that a ColorUIResource is always returned from
154     * getColorForState.</p>
155     */
156    private static final Color DEFAULT_COLOR = new ColorUIResource(Color.BLACK);
157    /**
158     * Simple Comparator for ordering the RuntimeStates according to their
159     * rank.
160     */
161    private static final Comparator<RuntimeState> STATE_COMPARATOR =
162        new Comparator<RuntimeState>() {
163            @Override
164            public int compare(RuntimeState a, RuntimeState b) {
165                return a.state - b.state;
166            }
167        };
168    /**
169     * The prefix for the component or region that this NimbusStyle
170     * represents. This prefix is used to lookup state in the UIManager.
171     * It should be something like Button or Slider.Thumb or "MyButton" or
172     * ComboBox."ComboBox.arrowButton" or "MyComboBox"."ComboBox.arrowButton"
173     */
174    private String prefix;
175    /**
176     * The SynthPainter that will be returned from this NimbusStyle. The
177     * SynthPainter returned will be a SynthPainterImpl, which will in turn
178     * delegate back to this NimbusStyle for the proper Painter (not
179     * SynthPainter) to use for painting the foreground, background, or border.
180     */
181    private SynthPainter painter;
182    /**
183     * Data structure containing all of the defaults, insets, states, and other
184     * values associated with this style. This instance refers to default
185     * values, and are used when no overrides are discovered in the client
186     * properties of a component. These values are lazily created on first
187     * access.
188     */
189    private Values values;
190
191    /**
192     * A temporary CacheKey used to perform lookups. This pattern avoids
193     * creating useless garbage keys, or concatenating strings, etc.
194     */
195    private CacheKey tmpKey = new CacheKey("", 0);
196
197    /**
198     * Some NimbusStyles are created for a specific component only. In Nimbus,
199     * this happens whenever the component has as a client property a
200     * UIDefaults which overrides (or supplements) those defaults found in
201     * UIManager.
202     */
203    private WeakReference<JComponent> component;
204
205    /**
206     * Create a new NimbusStyle. Only the prefix must be supplied. At the
207     * appropriate time, installDefaults will be called. At that point, all of
208     * the state information will be pulled from UIManager and stored locally
209     * within this style.
210     *
211     * @param prefix Something like Button or Slider.Thumb or
212     *        org.jdesktop.swingx.JXStatusBar or ComboBox."ComboBox.arrowButton"
213     * @param c an optional reference to a component that this NimbusStyle
214     *        should be associated with. This is only used when the component
215     *        has Nimbus overrides registered in its client properties and
216     *        should be null otherwise.
217     */
218    NimbusStyle(String prefix, JComponent c) {
219        if (c != null) {
220            this.component = new WeakReference<JComponent>(c);
221        }
222        this.prefix = prefix;
223        this.painter = new SynthPainterImpl(this);
224    }
225
226    /**
227     * {@inheritDoc}
228     *
229     * Overridden to cause this style to populate itself with data from
230     * UIDefaults, if necessary.
231     */
232    @Override public void installDefaults(SynthContext ctx) {
233        validate();
234
235        //delegate to the superclass to install defaults such as background,
236        //foreground, font, and opaque onto the swing component.
237        super.installDefaults(ctx);
238    }
239
240    /**
241     * Pulls data out of UIDefaults, if it has not done so already, and sets
242     * up the internal state.
243     */
244    private void validate() {
245        // a non-null values object is the flag we use to determine whether
246        // to reparse from UIManager.
247        if (values != null) return;
248
249        // reconstruct this NimbusStyle based on the entries in the UIManager
250        // and possibly based on any overrides within the component's
251        // client properties (assuming such a component exists and contains
252        // any Nimbus.Overrides)
253        values = new Values();
254
255        Map<String, Object> defaults =
256                ((NimbusLookAndFeel) UIManager.getLookAndFeel()).
257                        getDefaultsForPrefix(prefix);
258
259        // inspect the client properties for the key "Nimbus.Overrides". If the
260        // value is an instance of UIDefaults, then these defaults are used
261        // in place of, or in addition to, the defaults in UIManager.
262        if (component != null) {
263            // We know component.get() is non-null here, as if the component
264            // were GC'ed, we wouldn't be processing its style.
265            Object o = component.get().getClientProperty("Nimbus.Overrides");
266            if (o instanceof UIDefaults) {
267                Object i = component.get().getClientProperty(
268                        "Nimbus.Overrides.InheritDefaults");
269                boolean inherit = i instanceof Boolean ? (Boolean)i : true;
270                UIDefaults d = (UIDefaults)o;
271                TreeMap<String, Object> map = new TreeMap<String, Object>();
272                for (Object obj : d.keySet()) {
273                    if (obj instanceof String) {
274                        String key = (String)obj;
275                        if (key.startsWith(prefix)) {
276                            map.put(key, d.get(key));
277                        }
278                    }
279                }
280                if (inherit) {
281                    defaults.putAll(map);
282                } else {
283                    defaults = map;
284                }
285            }
286        }
287
288        //a list of the different types of states used by this style. This
289        //list may contain only "standard" states (those defined by Synth),
290        //or it may contain custom states, or it may contain only "standard"
291        //states but list them in a non-standard order.
292        List<State<?>> states = new ArrayList<>();
293        //a map of state name to code
294        Map<String,Integer> stateCodes = new HashMap<>();
295        //This is a list of runtime "state" context objects. These contain
296        //the values associated with each state.
297        List<RuntimeState> runtimeStates = new ArrayList<>();
298
299        //determine whether there are any custom states, or custom state
300        //order. If so, then read all those custom states and define the
301        //"values" stateTypes to be a non-null array.
302        //Otherwise, let the "values" stateTypes be null to indicate that
303        //there are no custom states or custom state ordering
304        String statesString = (String)defaults.get(prefix + ".States");
305        if (statesString != null) {
306            String s[] = statesString.split(",");
307            for (int i=0; i<s.length; i++) {
308                s[i] = s[i].trim();
309                if (!State.isStandardStateName(s[i])) {
310                    //this is a non-standard state name, so look for the
311                    //custom state associated with it
312                    String stateName = prefix + "." + s[i];
313                    State<?> customState = (State)defaults.get(stateName);
314                    if (customState != null) {
315                        states.add(customState);
316                    }
317                } else {
318                    states.add(State.getStandardState(s[i]));
319                }
320            }
321
322            //if there were any states defined, then set the stateTypes array
323            //to be non-null. Otherwise, leave it null (meaning, use the
324            //standard synth states).
325            if (states.size() > 0) {
326                values.stateTypes = states.toArray(new State<?>[states.size()]);
327            }
328
329            //assign codes for each of the state types
330            int code = 1;
331            for (State<?> state : states) {
332                stateCodes.put(state.getName(), code);
333                code <<= 1;
334            }
335        } else {
336            //since there were no custom states defined, setup the list of
337            //standard synth states. Note that the "v.stateTypes" is not
338            //being set here, indicating that at runtime the state selection
339            //routines should use standard synth states instead of custom
340            //states. I do need to popuplate this temp list now though, so that
341            //the remainder of this method will function as expected.
342            states.add(State.Enabled);
343            states.add(State.MouseOver);
344            states.add(State.Pressed);
345            states.add(State.Disabled);
346            states.add(State.Focused);
347            states.add(State.Selected);
348            states.add(State.Default);
349
350            //assign codes for the states
351            stateCodes.put("Enabled", ENABLED);
352            stateCodes.put("MouseOver", MOUSE_OVER);
353            stateCodes.put("Pressed", PRESSED);
354            stateCodes.put("Disabled", DISABLED);
355            stateCodes.put("Focused", FOCUSED);
356            stateCodes.put("Selected", SELECTED);
357            stateCodes.put("Default", DEFAULT);
358        }
359
360        //Now iterate over all the keys in the defaults table
361        for (String key : defaults.keySet()) {
362            //The key is something like JButton.Enabled.backgroundPainter,
363            //or JButton.States, or JButton.background.
364            //Remove the "JButton." portion of the key
365            String temp = key.substring(prefix.length());
366            //if there is a " or : then we skip it because it is a subregion
367            //of some kind
368            if (temp.indexOf('"') != -1 || temp.indexOf(':') != -1) continue;
369            //remove the separator
370            temp = temp.substring(1);
371            //At this point, temp may be any of the following:
372            //background
373            //[Enabled].background
374            //[Enabled+MouseOver].background
375            //property.foo
376
377            //parse out the states and the property
378            String stateString = null;
379            String property = null;
380            int bracketIndex = temp.indexOf(']');
381            if (bracketIndex < 0) {
382                //there is not a state string, so property = temp
383                property = temp;
384            } else {
385                stateString = temp.substring(0, bracketIndex);
386                property = temp.substring(bracketIndex + 2);
387            }
388
389            //now that I have the state (if any) and the property, get the
390            //value for this property and install it where it belongs
391            if (stateString == null) {
392                //there was no state, just a property. Check for the custom
393                //"contentMargins" property (which is handled specially by
394                //Synth/Nimbus). Also check for the property being "States",
395                //in which case it is not a real property and should be ignored.
396                //otherwise, assume it is a property and install it on the
397                //values object
398                if ("contentMargins".equals(property)) {
399                    values.contentMargins = (Insets)defaults.get(key);
400                } else if ("States".equals(property)) {
401                    //ignore
402                } else {
403                    values.defaults.put(property, defaults.get(key));
404                }
405            } else {
406                //it is possible that the developer has a malformed UIDefaults
407                //entry, such that something was specified in the place of
408                //the State portion of the key but it wasn't a state. In this
409                //case, skip will be set to true
410                boolean skip = false;
411                //this variable keeps track of the int value associated with
412                //the state. See SynthState for details.
413                int componentState = 0;
414                //Multiple states may be specified in the string, such as
415                //Enabled+MouseOver
416                String[] stateParts = stateString.split("\\+");
417                //For each state, we need to find the State object associated
418                //with it, or skip it if it cannot be found.
419                for (String s : stateParts) {
420                    if (stateCodes.containsKey(s)) {
421                        componentState |= stateCodes.get(s);
422                    } else {
423                        //Was not a state. Maybe it was a subregion or something
424                        //skip it.
425                        skip = true;
426                        break;
427                    }
428                }
429
430                if (skip) continue;
431
432                //find the RuntimeState for this State
433                RuntimeState rs = null;
434                for (RuntimeState s : runtimeStates) {
435                    if (s.state == componentState) {
436                        rs = s;
437                        break;
438                    }
439                }
440
441                //couldn't find the runtime state, so create a new one
442                if (rs == null) {
443                    rs = new RuntimeState(componentState, stateString);
444                    runtimeStates.add(rs);
445                }
446
447                //check for a couple special properties, such as for the
448                //painters. If these are found, then set the specially on
449                //the runtime state. Else, it is just a normal property,
450                //so put it in the UIDefaults associated with that runtime
451                //state
452                if ("backgroundPainter".equals(property)) {
453                    rs.backgroundPainter = getPainter(defaults, key);
454                } else if ("foregroundPainter".equals(property)) {
455                    rs.foregroundPainter = getPainter(defaults, key);
456                } else if ("borderPainter".equals(property)) {
457                    rs.borderPainter = getPainter(defaults, key);
458                } else {
459                    rs.defaults.put(property, defaults.get(key));
460                }
461            }
462        }
463
464        //now that I've collected all the runtime states, I'll sort them based
465        //on their integer "state" (see SynthState for how this works).
466        Collections.sort(runtimeStates, STATE_COMPARATOR);
467
468        //finally, set the array of runtime states on the values object
469        values.states = runtimeStates.toArray(new RuntimeState[runtimeStates.size()]);
470    }
471
472    private Painter<Object> getPainter(Map<String, Object> defaults, String key) {
473        Object p = defaults.get(key);
474        if (p instanceof UIDefaults.LazyValue) {
475            p = ((UIDefaults.LazyValue)p).createValue(UIManager.getDefaults());
476        }
477        @SuppressWarnings("unchecked")
478        Painter<Object> tmp = (p instanceof Painter ? (Painter)p : null);
479        return tmp;
480    }
481
482    /**
483     * {@inheritDoc}
484     *
485     * Overridden to cause this style to populate itself with data from
486     * UIDefaults, if necessary.
487     */
488    @Override public Insets getInsets(SynthContext ctx, Insets in) {
489        if (in == null) {
490            in = new Insets(0, 0, 0, 0);
491        }
492
493        Values v = getValues(ctx);
494
495        if (v.contentMargins == null) {
496            in.bottom = in.top = in.left = in.right = 0;
497            return in;
498        } else {
499            in.bottom = v.contentMargins.bottom;
500            in.top = v.contentMargins.top;
501            in.left = v.contentMargins.left;
502            in.right = v.contentMargins.right;
503            // Account for scale
504            // The key "JComponent.sizeVariant" is used to match Apple's LAF
505            String scaleKey = (String)ctx.getComponent().getClientProperty(
506                    "JComponent.sizeVariant");
507            if (scaleKey != null){
508                if (LARGE_KEY.equals(scaleKey)){
509                    in.bottom *= LARGE_SCALE;
510                    in.top *= LARGE_SCALE;
511                    in.left *= LARGE_SCALE;
512                    in.right *= LARGE_SCALE;
513                } else if (SMALL_KEY.equals(scaleKey)){
514                    in.bottom *= SMALL_SCALE;
515                    in.top *= SMALL_SCALE;
516                    in.left *= SMALL_SCALE;
517                    in.right *= SMALL_SCALE;
518                } else if (MINI_KEY.equals(scaleKey)){
519                    in.bottom *= MINI_SCALE;
520                    in.top *= MINI_SCALE;
521                    in.left *= MINI_SCALE;
522                    in.right *= MINI_SCALE;
523                }
524            }
525            return in;
526        }
527    }
528
529    /**
530     * {@inheritDoc}
531     *
532     * <p>Overridden to cause this style to populate itself with data from
533     * UIDefaults, if necessary.</p>
534     *
535     * <p>In addition, NimbusStyle handles ColorTypes slightly differently from
536     * Synth.</p>
537     * <ul>
538     *  <li>ColorType.BACKGROUND will equate to the color stored in UIDefaults
539     *      named "background".</li>
540     *  <li>ColorType.TEXT_BACKGROUND will equate to the color stored in
541     *      UIDefaults named "textBackground".</li>
542     *  <li>ColorType.FOREGROUND will equate to the color stored in UIDefaults
543     *      named "textForeground".</li>
544     *  <li>ColorType.TEXT_FOREGROUND will equate to the color stored in
545     *      UIDefaults named "textForeground".</li>
546     * </ul>
547     */
548    @Override protected Color getColorForState(SynthContext ctx, ColorType type) {
549        String key = null;
550        if (type == ColorType.BACKGROUND) {
551            key = "background";
552        } else if (type == ColorType.FOREGROUND) {
553            //map FOREGROUND as TEXT_FOREGROUND
554            key = "textForeground";
555        } else if (type == ColorType.TEXT_BACKGROUND) {
556            key = "textBackground";
557        } else if (type == ColorType.TEXT_FOREGROUND) {
558            key = "textForeground";
559        } else if (type == ColorType.FOCUS) {
560            key = "focus";
561        } else if (type != null) {
562            key = type.toString();
563        } else {
564            return DEFAULT_COLOR;
565        }
566        Color c = (Color) get(ctx, key);
567        //if all else fails, return a default color (which is a ColorUIResource)
568        if (c == null) c = DEFAULT_COLOR;
569        return c;
570    }
571
572    /**
573     * {@inheritDoc}
574     *
575     * Overridden to cause this style to populate itself with data from
576     * UIDefaults, if necessary. If a value named "font" is not found in
577     * UIDefaults, then the "defaultFont" font in UIDefaults will be returned
578     * instead.
579     */
580    @Override protected Font getFontForState(SynthContext ctx) {
581        Font f = (Font)get(ctx, "font");
582        if (f == null) f = UIManager.getFont("defaultFont");
583
584        // Account for scale
585        // The key "JComponent.sizeVariant" is used to match Apple's LAF
586        String scaleKey = (String)ctx.getComponent().getClientProperty(
587                "JComponent.sizeVariant");
588        if (scaleKey != null){
589            if (LARGE_KEY.equals(scaleKey)){
590                f = f.deriveFont(Math.round(f.getSize2D()*LARGE_SCALE));
591            } else if (SMALL_KEY.equals(scaleKey)){
592                f = f.deriveFont(Math.round(f.getSize2D()*SMALL_SCALE));
593            } else if (MINI_KEY.equals(scaleKey)){
594                f = f.deriveFont(Math.round(f.getSize2D()*MINI_SCALE));
595            }
596        }
597        return f;
598    }
599
600    /**
601     * {@inheritDoc}
602     *
603     * Returns the SynthPainter for this style, which ends up delegating to
604     * the Painters installed in this style.
605     */
606    @Override public SynthPainter getPainter(SynthContext ctx) {
607        return painter;
608    }
609
610    /**
611     * {@inheritDoc}
612     *
613     * Overridden to cause this style to populate itself with data from
614     * UIDefaults, if necessary. If opacity is not specified in UI defaults,
615     * then it defaults to being non-opaque.
616     */
617    @Override public boolean isOpaque(SynthContext ctx) {
618        // Force Table CellRenderers to be opaque
619        if ("Table.cellRenderer".equals(ctx.getComponent().getName())) {
620            return true;
621        }
622        Boolean opaque = (Boolean)get(ctx, "opaque");
623        return opaque == null ? false : opaque;
624    }
625
626    /**
627     * {@inheritDoc}
628     *
629     * <p>Overridden to cause this style to populate itself with data from
630     * UIDefaults, if necessary.</p>
631     *
632     * <p>Properties in UIDefaults may be specified in a chained manner. For
633     * example:
634     * <pre>
635     * background
636     * Button.opacity
637     * Button.Enabled.foreground
638     * Button.Enabled+Selected.background
639     * </pre>
640     *
641     * <p>In this example, suppose you were in the Enabled+Selected state and
642     * searched for "foreground". In this case, we first check for
643     * Button.Enabled+Selected.foreground, but no such color exists. We then
644     * fall back to the next valid state, in this case,
645     * Button.Enabled.foreground, and have a match. So we return it.</p>
646     *
647     * <p>Again, if we were in the state Enabled and looked for "background", we
648     * wouldn't find it in Button.Enabled, or in Button, but would at the top
649     * level in UIManager. So we return that value.</p>
650     *
651     * <p>One special note: the "key" passed to this method could be of the form
652     * "background" or "Button.background" where "Button" equals the prefix
653     * passed to the NimbusStyle constructor. In either case, it looks for
654     * "background".</p>
655     *
656     * @param ctx SynthContext identifying requester
657     * @param key must not be null
658     */
659    @Override public Object get(SynthContext ctx, Object key) {
660        Values v = getValues(ctx);
661
662        // strip off the prefix, if there is one.
663        String fullKey = key.toString();
664        String partialKey = fullKey.substring(fullKey.indexOf('.') + 1);
665
666        Object obj = null;
667        int xstate = getExtendedState(ctx, v);
668
669        // check the cache
670        tmpKey.init(partialKey, xstate);
671        obj = v.cache.get(tmpKey);
672        boolean wasInCache = obj != null;
673        if (!wasInCache){
674            // Search exact matching states and then lesser matching states
675            RuntimeState s = null;
676            int[] lastIndex = new int[] {-1};
677            while (obj == null &&
678                    (s = getNextState(v.states, lastIndex, xstate)) != null) {
679                obj = s.defaults.get(partialKey);
680            }
681            // Search Region Defaults
682            if (obj == null && v.defaults != null) {
683                obj = v.defaults.get(partialKey);
684            }
685            // return found object
686            // Search UIManager Defaults
687            if (obj == null) obj = UIManager.get(fullKey);
688            // Search Synth Defaults for InputMaps
689            if (obj == null && partialKey.equals("focusInputMap")) {
690                obj = super.get(ctx, fullKey);
691            }
692            // if all we got was a null, store this fact for later use
693            v.cache.put(new CacheKey(partialKey, xstate),
694                    obj == null ? NULL : obj);
695        }
696        // return found object
697        return obj == NULL ? null : obj;
698    }
699
700    @SuppressWarnings("unchecked")
701    private static Painter<Object> paintFilter(@SuppressWarnings("rawtypes") Painter painter) {
702        return (Painter<Object>) painter;
703    }
704
705
706    /**
707     * Gets the appropriate background Painter, if there is one, for the state
708     * specified in the given SynthContext. This method does appropriate
709     * fallback searching, as described in #get.
710     *
711     * @param ctx The SynthContext. Must not be null.
712     * @return The background painter associated for the given state, or null if
713     * none could be found.
714     */
715    public Painter<Object> getBackgroundPainter(SynthContext ctx) {
716        Values v = getValues(ctx);
717        int xstate = getExtendedState(ctx, v);
718        Painter<Object> p = null;
719
720        // check the cache
721        tmpKey.init("backgroundPainter$$instance", xstate);
722        p = paintFilter((Painter)v.cache.get(tmpKey));
723        if (p != null) return p;
724
725        // not in cache, so lookup and store in cache
726        RuntimeState s = null;
727        int[] lastIndex = new int[] {-1};
728        while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
729            if (s.backgroundPainter != null) {
730                p = paintFilter(s.backgroundPainter);
731                break;
732            }
733        }
734        if (p == null) p = paintFilter((Painter)get(ctx, "backgroundPainter"));
735        if (p != null) {
736            v.cache.put(new CacheKey("backgroundPainter$$instance", xstate), p);
737        }
738        return p;
739    }
740
741    /**
742     * Gets the appropriate foreground Painter, if there is one, for the state
743     * specified in the given SynthContext. This method does appropriate
744     * fallback searching, as described in #get.
745     *
746     * @param ctx The SynthContext. Must not be null.
747     * @return The foreground painter associated for the given state, or null if
748     * none could be found.
749     */
750    public Painter<Object> getForegroundPainter(SynthContext ctx) {
751        Values v = getValues(ctx);
752        int xstate = getExtendedState(ctx, v);
753        Painter<Object> p = null;
754
755        // check the cache
756        tmpKey.init("foregroundPainter$$instance", xstate);
757        p = paintFilter((Painter)v.cache.get(tmpKey));
758        if (p != null) return p;
759
760        // not in cache, so lookup and store in cache
761        RuntimeState s = null;
762        int[] lastIndex = new int[] {-1};
763        while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
764            if (s.foregroundPainter != null) {
765                p = paintFilter(s.foregroundPainter);
766                break;
767            }
768        }
769        if (p == null) p = paintFilter((Painter)get(ctx, "foregroundPainter"));
770        if (p != null) {
771            v.cache.put(new CacheKey("foregroundPainter$$instance", xstate), p);
772        }
773        return p;
774    }
775
776    /**
777     * Gets the appropriate border Painter, if there is one, for the state
778     * specified in the given SynthContext. This method does appropriate
779     * fallback searching, as described in #get.
780     *
781     * @param ctx The SynthContext. Must not be null.
782     * @return The border painter associated for the given state, or null if
783     * none could be found.
784     */
785    public Painter<Object> getBorderPainter(SynthContext ctx) {
786        Values v = getValues(ctx);
787        int xstate = getExtendedState(ctx, v);
788        Painter<Object> p = null;
789
790        // check the cache
791        tmpKey.init("borderPainter$$instance", xstate);
792        p = paintFilter((Painter)v.cache.get(tmpKey));
793        if (p != null) return p;
794
795        // not in cache, so lookup and store in cache
796        RuntimeState s = null;
797        int[] lastIndex = new int[] {-1};
798        while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
799            if (s.borderPainter != null) {
800                p = paintFilter(s.borderPainter);
801                break;
802            }
803        }
804        if (p == null) p = paintFilter((Painter)get(ctx, "borderPainter"));
805        if (p != null) {
806            v.cache.put(new CacheKey("borderPainter$$instance", xstate), p);
807        }
808        return p;
809    }
810
811    /**
812     * Utility method which returns the proper Values based on the given
813     * SynthContext. Ensures that parsing of the values has occurred, or
814     * reoccurs as necessary.
815     *
816     * @param ctx The SynthContext
817     * @return a non-null values reference
818     */
819    private Values getValues(SynthContext ctx) {
820        validate();
821        return values;
822    }
823
824    /**
825     * Simple utility method that searches the given array of Strings for the
826     * given string. This method is only called from getExtendedState if
827     * the developer has specified a specific state for the component to be
828     * in (ie, has "wedged" the component in that state) by specifying
829     * they client property "Nimbus.State".
830     *
831     * @param names a non-null array of strings
832     * @param name the name to look for in the array
833     * @return true or false based on whether the given name is in the array
834     */
835    private boolean contains(String[] names, String name) {
836        assert name != null;
837        for (int i=0; i<names.length; i++) {
838            if (name.equals(names[i])) {
839                return true;
840            }
841        }
842        return false;
843    }
844
845    /**
846     * <p>Gets the extended state for a given synth context. Nimbus supports the
847     * ability to define custom states. The algorithm used for choosing what
848     * style information to use for a given state requires a single integer
849     * bit string where each bit in the integer represents a different state
850     * that the component is in. This method uses the componentState as
851     * reported in the SynthContext, in addition to custom states, to determine
852     * what this extended state is.</p>
853     *
854     * <p>In addition, this method checks the component in the given context
855     * for a client property called "Nimbus.State". If one exists, then it will
856     * decompose the String associated with that property to determine what
857     * state to return. In this way, the developer can force a component to be
858     * in a specific state, regardless of what the "real" state of the component
859     * is.</p>
860     *
861     * <p>The string associated with "Nimbus.State" would be of the form:
862     * <pre>Enabled+CustomState+MouseOver</pre></p>
863     *
864     * @param ctx
865     * @param v
866     * @return
867     */
868    @SuppressWarnings({"unchecked", "rawtypes"})
869    private int getExtendedState(SynthContext ctx, Values v) {
870        JComponent c = ctx.getComponent();
871        int xstate = 0;
872        int mask = 1;
873        //check for the Nimbus.State client property
874        //Performance NOTE: getClientProperty ends up inside a synchronized
875        //block, so there is some potential for performance issues here, however
876        //I'm not certain that there is one on a modern VM.
877        Object property = c.getClientProperty("Nimbus.State");
878        if (property != null) {
879            String stateNames = property.toString();
880            String[] states = stateNames.split("\\+");
881            if (v.stateTypes == null){
882                // standard states only
883                for (String stateStr : states) {
884                    State.StandardState s = State.getStandardState(stateStr);
885                    if (s != null) xstate |= s.getState();
886                }
887            } else {
888                // custom states
889                for (State<?> s : v.stateTypes) {
890                    if (contains(states, s.getName())) {
891                        xstate |= mask;
892                    }
893                    mask <<= 1;
894                }
895            }
896        } else {
897            //if there are no custom states defined, then simply return the
898            //state that Synth reported
899            if (v.stateTypes == null) return ctx.getComponentState();
900
901            //there are custom states on this values, so I'll have to iterate
902            //over them all and return a custom extended state
903            int state = ctx.getComponentState();
904            for (State s : v.stateTypes) {
905                if (s.isInState(c, state)) {
906                    xstate |= mask;
907                }
908                mask <<= 1;
909            }
910        }
911        return xstate;
912    }
913
914    /**
915     * <p>Gets the RuntimeState that most closely matches the state in the given
916     * context, but is less specific than the given "lastState". Essentially,
917     * this allows you to search for the next best state.</p>
918     *
919     * <p>For example, if you had the following three states:
920     * <pre>
921     * Enabled
922     * Enabled+Pressed
923     * Disabled
924     * </pre>
925     * And you wanted to find the state that best represented
926     * ENABLED+PRESSED+FOCUSED and <code>lastState</code> was null (or an
927     * empty array, or an array with a single int with index == -1), then
928     * Enabled+Pressed would be returned. If you then call this method again but
929     * pass the index of Enabled+Pressed as the "lastState", then
930     * Enabled would be returned. If you call this method a third time and pass
931     * the index of Enabled in as the <code>lastState</code>, then null would be
932     * returned.</p>
933     *
934     * <p>The actual code path for determining the proper state is the same as
935     * in Synth.</p>
936     *
937     * @param lastState a 1 element array, allowing me to do pass-by-reference.
938     */
939    private RuntimeState getNextState(RuntimeState[] states,
940                                      int[] lastState,
941                                      int xstate) {
942        // Use the StateInfo with the most bits that matches that of state.
943        // If there are none, then fallback to
944        // the StateInfo with a state of 0, indicating it'll match anything.
945
946        // Consider if we have 3 StateInfos a, b and c with states:
947        // SELECTED, SELECTED | ENABLED, 0
948        //
949        // Input                          Return Value
950        // -----                          ------------
951        // SELECTED                       a
952        // SELECTED | ENABLED             b
953        // MOUSE_OVER                     c
954        // SELECTED | ENABLED | FOCUSED   b
955        // ENABLED                        c
956
957        if (states != null && states.length > 0) {
958            int bestCount = 0;
959            int bestIndex = -1;
960            int wildIndex = -1;
961
962            //if xstate is 0, then search for the runtime state with component
963            //state of 0. That is, find the exact match and return it.
964            if (xstate == 0) {
965                for (int counter = states.length - 1; counter >= 0; counter--) {
966                    if (states[counter].state == 0) {
967                        lastState[0] = counter;
968                        return states[counter];
969                    }
970                }
971                //an exact match couldn't be found, so there was no match.
972                lastState[0] = -1;
973                return null;
974            }
975
976            //xstate is some value != 0
977
978            //determine from which index to start looking. If lastState[0] is -1
979            //then we know to start from the end of the state array. Otherwise,
980            //we start at the lastIndex - 1.
981            int lastStateIndex = lastState == null || lastState[0] == -1 ?
982                states.length : lastState[0];
983
984            for (int counter = lastStateIndex - 1; counter >= 0; counter--) {
985                int oState = states[counter].state;
986
987                if (oState == 0) {
988                    if (wildIndex == -1) {
989                        wildIndex = counter;
990                    }
991                } else if ((xstate & oState) == oState) {
992                    // This is key, we need to make sure all bits of the
993                    // StateInfo match, otherwise a StateInfo with
994                    // SELECTED | ENABLED would match ENABLED, which we
995                    // don't want.
996                    int bitCount = Integer.bitCount(oState);
997                    if (bitCount > bestCount) {
998                        bestIndex = counter;
999                        bestCount = bitCount;
1000                    }
1001                }
1002            }
1003            if (bestIndex != -1) {
1004                lastState[0] = bestIndex;
1005                return states[bestIndex];
1006            }
1007            if (wildIndex != -1) {
1008                lastState[0] = wildIndex;
1009                return states[wildIndex];
1010            }
1011        }
1012        lastState[0] = -1;
1013        return null;
1014    }
1015
1016    /**
1017     * Contains values such as the UIDefaults and painters associated with
1018     * a state. Whereas <code>State</code> represents a distinct state that a
1019     * component can be in (such as Enabled), this class represents the colors,
1020     * fonts, painters, etc associated with some state for this
1021     * style.
1022     */
1023    private final class RuntimeState implements Cloneable {
1024        int state;
1025        Painter<Object> backgroundPainter;
1026        Painter<Object> foregroundPainter;
1027        Painter<Object> borderPainter;
1028        String stateName;
1029        UIDefaults defaults = new UIDefaults(10, .7f);
1030
1031        private RuntimeState(int state, String stateName) {
1032            this.state = state;
1033            this.stateName = stateName;
1034        }
1035
1036        @Override
1037        public String toString() {
1038            return stateName;
1039        }
1040
1041        @Override
1042        public RuntimeState clone() {
1043            RuntimeState clone = new RuntimeState(state, stateName);
1044            clone.backgroundPainter = backgroundPainter;
1045            clone.foregroundPainter = foregroundPainter;
1046            clone.borderPainter = borderPainter;
1047            clone.defaults.putAll(defaults);
1048            return clone;
1049        }
1050    }
1051
1052    /**
1053     * Essentially a struct of data for a style. A default instance of this
1054     * class is used by NimbusStyle. Additional instances exist for each
1055     * component that has overrides.
1056     */
1057    private static final class Values {
1058        /**
1059         * The list of State types. A State represents a type of state, such
1060         * as Enabled, Default, WindowFocused, etc. These can be custom states.
1061         */
1062        State<?>[] stateTypes = null;
1063        /**
1064         * The list of actual runtime state representations. These can represent things such
1065         * as Enabled + Focused. Thus, they differ from States in that they contain
1066         * several states together, and have associated properties, data, etc.
1067         */
1068        RuntimeState[] states = null;
1069        /**
1070         * The content margins for this region.
1071         */
1072        Insets contentMargins;
1073        /**
1074         * Defaults on the region/component level.
1075         */
1076        UIDefaults defaults = new UIDefaults(10, .7f);
1077        /**
1078         * Simple cache. After a value has been looked up, it is stored
1079         * in this cache for later retrieval. The key is a concatenation of
1080         * the property being looked up, two dollar signs, and the extended
1081         * state. So for example:
1082         *
1083         * foo.bar$$2353
1084         */
1085        Map<CacheKey,Object> cache = new HashMap<CacheKey,Object>();
1086    }
1087
1088    /**
1089     * This implementation presupposes that key is never null and that
1090     * the two keys being checked for equality are never null
1091     */
1092    private static final class CacheKey {
1093        private String key;
1094        private int xstate;
1095
1096        CacheKey(Object key, int xstate) {
1097            init(key, xstate);
1098        }
1099
1100        void init(Object key, int xstate) {
1101            this.key = key.toString();
1102            this.xstate = xstate;
1103        }
1104
1105        @Override
1106        public boolean equals(Object obj) {
1107            final CacheKey other = (CacheKey) obj;
1108            if (obj == null) return false;
1109            if (this.xstate != other.xstate) return false;
1110            if (!this.key.equals(other.key)) return false;
1111            return true;
1112        }
1113
1114        @Override
1115        public int hashCode() {
1116            int hash = 3;
1117            hash = 29 * hash + this.key.hashCode();
1118            hash = 29 * hash + this.xstate;
1119            return hash;
1120        }
1121    }
1122}
1123