KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > java > swing > plaf > nimbus > NimbusStyle


1 /*
2  * @(#)NimbusStyle.java 1.8 08/01/29
3  *
4  * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package com.sun.java.swing.plaf.nimbus;
8
9 import com.sun.java.swing.Painter;
10
11 import javax.swing.JComponent JavaDoc;
12 import javax.swing.UIDefaults JavaDoc;
13 import javax.swing.UIManager JavaDoc;
14 import javax.swing.plaf.ColorUIResource JavaDoc;
15 import javax.swing.plaf.synth.ColorType JavaDoc;
16 import static javax.swing.plaf.synth.SynthConstants JavaDoc.*;
17 import javax.swing.plaf.synth.SynthContext JavaDoc;
18 import javax.swing.plaf.synth.SynthPainter JavaDoc;
19 import javax.swing.plaf.synth.SynthStyle JavaDoc;
20 import java.awt.Color JavaDoc;
21 import java.awt.Font JavaDoc;
22 import java.awt.Insets JavaDoc;
23 import java.util.ArrayList JavaDoc;
24 import java.util.Collections JavaDoc;
25 import java.util.Comparator JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.HashSet JavaDoc;
28 import java.util.List JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.TreeMap JavaDoc;
31 import java.util.WeakHashMap JavaDoc;
32 import javax.swing.plaf.synth.SynthConstants JavaDoc;
33
34 /**
35  * <p>A SynthStyle implementation used by Nimbus. Each Region that has been
36  * registered with the NimbusLookAndFeel will have an associated NimbusStyle.
37  * Third party components that are registered with the NimbusLookAndFeel will
38  * therefore be handed a NimbusStyle from the look and feel from the
39  * #getStyle(JComponent, Region) method.</p>
40  *
41  * <p>This class properly reads and retrieves values placed in the UIDefaults
42  * according to the standard Nimbus naming conventions. It will create and
43  * retrieve painters, fonts, colors, and other data stored there.</p>
44  *
45  * <p>NimbusStyle also supports the ability to override settings on a per
46  * component basis. NimbusStyle checks the component's client property map for
47  * "Nimbus.Overrides". If the value associated with this key is an instance of
48  * UIDefaults, then the values in that defaults table will override the standard
49  * Nimbus defaults in UIManager, but for that component instance only.</p>
50  *
51  * <p>Optionally, you may specify the client property
52  * "Nimbus.Overrides.InheritDefaults". If true, this client property indicates
53  * that the defaults located in UIManager should first be read, and then
54  * replaced with defaults located in the component client properties. If false,
55  * then only the defaults located in the component client property map will
56  * be used. If not specified, it is assumed to be true.</p>
57  *
58  * <p>You must specify "Nimbus.Overrides" for "Nimbus.Overrides.InheritDefaults"
59  * to have any effect. "Nimbus.Overrides" indicates whether there are any
60  * overrides, while "Nimbus.Overrides.InheritDefaults" indicates whether those
61  * overrides should first be initialized with the defaults from UIManager.</p>
62  *
63  * <p>The NimbusStyle is reloaded whenever a property change event is fired
64  * for a component for "Nimbus.Overrides" or "Nimbus.Overrides.InheritDefaults".
65  * So for example, setting a new UIDefaults on a component would cause the
66  * style to be reloaded.</p>
67  *
68  * <p>The values are only read out of UIManager once, and then cached. If
69  * you need to read the values again (for example, if the UI is being reloaded),
70  * then discard this NimbusStyle and read a new one from NimbusLookAndFeel
71  * using NimbusLookAndFeel.getStyle.</p>
72  *
73  * <p>The primary API of interest in this class for 3rd party component authors
74  * are the three methods which retrieve painters: #getBackgroundPainter,
75  * #getForegroundPainter, and #getBorderPainter.</p>
76  *
77  * <p>NimbusStyle allows you to specify custom states, or modify the order of
78  * states. Synth (and thus Nimbus) has the concept of a "state". For example,
79  * a JButton might be in the "MOUSE_OVER" state, or the "ENABLED" state, or the
80  * "DISABLED" state. These are all "standard" states which are defined in synth,
81  * and which apply to all synth Regions.</p>
82  *
83  * <p>Sometimes, however, you need to have a custom state. For example, you
84  * want JButton to render differently if it's parent is a JToolbar. In Nimbus,
85  * you specify these custom states by including a special key in UIDefaults.
86  * The following UIDefaults entries define three states for this button:</p>
87  *
88  * <pre><code>
89  * JButton.States = Enabled, Disabled, Toolbar
90  * JButton[Enabled].backgroundPainter = somePainter
91  * JButton[Disabled].background = BLUE
92  * JButton[Toolbar].backgroundPainter = someOtherPaint
93  * </code></pre>
94  *
95  * <p>As you can see, the <code>JButton.States</code> entry lists the states
96  * that the JButton style will support. You then specify the settings for
97  * each state. If you do not specify the <code>JButton.States</code> entry,
98  * then the standard Synth states will be assumed. If you specify the entry
99  * but the list of states is empty or null, then the standard synth states
100  * will be assumed.</p>
101  *
102  * @author Richard Bair
103  * @author Jasper Potts
104  */

