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