annotate src/share/classes/javax/swing/plaf/nimbus/Defaults.template @ 2139:e2f7e92c30f1

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