105 public final class NimbusStyle extends SynthStyle JavaDoc {
106
107     /* Keys and scales for large/small/mini components, based on Apples sizes */
108     public static final String JavaDoc LARGE_KEY = "large";
109     public static final String JavaDoc SMALL_KEY = "small";
110     public static final String JavaDoc MINI_KEY = "mini";
111     public static final double LARGE_SCALE = 1.15;
112     public static final double SMALL_SCALE = 0.857;
113     public static final double MINI_SCALE = 0.714;
114
115     /**
116      * Special constant used for performance reasons during the get() method.
117      * If get() runs through all of the search locations and determines that
118      * there is no value, then NULL will be placed into the values map. This way
119      * on subsequent lookups it will simply extract NULL, see it, and return
120      * null rather than continuing the lookup procedure.
121      */

122     private static final Object JavaDoc NULL = '\0';
123     /**
124      * <p>The Color to return from getColorForState if it would otherwise have
125      * returned null.</p>
126      *
127      * <p>Returning null from getColorForState is a very bad thing, as it causes
128      * the AWT peer for the component to install a SystemColor, which is not a
129      * UIResource. As a result, if <code>null</code> is returned from
130      * getColorForState, then thereafter the color is not updated for other
131      * states or on LAF changes or updates. This DEFAULT_COLOR is used to
132      * ensure that a ColorUIResource is always returned from
133      * getColorForState.</p>
134      */

135     private static final Color JavaDoc DEFAULT_COLOR = new ColorUIResource JavaDoc(Color.BLACK);
136     /**
137      * The prefix for the component or region that this NimbusStyle
138      * represents. This prefix is used to lookup state in the UIManager.
139      * It should be something like Button or Slider.Thumb or "MyButton" or
140      * ComboBox."ComboBox.arrowButton" or "MyComboBox"."ComboBox.arrowButton"
141      */

142     private String JavaDoc prefix;
143     /**
144      * The SynthPainter that will be returned from this NimbusStyle. The
145      * SynthPainter returned will be a SynthPainterImpl, which will in turn
146      * delegate back to this NimbusStyle for the proper Painter (not
147      * SynthPainter) to use for painting the foreground, background, or border.
148      */

149     private SynthPainter JavaDoc painter;
150     /**
151      * Data structure containing all of the defaults, insets, states, and other
152      * values associated with this style. This instance refers to default
153      * values, and are used when no overrides are discovered in the client
154      * properties of a component. These values are lazily created on first
155      * access.
156      */

157     private Values values;
158     /**
159      * <p>This weak hash map stores a reference to all components that have
160      * been passed to this style that have overrides in their client properties.
161      * Since this is a weak hash map, when the component is garbage collected,
162      * it will be removed automatically from this map.</p>
163      *
164      * <p>This map is necessary so that we can reference the Values for
165      * components on subsequent calls to NimbusStyle.</p>
166      */

167     private WeakHashMap JavaDoc<JComponent JavaDoc, Values> overrides;
168
169     /**
170      * A temporary CacheKey used to perform lookups. This pattern avoids
171      * creating useless garbage keys, or concatenating strings, etc.
172      */

173     private CacheKey tmpKey = new CacheKey("", 0);
174     
175     /**
176      * Create a new NimbusStyle. Only the prefix must be supplied. At the
177      * appropriate time, installDefaults will be called. At that point, all of
178      * the state information will be pulled from UIManager and stored locally
179      * within this style.
180      *
181      * @param prefix Something like Button or Slider.Thumb or
182      * org.jdesktop.swingx.JXStatusBar or ComboBox."ComboBox.arrowButton"
183      */

184     NimbusStyle(String JavaDoc prefix) {
185         this.prefix = prefix;
186         overrides = new WeakHashMap JavaDoc<JComponent JavaDoc, Values>();
187         this.painter = new SynthPainterImpl(this);
188     }
189
190     /**
191      * @InheritDoc
192      *
193      * Overridden to cause this style to populate itself with data from
194      * UIDefaults, if necessary.
195      */

196     @Override JavaDoc public void installDefaults(SynthContext JavaDoc ctx) {
197         validate();
198
199         //delegate to the superclass to install defaults such as background,
200
//foreground, font, and opaque onto the swing component.
201
super.installDefaults(ctx);
202     }
203
204     /**
205      * Pulls data out of UIDefaults, if it has not done so already, and sets
206      * up the internal state.
207      */

208     private void validate() {
209         // a non-null values object is the flag we use to determine whether
210
// to reparse from UIManager.
211
if (values != null) return;
212         
213         //reconstruct this NimbusStyle based on the entries in the UIManager
214
values = new Values();
215
216         //a map of defaults that apply to this NimbusStyle
217
TreeMap JavaDoc<String JavaDoc, Object JavaDoc> myDefaults = new TreeMap JavaDoc<String JavaDoc, Object JavaDoc>();
218         
219         //iterate over all of the entries in UIManager. If the key begins with
220
//the prefix, then put it, and the value, into myDefaults
221
for (Object JavaDoc obj : new HashSet JavaDoc(UIManager.getDefaults().keySet())) {
222             String JavaDoc key = obj == null ? null : obj.toString();
223             if (key != null && key.startsWith(prefix)) {
224                 myDefaults.put(key, UIManager.getDefaults().get(key));
225             }
226         }
227         
228         //This second loop pulls defaults from the look and feel defaults
229
UIDefaults JavaDoc lafDefaults = UIManager.getLookAndFeelDefaults();
230         for (Object JavaDoc obj : new HashSet JavaDoc(lafDefaults.keySet())) {
231             String JavaDoc key = obj == null ? null : obj.toString();
232             if (key != null && key.startsWith(prefix)) {
233                 myDefaults.put(key, lafDefaults.get(key));
234             }
235         }
236
237         //Now that I've accumulated all the defaults pertaining to this
238
//style, call init which will read these defaults and configure
239
//the default "values".
240
init(values, myDefaults);
241     }
242
243     /**
244      * Initializes the given <code>Values</code> object with the defaults
245      * contained in the given TreeMap.
246      *
247      * @param v The Values object to be initialized
248      * @param myDefaults a map of UIDefaults to use in initializing the Values.
249      * This map must contain only keys associated with this Style.
250      */

251     private void init(Values v, TreeMap JavaDoc<String JavaDoc, Object JavaDoc> myDefaults) {
252         //a list of the different types of states used by this style. This
253
//list may contain only "standard" states (those defined by Synth),
254
//or it may contain custom states, or it may contain only "standard"
255
//states but list them in a non-standard order.
256
List JavaDoc<State> states = new ArrayList JavaDoc<State>();
257         //a map of state name to code
258
Map JavaDoc<String JavaDoc,Integer JavaDoc> stateCodes = new HashMap JavaDoc<String JavaDoc,Integer JavaDoc>();
259         //This is a list of runtime "state" context objects. These contain
260
//the values associated with each state.
261
List JavaDoc<RuntimeState> runtimeStates = new ArrayList JavaDoc<RuntimeState>();
262         
263         //determine whether there are any custom states, or custom state
264
//order. If so, then read all those custom states and define the
265
//"values" stateTypes to be a non-null array.
266
//Otherwise, let the "values" stateTypes be null to indicate that
267
//there are no custom states or custom state ordering
268
String JavaDoc statesString = (String JavaDoc)myDefaults.get(prefix + ".States");
269         if (statesString != null) {
270             String JavaDoc s[] = statesString.split(",");
271             for (int i=0; i<s.length; i++) {
272                 s[i] = s[i].trim();
273                 if (!State.isStandardStateName(s[i])) {
274                     //this is a non-standard state name, so look for the
275
//custom state associated with it
276
String JavaDoc stateName = prefix + "." + s[i];
277                     State customState = (State)myDefaults.get(stateName);
278                     if (customState != null) {
279                         states.add(customState);
280                     }
281                 } else {
282                     states.add(State.getStandardState(s[i]));
283                 }
284             }
285             
286             //if there were any states defined, then set the stateTypes array
287
//to be non-null. Otherwise, leave it null (meaning, use the
288
//standard synth states).
289
if (states.size() > 0) {
290                 v.stateTypes = states.toArray(new State[states.size()]);
291             }
292             
293             //assign codes for each of the state types
294
int code = 1;
295             for (State state : states) {
296                 stateCodes.put(state.getName(), code);
297                 code <<= 1;
298             }
299         } else {
300             //since there were no custom states defined, setup the list of
301
//standard synth states. Note that the "v.stateTypes" is not
302
//being set here, indicating that at runtime the state selection
303
//routines should use standard synth states instead of custom
304
//states. I do need to popuplate this temp list now though, so that
305
//the remainder of this method will function as expected.
306
states.add(State.Enabled);
307             states.add(State.MouseOver);
308             states.add(State.Pressed);
309             states.add(State.Disabled);
310             states.add(State.Focused);
311             states.add(State.Selected);
312             states.add(State.Default);
313             
314             //assign codes for the states
315
stateCodes.put("Enabled", ENABLED);
316             stateCodes.put("MouseOver", MOUSE_OVER);
317             stateCodes.put("Pressed", PRESSED);
318             stateCodes.put("Disabled", DISABLED);
319             stateCodes.put("Focused", FOCUSED);
320             stateCodes.put("Selected", SELECTED);
321             stateCodes.put("Default", DEFAULT);
322         }
323         
324         
325         //Now iterate over all the keys in the defaults table
326
for (String JavaDoc key : myDefaults.keySet()) {
327             //The key is something like JButton.Enabled.backgroundPainter,
328
//or JButton.States, or JButton.background.
329
//Remove the "JButton." portion of the key
330
String JavaDoc temp = key.substring(prefix.length());
331             //if there is a " or : then we skip it because it is a subregion
332
//of some kind
333
if (temp.indexOf('"') != -1 || temp.indexOf(':') != -1) continue;
334             //remove the separator
335
temp = temp.substring(1);
336             //At this point, temp may be any of the following:
337
//background
338
//[Enabled].background
339
//[Enabled+MouseOver].background
340
//property.foo
341

342             //parse out the states and the property
343
String JavaDoc stateString = null;
344             String JavaDoc property = null;
345             int bracketIndex = temp.indexOf(']');
346             if (bracketIndex < 0) {
347                 //there is not a state string, so property = temp
348
property = temp;
349             } else {
350                 stateString = temp.substring(0, bracketIndex);
351                 property = temp.substring(bracketIndex + 2);
352             }
353
354             //now that I have the state (if any) and the property, get the
355
//value for this property and install it where it belongs
356
if (stateString == null) {
357                 //there was no state, just a property. Check for the custom
358
//"contentMargins" property (which is handled specially by
359
//Synth/Nimbus). Also check for the property being "States",
360
//in which case it is not a real property and should be ignored.
361
//otherwise, assume it is a property and install it on the
362
//values object
363
if ("contentMargins".equals(property)) {
364                     v.contentMargins = (Insets JavaDoc)myDefaults.get(key);
365                 } else if ("States".equals(property)) {
366                     //ignore
367
} else {
368                     v.defaults.put(property, myDefaults.get(key));
369                 }
370             } else {
371                 //it is possible that the developer has a malformed UIDefaults
372
//entry, such that something was specified in the place of
373
//the State portion of the key but it wasn't a state. In this
374
//case, skip will be set to true
375
boolean skip = false;
376                 //this variable keeps track of the int value associated with
377
//the state. See SynthState for details.
378
int componentState = 0;
379                 //Multiple states may be specified in the string, such as
380
//Enabled+MouseOver
381
String JavaDoc[] stateParts = stateString.split("\\+");
382                 //For each state, we need to find the State object associated
383
//with it, or skip it if it cannot be found.
384
for (String JavaDoc s : stateParts) {
385                     if (stateCodes.containsKey(s)) {
386                         componentState |= stateCodes.get(s);
387                     } else {
388                         //Was not a state. Maybe it was a subregion or something
389
//skip it.
390
skip = true;
391                         break;
392                     }
393                 }
394                 
395                 if (skip) continue;
396                 
397                 //find the RuntimeState for this State
398
RuntimeState rs = null;
399                 for (RuntimeState s : runtimeStates) {
400                     if (s.state == componentState) {
401                         rs = s;
402                         break;
403                     }
404                 }
405                 
406                 //couldn't find the runtime state, so create a new one
407
if (rs == null) {
408                     rs = new RuntimeState(componentState, stateString);
409                     runtimeStates.add(rs);
410                 }
411                 
412                 //check for a couple special properties, such as for the
413
//painters. If these are found, then set the specially on
414
//the runtime state. Else, it is just a normal property,
415
//so put it in the UIDefaults associated with that runtime
416
//state
417
if ("backgroundPainter".equals(property)) {
418                     rs.backgroundPainter = (Painter)myDefaults.get(key);
419                 } else if ("foregroundPainter".equals(property)) {
420                     rs.foregroundPainter = (Painter) myDefaults.get(key);
421                 } else if ("borderPainter".equals(property)) {
422                     rs.borderPainter = (Painter) myDefaults.get(key);
423                 } else {
424                     rs.defaults.put(property, myDefaults.get(key));
425                 }
426             }
427         }
428
429         //now that I've collected all the runtime states, I'll sort them based
430
//on their integer "state" (see SynthState for how this works).
431
Collections.sort(runtimeStates,new Comparator JavaDoc<RuntimeState>() {
432             @Override JavaDoc
433             public int compare(RuntimeState a, RuntimeState b) {
434                 return a.state - b.state;
435             }
436         });
437         
438         //finally, set the array of runtimes states on the values object
439
v.states = runtimeStates.toArray(new RuntimeState[0]);
440     }
441     
442     /**
443      * @InheritDoc
444      *
445      * Overridden to cause this style to populate itself with data from
446      * UIDefaults, if necessary.
447      */

448     @Override JavaDoc public Insets JavaDoc getInsets(SynthContext JavaDoc ctx, Insets JavaDoc in) {
449         if (in == null) {
450             in = new Insets JavaDoc(0, 0, 0, 0);
451         }
452
453         Values v = getValues(ctx);
454         
455         if (v.contentMargins == null) {
456             in.bottom = in.top = in.left = in.right = 0;
457             return in;
458         } else {
459             in.bottom = v.contentMargins.bottom;
460             in.top = v.contentMargins.top;
461             in.left = v.contentMargins.left;
462             in.right = v.contentMargins.right;
463             // Account for scale
464
// The key "JComponent.sizeVariant" is used to match Apple's LAF
465
String JavaDoc scaleKey = (String JavaDoc)ctx.getComponent().getClientProperty(
466                     "JComponent.sizeVariant");
467             if (scaleKey != null){
468                 if (LARGE_KEY.equals(scaleKey)){
469                     in.bottom *= LARGE_SCALE;
470                     in.top *= LARGE_SCALE;
471                     in.left *= LARGE_SCALE;
472                     in.right *= LARGE_SCALE;
473                 } else if (SMALL_KEY.equals(scaleKey)){
474                     in.bottom *= SMALL_SCALE;
475                     in.top *= SMALL_SCALE;
476                     in.left *= SMALL_SCALE;
477                     in.right *= SMALL_SCALE;
478                 } else if (MINI_KEY.equals(scaleKey)){
479                     in.bottom *= MINI_SCALE;
480                     in.top *= MINI_SCALE;
481                     in.left *= MINI_SCALE;
482                     in.right *= MINI_SCALE;
483                 }
484             }
485             return in;
486         }
487     }
488
489     /**
490      * @InheritDoc
491      *
492      * <p>Overridden to cause this style to populate itself with data from
493      * UIDefaults, if necessary.</p>
494      *
495      * <p>In addition, NimbusStyle handles ColorTypes slightly differently from
496      * Synth.</p>
497      * <ul>
498      * <li>ColorType.BACKGROUND will equate to the color stored in UIDefaults
499      * named "background".</li>
500      * <li>ColorType.TEXT_BACKGROUND will equate to the color stored in
501      * UIDefaults named "textBackground".</li>
502      * <li>ColorType.FOREGROUND will equate to the color stored in UIDefaults
503      * named "textForeground".</li>
504      * <li>ColorType.TEXT_FOREGROUND will equate to the color stored in
505      * UIDefaults named "textForeground".</li>
506      * </ul>
507      */

508     @Override JavaDoc protected Color JavaDoc getColorForState(SynthContext JavaDoc ctx, ColorType JavaDoc type) {
509         String JavaDoc key = null;
510         if (type == ColorType.BACKGROUND) {
511             key = "background";
512         } else if (type == ColorType.FOREGROUND) {
513             //map FOREGROUND as TEXT_FOREGROUND
514
key = "textForeground";
515         } else if (type == ColorType.TEXT_BACKGROUND) {
516             key = "textBackground";
517         } else if (type == ColorType.TEXT_FOREGROUND) {
518             key = "textForeground";
519         } else if (type == ColorType.FOCUS) {
520             key = "focus";
521         } else if (type != null) {
522             key = type.toString();
523         } else {
524             return DEFAULT_COLOR;
525         }
526         Color JavaDoc c = (Color JavaDoc) get(ctx, key);
527         //if all else fails, return a default color (which is a ColorUIResource)
528
if (c == null) c = DEFAULT_COLOR;
529         return c;
530     }
531
532     /**
533      * @InheritDoc
534      *
535      * Overridden to cause this style to populate itself with data from
536      * UIDefaults, if necessary. If a value named "font" is not found in
537      * UIDefaults, then the "defaultFont" font in UIDefaults will be returned
538      * instead.
539      */

540     @Override JavaDoc protected Font JavaDoc getFontForState(SynthContext JavaDoc ctx) {
541         Font JavaDoc f = (Font JavaDoc)get(ctx, "font");
542         if (f == null) f = UIManager.getFont("defaultFont");
543
544         // Account for scale
545
// The key "JComponent.sizeVariant" is used to match Apple's LAF
546
String JavaDoc scaleKey = (String JavaDoc)ctx.getComponent().getClientProperty(
547                 "JComponent.sizeVariant");
548         if (scaleKey != null){
549             if (LARGE_KEY.equals(scaleKey)){
550                 f = f.deriveFont(Math.round(f.getSize2D()*LARGE_SCALE));
551             } else if (SMALL_KEY.equals(scaleKey)){
552                 f = f.deriveFont(Math.round(f.getSize2D()*SMALL_SCALE));
553             } else if (MINI_KEY.equals(scaleKey)){
554                 f = f.deriveFont(Math.round(f.getSize2D()*MINI_SCALE));
555             }
556         }
557         return f;
558
559     }
560
561     /**
562      * @InheritDoc
563      *
564      * Returns the SynthPainter for this style, which ends up delegating to
565      * the Painters installed in this style.
566      */

567     @Override JavaDoc public SynthPainter JavaDoc getPainter(SynthContext JavaDoc ctx) {
568         return painter;
569     }
570
571     /**
572      * @InheritDoc
573      *
574      * Overridden to cause this style to populate itself with data from
575      * UIDefaults, if necessary. If opacity is not specified in UI defaults,
576      * then it defaults to being non-opaque.
577      */

578     @Override JavaDoc public boolean isOpaque(SynthContext JavaDoc ctx) {
579         // Force Table CellRenderers to be opaque
580
if ("Table.cellRenderer".equals(ctx.getComponent().getName())) {
581             return true;
582         }
583         Boolean JavaDoc opaque = (Boolean JavaDoc)get(ctx, "opaque");
584         return opaque == null ? false : opaque;
585     }
586
587     /**
588      * @InheritDoc
589      *
590      * <p>Overridden to cause this style to populate itself with data from
591      * UIDefaults, if necessary.</p>
592      *
593      * <p>Properties in UIDefaults may be specified in a chained manner. For
594      * example:
595      * <pre>
596      * background
597      * Button.opacity
598      * Button.Enabled.foreground
599      * Button.Enabled+Selected.background
600      * </pre></p>
601      *
602      * <p>In this example, suppose you were in the Enabled+Selected state and
603      * searched for "foreground". In this case, we first check for
604      * Button.Enabled+Selected.foreground, but no such color exists. We then
605      * fall back to the next valid state, in this case,
606      * Button.Enabled.foreground, and have a match. So we return it.</p>
607      *
608      * <p>Again, if we were in the state Enabled and looked for "background", we
609      * wouldn't find it in Button.Enabled, or in Button, but would at the top
610      * level in UIManager. So we return that value.</p>
611      *
612      * <p>One special note: the "key" passed to this method could be of the form
613      * "background" or "Button.background" where "Button" equals the prefix
614      * passed to the NimbusStyle constructor. In either case, it looks for
615      * "background".</p>
616      *
617      * @param ctx
618      * @param key must not be null
619      */

620     @Override JavaDoc public Object JavaDoc get(SynthContext JavaDoc ctx, Object JavaDoc key) {
621         Values v = getValues(ctx);
622
623         // strip off the prefix, if there is one.
624
String JavaDoc fullKey = key.toString();
625         String JavaDoc partialKey = fullKey.substring(fullKey.indexOf(".") + 1);
626
627         Object JavaDoc obj = null;
628         int xstate = getExtendedState(ctx, v);
629         
630         // check the cache
631
tmpKey.init(partialKey, xstate);
632         obj = v.cache.get(tmpKey);
633         boolean wasInCache = obj != null;
634         if (!wasInCache){
635             // Search exact matching states and then lesser matching states
636
RuntimeState s = null;
637             int[] lastIndex = new int[] {-1};
638             while (obj == null &&
639                     (s = getNextState(v.states, lastIndex, xstate)) != null) {
640                 obj = s.defaults.get(partialKey);
641             }
642             // Search Region Defaults
643
if (obj == null && v.defaults != null) {
644                 obj = v.defaults.get(partialKey);
645             }
646             // return found object
647
// Search UIManager Defaults
648
if (obj == null) obj = UIManager.get(fullKey);
649             // Search Synth Defaults for InputMaps
650
if (obj == null && partialKey.equals("focusInputMap")) {
651                 obj = super.get(ctx, fullKey);
652             }
653             // if all we got was a null, store this fact for later use
654
v.cache.put(new CacheKey(partialKey, xstate),
655                     obj == null ? NULL : obj);
656         }
657         // return found object
658
return obj == NULL ? null : obj;
659     }
660
661     /**
662      * Gets the appropriate background Painter, if there is one, for the state
663      * specified in the given SynthContext. This method does appropriate
664      * fallback searching, as described in #get.
665      *
666      * @param ctx The SynthContext. Must not be null.
667      * @return The background painter associated for the given state, or null if
668      * none could be found.
669      */

670     public Painter getBackgroundPainter(SynthContext JavaDoc ctx) {
671         Values v = getValues(ctx);
672         int xstate = getExtendedState(ctx, v);
673         Painter p = null;
674
675         // check the cache
676
tmpKey.init("backgroundPainter$$instance", xstate);
677         p = (Painter)v.cache.get(tmpKey);
678         if (p != null) return p;
679
680         // not in cache, so lookup and store in cache
681
RuntimeState s = null;
682         int[] lastIndex = new int[] {-1};
683         while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
684             if (s.backgroundPainter != null) {
685                 p = s.backgroundPainter;
686                 break;
687             }
688         }
689         if (p == null) p = (Painter)get(ctx, "backgroundPainter");
690         if (p != null) {
691             v.cache.put(new CacheKey("backgroundPainter$$instance", xstate), p);
692         }
693         return p;
694     }
695
696     /**
697      * Gets the appropriate foreground Painter, if there is one, for the state
698      * specified in the given SynthContext. This method does appropriate
699      * fallback searching, as described in #get.
700      *
701      * @param ctx The SynthContext. Must not be null.
702      * @return The foreground painter associated for the given state, or null if
703      * none could be found.
704      */

