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