annotate src/share/classes/javax/swing/plaf/nimbus/Defaults.template @ 1173:7f45fcc04f8e

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