705     public Painter getForegroundPainter(SynthContext JavaDoc ctx) {
706         Values v = getValues(ctx);
707         int xstate = getExtendedState(ctx, v);
708         Painter p = null;
709
710         // check the cache
711
tmpKey.init("foregroundPainter$$instance", xstate);
712         p = (Painter)v.cache.get(tmpKey);
713         if (p != null) return p;
714
715         // not in cache, so lookup and store in cache
716
RuntimeState s = null;
717         int[] lastIndex = new int[] {-1};
718         while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
719             if (s.foregroundPainter != null) {
720                 p = s.foregroundPainter;
721                 break;
722             }
723         }
724         if (p == null) p = (Painter)get(ctx, "foregroundPainter");
725         if (p != null) {
726             v.cache.put(new CacheKey("foregroundPainter$$instance", xstate), p);
727         }
728         return p;
729     }
730
731     /**
732      * Gets the appropriate border Painter, if there is one, for the state
733      * specified in the given SynthContext. This method does appropriate
734      * fallback searching, as described in #get.
735      *
736      * @param ctx The SynthContext. Must not be null.
737      * @return The border painter associated for the given state, or null if
738      * none could be found.
739      */

740     public Painter getBorderPainter(SynthContext JavaDoc ctx) {
741         Values v = getValues(ctx);
742         int xstate = getExtendedState(ctx, v);
743         Painter p = null;
744
745         // check the cache
746
tmpKey.init("borderPainter$$instance", xstate);
747         p = (Painter)v.cache.get(tmpKey);
748         if (p != null) return p;
749
750         // not in cache, so lookup and store in cache
751
RuntimeState s = null;
752         int[] lastIndex = new int[] {-1};
753         while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
754             if (s.borderPainter != null) {
755                 p = s.borderPainter;
756                 break;
757             }
758         }
759         if (p == null) p = (Painter)get(ctx, "borderPainter");
760         if (p != null) {
761             v.cache.put(new CacheKey("borderPainter$$instance", xstate), p);
762         }
763         return p;
764     }
765
766     /**
767      * Utility method which returns the proper Values based on the given
768      * SynthContext. If the component in this context has specified UIDefaults
769      * in its client property map with the key "Nimbus.Overrides", then a
770      * custom Values object will be returned. Otherwise, the default values
771      * instance for this NimbusStyle instance will be returned.
772      *
773      * @param ctx The SynthContext
774      * @return a non-null values reference
775      */

