1 /* 2 * Copyright 2005-2006 Sun Microsystems, Inc. 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. Sun designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 * CA 95054 USA or visit www.sun.com if you need additional information or 23 * have any questions. 24 */ 25 package ${PACKAGE}; 26 27 import javax.swing.Painter; 28 import java.awt.Graphics; 29 import sun.font.FontUtilities; 30 import sun.swing.plaf.synth.DefaultSynthStyle; 31 import javax.swing.BorderFactory; 32 import javax.swing.JComponent; 33 import javax.swing.JInternalFrame; 34 import javax.swing.UIDefaults; 35 import javax.swing.UIManager; 36 import javax.swing.plaf.BorderUIResource; 37 import javax.swing.plaf.ColorUIResource; 38 import javax.swing.plaf.DimensionUIResource; 39 import javax.swing.plaf.FontUIResource; 40 import javax.swing.plaf.InsetsUIResource; 41 import javax.swing.plaf.synth.Region; 42 import javax.swing.plaf.synth.SynthStyle; 43 import java.awt.Color; 44 import java.awt.Component; 45 import java.awt.Dimension; 46 import java.awt.Font; 47 import java.awt.Graphics2D; 48 import java.awt.Insets; 49 import java.awt.image.BufferedImage; 50 import static java.awt.image.BufferedImage.*; 51 import java.beans.PropertyChangeEvent; 52 import java.beans.PropertyChangeListener; 53 import java.lang.ref.WeakReference; 54 import java.lang.reflect.Constructor; 55 import java.util.ArrayList; 56 import java.util.HashMap; 57 import java.util.LinkedList; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Set; 61 import java.util.WeakHashMap; 62 import javax.swing.border.Border; 63 import javax.swing.plaf.UIResource; 64 65 /** 66 * This class contains all the implementation details related to 67 * ${LAF_NAME}. It contains all the code for initializing the UIDefaults table, 68 * as well as for selecting 69 * a SynthStyle based on a JComponent/Region pair. 70 * 71 * @author Richard Bair 72 */ 73 final class ${LAF_NAME}Defaults { 74 /** 75 * The map of SynthStyles. This map is keyed by Region. Each Region maps 76 * to a List of LazyStyles. Each LazyStyle has a reference to the prefix 77 * that was registered with it. This reference can then be inspected to see 78 * if it is the proper lazy style. 79 * <p/> 80 * There can be more than one LazyStyle for a single Region if there is more 81 * than one prefix defined for a given region. For example, both Button and 82 * "MyButton" might be prefixes assigned to the Region.Button region. 83 */ 84 private Map<Region, List<LazyStyle>> m; 85 /** 86 * A map of regions which have been registered. 87 * This mapping is maintained so that the Region can be found based on 88 * prefix in a very fast manner. This is used in the "matches" method of 89 * LazyStyle. 90 */ 91 private Map<String, Region> registeredRegions = 92 new HashMap<String, Region>(); 93 /** 94 * Our fallback style to avoid NPEs if the proper style cannot be found in 95 * this class. Not sure if relying on DefaultSynthStyle is the best choice. 96 */ 97 private DefaultSynthStyle defaultStyle; 98 /** 99 * The default font that will be used. I store this value so that it can be 100 * set in the UIDefaults when requested. 101 */ 102 private FontUIResource defaultFont; 103 104 /** 105 * Map of lists of derived colors keyed by the DerivedColorKeys 106 */ 107 private Map<DerivedColorKey, DerivedColor> derivedColorsMap = 108 new HashMap<DerivedColorKey, DerivedColor>(); 109 110 /** Tempory key used for fetching from the derivedColorsMap */ 111 private final DerivedColorKey tmpDCKey = new DerivedColorKey(); 112 113 /** Listener for changes to user defaults table */ 114 private DefaultsListener defaultsListener = new DefaultsListener(); 115 116 /** Called by UIManager when this look and feel is installed. */ 117 void initialize() { 118 // add listener for derived colors 119 UIManager.addPropertyChangeListener(defaultsListener); 120 UIManager.getDefaults().addPropertyChangeListener(defaultsListener); 121 } 122 123 /** Called by UIManager when this look and feel is uninstalled. */ 124 void uninitialize() { 125 // remove listener for derived colors 126 UIManager.getDefaults().removePropertyChangeListener(defaultsListener); 127 UIManager.removePropertyChangeListener(defaultsListener); 128 } 129 130 /** 131 * Create a new ${LAF_NAME}Defaults. This constructor is only called from 132 * within ${LAF_NAME}LookAndFeel. 133 */ 134 ${LAF_NAME}Defaults() { 135 m = new HashMap<Region, List<LazyStyle>>(); 136 137 //Create the default font and default style. Also register all of the 138 //regions and their states that this class will use for later lookup. 139 //Additional regions can be registered later by 3rd party components. 140 //These are simply the default registrations. 141 defaultFont = FontUtilities.getFontConfigFUIR("sans", Font.PLAIN, 12); 142 defaultStyle = new DefaultSynthStyle(); 143 defaultStyle.setFont(defaultFont); 144 145 //initialize the map of styles 146 ${STYLE_INIT} 147 } 148 149 //--------------- Methods called by ${LAF_NAME}LookAndFeel 150 151 /** 152 * Called from ${LAF_NAME}LookAndFeel to initialize the UIDefaults. 153 * 154 * @param d UIDefaults table to initialize. This will never be null. 155 * If listeners are attached to <code>d</code>, then you will 156 * only receive notification of LookAndFeel level defaults, not 157 * all defaults on the UIManager. 158 */ 159 void initializeDefaults(UIDefaults d) { 160 ${UI_DEFAULT_INIT} 161 } 162 163 /** 164 * <p>Registers the given region and prefix. The prefix, if it contains 165 * quoted sections, refers to certain named components. If there are not 166 * quoted sections, then the prefix refers to a generic component type.</p> 167 * 168 * <p>If the given region/prefix combo has already been registered, then 169 * it will not be registered twice. The second registration attempt will 170 * fail silently.</p> 171 * 172 * @param region The Synth Region that is being registered. Such as Button, 173 * or ScrollBarThumb. 174 * @param prefix The UIDefault prefix. For example, could be ComboBox, or if 175 * a named components, "MyComboBox", or even something like 176 * ToolBar:"MyComboBox":"ComboBox.arrowButton" 177 */ 178 void register(Region region, String prefix) { 179 //validate the method arguments 180 if (region == null || prefix == null) { 181 throw new IllegalArgumentException( 182 "Neither Region nor Prefix may be null"); 183 } 184 185 //Add a LazyStyle for this region/prefix to m. 186 List<LazyStyle> styles = m.get(region); 187 if (styles == null) { 188 styles = new LinkedList<LazyStyle>(); 189 styles.add(new LazyStyle(prefix)); 190 m.put(region, styles); 191 } else { 192 //iterate over all the current styles and see if this prefix has 193 //already been registered. If not, then register it. 194 for (LazyStyle s : styles) { 195 if (prefix.equals(s.prefix)) { 196 return; 197 } 198 } 199 styles.add(new LazyStyle(prefix)); 200 } 201 202 //add this region to the map of registered regions 203 registeredRegions.put(region.getName(), region); 204 } 205 206 /** 207 * <p>Locate the style associated with the given region, and component. 208 * This is called from ${LAF_NAME}LookAndFeel in the SynthStyleFactory 209 * implementation.</p> 210 * 211 * <p>Lookup occurs as follows:<br/> 212 * Check the map of styles <code>m</code>. If the map contains no styles at 213 * all, then simply return the defaultStyle. If the map contains styles, 214 * then iterate over all of the styles for the Region <code>r</code> looking 215 * for the best match, based on prefix. If a match was made, then return 216 * that SynthStyle. Otherwise, return the defaultStyle.</p> 217 * 218 * @param comp The component associated with this region. For example, if 219 * the Region is Region.Button then the component will be a JButton. 220 * If the Region is a subregion, such as ScrollBarThumb, then the 221 * associated component will be the component that subregion belongs 222 * to, such as JScrollBar. The JComponent may be named. It may not be 223 * null. 224 * @param r The region we are looking for a style for. May not be null. 225 */ 226 SynthStyle getStyle(JComponent comp, Region r) { 227 //validate method arguments 228 if (comp == null || r == null) { 229 throw new IllegalArgumentException( 230 "Neither comp nor r may be null"); 231 } 232 233 //if there are no lazy styles registered for the region r, then return 234 //the default style 235 List<LazyStyle> styles = m.get(r); 236 if (styles == null || styles.size() == 0) { 237 return defaultStyle; 238 } 239 240 //Look for the best SynthStyle for this component/region pair. 241 LazyStyle foundStyle = null; 242 for (LazyStyle s : styles) { 243 if (s.matches(comp)) { 244 //replace the foundStyle if foundStyle is null, or 245 //if the new style "s" is more specific (ie, its path was 246 //longer), or if the foundStyle was "simple" and the new style 247 //was not (ie: the foundStyle was for something like Button and 248 //the new style was for something like "MyButton", hence, being 249 //more specific.) In all cases, favor the most specific style 250 //found. 251 if (foundStyle == null || 252 (foundStyle.parts.length < s.parts.length) || 253 (foundStyle.parts.length == s.parts.length 254 && foundStyle.simple && !s.simple)) { 255 foundStyle = s; 256 } 257 } 258 } 259 260 //return the style, if found, or the default style if not found 261 return foundStyle == null ? defaultStyle : foundStyle.getStyle(comp); 262 } 263 264 /* 265 Various public helper classes. 266 These may be used to register 3rd party values into UIDefaults 267 */ 268 269 /** 270 * <p>Derives its font value based on a parent font and a set of offsets and 271 * attributes. This class is an ActiveValue, meaning that it will recompute 272 * its value each time it is requested from UIDefaults. It is therefore 273 * recommended to read this value once and cache it in the UI delegate class 274 * until asked to reinitialize.</p> 275 * 276 * <p>To use this class, create an instance with the key of the font in the 277 * UI defaults table from which to derive this font, along with a size 278 * offset (if any), and whether it is to be bold, italic, or left in its 279 * default form.</p> 280 */ 281 static final class DerivedFont implements UIDefaults.ActiveValue { 282 private float sizeOffset; 283 private Boolean bold; 284 private Boolean italic; 285 private String parentKey; 286 287 /** 288 * Create a new DerivedFont. 289 * 290 * @param key The UIDefault key associated with this derived font's 291 * parent or source. If this key leads to a null value, or a 292 * value that is not a font, then null will be returned as 293 * the derived font. The key must not be null. 294 * @param sizeOffset The size offset, as a percentage, to use. For 295 * example, if the source font was a 12pt font and the 296 * sizeOffset were specified as .9, then the new font 297 * will be 90% of what the source font was, or, 10.8 298 * pts which is rounded to 11pts. This fractional 299 * based offset allows for proper font scaling in high 300 * DPI or large system font scenarios. 301 * @param bold Whether the new font should be bold. If null, then this 302 * new font will inherit the bold setting of the source 303 * font. 304 * @param italic Whether the new font should be italicized. If null, 305 * then this new font will inherit the italic setting of 306 * the source font. 307 */ 308 public DerivedFont(String key, float sizeOffset, Boolean bold, 309 Boolean italic) { 310 //validate the constructor arguments 311 if (key == null) { 312 throw new IllegalArgumentException("You must specify a key"); 313 } 314 315 //set the values 316 this.parentKey = key; 317 this.sizeOffset = sizeOffset; 318 this.bold = bold; 319 this.italic = italic; 320 } 321 322 /** 323 * @inheritDoc 324 */ 325 @Override 326 public Object createValue(UIDefaults defaults) { 327 Font f = defaults.getFont(parentKey); 328 if (f != null) { 329 // always round size for now so we have exact int font size 330 // (or we may have lame looking fonts) 331 float size = Math.round(f.getSize2D() * sizeOffset); 332 int style = f.getStyle(); 333 if (bold != null) { 334 if (bold.booleanValue()) { 335 style = style | Font.BOLD; 336 } else { 337 style = style & ~Font.BOLD; 338 } 339 } 340 if (italic != null) { 341 if (italic.booleanValue()) { 342 style = style | Font.ITALIC; 343 } else { 344 style = style & ~Font.ITALIC; 345 } 346 } 347 return f.deriveFont(style, size); 348 } else { 349 return null; 350 } 351 } 352 } 353 354 355 /** 356 * This class is private because it relies on the constructor of the 357 * auto-generated AbstractRegionPainter subclasses. Hence, it is not 358 * generally useful, and is private. 359 * <p/> 360 * LazyPainter is a LazyValue class. It will create the 361 * AbstractRegionPainter lazily, when asked. It uses reflection to load the 362 * proper class and invoke its constructor. 363 */ 364 private static final class LazyPainter implements UIDefaults.LazyValue { 365 private int which; 366 private AbstractRegionPainter.PaintContext ctx; 367 private String className; 368 369 LazyPainter(String className, int which, Insets insets, 370 Dimension canvasSize, boolean inverted) { 371 if (className == null) { 372 throw new IllegalArgumentException( 373 "The className must be specified"); 374 } 375 376 this.className = className; 377 this.which = which; 378 this.ctx = new AbstractRegionPainter.PaintContext( 379 insets, canvasSize, inverted); 380 } 381 382 LazyPainter(String className, int which, Insets insets, 383 Dimension canvasSize, boolean inverted, 384 AbstractRegionPainter.PaintContext.CacheMode cacheMode, 385 double maxH, double maxV) { 386 if (className == null) { 387 throw new IllegalArgumentException( 388 "The className must be specified"); 389 } 390 391 this.className = className; 392 this.which = which; 393 this.ctx = new AbstractRegionPainter.PaintContext( 394 insets, canvasSize, inverted, cacheMode, maxH, maxV); 395 } 396 397 @Override 398 public Object createValue(UIDefaults table) { 399 try { 400 Class c; 401 Object cl; 402 // See if we should use a separate ClassLoader 403 if (table == null || !((cl = table.get("ClassLoader")) 404 instanceof ClassLoader)) { 405 cl = Thread.currentThread(). 406 getContextClassLoader(); 407 if (cl == null) { 408 // Fallback to the system class loader. 409 cl = ClassLoader.getSystemClassLoader(); 410 } 411 } 412 413 c = Class.forName(className, true, (ClassLoader)cl); 414 Constructor constructor = c.getConstructor( 415 AbstractRegionPainter.PaintContext.class, int.class); 416 if (constructor == null) { 417 throw new NullPointerException( 418 "Failed to find the constructor for the class: " + 419 className); 420 } 421 return constructor.newInstance(ctx, which); 422 } catch (Exception e) { 423 e.printStackTrace(); 424 return null; 425 } 426 } 427 } 428 429 /** 430 * A class which creates the NimbusStyle associated with it lazily, but also 431 * manages a lot more information about the style. It is less of a LazyValue 432 * type of class, and more of an Entry or Item type of class, as it 433 * represents an entry in the list of LazyStyles in the map m. 434 * 435 * The primary responsibilities of this class include: 436 * <ul> 437 * <li>Determining whether a given component/region pair matches this 438 * style</li> 439 * <li>Splitting the prefix specified in the constructor into its 440 * constituent parts to facilitate quicker matching</li> 441 * <li>Creating and vending a NimbusStyle lazily.</li> 442 * </ul> 443 */ 444 private final class LazyStyle { 445 /** 446 * The prefix this LazyStyle was registered with. Something like 447 * Button or ComboBox:"ComboBox.arrowButton" 448 */ 449 private String prefix; 450 /** 451 * Whether or not this LazyStyle represents an unnamed component 452 */ 453 private boolean simple = true; 454 /** 455 * The various parts, or sections, of the prefix. For example, 456 * the prefix: 457 * ComboBox:"ComboBox.arrowButton" 458 * 459 * will be broken into two parts, 460 * ComboBox and "ComboBox.arrowButton" 461 */ 462 private Part[] parts; 463 /** 464 * Cached shared style. 465 */ 466 private NimbusStyle style; 467 /** 468 * A weakly referenced hash map such that if the reference JComponent 469 * key is garbage collected then the entry is removed from the map. 470 * This cache exists so that when a JComponent has nimbus overrides 471 * in its client map, a unique style will be created and returned 472 * for that JComponent instance, always. In such a situation each 473 * JComponent instance must have its own instance of NimbusStyle. 474 */ 475 private WeakHashMap<JComponent, WeakReference<NimbusStyle>> overridesCache; 476 477 /** 478 * Create a new LazyStyle. 479 * 480 * @param prefix The prefix associated with this style. Cannot be null. 481 */ 482 private LazyStyle(String prefix) { 483 if (prefix == null) { 484 throw new IllegalArgumentException( 485 "The prefix must not be null"); 486 } 487 488 this.prefix = prefix; 489 490 //there is one odd case that needs to be supported here: cell 491 //renderers. A cell renderer is defined as a named internal 492 //component, so for example: 493 // List."List.cellRenderer" 494 //The problem is that the component named List.cellRenderer is not a 495 //child of a JList. Rather, it is treated more as a direct component 496 //Thus, if the prefix ends with "cellRenderer", then remove all the 497 //previous dotted parts of the prefix name so that it becomes, for 498 //example: "List.cellRenderer" 499 //Likewise, we have a hacked work around for cellRenderer, renderer, 500 //and listRenderer. 501 String temp = prefix; 502 if (temp.endsWith("cellRenderer\"") 503 || temp.endsWith("renderer\"") 504 || temp.endsWith("listRenderer\"")) { 505 temp = temp.substring(temp.lastIndexOf(":\"") + 1); 506 } 507 508 //otherwise, normal code path 509 List<String> sparts = split(temp); 510 parts = new Part[sparts.size()]; 511 for (int i = 0; i < parts.length; i++) { 512 parts[i] = new Part(sparts.get(i)); 513 if (parts[i].named) { 514 simple = false; 515 } 516 } 517 } 518 519 /** 520 * Gets the style. Creates it if necessary. 521 * @return the style 522 */ 523 SynthStyle getStyle(JComponent c) { 524 // if the component has overrides, it gets its own unique style 525 // instead of the shared style. 526 if (c.getClientProperty("Nimbus.Overrides") != null) { 527 if (overridesCache == null) 528 overridesCache = new WeakHashMap<JComponent, WeakReference<NimbusStyle>>(); 529 WeakReference<NimbusStyle> ref = overridesCache.get(c); 530 NimbusStyle s = ref == null ? null : ref.get(); 531 if (s == null) { 532 s = new NimbusStyle(prefix, c); 533 overridesCache.put(c, new WeakReference<NimbusStyle>(s)); 534 } 535 return s; 536 } 537 538 // lazily create the style if necessary 539 if (style == null) 540 style = new NimbusStyle(prefix, null); 541 542 // return the style 543 return style; 544 } 545 546 /** 547 * This LazyStyle is a match for the given component if, and only if, 548 * for each part of the prefix the component hierarchy matches exactly. 549 * That is, if given "a":something:"b", then: 550 * c.getName() must equals "b" 551 * c.getParent() can be anything 552 * c.getParent().getParent().getName() must equal "a". 553 */ 554 boolean matches(JComponent c) { 555 return matches(c, parts.length - 1); 556 } 557 558 private boolean matches(Component c, int partIndex) { 559 if (partIndex < 0) return true; 560 if (c == null) return false; 561 //only get here if partIndex > 0 and c == null 562 563 String name = c.getName(); 564 if (parts[partIndex].named && parts[partIndex].s.equals(name)) { 565 //so far so good, recurse 566 return matches(c.getParent(), partIndex - 1); 567 } else if (!parts[partIndex].named) { 568 //if c is not named, and parts[partIndex] has an expected class 569 //type registered, then check to make sure c is of the 570 //right type; 571 Class clazz = parts[partIndex].c; 572 if (clazz != null && clazz.isAssignableFrom(c.getClass())) { 573 //so far so good, recurse 574 return matches(c.getParent(), partIndex - 1); 575 } else if (clazz == null && 576 registeredRegions.containsKey(parts[partIndex].s)) { 577 Region r = registeredRegions.get(parts[partIndex].s); 578 Component parent = r.isSubregion() ? c : c.getParent(); 579 //special case the JInternalFrameTitlePane, because it 580 //doesn't fit the mold. very, very funky. 581 if (r == Region.INTERNAL_FRAME_TITLE_PANE && parent != null 582 && parent instanceof JInternalFrame.JDesktopIcon) { 583 JInternalFrame.JDesktopIcon icon = 584 (JInternalFrame.JDesktopIcon) parent; 585 parent = icon.getInternalFrame(); 586 } 587 //it was the name of a region. So far, so good. Recurse. 588 return matches(parent, partIndex - 1); 589 } 590 } 591 592 return false; 593 } 594 595 /** 596 * Given some dot separated prefix, split on the colons that are 597 * not within quotes, and not within brackets. 598 * 599 * @param prefix 600 * @return 601 */ 602 private List<String> split(String prefix) { 603 List<String> parts = new ArrayList<String>(); 604 int bracketCount = 0; 605 boolean inquotes = false; 606 int lastIndex = 0; 607 for (int i = 0; i < prefix.length(); i++) { 608 char c = prefix.charAt(i); 609 610 if (c == '[') { 611 bracketCount++; 612 continue; 613 } else if (c == '"') { 614 inquotes = !inquotes; 615 continue; 616 } else if (c == ']') { 617 bracketCount--; 618 if (bracketCount < 0) { 619 throw new RuntimeException( 620 "Malformed prefix: " + prefix); 621 } 622 continue; 623 } 624 625 if (c == ':' && !inquotes && bracketCount == 0) { 626 //found a character to split on. 627 parts.add(prefix.substring(lastIndex, i)); 628 lastIndex = i + 1; 629 } 630 } 631 if (lastIndex < prefix.length() - 1 && !inquotes 632 && bracketCount == 0) { 633 parts.add(prefix.substring(lastIndex)); 634 } 635 return parts; 636 637 } 638 639 private final class Part { 640 private String s; 641 //true if this part represents a component name 642 private boolean named; 643 private Class c; 644 645 Part(String s) { 646 named = s.charAt(0) == '"' && s.charAt(s.length() - 1) == '"'; 647 if (named) { 648 this.s = s.substring(1, s.length() - 1); 649 } else { 650 this.s = s; 651 //TODO use a map of known regions for Synth and Swing, and 652 //then use [classname] instead of org_class_name style 653 try { 654 c = Class.forName("javax.swing.J" + s); 655 } catch (Exception e) { 656 } 657 try { 658 c = Class.forName(s.replace("_", ".")); 659 } catch (Exception e) { 660 } 661 } 662 } 663 } 664 } 665 666 /** 667 * Get a derived color, derived colors are shared instances and will be 668 * updated when its parent UIDefault color changes. 669 * 670 * @param uiDefaultParentName The parent UIDefault key 671 * @param hOffset The hue offset 672 * @param sOffset The saturation offset 673 * @param bOffset The brightness offset 674 * @param aOffset The alpha offset 675 * @return The stored derived color 676 */ 677 public DerivedColor getDerivedColor(String uiDefaultParentName, 678 float hOffset, float sOffset, 679 float bOffset, int aOffset){ 680 return getDerivedColor(uiDefaultParentName, hOffset, sOffset, 681 bOffset, aOffset, true); 682 } 683 684 /** 685 * Get a derived color, derived colors are shared instances and will be 686 * updated when its parent UIDefault color changes. 687 * 688 * @param uiDefaultParentName The parent UIDefault key 689 * @param hOffset The hue offset 690 * @param sOffset The saturation offset 691 * @param bOffset The brightness offset 692 * @param aOffset The alpha offset 693 * @param uiResource True if the derived color should be a UIResource, 694 * false if it should not be a UIResource 695 * @return The stored derived color 696 */ 697 public DerivedColor getDerivedColor(String uiDefaultParentName, 698 float hOffset, float sOffset, 699 float bOffset, int aOffset, 700 boolean uiResource){ 701 tmpDCKey.set(uiDefaultParentName, hOffset, sOffset, bOffset, aOffset, 702 uiResource); 703 DerivedColor color = derivedColorsMap.get(tmpDCKey); 704 if (color == null){ 705 if (uiResource) { 706 color = new DerivedColor.UIResource(uiDefaultParentName, 707 hOffset, sOffset, bOffset, aOffset); 708 } else { 709 color = new DerivedColor(uiDefaultParentName, hOffset, sOffset, 710 bOffset, aOffset); 711 } 712 // calculate the initial value 713 color.rederiveColor(); 714 // add the listener so that if the color changes we'll propogate it 715 color.addPropertyChangeListener(defaultsListener); 716 // add to the derived colors table 717 derivedColorsMap.put(new DerivedColorKey(uiDefaultParentName, 718 hOffset, sOffset, bOffset, aOffset, uiResource),color); 719 } 720 return color; 721 } 722 723 /** 724 * Key class for derived colors 725 */ 726 private class DerivedColorKey { 727 private String uiDefaultParentName; 728 private float hOffset, sOffset, bOffset; 729 private int aOffset; 730 private boolean uiResource; 731 732 DerivedColorKey(){} 733 734 DerivedColorKey(String uiDefaultParentName, float hOffset, 735 float sOffset, float bOffset, int aOffset, 736 boolean uiResource) { 737 set(uiDefaultParentName, hOffset, sOffset, bOffset, aOffset, uiResource); 738 } 739 740 void set (String uiDefaultParentName, float hOffset, 741 float sOffset, float bOffset, int aOffset, 742 boolean uiResource) { 743 this.uiDefaultParentName = uiDefaultParentName; 744 this.hOffset = hOffset; 745 this.sOffset = sOffset; 746 this.bOffset = bOffset; 747 this.aOffset = aOffset; 748 this.uiResource = uiResource; 749 } 750 751 @Override 752 public boolean equals(Object o) { 753 if (this == o) return true; 754 if (!(o instanceof DerivedColorKey)) return false; 755 DerivedColorKey that = (DerivedColorKey) o; 756 if (aOffset != that.aOffset) return false; 757 if (Float.compare(that.bOffset, bOffset) != 0) return false; 758 if (Float.compare(that.hOffset, hOffset) != 0) return false; 759 if (Float.compare(that.sOffset, sOffset) != 0) return false; 760 if (uiDefaultParentName != null ? 761 !uiDefaultParentName.equals(that.uiDefaultParentName) : 762 that.uiDefaultParentName != null) return false; 763 if (this.uiResource != that.uiResource) return false; 764 return true; 765 } 766 767 @Override 768 public int hashCode() { 769 int result = super.hashCode(); 770 result = 31 * result + uiDefaultParentName.hashCode(); 771 result = 31 * result + hOffset != +0.0f ? 772 Float.floatToIntBits(hOffset) : 0; 773 result = 31 * result + sOffset != +0.0f ? 774 Float.floatToIntBits(sOffset) : 0; 775 result = 31 * result + bOffset != +0.0f ? 776 Float.floatToIntBits(bOffset) : 0; 777 result = 31 * result + aOffset; 778 result = 31 * result + (uiResource ? 1 : 0); 779 return result; 780 } 781 } 782 783 /** 784 * Listener to update derived colors on UIManager Defaults changes 785 */ 786 private class DefaultsListener implements PropertyChangeListener { 787 @Override 788 public void propertyChange(PropertyChangeEvent evt) { 789 Object src = evt.getSource(); 790 String key = evt.getPropertyName(); 791 if (key.equals("lookAndFeel")){ 792 // LAF has been installed, this is the first point at which we 793 // can access our defaults table via UIManager so before now 794 // all derived colors will be incorrect. 795 // First we need to update 796 for (DerivedColor color : derivedColorsMap.values()) { 797 color.rederiveColor(); 798 } 799 } else if (src instanceof DerivedColor && key.equals("rgb")) { 800 // derived color that is in UIManager defaults has changed 801 // update all its dependent colors. Don't worry about doing 802 // this recursively since calling rederiveColor will cause 803 // another PCE to be fired, ending up here and essentially 804 // recursing 805 DerivedColor parentColor = (DerivedColor)src; 806 String parentKey = null; 807 Set<Map.Entry<Object,Object>> entries = 808 UIManager.getDefaults().entrySet(); 809 810 for (Map.Entry entry : entries) { 811 Object value = entry.getValue(); 812 if (value == parentColor) { 813 parentKey = entry.getKey().toString(); 814 } 815 } 816 817 if (parentKey == null) { 818 //couldn't find the DerivedColor in the UIDefaults map, 819 //so we just bail. 820 return; 821 } 822 823 for (Map.Entry entry : entries) { 824 Object value = entry.getValue(); 825 if (value instanceof DerivedColor) { 826 DerivedColor color = (DerivedColor)entry.getValue(); 827 if (parentKey.equals(color.getUiDefaultParentName())) { 828 color.rederiveColor(); 829 } 830 } 831 } 832 } 833 } 834 } 835 836 private static final class PainterBorder implements Border, UIResource { 837 private Insets insets; 838 private Painter painter; 839 private String painterKey; 840 841 PainterBorder(String painterKey, Insets insets) { 842 this.insets = insets; 843 this.painterKey = painterKey; 844 } 845 846 @Override 847 public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) { 848 if (painter == null) { 849 painter = (Painter)UIManager.get(painterKey); 850 if (painter == null) return; 851 } 852 853 g.translate(x, y); 854 if (g instanceof Graphics2D) 855 painter.paint((Graphics2D)g, c, w, h); 856 else { 857 BufferedImage img = new BufferedImage(w, h, TYPE_INT_ARGB); 858 Graphics2D gfx = img.createGraphics(); 859 painter.paint(gfx, c, w, h); 860 gfx.dispose(); 861 g.drawImage(img, x, y, null); 862 img = null; 863 } 864 g.translate(-x, -y); 865 } 866 867 @Override 868 public Insets getBorderInsets(Component c) { 869 return (Insets)insets.clone(); 870 } 871 872 @Override 873 public boolean isBorderOpaque() { 874 return false; 875 } 876 } 877 } 878