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