776     private Values getValues(SynthContext JavaDoc ctx) {
777         validate();
778         
779         //check the "overrides" map for the component defined in ctx
780
//if there are values, then return them
781
JComponent JavaDoc c = ctx.getComponent();
782         Values v = overrides.get(c);
783         if (v != null) return v;
784         
785         //inspect the client properties for the key "Nimbus.Overrides". The
786
//value must be an instance of UIDefaults. From this, extract the keys
787
//with "prefix" and pass these on to init
788
Object JavaDoc o = c.getClientProperty("Nimbus.Overrides");
789         
790         //if o is null, then there clearly are no overrides, so return
791
//the default values
792
if (o == null) return values;
793         
794         //there may be overrides, and v has not been defined, so time to
795
//look for those overrides and read them and store them for future
796
//reference
797
if (o instanceof UIDefaults JavaDoc) {
798             Object JavaDoc i = c.getClientProperty("Nimbus.Overrides.InheritDefaults");
799             boolean inheritDefaults = i instanceof Boolean JavaDoc ? (Boolean JavaDoc)i : true;
800             UIDefaults JavaDoc d = (UIDefaults JavaDoc)o;
801             TreeMap JavaDoc<String JavaDoc, Object JavaDoc> map = new TreeMap JavaDoc<String JavaDoc, Object JavaDoc>();
802             for (Object JavaDoc obj : d.keySet()) {
803                 if (obj instanceof String JavaDoc) {
804                     String JavaDoc key = (String JavaDoc)obj;
805                     if (key.startsWith(prefix + ".")) {
806                         map.put(key, d.get(key));
807                     }
808                 }
809             }
810             v = new Values();
811             if (inheritDefaults) {
812                 //copy everything over from values
813
if (values.stateTypes != null) {
814                     v.stateTypes = new State[values.stateTypes.length];
815                     System.arraycopy(values.stateTypes, 0,v.stateTypes, 0,
816                             values.stateTypes.length);
817                 }
818                 v.contentMargins = values.contentMargins;
819                 if (values.states != null) {
820                     v.states = new RuntimeState[values.states.length];
821                     for (int j=0; j<v.states.length; j++) {
822                         v.states[j] = values.states[j].clone();
823                     }
824                 }
825                 v.defaults.putAll(values.defaults);
826             }
827             init(v, map);
828             overrides.put(c, v);
829         }
830
831         return v == null ? values : v;
832     }
833     
834     /**
835      * Simple utility method that searchs the given array of Strings for the
836      * given string. This method is only called from getExtendedState if
837      * the developer has specified a specific state for the component to be
838      * in (ie, has "wedged" the component in that state) by specifying
839      * they client property "Nimbus.State".
840      *
841      * @param names a non-null array of strings
842      * @param name the name to look for in the array
843      * @return true or false based on whether the given name is in the array
844      */

