alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (NimbusStyle.java)

This example Java source code file (NimbusStyle.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

awt, boolean, cachekey, color, gui, insets, large_scale, mini_scale, object, override, painter, plaf, runtimestate, small_scale, string, swing, uidefaults, util, values

The NimbusStyle.java Java example source code

/*
 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package javax.swing.plaf.nimbus;

import javax.swing.Painter;

import javax.swing.JComponent;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.synth.ColorType;
import static javax.swing.plaf.synth.SynthConstants.*;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthPainter;
import javax.swing.plaf.synth.SynthStyle;
import java.awt.Color;
import java.awt.Font;
import java.awt.Insets;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * <p>A SynthStyle implementation used by Nimbus. Each Region that has been
 * registered with the NimbusLookAndFeel will have an associated NimbusStyle.
 * Third party components that are registered with the NimbusLookAndFeel will
 * therefore be handed a NimbusStyle from the look and feel from the
 * #getStyle(JComponent, Region) method.</p>
 *
 * <p>This class properly reads and retrieves values placed in the UIDefaults
 * according to the standard Nimbus naming conventions. It will create and
 * retrieve painters, fonts, colors, and other data stored there.</p>
 *
 * <p>NimbusStyle also supports the ability to override settings on a per
 * component basis. NimbusStyle checks the component's client property map for
 * "Nimbus.Overrides". If the value associated with this key is an instance of
 * UIDefaults, then the values in that defaults table will override the standard
 * Nimbus defaults in UIManager, but for that component instance only.</p>
 *
 * <p>Optionally, you may specify the client property
 * "Nimbus.Overrides.InheritDefaults". If true, this client property indicates
 * that the defaults located in UIManager should first be read, and then
 * replaced with defaults located in the component client properties. If false,
 * then only the defaults located in the component client property map will
 * be used. If not specified, it is assumed to be true.</p>
 *
 * <p>You must specify "Nimbus.Overrides" for "Nimbus.Overrides.InheritDefaults"
 * to have any effect. "Nimbus.Overrides" indicates whether there are any
 * overrides, while "Nimbus.Overrides.InheritDefaults" indicates whether those
 * overrides should first be initialized with the defaults from UIManager.</p>
 *
 * <p>The NimbusStyle is reloaded whenever a property change event is fired
 * for a component for "Nimbus.Overrides" or "Nimbus.Overrides.InheritDefaults".
 * So for example, setting a new UIDefaults on a component would cause the
 * style to be reloaded.</p>
 *
 * <p>The values are only read out of UIManager once, and then cached. If
 * you need to read the values again (for example, if the UI is being reloaded),
 * then discard this NimbusStyle and read a new one from NimbusLookAndFeel
 * using NimbusLookAndFeel.getStyle.</p>
 *
 * <p>The primary API of interest in this class for 3rd party component authors
 * are the three methods which retrieve painters: #getBackgroundPainter,
 * #getForegroundPainter, and #getBorderPainter.</p>
 *
 * <p>NimbusStyle allows you to specify custom states, or modify the order of
 * states. Synth (and thus Nimbus) has the concept of a "state". For example,
 * a JButton might be in the "MOUSE_OVER" state, or the "ENABLED" state, or the
 * "DISABLED" state. These are all "standard" states which are defined in synth,
 * and which apply to all synth Regions.</p>
 *
 * <p>Sometimes, however, you need to have a custom state. For example, you
 * want JButton to render differently if it's parent is a JToolbar. In Nimbus,
 * you specify these custom states by including a special key in UIDefaults.
 * The following UIDefaults entries define three states for this button:</p>
 *
 * <pre>
 *     JButton.States = Enabled, Disabled, Toolbar
 *     JButton[Enabled].backgroundPainter = somePainter
 *     JButton[Disabled].background = BLUE
 *     JButton[Toolbar].backgroundPainter = someOtherPaint
 * </code>
* * <p>As you can see, the JButton.States entry lists the states * that the JButton style will support. You then specify the settings for * each state. If you do not specify the <code>JButton.States entry, * then the standard Synth states will be assumed. If you specify the entry * but the list of states is empty or null, then the standard synth states * will be assumed.</p> * * @author Richard Bair * @author Jasper Potts */ public final class NimbusStyle extends SynthStyle { /* Keys and scales for large/small/mini components, based on Apples sizes */ public static final String LARGE_KEY = "large"; public static final String SMALL_KEY = "small"; public static final String MINI_KEY = "mini"; public static final double LARGE_SCALE = 1.15; public static final double SMALL_SCALE = 0.857; public static final double MINI_SCALE = 0.714; /** * Special constant used for performance reasons during the get() method. * If get() runs through all of the search locations and determines that * there is no value, then NULL will be placed into the values map. This way * on subsequent lookups it will simply extract NULL, see it, and return * null rather than continuing the lookup procedure. */ private static final Object NULL = '\0'; /** * <p>The Color to return from getColorForState if it would otherwise have * returned null.</p> * * <p>Returning null from getColorForState is a very bad thing, as it causes * the AWT peer for the component to install a SystemColor, which is not a * UIResource. As a result, if <code>null is returned from * getColorForState, then thereafter the color is not updated for other * states or on LAF changes or updates. This DEFAULT_COLOR is used to * ensure that a ColorUIResource is always returned from * getColorForState.</p> */ private static final Color DEFAULT_COLOR = new ColorUIResource(Color.BLACK); /** * Simple Comparator for ordering the RuntimeStates according to their * rank. */ private static final Comparator<RuntimeState> STATE_COMPARATOR = new Comparator<RuntimeState>() { @Override public int compare(RuntimeState a, RuntimeState b) { return a.state - b.state; } }; /** * The prefix for the component or region that this NimbusStyle * represents. This prefix is used to lookup state in the UIManager. * It should be something like Button or Slider.Thumb or "MyButton" or * ComboBox."ComboBox.arrowButton" or "MyComboBox"."ComboBox.arrowButton" */ private String prefix; /** * The SynthPainter that will be returned from this NimbusStyle. The * SynthPainter returned will be a SynthPainterImpl, which will in turn * delegate back to this NimbusStyle for the proper Painter (not * SynthPainter) to use for painting the foreground, background, or border. */ private SynthPainter painter; /** * Data structure containing all of the defaults, insets, states, and other * values associated with this style. This instance refers to default * values, and are used when no overrides are discovered in the client * properties of a component. These values are lazily created on first * access. */ private Values values; /** * A temporary CacheKey used to perform lookups. This pattern avoids * creating useless garbage keys, or concatenating strings, etc. */ private CacheKey tmpKey = new CacheKey("", 0); /** * Some NimbusStyles are created for a specific component only. In Nimbus, * this happens whenever the component has as a client property a * UIDefaults which overrides (or supplements) those defaults found in * UIManager. */ private WeakReference<JComponent> component; /** * Create a new NimbusStyle. Only the prefix must be supplied. At the * appropriate time, installDefaults will be called. At that point, all of * the state information will be pulled from UIManager and stored locally * within this style. * * @param prefix Something like Button or Slider.Thumb or * org.jdesktop.swingx.JXStatusBar or ComboBox."ComboBox.arrowButton" * @param c an optional reference to a component that this NimbusStyle * should be associated with. This is only used when the component * has Nimbus overrides registered in its client properties and * should be null otherwise. */ NimbusStyle(String prefix, JComponent c) { if (c != null) { this.component = new WeakReference<JComponent>(c); } this.prefix = prefix; this.painter = new SynthPainterImpl(this); } /** * {@inheritDoc} * * Overridden to cause this style to populate itself with data from * UIDefaults, if necessary. */ @Override public void installDefaults(SynthContext ctx) { validate(); //delegate to the superclass to install defaults such as background, //foreground, font, and opaque onto the swing component. super.installDefaults(ctx); } /** * Pulls data out of UIDefaults, if it has not done so already, and sets * up the internal state. */ private void validate() { // a non-null values object is the flag we use to determine whether // to reparse from UIManager. if (values != null) return; // reconstruct this NimbusStyle based on the entries in the UIManager // and possibly based on any overrides within the component's // client properties (assuming such a component exists and contains // any Nimbus.Overrides) values = new Values(); Map<String, Object> defaults = ((NimbusLookAndFeel) UIManager.getLookAndFeel()). getDefaultsForPrefix(prefix); // inspect the client properties for the key "Nimbus.Overrides". If the // value is an instance of UIDefaults, then these defaults are used // in place of, or in addition to, the defaults in UIManager. if (component != null) { // We know component.get() is non-null here, as if the component // were GC'ed, we wouldn't be processing its style. Object o = component.get().getClientProperty("Nimbus.Overrides"); if (o instanceof UIDefaults) { Object i = component.get().getClientProperty( "Nimbus.Overrides.InheritDefaults"); boolean inherit = i instanceof Boolean ? (Boolean)i : true; UIDefaults d = (UIDefaults)o; TreeMap<String, Object> map = new TreeMap(); for (Object obj : d.keySet()) { if (obj instanceof String) { String key = (String)obj; if (key.startsWith(prefix)) { map.put(key, d.get(key)); } } } if (inherit) { defaults.putAll(map); } else { defaults = map; } } } //a list of the different types of states used by this style. This //list may contain only "standard" states (those defined by Synth), //or it may contain custom states, or it may contain only "standard" //states but list them in a non-standard order. List<State> states = new ArrayList(); //a map of state name to code Map<String,Integer> stateCodes = new HashMap(); //This is a list of runtime "state" context objects. These contain //the values associated with each state. List<RuntimeState> runtimeStates = new ArrayList(); //determine whether there are any custom states, or custom state //order. If so, then read all those custom states and define the //"values" stateTypes to be a non-null array. //Otherwise, let the "values" stateTypes be null to indicate that //there are no custom states or custom state ordering String statesString = (String)defaults.get(prefix + ".States"); if (statesString != null) { String s[] = statesString.split(","); for (int i=0; i<s.length; i++) { s[i] = s[i].trim(); if (!State.isStandardStateName(s[i])) { //this is a non-standard state name, so look for the //custom state associated with it String stateName = prefix + "." + s[i]; State customState = (State)defaults.get(stateName); if (customState != null) { states.add(customState); } } else { states.add(State.getStandardState(s[i])); } } //if there were any states defined, then set the stateTypes array //to be non-null. Otherwise, leave it null (meaning, use the //standard synth states). if (states.size() > 0) { values.stateTypes = states.toArray(new State[states.size()]); } //assign codes for each of the state types int code = 1; for (State state : states) { stateCodes.put(state.getName(), code); code <<= 1; } } else { //since there were no custom states defined, setup the list of //standard synth states. Note that the "v.stateTypes" is not //being set here, indicating that at runtime the state selection //routines should use standard synth states instead of custom //states. I do need to popuplate this temp list now though, so that //the remainder of this method will function as expected. states.add(State.Enabled); states.add(State.MouseOver); states.add(State.Pressed); states.add(State.Disabled); states.add(State.Focused); states.add(State.Selected); states.add(State.Default); //assign codes for the states stateCodes.put("Enabled", ENABLED); stateCodes.put("MouseOver", MOUSE_OVER); stateCodes.put("Pressed", PRESSED); stateCodes.put("Disabled", DISABLED); stateCodes.put("Focused", FOCUSED); stateCodes.put("Selected", SELECTED); stateCodes.put("Default", DEFAULT); } //Now iterate over all the keys in the defaults table for (String key : defaults.keySet()) { //The key is something like JButton.Enabled.backgroundPainter, //or JButton.States, or JButton.background. //Remove the "JButton." portion of the key String temp = key.substring(prefix.length()); //if there is a " or : then we skip it because it is a subregion //of some kind if (temp.indexOf('"') != -1 || temp.indexOf(':') != -1) continue; //remove the separator temp = temp.substring(1); //At this point, temp may be any of the following: //background //[Enabled].background //[Enabled+MouseOver].background //property.foo //parse out the states and the property String stateString = null; String property = null; int bracketIndex = temp.indexOf(']'); if (bracketIndex < 0) { //there is not a state string, so property = temp property = temp; } else { stateString = temp.substring(0, bracketIndex); property = temp.substring(bracketIndex + 2); } //now that I have the state (if any) and the property, get the //value for this property and install it where it belongs if (stateString == null) { //there was no state, just a property. Check for the custom //"contentMargins" property (which is handled specially by //Synth/Nimbus). Also check for the property being "States", //in which case it is not a real property and should be ignored. //otherwise, assume it is a property and install it on the //values object if ("contentMargins".equals(property)) { values.contentMargins = (Insets)defaults.get(key); } else if ("States".equals(property)) { //ignore } else { values.defaults.put(property, defaults.get(key)); } } else { //it is possible that the developer has a malformed UIDefaults //entry, such that something was specified in the place of //the State portion of the key but it wasn't a state. In this //case, skip will be set to true boolean skip = false; //this variable keeps track of the int value associated with //the state. See SynthState for details. int componentState = 0; //Multiple states may be specified in the string, such as //Enabled+MouseOver String[] stateParts = stateString.split("\\+"); //For each state, we need to find the State object associated //with it, or skip it if it cannot be found. for (String s : stateParts) { if (stateCodes.containsKey(s)) { componentState |= stateCodes.get(s); } else { //Was not a state. Maybe it was a subregion or something //skip it. skip = true; break; } } if (skip) continue; //find the RuntimeState for this State RuntimeState rs = null; for (RuntimeState s : runtimeStates) { if (s.state == componentState) { rs = s; break; } } //couldn't find the runtime state, so create a new one if (rs == null) { rs = new RuntimeState(componentState, stateString); runtimeStates.add(rs); } //check for a couple special properties, such as for the //painters. If these are found, then set the specially on //the runtime state. Else, it is just a normal property, //so put it in the UIDefaults associated with that runtime //state if ("backgroundPainter".equals(property)) { rs.backgroundPainter = getPainter(defaults, key); } else if ("foregroundPainter".equals(property)) { rs.foregroundPainter = getPainter(defaults, key); } else if ("borderPainter".equals(property)) { rs.borderPainter = getPainter(defaults, key); } else { rs.defaults.put(property, defaults.get(key)); } } } //now that I've collected all the runtime states, I'll sort them based //on their integer "state" (see SynthState for how this works). Collections.sort(runtimeStates, STATE_COMPARATOR); //finally, set the array of runtime states on the values object values.states = runtimeStates.toArray(new RuntimeState[runtimeStates.size()]); } private Painter getPainter(Map<String, Object> defaults, String key) { Object p = defaults.get(key); if (p instanceof UIDefaults.LazyValue) { p = ((UIDefaults.LazyValue)p).createValue(UIManager.getDefaults()); } return (p instanceof Painter ? (Painter)p : null); } /** * {@inheritDoc} * * Overridden to cause this style to populate itself with data from * UIDefaults, if necessary. */ @Override public Insets getInsets(SynthContext ctx, Insets in) { if (in == null) { in = new Insets(0, 0, 0, 0); } Values v = getValues(ctx); if (v.contentMargins == null) { in.bottom = in.top = in.left = in.right = 0; return in; } else { in.bottom = v.contentMargins.bottom; in.top = v.contentMargins.top; in.left = v.contentMargins.left; in.right = v.contentMargins.right; // Account for scale // The key "JComponent.sizeVariant" is used to match Apple's LAF String scaleKey = (String)ctx.getComponent().getClientProperty( "JComponent.sizeVariant"); if (scaleKey != null){ if (LARGE_KEY.equals(scaleKey)){ in.bottom *= LARGE_SCALE; in.top *= LARGE_SCALE; in.left *= LARGE_SCALE; in.right *= LARGE_SCALE; } else if (SMALL_KEY.equals(scaleKey)){ in.bottom *= SMALL_SCALE; in.top *= SMALL_SCALE; in.left *= SMALL_SCALE; in.right *= SMALL_SCALE; } else if (MINI_KEY.equals(scaleKey)){ in.bottom *= MINI_SCALE; in.top *= MINI_SCALE; in.left *= MINI_SCALE; in.right *= MINI_SCALE; } } return in; } } /** * {@inheritDoc} * * <p>Overridden to cause this style to populate itself with data from * UIDefaults, if necessary.</p> * * <p>In addition, NimbusStyle handles ColorTypes slightly differently from * Synth.</p> * <ul> * <li>ColorType.BACKGROUND will equate to the color stored in UIDefaults * named "background".</li> * <li>ColorType.TEXT_BACKGROUND will equate to the color stored in * UIDefaults named "textBackground".</li> * <li>ColorType.FOREGROUND will equate to the color stored in UIDefaults * named "textForeground".</li> * <li>ColorType.TEXT_FOREGROUND will equate to the color stored in * UIDefaults named "textForeground".</li> * </ul> */ @Override protected Color getColorForState(SynthContext ctx, ColorType type) { String key = null; if (type == ColorType.BACKGROUND) { key = "background"; } else if (type == ColorType.FOREGROUND) { //map FOREGROUND as TEXT_FOREGROUND key = "textForeground"; } else if (type == ColorType.TEXT_BACKGROUND) { key = "textBackground"; } else if (type == ColorType.TEXT_FOREGROUND) { key = "textForeground"; } else if (type == ColorType.FOCUS) { key = "focus"; } else if (type != null) { key = type.toString(); } else { return DEFAULT_COLOR; } Color c = (Color) get(ctx, key); //if all else fails, return a default color (which is a ColorUIResource) if (c == null) c = DEFAULT_COLOR; return c; } /** * {@inheritDoc} * * Overridden to cause this style to populate itself with data from * UIDefaults, if necessary. If a value named "font" is not found in * UIDefaults, then the "defaultFont" font in UIDefaults will be returned * instead. */ @Override protected Font getFontForState(SynthContext ctx) { Font f = (Font)get(ctx, "font"); if (f == null) f = UIManager.getFont("defaultFont"); // Account for scale // The key "JComponent.sizeVariant" is used to match Apple's LAF String scaleKey = (String)ctx.getComponent().getClientProperty( "JComponent.sizeVariant"); if (scaleKey != null){ if (LARGE_KEY.equals(scaleKey)){ f = f.deriveFont(Math.round(f.getSize2D()*LARGE_SCALE)); } else if (SMALL_KEY.equals(scaleKey)){ f = f.deriveFont(Math.round(f.getSize2D()*SMALL_SCALE)); } else if (MINI_KEY.equals(scaleKey)){ f = f.deriveFont(Math.round(f.getSize2D()*MINI_SCALE)); } } return f; } /** * {@inheritDoc} * * Returns the SynthPainter for this style, which ends up delegating to * the Painters installed in this style. */ @Override public SynthPainter getPainter(SynthContext ctx) { return painter; } /** * {@inheritDoc} * * Overridden to cause this style to populate itself with data from * UIDefaults, if necessary. If opacity is not specified in UI defaults, * then it defaults to being non-opaque. */ @Override public boolean isOpaque(SynthContext ctx) { // Force Table CellRenderers to be opaque if ("Table.cellRenderer".equals(ctx.getComponent().getName())) { return true; } Boolean opaque = (Boolean)get(ctx, "opaque"); return opaque == null ? false : opaque; } /** * {@inheritDoc} * * <p>Overridden to cause this style to populate itself with data from * UIDefaults, if necessary.</p> * * <p>Properties in UIDefaults may be specified in a chained manner. For * example: * <pre> * background * Button.opacity * Button.Enabled.foreground * Button.Enabled+Selected.background * </pre> * * <p>In this example, suppose you were in the Enabled+Selected state and * searched for "foreground". In this case, we first check for * Button.Enabled+Selected.foreground, but no such color exists. We then * fall back to the next valid state, in this case, * Button.Enabled.foreground, and have a match. So we return it.</p> * * <p>Again, if we were in the state Enabled and looked for "background", we * wouldn't find it in Button.Enabled, or in Button, but would at the top * level in UIManager. So we return that value.</p> * * <p>One special note: the "key" passed to this method could be of the form * "background" or "Button.background" where "Button" equals the prefix * passed to the NimbusStyle constructor. In either case, it looks for * "background".</p> * * @param ctx * @param key must not be null */ @Override public Object get(SynthContext ctx, Object key) { Values v = getValues(ctx); // strip off the prefix, if there is one. String fullKey = key.toString(); String partialKey = fullKey.substring(fullKey.indexOf(".") + 1); Object obj = null; int xstate = getExtendedState(ctx, v); // check the cache tmpKey.init(partialKey, xstate); obj = v.cache.get(tmpKey); boolean wasInCache = obj != null; if (!wasInCache){ // Search exact matching states and then lesser matching states RuntimeState s = null; int[] lastIndex = new int[] {-1}; while (obj == null && (s = getNextState(v.states, lastIndex, xstate)) != null) { obj = s.defaults.get(partialKey); } // Search Region Defaults if (obj == null && v.defaults != null) { obj = v.defaults.get(partialKey); } // return found object // Search UIManager Defaults if (obj == null) obj = UIManager.get(fullKey); // Search Synth Defaults for InputMaps if (obj == null && partialKey.equals("focusInputMap")) { obj = super.get(ctx, fullKey); } // if all we got was a null, store this fact for later use v.cache.put(new CacheKey(partialKey, xstate), obj == null ? NULL : obj); } // return found object return obj == NULL ? null : obj; } /** * Gets the appropriate background Painter, if there is one, for the state * specified in the given SynthContext. This method does appropriate * fallback searching, as described in #get. * * @param ctx The SynthContext. Must not be null. * @return The background painter associated for the given state, or null if * none could be found. */ public Painter getBackgroundPainter(SynthContext ctx) { Values v = getValues(ctx); int xstate = getExtendedState(ctx, v); Painter p = null; // check the cache tmpKey.init("backgroundPainter$$instance", xstate); p = (Painter)v.cache.get(tmpKey); if (p != null) return p; // not in cache, so lookup and store in cache RuntimeState s = null; int[] lastIndex = new int[] {-1}; while ((s = getNextState(v.states, lastIndex, xstate)) != null) { if (s.backgroundPainter != null) { p = s.backgroundPainter; break; } } if (p == null) p = (Painter)get(ctx, "backgroundPainter"); if (p != null) { v.cache.put(new CacheKey("backgroundPainter$$instance", xstate), p); } return p; } /** * Gets the appropriate foreground Painter, if there is one, for the state * specified in the given SynthContext. This method does appropriate * fallback searching, as described in #get. * * @param ctx The SynthContext. Must not be null. * @return The foreground painter associated for the given state, or null if * none could be found. */ public Painter getForegroundPainter(SynthContext ctx) { Values v = getValues(ctx); int xstate = getExtendedState(ctx, v); Painter p = null; // check the cache tmpKey.init("foregroundPainter$$instance", xstate); p = (Painter)v.cache.get(tmpKey); if (p != null) return p; // not in cache, so lookup and store in cache RuntimeState s = null; int[] lastIndex = new int[] {-1}; while ((s = getNextState(v.states, lastIndex, xstate)) != null) { if (s.foregroundPainter != null) { p = s.foregroundPainter; break; } } if (p == null) p = (Painter)get(ctx, "foregroundPainter"); if (p != null) { v.cache.put(new CacheKey("foregroundPainter$$instance", xstate), p); } return p; } /** * Gets the appropriate border Painter, if there is one, for the state * specified in the given SynthContext. This method does appropriate * fallback searching, as described in #get. * * @param ctx The SynthContext. Must not be null. * @return The border painter associated for the given state, or null if * none could be found. */ public Painter getBorderPainter(SynthContext ctx) { Values v = getValues(ctx); int xstate = getExtendedState(ctx, v); Painter p = null; // check the cache tmpKey.init("borderPainter$$instance", xstate); p = (Painter)v.cache.get(tmpKey); if (p != null) return p; // not in cache, so lookup and store in cache RuntimeState s = null; int[] lastIndex = new int[] {-1}; while ((s = getNextState(v.states, lastIndex, xstate)) != null) { if (s.borderPainter != null) { p = s.borderPainter; break; } } if (p == null) p = (Painter)get(ctx, "borderPainter"); if (p != null) { v.cache.put(new CacheKey("borderPainter$$instance", xstate), p); } return p; } /** * Utility method which returns the proper Values based on the given * SynthContext. Ensures that parsing of the values has occurred, or * reoccurs as necessary. * * @param ctx The SynthContext * @return a non-null values reference */ private Values getValues(SynthContext ctx) { validate(); return values; } /** * Simple utility method that searches the given array of Strings for the * given string. This method is only called from getExtendedState if * the developer has specified a specific state for the component to be * in (ie, has "wedged" the component in that state) by specifying * they client property "Nimbus.State". * * @param names a non-null array of strings * @param name the name to look for in the array * @return true or false based on whether the given name is in the array */ private boolean contains(String[] names, String name) { assert name != null; for (int i=0; i<names.length; i++) { if (name.equals(names[i])) { return true; } } return false; } /** * <p>Gets the extended state for a given synth context. Nimbus supports the * ability to define custom states. The algorithm used for choosing what * style information to use for a given state requires a single integer * bit string where each bit in the integer represents a different state * that the component is in. This method uses the componentState as * reported in the SynthContext, in addition to custom states, to determine * what this extended state is.</p> * * <p>In addition, this method checks the component in the given context * for a client property called "Nimbus.State". If one exists, then it will * decompose the String associated with that property to determine what * state to return. In this way, the developer can force a component to be * in a specific state, regardless of what the "real" state of the component * is.</p> * * <p>The string associated with "Nimbus.State" would be of the form: * <pre>Enabled+CustomState+MouseOver

* * @param ctx * @param v * @return */ private int getExtendedState(SynthContext ctx, Values v) { JComponent c = ctx.getComponent(); int xstate = 0; int mask = 1; //check for the Nimbus.State client property //Performance NOTE: getClientProperty ends up inside a synchronized //block, so there is some potential for performance issues here, however //I'm not certain that there is one on a modern VM. Object property = c.getClientProperty("Nimbus.State"); if (property != null) { String stateNames = property.toString(); String[] states = stateNames.split("\\+"); if (v.stateTypes == null){ // standard states only for (String stateStr : states) { State.StandardState s = State.getStandardState(stateStr); if (s != null) xstate |= s.getState(); } } else { // custom states for (State s : v.stateTypes) { if (contains(states, s.getName())) { xstate |= mask; } mask <<= 1; } } } else { //if there are no custom states defined, then simply return the //state that Synth reported if (v.stateTypes == null) return ctx.getComponentState(); //there are custom states on this values, so I'll have to iterate //over them all and return a custom extended state int state = ctx.getComponentState(); for (State s : v.stateTypes) { if (s.isInState(c, state)) { xstate |= mask; } mask <<= 1; } } return xstate; } /** * <p>Gets the RuntimeState that most closely matches the state in the given * context, but is less specific than the given "lastState". Essentially, * this allows you to search for the next best state.</p> * * <p>For example, if you had the following three states: * <pre> * Enabled * Enabled+Pressed * Disabled * </pre> * And you wanted to find the state that best represented * ENABLED+PRESSED+FOCUSED and <code>lastState was null (or an * empty array, or an array with a single int with index == -1), then * Enabled+Pressed would be returned. If you then call this method again but * pass the index of Enabled+Pressed as the "lastState", then * Enabled would be returned. If you call this method a third time and pass * the index of Enabled in as the <code>lastState, then null would be * returned.</p> * * <p>The actual code path for determining the proper state is the same as * in Synth.</p> * * @param ctx * @param lastState a 1 element array, allowing me to do pass-by-reference. * @return */ private RuntimeState getNextState(RuntimeState[] states, int[] lastState, int xstate) { // Use the StateInfo with the most bits that matches that of state. // If there are none, then fallback to // the StateInfo with a state of 0, indicating it'll match anything. // Consider if we have 3 StateInfos a, b and c with states: // SELECTED, SELECTED | ENABLED, 0 // // Input Return Value // ----- ------------ // SELECTED a // SELECTED | ENABLED b // MOUSE_OVER c // SELECTED | ENABLED | FOCUSED b // ENABLED c if (states != null && states.length > 0) { int bestCount = 0; int bestIndex = -1; int wildIndex = -1; //if xstate is 0, then search for the runtime state with component //state of 0. That is, find the exact match and return it. if (xstate == 0) { for (int counter = states.length - 1; counter >= 0; counter--) { if (states[counter].state == 0) { lastState[0] = counter; return states[counter]; } } //an exact match couldn't be found, so there was no match. lastState[0] = -1; return null; } //xstate is some value != 0 //determine from which index to start looking. If lastState[0] is -1 //then we know to start from the end of the state array. Otherwise, //we start at the lastIndex - 1. int lastStateIndex = lastState == null || lastState[0] == -1 ? states.length : lastState[0]; for (int counter = lastStateIndex - 1; counter >= 0; counter--) { int oState = states[counter].state; if (oState == 0) { if (wildIndex == -1) { wildIndex = counter; } } else if ((xstate & oState) == oState) { // This is key, we need to make sure all bits of the // StateInfo match, otherwise a StateInfo with // SELECTED | ENABLED would match ENABLED, which we // don't want. // This comes from BigInteger.bitCnt int bitCount = oState; bitCount -= (0xaaaaaaaa & bitCount) >>> 1; bitCount = (bitCount & 0x33333333) + ((bitCount >>> 2) & 0x33333333); bitCount = bitCount + (bitCount >>> 4) & 0x0f0f0f0f; bitCount += bitCount >>> 8; bitCount += bitCount >>> 16; bitCount = bitCount & 0xff; if (bitCount > bestCount) { bestIndex = counter; bestCount = bitCount; } } } if (bestIndex != -1) { lastState[0] = bestIndex; return states[bestIndex]; } if (wildIndex != -1) { lastState[0] = wildIndex; return states[wildIndex]; } } lastState[0] = -1; return null; } /** * Contains values such as the UIDefaults and painters associated with * a state. Whereas <code>State represents a distinct state that a * component can be in (such as Enabled), this class represents the colors, * fonts, painters, etc associated with some state for this * style. */ private final class RuntimeState implements Cloneable { int state; Painter backgroundPainter; Painter foregroundPainter; Painter borderPainter; String stateName; UIDefaults defaults = new UIDefaults(10, .7f); private RuntimeState(int state, String stateName) { this.state = state; this.stateName = stateName; } @Override public String toString() { return stateName; } @Override public RuntimeState clone() { RuntimeState clone = new RuntimeState(state, stateName); clone.backgroundPainter = backgroundPainter; clone.foregroundPainter = foregroundPainter; clone.borderPainter = borderPainter; clone.defaults.putAll(defaults); return clone; } } /** * Essentially a struct of data for a style. A default instance of this * class is used by NimbusStyle. Additional instances exist for each * component that has overrides. */ private static final class Values { /** * The list of State types. A State represents a type of state, such * as Enabled, Default, WindowFocused, etc. These can be custom states. */ State[] stateTypes = null; /** * The list of actual runtime state representations. These can represent things such * as Enabled + Focused. Thus, they differ from States in that they contain * several states together, and have associated properties, data, etc. */ RuntimeState[] states = null; /** * The content margins for this region. */ Insets contentMargins; /** * Defaults on the region/component level. */ UIDefaults defaults = new UIDefaults(10, .7f); /** * Simple cache. After a value has been looked up, it is stored * in this cache for later retrieval. The key is a concatenation of * the property being looked up, two dollar signs, and the extended * state. So for example: * * foo.bar$$2353 */ Map<CacheKey,Object> cache = new HashMap(); } /** * This implementation presupposes that key is never null and that * the two keys being checked for equality are never null */ private static final class CacheKey { private String key; private int xstate; CacheKey(Object key, int xstate) { init(key, xstate); } void init(Object key, int xstate) { this.key = key.toString(); this.xstate = xstate; } @Override public boolean equals(Object obj) { final CacheKey other = (CacheKey) obj; if (obj == null) return false; if (this.xstate != other.xstate) return false; if (!this.key.equals(other.key)) return false; return true; } @Override public int hashCode() { int hash = 3; hash = 29 * hash + this.key.hashCode(); hash = 29 * hash + this.xstate; return hash; } } }

Other Java examples (source code examples)

Here is a short list of links related to this Java NimbusStyle.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.