1 /* 2 * Copyright (c) 2005, 2014, 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<>(); 287 //a map of state name to code 288 Map<String,Integer> stateCodes = new HashMap<>(); 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<>(); 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<Object> 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 @SuppressWarnings("unchecked") 472 Painter<Object> tmp = (p instanceof Painter ? (Painter)p : null); 473 return tmp; 474 } 475 476 /** 477 * {@inheritDoc} 478 * 479 * Overridden to cause this style to populate itself with data from 480 * UIDefaults, if necessary. 481 */ 482 @Override public Insets getInsets(SynthContext ctx, Insets in) { 483 if (in == null) { 484 in = new Insets(0, 0, 0, 0); 485 } 486 487 Values v = getValues(ctx); 488 489 if (v.contentMargins == null) { 490 in.bottom = in.top = in.left = in.right = 0; 491 return in; 492 } else { 493 in.bottom = v.contentMargins.bottom; 494 in.top = v.contentMargins.top; 495 in.left = v.contentMargins.left; 496 in.right = v.contentMargins.right; 497 // Account for scale 498 // The key "JComponent.sizeVariant" is used to match Apple's LAF 499 String scaleKey = (String)ctx.getComponent().getClientProperty( 500 "JComponent.sizeVariant"); 501 if (scaleKey != null){ 502 if (LARGE_KEY.equals(scaleKey)){ 503 in.bottom *= LARGE_SCALE; 504 in.top *= LARGE_SCALE; 505 in.left *= LARGE_SCALE; 506 in.right *= LARGE_SCALE; 507 } else if (SMALL_KEY.equals(scaleKey)){ 508 in.bottom *= SMALL_SCALE; 509 in.top *= SMALL_SCALE; 510 in.left *= SMALL_SCALE; 511 in.right *= SMALL_SCALE; 512 } else if (MINI_KEY.equals(scaleKey)){ 513 in.bottom *= MINI_SCALE; 514 in.top *= MINI_SCALE; 515 in.left *= MINI_SCALE; 516 in.right *= MINI_SCALE; 517 } 518 } 519 return in; 520 } 521 } 522 523 /** 524 * {@inheritDoc} 525 * 526 * <p>Overridden to cause this style to populate itself with data from 527 * UIDefaults, if necessary.</p> 528 * 529 * <p>In addition, NimbusStyle handles ColorTypes slightly differently from 530 * Synth.</p> 531 * <ul> 532 * <li>ColorType.BACKGROUND will equate to the color stored in UIDefaults 533 * named "background".</li> 534 * <li>ColorType.TEXT_BACKGROUND will equate to the color stored in 535 * UIDefaults named "textBackground".</li> 536 * <li>ColorType.FOREGROUND will equate to the color stored in UIDefaults 537 * named "textForeground".</li> 538 * <li>ColorType.TEXT_FOREGROUND will equate to the color stored in 539 * UIDefaults named "textForeground".</li> 540 * </ul> 541 */ 542 @Override protected Color getColorForState(SynthContext ctx, ColorType type) { 543 String key = null; 544 if (type == ColorType.BACKGROUND) { 545 key = "background"; 546 } else if (type == ColorType.FOREGROUND) { 547 //map FOREGROUND as TEXT_FOREGROUND 548 key = "textForeground"; 549 } else if (type == ColorType.TEXT_BACKGROUND) { 550 key = "textBackground"; 551 } else if (type == ColorType.TEXT_FOREGROUND) { 552 key = "textForeground"; 553 } else if (type == ColorType.FOCUS) { 554 key = "focus"; 555 } else if (type != null) { 556 key = type.toString(); 557 } else { 558 return DEFAULT_COLOR; 559 } 560 Color c = (Color) get(ctx, key); 561 //if all else fails, return a default color (which is a ColorUIResource) 562 if (c == null) c = DEFAULT_COLOR; 563 return c; 564 } 565 566 /** 567 * {@inheritDoc} 568 * 569 * Overridden to cause this style to populate itself with data from 570 * UIDefaults, if necessary. If a value named "font" is not found in 571 * UIDefaults, then the "defaultFont" font in UIDefaults will be returned 572 * instead. 573 */ 574 @Override protected Font getFontForState(SynthContext ctx) { 575 Font f = (Font)get(ctx, "font"); 576 if (f == null) f = UIManager.getFont("defaultFont"); 577 578 // Account for scale 579 // The key "JComponent.sizeVariant" is used to match Apple's LAF 580 String scaleKey = (String)ctx.getComponent().getClientProperty( 581 "JComponent.sizeVariant"); 582 if (scaleKey != null){ 583 if (LARGE_KEY.equals(scaleKey)){ 584 f = f.deriveFont(Math.round(f.getSize2D()*LARGE_SCALE)); 585 } else if (SMALL_KEY.equals(scaleKey)){ 586 f = f.deriveFont(Math.round(f.getSize2D()*SMALL_SCALE)); 587 } else if (MINI_KEY.equals(scaleKey)){ 588 f = f.deriveFont(Math.round(f.getSize2D()*MINI_SCALE)); 589 } 590 } 591 return f; 592 } 593 594 /** 595 * {@inheritDoc} 596 * 597 * Returns the SynthPainter for this style, which ends up delegating to 598 * the Painters installed in this style. 599 */ 600 @Override public SynthPainter getPainter(SynthContext ctx) { 601 return painter; 602 } 603 604 /** 605 * {@inheritDoc} 606 * 607 * Overridden to cause this style to populate itself with data from 608 * UIDefaults, if necessary. If opacity is not specified in UI defaults, 609 * then it defaults to being non-opaque. 610 */ 611 @Override public boolean isOpaque(SynthContext ctx) { 612 // Force Table CellRenderers to be opaque 613 if ("Table.cellRenderer".equals(ctx.getComponent().getName())) { 614 return true; 615 } 616 Boolean opaque = (Boolean)get(ctx, "opaque"); 617 return opaque == null ? false : opaque; 618 } 619 620 /** 621 * {@inheritDoc} 622 * 623 * <p>Overridden to cause this style to populate itself with data from 624 * UIDefaults, if necessary.</p> 625 * 626 * <p>Properties in UIDefaults may be specified in a chained manner. For 627 * example: 628 * <pre> 629 * background 630 * Button.opacity 631 * Button.Enabled.foreground 632 * Button.Enabled+Selected.background 633 * </pre> 634 * 635 * <p>In this example, suppose you were in the Enabled+Selected state and 636 * searched for "foreground". In this case, we first check for 637 * Button.Enabled+Selected.foreground, but no such color exists. We then 638 * fall back to the next valid state, in this case, 639 * Button.Enabled.foreground, and have a match. So we return it.</p> 640 * 641 * <p>Again, if we were in the state Enabled and looked for "background", we 642 * wouldn't find it in Button.Enabled, or in Button, but would at the top 643 * level in UIManager. So we return that value.</p> 644 * 645 * <p>One special note: the "key" passed to this method could be of the form 646 * "background" or "Button.background" where "Button" equals the prefix 647 * passed to the NimbusStyle constructor. In either case, it looks for 648 * "background".</p> 649 * 650 * @param ctx SynthContext identifying requester 651 * @param key must not be null 652 */ 653 @Override public Object get(SynthContext ctx, Object key) { 654 Values v = getValues(ctx); 655 656 // strip off the prefix, if there is one. 657 String fullKey = key.toString(); 658 String partialKey = fullKey.substring(fullKey.indexOf('.') + 1); 659 660 Object obj = null; 661 int xstate = getExtendedState(ctx, v); 662 663 // check the cache 664 tmpKey.init(partialKey, xstate); 665 obj = v.cache.get(tmpKey); 666 boolean wasInCache = obj != null; 667 if (!wasInCache){ 668 // Search exact matching states and then lesser matching states 669 RuntimeState s = null; 670 int[] lastIndex = new int[] {-1}; 671 while (obj == null && 672 (s = getNextState(v.states, lastIndex, xstate)) != null) { 673 obj = s.defaults.get(partialKey); 674 } 675 // Search Region Defaults 676 if (obj == null && v.defaults != null) { 677 obj = v.defaults.get(partialKey); 678 } 679 // return found object 680 // Search UIManager Defaults 681 if (obj == null) obj = UIManager.get(fullKey); 682 // Search Synth Defaults for InputMaps 683 if (obj == null && partialKey.equals("focusInputMap")) { 684 obj = super.get(ctx, fullKey); 685 } 686 // if all we got was a null, store this fact for later use 687 v.cache.put(new CacheKey(partialKey, xstate), 688 obj == null ? NULL : obj); 689 } 690 // return found object 691 return obj == NULL ? null : obj; 692 } 693 694 @SuppressWarnings("unchecked") 695 private static Painter<Object> paintFilter(@SuppressWarnings("rawtypes") Painter painter) { 696 return (Painter<Object>) painter; 697 } 698 699 700 /** 701 * Gets the appropriate background Painter, if there is one, for the state 702 * specified in the given SynthContext. This method does appropriate 703 * fallback searching, as described in #get. 704 * 705 * @param ctx The SynthContext. Must not be null. 706 * @return The background painter associated for the given state, or null if 707 * none could be found. 708 */ 709 public Painter<Object> getBackgroundPainter(SynthContext ctx) { 710 Values v = getValues(ctx); 711 int xstate = getExtendedState(ctx, v); 712 Painter<Object> p = null; 713 714 // check the cache 715 tmpKey.init("backgroundPainter$$instance", xstate); 716 p = paintFilter((Painter)v.cache.get(tmpKey)); 717 if (p != null) return p; 718 719 // not in cache, so lookup and store in cache 720 RuntimeState s = null; 721 int[] lastIndex = new int[] {-1}; 722 while ((s = getNextState(v.states, lastIndex, xstate)) != null) { 723 if (s.backgroundPainter != null) { 724 p = paintFilter(s.backgroundPainter); 725 break; 726 } 727 } 728 if (p == null) p = paintFilter((Painter)get(ctx, "backgroundPainter")); 729 if (p != null) { 730 v.cache.put(new CacheKey("backgroundPainter$$instance", xstate), p); 731 } 732 return p; 733 } 734 735 /** 736 * Gets the appropriate foreground Painter, if there is one, for the state 737 * specified in the given SynthContext. This method does appropriate 738 * fallback searching, as described in #get. 739 * 740 * @param ctx The SynthContext. Must not be null. 741 * @return The foreground painter associated for the given state, or null if 742 * none could be found. 743 */ 744 public Painter<Object> getForegroundPainter(SynthContext ctx) { 745 Values v = getValues(ctx); 746 int xstate = getExtendedState(ctx, v); 747 Painter<Object> p = null; 748 749 // check the cache 750 tmpKey.init("foregroundPainter$$instance", xstate); 751 p = paintFilter((Painter)v.cache.get(tmpKey)); 752 if (p != null) return p; 753 754 // not in cache, so lookup and store in cache 755 RuntimeState s = null; 756 int[] lastIndex = new int[] {-1}; 757 while ((s = getNextState(v.states, lastIndex, xstate)) != null) { 758 if (s.foregroundPainter != null) { 759 p = paintFilter(s.foregroundPainter); 760 break; 761 } 762 } 763 if (p == null) p = paintFilter((Painter)get(ctx, "foregroundPainter")); 764 if (p != null) { 765 v.cache.put(new CacheKey("foregroundPainter$$instance", xstate), p); 766 } 767 return p; 768 } 769 770 /** 771 * Gets the appropriate border Painter, if there is one, for the state 772 * specified in the given SynthContext. This method does appropriate 773 * fallback searching, as described in #get. 774 * 775 * @param ctx The SynthContext. Must not be null. 776 * @return The border painter associated for the given state, or null if 777 * none could be found. 778 */ 779 public Painter<Object> getBorderPainter(SynthContext ctx) { 780 Values v = getValues(ctx); 781 int xstate = getExtendedState(ctx, v); 782 Painter<Object> p = null; 783 784 // check the cache 785 tmpKey.init("borderPainter$$instance", xstate); 786 p = paintFilter((Painter)v.cache.get(tmpKey)); 787 if (p != null) return p; 788 789 // not in cache, so lookup and store in cache 790 RuntimeState s = null; 791 int[] lastIndex = new int[] {-1}; 792 while ((s = getNextState(v.states, lastIndex, xstate)) != null) { 793 if (s.borderPainter != null) { 794 p = paintFilter(s.borderPainter); 795 break; 796 } 797 } 798 if (p == null) p = paintFilter((Painter)get(ctx, "borderPainter")); 799 if (p != null) { 800 v.cache.put(new CacheKey("borderPainter$$instance", xstate), p); 801 } 802 return p; 803 } 804 805 /** 806 * Utility method which returns the proper Values based on the given 807 * SynthContext. Ensures that parsing of the values has occurred, or 808 * reoccurs as necessary. 809 * 810 * @param ctx The SynthContext 811 * @return a non-null values reference 812 */ 813 private Values getValues(SynthContext ctx) { 814 validate(); 815 return values; 816 } 817 818 /** 819 * Simple utility method that searches the given array of Strings for the 820 * given string. This method is only called from getExtendedState if 821 * the developer has specified a specific state for the component to be 822 * in (ie, has "wedged" the component in that state) by specifying 823 * they client property "Nimbus.State". 824 * 825 * @param names a non-null array of strings 826 * @param name the name to look for in the array 827 * @return true or false based on whether the given name is in the array 828 */ 829 private boolean contains(String[] names, String name) { 830 assert name != null; 831 for (int i=0; i<names.length; i++) { 832 if (name.equals(names[i])) { 833 return true; 834 } 835 } 836 return false; 837 } 838 839 /** 840 * <p>Gets the extended state for a given synth context. Nimbus supports the 841 * ability to define custom states. The algorithm used for choosing what 842 * style information to use for a given state requires a single integer 843 * bit string where each bit in the integer represents a different state 844 * that the component is in. This method uses the componentState as 845 * reported in the SynthContext, in addition to custom states, to determine 846 * what this extended state is.</p> 847 * 848 * <p>In addition, this method checks the component in the given context 849 * for a client property called "Nimbus.State". If one exists, then it will 850 * decompose the String associated with that property to determine what 851 * state to return. In this way, the developer can force a component to be 852 * in a specific state, regardless of what the "real" state of the component 853 * is.</p> 854 * 855 * <p>The string associated with "Nimbus.State" would be of the form: 856 * <pre>Enabled+CustomState+MouseOver</pre></p> 857 * 858 * @param ctx 859 * @param v 860 * @return 861 */ 862 @SuppressWarnings({"unchecked", "rawtypes"}) 863 private int getExtendedState(SynthContext ctx, Values v) { 864 JComponent c = ctx.getComponent(); 865 int xstate = 0; 866 int mask = 1; 867 //check for the Nimbus.State client property 868 //Performance NOTE: getClientProperty ends up inside a synchronized 869 //block, so there is some potential for performance issues here, however 870 //I'm not certain that there is one on a modern VM. 871 Object property = c.getClientProperty("Nimbus.State"); 872 if (property != null) { 873 String stateNames = property.toString(); 874 String[] states = stateNames.split("\\+"); 875 if (v.stateTypes == null){ 876 // standard states only 877 for (String stateStr : states) { 878 State.StandardState s = State.getStandardState(stateStr); 879 if (s != null) xstate |= s.getState(); 880 } 881 } else { 882 // custom states 883 for (State<?> s : v.stateTypes) { 884 if (contains(states, s.getName())) { 885 xstate |= mask; 886 } 887 mask <<= 1; 888 } 889 } 890 } else { 891 //if there are no custom states defined, then simply return the 892 //state that Synth reported 893 if (v.stateTypes == null) return ctx.getComponentState(); 894 895 //there are custom states on this values, so I'll have to iterate 896 //over them all and return a custom extended state 897 int state = ctx.getComponentState(); 898 for (State s : v.stateTypes) { 899 if (s.isInState(c, state)) { 900 xstate |= mask; 901 } 902 mask <<= 1; 903 } 904 } 905 return xstate; 906 } 907 908 /** 909 * <p>Gets the RuntimeState that most closely matches the state in the given 910 * context, but is less specific than the given "lastState". Essentially, 911 * this allows you to search for the next best state.</p> 912 * 913 * <p>For example, if you had the following three states: 914 * <pre> 915 * Enabled 916 * Enabled+Pressed 917 * Disabled 918 * </pre> 919 * And you wanted to find the state that best represented 920 * ENABLED+PRESSED+FOCUSED and <code>lastState</code> was null (or an 921 * empty array, or an array with a single int with index == -1), then 922 * Enabled+Pressed would be returned. If you then call this method again but 923 * pass the index of Enabled+Pressed as the "lastState", then 924 * Enabled would be returned. If you call this method a third time and pass 925 * the index of Enabled in as the <code>lastState</code>, then null would be 926 * returned.</p> 927 * 928 * <p>The actual code path for determining the proper state is the same as 929 * in Synth.</p> 930 * 931 * @param ctx 932 * @param lastState a 1 element array, allowing me to do pass-by-reference. 933 * @return 934 */ 935 private RuntimeState getNextState(RuntimeState[] states, 936 int[] lastState, 937 int xstate) { 938 // Use the StateInfo with the most bits that matches that of state. 939 // If there are none, then fallback to 940 // the StateInfo with a state of 0, indicating it'll match anything. 941 942 // Consider if we have 3 StateInfos a, b and c with states: 943 // SELECTED, SELECTED | ENABLED, 0 944 // 945 // Input Return Value 946 // ----- ------------ 947 // SELECTED a 948 // SELECTED | ENABLED b 949 // MOUSE_OVER c 950 // SELECTED | ENABLED | FOCUSED b 951 // ENABLED c 952 953 if (states != null && states.length > 0) { 954 int bestCount = 0; 955 int bestIndex = -1; 956 int wildIndex = -1; 957 958 //if xstate is 0, then search for the runtime state with component 959 //state of 0. That is, find the exact match and return it. 960 if (xstate == 0) { 961 for (int counter = states.length - 1; counter >= 0; counter--) { 962 if (states[counter].state == 0) { 963 lastState[0] = counter; 964 return states[counter]; 965 } 966 } 967 //an exact match couldn't be found, so there was no match. 968 lastState[0] = -1; 969 return null; 970 } 971 972 //xstate is some value != 0 973 974 //determine from which index to start looking. If lastState[0] is -1 975 //then we know to start from the end of the state array. Otherwise, 976 //we start at the lastIndex - 1. 977 int lastStateIndex = lastState == null || lastState[0] == -1 ? 978 states.length : lastState[0]; 979 980 for (int counter = lastStateIndex - 1; counter >= 0; counter--) { 981 int oState = states[counter].state; 982 983 if (oState == 0) { 984 if (wildIndex == -1) { 985 wildIndex = counter; 986 } 987 } else if ((xstate & oState) == oState) { 988 // This is key, we need to make sure all bits of the 989 // StateInfo match, otherwise a StateInfo with 990 // SELECTED | ENABLED would match ENABLED, which we 991 // don't want. 992 993 // This comes from BigInteger.bitCnt 994 int bitCount = oState; 995 bitCount -= (0xaaaaaaaa & bitCount) >>> 1; 996 bitCount = (bitCount & 0x33333333) + ((bitCount >>> 2) & 997 0x33333333); 998 bitCount = bitCount + (bitCount >>> 4) & 0x0f0f0f0f; 999 bitCount += bitCount >>> 8; 1000 bitCount += bitCount >>> 16; 1001 bitCount = bitCount & 0xff; 1002 if (bitCount > bestCount) { 1003 bestIndex = counter; 1004 bestCount = bitCount; 1005 } 1006 } 1007 } 1008 if (bestIndex != -1) { 1009 lastState[0] = bestIndex; 1010 return states[bestIndex]; 1011 } 1012 if (wildIndex != -1) { 1013 lastState[0] = wildIndex; 1014 return states[wildIndex]; 1015 } 1016 } 1017 lastState[0] = -1; 1018 return null; 1019 } 1020 1021 /** 1022 * Contains values such as the UIDefaults and painters associated with 1023 * a state. Whereas <code>State</code> represents a distinct state that a 1024 * component can be in (such as Enabled), this class represents the colors, 1025 * fonts, painters, etc associated with some state for this 1026 * style. 1027 */ 1028 private final class RuntimeState implements Cloneable { 1029 int state; 1030 Painter<Object> backgroundPainter; 1031 Painter<Object> foregroundPainter; 1032 Painter<Object> borderPainter; 1033 String stateName; 1034 UIDefaults defaults = new UIDefaults(10, .7f); 1035 1036 private RuntimeState(int state, String stateName) { 1037 this.state = state; 1038 this.stateName = stateName; 1039 } 1040 1041 @Override 1042 public String toString() { 1043 return stateName; 1044 } 1045 1046 @Override 1047 public RuntimeState clone() { 1048 RuntimeState clone = new RuntimeState(state, stateName); 1049 clone.backgroundPainter = backgroundPainter; 1050 clone.foregroundPainter = foregroundPainter; 1051 clone.borderPainter = borderPainter; 1052 clone.defaults.putAll(defaults); 1053 return clone; 1054 } 1055 } 1056 1057 /** 1058 * Essentially a struct of data for a style. A default instance of this 1059 * class is used by NimbusStyle. Additional instances exist for each 1060 * component that has overrides. 1061 */ 1062 private static final class Values { 1063 /** 1064 * The list of State types. A State represents a type of state, such 1065 * as Enabled, Default, WindowFocused, etc. These can be custom states. 1066 */ 1067 State<?>[] stateTypes = null; 1068 /** 1069 * The list of actual runtime state representations. These can represent things such 1070 * as Enabled + Focused. Thus, they differ from States in that they contain 1071 * several states together, and have associated properties, data, etc. 1072 */ 1073 RuntimeState[] states = null; 1074 /** 1075 * The content margins for this region. 1076 */ 1077 Insets contentMargins; 1078 /** 1079 * Defaults on the region/component level. 1080 */ 1081 UIDefaults defaults = new UIDefaults(10, .7f); 1082 /** 1083 * Simple cache. After a value has been looked up, it is stored 1084 * in this cache for later retrieval. The key is a concatenation of 1085 * the property being looked up, two dollar signs, and the extended 1086 * state. So for example: 1087 * 1088 * foo.bar$$2353 1089 */ 1090 Map<CacheKey,Object> cache = new HashMap<CacheKey,Object>(); 1091 } 1092 1093 /** 1094 * This implementation presupposes that key is never null and that 1095 * the two keys being checked for equality are never null 1096 */ 1097 private static final class CacheKey { 1098 private String key; 1099 private int xstate; 1100 1101 CacheKey(Object key, int xstate) { 1102 init(key, xstate); 1103 } 1104 1105 void init(Object key, int xstate) { 1106 this.key = key.toString(); 1107 this.xstate = xstate; 1108 } 1109 1110 @Override 1111 public boolean equals(Object obj) { 1112 final CacheKey other = (CacheKey) obj; 1113 if (obj == null) return false; 1114 if (this.xstate != other.xstate) return false; 1115 if (!this.key.equals(other.key)) return false; 1116 return true; 1117 } 1118 1119 @Override 1120 public int hashCode() { 1121 int hash = 3; 1122 hash = 29 * hash + this.key.hashCode(); 1123 hash = 29 * hash + this.xstate; 1124 return hash; 1125 } 1126 } 1127 }