845     private boolean contains(String JavaDoc[] names, String JavaDoc name) {
846         assert name != null;
847         for (int i=0; i<names.length; i++) {
848             if (name.equals(names[i])) {
849                 return true;
850             }
851         }
852         return false;
853     }
854     
855     /**
856      * <p>Gets the extended state for a given synth context. Nimbus supports the
857      * ability to define custom states. The algorithm used for choosing what
858      * style information to use for a given state requires a single integer
859      * bit string where each bit in the integer represents a different state
860      * that the component is in. This method uses the componentState as
861      * reported in the SynthContext, in addition to custom states, to determine
862      * what this extended state is.</p>
863      *
864      * <p>In addition, this method checks the component in the given context
865      * for a client property called "Nimbus.State". If one exists, then it will
866      * decompose the String associated with that property to determine what
867      * state to return. In this way, the developer can force a component to be
868      * in a specific state, regardless of what the "real" state of the component
869      * is.</p>
870      *
871      * <p>The string associated with "Nimbus.State" would be of the form:
872      * <pre>Enabled+CustomState+MouseOver</pre></p>
873      *
874      * @param ctx
875      * @param v
876      * @return
877      */

878     private int getExtendedState(SynthContext JavaDoc ctx, Values v) {
879         JComponent JavaDoc c = ctx.getComponent();
880         int xstate = 0;
881         int mask = 1;
882         //check for the Nimbus.State client property
883
//Performance NOTE: getClientProperty ends up inside a synchronized
884
//block, so there is some potential for performance issues here, however
885
//I'm not certain that there is one on a modern VM.
886
Object JavaDoc property = c.getClientProperty("Nimbus.State");
887         if (property != null) {
888             String JavaDoc stateNames = property.toString();
889             String JavaDoc[] states = stateNames.split("\\+");
890             if (v.stateTypes == null){
891                 // standard states only
892
for (String JavaDoc stateStr : states) {
893                     State.StandardState s = State.getStandardState(stateStr);
894                     if (s != null) xstate |= s.getState();
895                 }
896             } else {
897                 // custom states
898
for (State s : v.stateTypes) {
899                     if (contains(states, s.getName())) {
900                         xstate |= mask;
901                     }
902                     mask <<= 1;
903                 }
904             }
905         } else {
906             //if there are no custom states defined, then simply return the
907
//state that Synth reported
908
if (v.stateTypes == null) return ctx.getComponentState();
909             
910             //there are custom states on this values, so I'll have to iterate
911
//over them all and return a custom extended state
912
int state = ctx.getComponentState();
913             for (State s : v.stateTypes) {
914                 if (s.isInState(c, state)) {
915                     xstate |= mask;
916                 }
917                 mask <<= 1;
918             }
919         }
920         return xstate;
921     }
922
923     /**
924      * <p>Gets the RuntimeState that most closely matches the state in the given
925      * context, but is less specific than the given "lastState". Essentially,
926      * this allows you to search for the next best state.</p>
927      *
928      * <p>For example, if you had the following three states:
929      * <pre>
930      * Enabled
931      * Enabled+Pressed
932      * Disabled
933      * </pre>
934      * And you wanted to find the state that best represented
935      * ENABLED+PRESSED+FOCUSED and <code>lastState</code> was null (or an
936      * empty array, or an array with a single int with index == -1), then
937      * Enabled+Pressed would be returned. If you then call this method again but
938      * pass the index of Enabled+Pressed as the "lastState", then
939      * Enabled would be returned. If you call this method a third time and pass
940      * the index of Enabled in as the <code>lastState</code>, then null would be
941      * returned.</p>
942      *
943      * <p>The actual code path for determining the proper state is the same as
944      * in Synth.</p>
945      *
946      * @param ctx
947      * @param lastState a 1 element array, allowing me to do pass-by-reference.
948      * @return
949      */

