annotate src/share/classes/javax/swing/plaf/nimbus/Defaults.template @ 1629:f7d606ca25a9

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