950     private RuntimeState getNextState(RuntimeState[] states,
951                                       int[] lastState,
952                                       int xstate) {
953         // Use the StateInfo with the most bits that matches that of state.
954
// If there are none, then fallback to
955
// the StateInfo with a state of 0, indicating it'll match anything.
956

957         // Consider if we have 3 StateInfos a, b and c with states:
958
// SELECTED, SELECTED | ENABLED, 0
959
//
960
// Input Return Value
961
// ----- ------------
962
// SELECTED a
963
// SELECTED | ENABLED b
964
// MOUSE_OVER c
965
// SELECTED | ENABLED | FOCUSED b
966
// ENABLED c
967

968         if (states != null && states.length > 0) {
969             int bestCount = 0;
970             int bestIndex = -1;
971             int wildIndex = -1;
972
973             //if xstate is 0, then search for the runtime state with component
974
//state of 0. That is, find the exact match and return it.
975
if (xstate == 0) {
976                 for (int counter = states.length - 1; counter >= 0; counter--) {
977                     if (states[counter].state == 0) {
978                         lastState[0] = counter;
979                         return states[counter];
980                     }
981                 }
982                 //an exact match couldn't be found, so there was no match.
983
lastState[0] = -1;
984                 return null;
985             }
986
987             //xstate is some value != 0
988

989             //determine from which index to start looking. If lastState[0] is -1
990
//then we know to start from the end of the state array. Otherwise,
991
//we start at the lastIndex - 1.
992
int lastStateIndex = lastState == null || lastState[0] == -1 ?
993                 states.length : lastState[0];
994
995             for (int counter = lastStateIndex - 1; counter >= 0; counter--) {
996                 int oState = states[counter].state;
997
998                 if (oState == 0) {
999                     if (wildIndex == -1) {
1000                        wildIndex = counter;
1001                    }
1002                } else if ((xstate & oState) == oState) {
1003                    // This is key, we need to make sure all bits of the
1004
// StateInfo match, otherwise a StateInfo with
1005
// SELECTED | ENABLED would match ENABLED, which we
1006
// don't want.
1007

1008                    // This comes from BigInteger.bitCnt
1009
int bitCount = oState;
1010                    bitCount -= (0xaaaaaaaa & bitCount) >>> 1;
1011                    bitCount = (bitCount & 0x33333333) + ((bitCount >>> 2) &
1012                            0x33333333);
1013                    bitCount = bitCount + (bitCount >>> 4) & 0x0f0f0f0f;
1014                    bitCount += bitCount >>> 8;
1015                    bitCount += bitCount >>> 16;
1016                    bitCount = bitCount & 0xff;
1017                    if (bitCount > bestCount) {
1018                        bestIndex = counter;
1019                        bestCount = bitCount;
1020                    }
1021                }
1022            }
1023            if (bestIndex != -1) {
1024                lastState[0] = bestIndex;
1025                return states[bestIndex];
1026            }
1027            if (wildIndex != -1) {
1028                lastState[0] = wildIndex;
1029                return states[wildIndex];
1030            }
1031        }
1032        lastState[0] = -1;
1033        return null;
1034    }
1035
1036    /**
1037     * Contains values such as the UIDefaults and painters asssociated with
1038     * a state. Whereas <code>State</code> represents a distinct state that a
1039     * component can be in (such as Enabled), this class represents the colors,
1040     * fonts, painters, etc associated with some state for this
1041     * style.
1042     */

1043    private final class RuntimeState implements Cloneable JavaDoc {
1044        int state;
1045        Painter backgroundPainter;
1046        Painter foregroundPainter;
1047        Painter borderPainter;
1048        String JavaDoc stateName;
1049        UIDefaults JavaDoc defaults = new UIDefaults JavaDoc(10, .7f);
1050
1051        private RuntimeState(int state, String JavaDoc stateName) {
1052            this.state = state;
1053            this.stateName = stateName;
1054        }
1055
1056        @Override JavaDoc
1057        public String JavaDoc toString() {
1058            return stateName;
1059        }
1060        
1061        @Override JavaDoc
1062        public RuntimeState clone() {
1063            RuntimeState clone = new RuntimeState(state, stateName);
1064            clone.backgroundPainter = backgroundPainter;
1065            clone.foregroundPainter = foregroundPainter;
1066            clone.borderPainter = borderPainter;
1067            clone.defaults.putAll(defaults);
1068            return clone;
1069        }
1070    }
1071    
1072    /**
1073     * Essentially a struct of data for a style. A default instance of this
1074     * class is used by NimbusStyle. Additional instances exist for each
1075     * component that has overrides.
1076     */

1077    private static final class Values {
1078        /**
1079         * The list of State types. A State represents a type of state, such
1080         * as Enabled, Default, WindowFocused, etc. These can be custom states.
1081         */

1082        State[] stateTypes = null;
1083        /**
1084         * The list of actual runtime state representations. These can represent things such
1085         * as Enabled + Focused. Thus, they differ from States in that they contain
1086         * several states together, and have associated properties, data, etc.
1087         */

1088        RuntimeState[] states = null;
1089        /**
1090         * The content margins for this region.
1091         */

1092        Insets JavaDoc contentMargins;
1093        /**
1094         * Defaults on the region/component level.
1095         */

1096        UIDefaults JavaDoc defaults = new UIDefaults JavaDoc(10, .7f);
1097        /**
1098         * Simple cache. After a value has been looked up, it is stored
1099         * in this cache for later retrieval. The key is a concatenation of
1100         * the property being looked up, two dollar signs, and the extended
1101         * state. So for example:
1102         *
1103         * foo.bar$$2353
1104         */

1105        Map JavaDoc<CacheKey,Object JavaDoc> cache = new HashMap JavaDoc<CacheKey,Object JavaDoc>();
1106    }
1107    
1108    /**
1109     * This implementation presupposes that key is never null and that
1110     * the two keys being checked for equality are never null
1111     */

1112    private static final class CacheKey {
1113        private String JavaDoc key;
1114        private int xstate;
1115        
1116        CacheKey(Object JavaDoc key, int xstate) {
1117            init(key, xstate);
1118        }
1119        
1120        void init(Object JavaDoc key, int xstate) {
1121            this.key = key.toString();
1122            this.xstate = xstate;
1123        }
1124
1125        @Override JavaDoc
1126        public boolean equals(Object JavaDoc obj) {
1127            final CacheKey other = (CacheKey) obj;
1128            if (obj == null) return false;
1129            if (this.xstate != other.xstate) return false;
1130            if (!this.key.equals(other.key)) return false;
1131            return true;
1132        }
1133
1134        @Override JavaDoc
1135        public int hashCode() {
1136            int hash = 3;
1137            hash = 29 * hash + this.key.hashCode();
1138            hash = 29 * hash + this.xstate;
1139            return hash;
1140        }
1141    }
1142}
1143
Popular Tags