Home » openjdk-7 » javax.swing.plaf.nimbus » [javadoc | source]

    1   /*
    2    * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   package javax.swing.plaf.nimbus;
   26   
   27   import javax.swing.Painter;
   28   
   29   import javax.swing.JComponent;
   30   import javax.swing.UIDefaults;
   31   import javax.swing.UIManager;
   32   import javax.swing.plaf.ColorUIResource;
   33   import javax.swing.plaf.synth.ColorType;
   34   import static javax.swing.plaf.synth.SynthConstants.*;
   35   import javax.swing.plaf.synth.SynthContext;
   36   import javax.swing.plaf.synth.SynthPainter;
   37   import javax.swing.plaf.synth.SynthStyle;
   38   import java.awt.Color;
   39   import java.awt.Font;
   40   import java.awt.Insets;
   41   import java.lang.ref.WeakReference;
   42   import java.util.ArrayList;
   43   import java.util.Collections;
   44   import java.util.Comparator;
   45   import java.util.HashMap;
   46   import java.util.List;
   47   import java.util.Map;
   48   import java.util.TreeMap;
   49   
   50   /**
   51    * <p>A SynthStyle implementation used by Nimbus. Each Region that has been
   52    * registered with the NimbusLookAndFeel will have an associated NimbusStyle.
   53    * Third party components that are registered with the NimbusLookAndFeel will
   54    * therefore be handed a NimbusStyle from the look and feel from the
   55    * #getStyle(JComponent, Region) method.</p>
   56    *
   57    * <p>This class properly reads and retrieves values placed in the UIDefaults
   58    * according to the standard Nimbus naming conventions. It will create and
   59    * retrieve painters, fonts, colors, and other data stored there.</p>
   60    *
   61    * <p>NimbusStyle also supports the ability to override settings on a per
   62    * component basis. NimbusStyle checks the component's client property map for
   63    * "Nimbus.Overrides". If the value associated with this key is an instance of
   64    * UIDefaults, then the values in that defaults table will override the standard
   65    * Nimbus defaults in UIManager, but for that component instance only.</p>
   66    *
   67    * <p>Optionally, you may specify the client property
   68    * "Nimbus.Overrides.InheritDefaults". If true, this client property indicates
   69    * that the defaults located in UIManager should first be read, and then
   70    * replaced with defaults located in the component client properties. If false,
   71    * then only the defaults located in the component client property map will
   72    * be used. If not specified, it is assumed to be true.</p>
   73    *
   74    * <p>You must specify "Nimbus.Overrides" for "Nimbus.Overrides.InheritDefaults"
   75    * to have any effect. "Nimbus.Overrides" indicates whether there are any
   76    * overrides, while "Nimbus.Overrides.InheritDefaults" indicates whether those
   77    * overrides should first be initialized with the defaults from UIManager.</p>
   78    *
   79    * <p>The NimbusStyle is reloaded whenever a property change event is fired
   80    * for a component for "Nimbus.Overrides" or "Nimbus.Overrides.InheritDefaults".
   81    * So for example, setting a new UIDefaults on a component would cause the
   82    * style to be reloaded.</p>
   83    *
   84    * <p>The values are only read out of UIManager once, and then cached. If
   85    * you need to read the values again (for example, if the UI is being reloaded),
   86    * then discard this NimbusStyle and read a new one from NimbusLookAndFeel
   87    * using NimbusLookAndFeel.getStyle.</p>
   88    *
   89    * <p>The primary API of interest in this class for 3rd party component authors
   90    * are the three methods which retrieve painters: #getBackgroundPainter,
   91    * #getForegroundPainter, and #getBorderPainter.</p>
   92    *
   93    * <p>NimbusStyle allows you to specify custom states, or modify the order of
   94    * states. Synth (and thus Nimbus) has the concept of a "state". For example,
   95    * a JButton might be in the "MOUSE_OVER" state, or the "ENABLED" state, or the
   96    * "DISABLED" state. These are all "standard" states which are defined in synth,
   97    * and which apply to all synth Regions.</p>
   98    *
   99    * <p>Sometimes, however, you need to have a custom state. For example, you
  100    * want JButton to render differently if it's parent is a JToolbar. In Nimbus,
  101    * you specify these custom states by including a special key in UIDefaults.
  102    * The following UIDefaults entries define three states for this button:</p>
  103    *
  104    * <pre><code>
  105    *     JButton.States = Enabled, Disabled, Toolbar
  106    *     JButton[Enabled].backgroundPainter = somePainter
  107    *     JButton[Disabled].background = BLUE
  108    *     JButton[Toolbar].backgroundPainter = someOtherPaint
  109    * </code></pre>
  110    *
  111    * <p>As you can see, the <code>JButton.States</code> entry lists the states
  112    * that the JButton style will support. You then specify the settings for
  113    * each state. If you do not specify the <code>JButton.States</code> entry,
  114    * then the standard Synth states will be assumed. If you specify the entry
  115    * but the list of states is empty or null, then the standard synth states
  116    * will be assumed.</p>
  117    *
  118    * @author Richard Bair
  119    * @author Jasper Potts
  120    */
  121   public final class NimbusStyle extends SynthStyle {
  122       /* Keys and scales for large/small/mini components, based on Apples sizes */
  123       public static final String LARGE_KEY = "large";
  124       public static final String SMALL_KEY = "small";
  125       public static final String MINI_KEY = "mini";
  126       public static final double LARGE_SCALE = 1.15;
  127       public static final double SMALL_SCALE = 0.857;
  128       public static final double MINI_SCALE = 0.714;
  129   
  130       /**
  131        * Special constant used for performance reasons during the get() method.
  132        * If get() runs through all of the search locations and determines that
  133        * there is no value, then NULL will be placed into the values map. This way
  134        * on subsequent lookups it will simply extract NULL, see it, and return
  135        * null rather than continuing the lookup procedure.
  136        */
  137       private static final Object NULL = '\0';
  138       /**
  139        * <p>The Color to return from getColorForState if it would otherwise have
  140        * returned null.</p>
  141        *
  142        * <p>Returning null from getColorForState is a very bad thing, as it causes
  143        * the AWT peer for the component to install a SystemColor, which is not a
  144        * UIResource. As a result, if <code>null</code> is returned from
  145        * getColorForState, then thereafter the color is not updated for other
  146        * states or on LAF changes or updates. This DEFAULT_COLOR is used to
  147        * ensure that a ColorUIResource is always returned from
  148        * getColorForState.</p>
  149        */
  150       private static final Color DEFAULT_COLOR = new ColorUIResource(Color.BLACK);
  151       /**
  152        * Simple Comparator for ordering the RuntimeStates according to their
  153        * rank.
  154        */
  155       private static final Comparator<RuntimeState> STATE_COMPARATOR =
  156           new Comparator<RuntimeState>() {
  157               @Override
  158               public int compare(RuntimeState a, RuntimeState b) {
  159                   return a.state - b.state;
  160               }
  161           };
  162       /**
  163        * The prefix for the component or region that this NimbusStyle
  164        * represents. This prefix is used to lookup state in the UIManager.
  165        * It should be something like Button or Slider.Thumb or "MyButton" or
  166        * ComboBox."ComboBox.arrowButton" or "MyComboBox"."ComboBox.arrowButton"
  167        */
  168       private String prefix;
  169       /**
  170        * The SynthPainter that will be returned from this NimbusStyle. The
  171        * SynthPainter returned will be a SynthPainterImpl, which will in turn
  172        * delegate back to this NimbusStyle for the proper Painter (not
  173        * SynthPainter) to use for painting the foreground, background, or border.
  174        */
  175       private SynthPainter painter;
  176       /**
  177        * Data structure containing all of the defaults, insets, states, and other
  178        * values associated with this style. This instance refers to default
  179        * values, and are used when no overrides are discovered in the client
  180        * properties of a component. These values are lazily created on first
  181        * access.
  182        */
  183       private Values values;
  184   
  185       /**
  186        * A temporary CacheKey used to perform lookups. This pattern avoids
  187        * creating useless garbage keys, or concatenating strings, etc.
  188        */
  189       private CacheKey tmpKey = new CacheKey("", 0);
  190   
  191       /**
  192        * Some NimbusStyles are created for a specific component only. In Nimbus,
  193        * this happens whenever the component has as a client property a
  194        * UIDefaults which overrides (or supplements) those defaults found in
  195        * UIManager.
  196        */
  197       private WeakReference<JComponent> component;
  198   
  199       /**
  200        * Create a new NimbusStyle. Only the prefix must be supplied. At the
  201        * appropriate time, installDefaults will be called. At that point, all of
  202        * the state information will be pulled from UIManager and stored locally
  203        * within this style.
  204        *
  205        * @param prefix Something like Button or Slider.Thumb or
  206        *        org.jdesktop.swingx.JXStatusBar or ComboBox."ComboBox.arrowButton"
  207        * @param c an optional reference to a component that this NimbusStyle
  208        *        should be associated with. This is only used when the component
  209        *        has Nimbus overrides registered in its client properties and
  210        *        should be null otherwise.
  211        */
  212       NimbusStyle(String prefix, JComponent c) {
  213           if (c != null) {
  214               this.component = new WeakReference<JComponent>(c);
  215           }
  216           this.prefix = prefix;
  217           this.painter = new SynthPainterImpl(this);
  218       }
  219   
  220       /**
  221        * @inheritDoc
  222        *
  223        * Overridden to cause this style to populate itself with data from
  224        * UIDefaults, if necessary.
  225        */
  226       @Override public void installDefaults(SynthContext ctx) {
  227           validate();
  228   
  229           //delegate to the superclass to install defaults such as background,
  230           //foreground, font, and opaque onto the swing component.
  231           super.installDefaults(ctx);
  232       }
  233   
  234       /**
  235        * Pulls data out of UIDefaults, if it has not done so already, and sets
  236        * up the internal state.
  237        */
  238       private void validate() {
  239           // a non-null values object is the flag we use to determine whether
  240           // to reparse from UIManager.
  241           if (values != null) return;
  242   
  243           // reconstruct this NimbusStyle based on the entries in the UIManager
  244           // and possibly based on any overrides within the component's
  245           // client properties (assuming such a component exists and contains
  246           // any Nimbus.Overrides)
  247           values = new Values();
  248   
  249           Map<String, Object> defaults =
  250                   ((NimbusLookAndFeel) UIManager.getLookAndFeel()).
  251                           getDefaultsForPrefix(prefix);
  252   
  253           // inspect the client properties for the key "Nimbus.Overrides". If the
  254           // value is an instance of UIDefaults, then these defaults are used
  255           // in place of, or in addition to, the defaults in UIManager.
  256           if (component != null) {
  257               // We know component.get() is non-null here, as if the component
  258               // were GC'ed, we wouldn't be processing its style.
  259               Object o = component.get().getClientProperty("Nimbus.Overrides");
  260               if (o instanceof UIDefaults) {
  261                   Object i = component.get().getClientProperty(
  262                           "Nimbus.Overrides.InheritDefaults");
  263                   boolean inherit = i instanceof Boolean ? (Boolean)i : true;
  264                   UIDefaults d = (UIDefaults)o;
  265                   TreeMap<String, Object> map = new TreeMap<String, Object>();
  266                   for (Object obj : d.keySet()) {
  267                       if (obj instanceof String) {
  268                           String key = (String)obj;
  269                           if (key.startsWith(prefix)) {
  270                               map.put(key, d.get(key));
  271                           }
  272                       }
  273                   }
  274                   if (inherit) {
  275                       defaults.putAll(map);
  276                   } else {
  277                       defaults = map;
  278                   }
  279               }
  280           }
  281   
  282           //a list of the different types of states used by this style. This
  283           //list may contain only "standard" states (those defined by Synth),
  284           //or it may contain custom states, or it may contain only "standard"
  285           //states but list them in a non-standard order.
  286           List<State> states = new ArrayList<State>();
  287           //a map of state name to code
  288           Map<String,Integer> stateCodes = new HashMap<String,Integer>();
  289           //This is a list of runtime "state" context objects. These contain
  290           //the values associated with each state.
  291           List<RuntimeState> runtimeStates = new ArrayList<RuntimeState>();
  292   
  293           //determine whether there are any custom states, or custom state
  294           //order. If so, then read all those custom states and define the
  295           //"values" stateTypes to be a non-null array.
  296           //Otherwise, let the "values" stateTypes be null to indicate that
  297           //there are no custom states or custom state ordering
  298           String statesString = (String)defaults.get(prefix + ".States");
  299           if (statesString != null) {
  300               String s[] = statesString.split(",");
  301               for (int i=0; i<s.length; i++) {
  302                   s[i] = s[i].trim();
  303                   if (!State.isStandardStateName(s[i])) {
  304                       //this is a non-standard state name, so look for the
  305                       //custom state associated with it
  306                       String stateName = prefix + "." + s[i];
  307                       State customState = (State)defaults.get(stateName);
  308                       if (customState != null) {
  309                           states.add(customState);
  310                       }
  311                   } else {
  312                       states.add(State.getStandardState(s[i]));
  313                   }
  314               }
  315   
  316               //if there were any states defined, then set the stateTypes array
  317               //to be non-null. Otherwise, leave it null (meaning, use the
  318               //standard synth states).
  319               if (states.size() > 0) {
  320                   values.stateTypes = states.toArray(new State[states.size()]);
  321               }
  322   
  323               //assign codes for each of the state types
  324               int code = 1;
  325               for (State state : states) {
  326                   stateCodes.put(state.getName(), code);
  327                   code <<= 1;
  328               }
  329           } else {
  330               //since there were no custom states defined, setup the list of
  331               //standard synth states. Note that the "v.stateTypes" is not
  332               //being set here, indicating that at runtime the state selection
  333               //routines should use standard synth states instead of custom
  334               //states. I do need to popuplate this temp list now though, so that
  335               //the remainder of this method will function as expected.
  336               states.add(State.Enabled);
  337               states.add(State.MouseOver);
  338               states.add(State.Pressed);
  339               states.add(State.Disabled);
  340               states.add(State.Focused);
  341               states.add(State.Selected);
  342               states.add(State.Default);
  343   
  344               //assign codes for the states
  345               stateCodes.put("Enabled", ENABLED);
  346               stateCodes.put("MouseOver", MOUSE_OVER);
  347               stateCodes.put("Pressed", PRESSED);
  348               stateCodes.put("Disabled", DISABLED);
  349               stateCodes.put("Focused", FOCUSED);
  350               stateCodes.put("Selected", SELECTED);
  351               stateCodes.put("Default", DEFAULT);
  352           }
  353   
  354           //Now iterate over all the keys in the defaults table
  355           for (String key : defaults.keySet()) {
  356               //The key is something like JButton.Enabled.backgroundPainter,
  357               //or JButton.States, or JButton.background.
  358               //Remove the "JButton." portion of the key
  359               String temp = key.substring(prefix.length());
  360               //if there is a " or : then we skip it because it is a subregion
  361               //of some kind
  362               if (temp.indexOf('"') != -1 || temp.indexOf(':') != -1) continue;
  363               //remove the separator
  364               temp = temp.substring(1);
  365               //At this point, temp may be any of the following:
  366               //background
  367               //[Enabled].background
  368               //[Enabled+MouseOver].background
  369               //property.foo
  370   
  371               //parse out the states and the property
  372               String stateString = null;
  373               String property = null;
  374               int bracketIndex = temp.indexOf(']');
  375               if (bracketIndex < 0) {
  376                   //there is not a state string, so property = temp
  377                   property = temp;
  378               } else {
  379                   stateString = temp.substring(0, bracketIndex);
  380                   property = temp.substring(bracketIndex + 2);
  381               }
  382   
  383               //now that I have the state (if any) and the property, get the
  384               //value for this property and install it where it belongs
  385               if (stateString == null) {
  386                   //there was no state, just a property. Check for the custom
  387                   //"contentMargins" property (which is handled specially by
  388                   //Synth/Nimbus). Also check for the property being "States",
  389                   //in which case it is not a real property and should be ignored.
  390                   //otherwise, assume it is a property and install it on the
  391                   //values object
  392                   if ("contentMargins".equals(property)) {
  393                       values.contentMargins = (Insets)defaults.get(key);
  394                   } else if ("States".equals(property)) {
  395                       //ignore
  396                   } else {
  397                       values.defaults.put(property, defaults.get(key));
  398                   }
  399               } else {
  400                   //it is possible that the developer has a malformed UIDefaults
  401                   //entry, such that something was specified in the place of
  402                   //the State portion of the key but it wasn't a state. In this
  403                   //case, skip will be set to true
  404                   boolean skip = false;
  405                   //this variable keeps track of the int value associated with
  406                   //the state. See SynthState for details.
  407                   int componentState = 0;
  408                   //Multiple states may be specified in the string, such as
  409                   //Enabled+MouseOver
  410                   String[] stateParts = stateString.split("\\+");
  411                   //For each state, we need to find the State object associated
  412                   //with it, or skip it if it cannot be found.
  413                   for (String s : stateParts) {
  414                       if (stateCodes.containsKey(s)) {
  415                           componentState |= stateCodes.get(s);
  416                       } else {
  417                           //Was not a state. Maybe it was a subregion or something
  418                           //skip it.
  419                           skip = true;
  420                           break;
  421                       }
  422                   }
  423   
  424                   if (skip) continue;
  425   
  426                   //find the RuntimeState for this State
  427                   RuntimeState rs = null;
  428                   for (RuntimeState s : runtimeStates) {
  429                       if (s.state == componentState) {
  430                           rs = s;
  431                           break;
  432                       }
  433                   }
  434   
  435                   //couldn't find the runtime state, so create a new one
  436                   if (rs == null) {
  437                       rs = new RuntimeState(componentState, stateString);
  438                       runtimeStates.add(rs);
  439                   }
  440   
  441                   //check for a couple special properties, such as for the
  442                   //painters. If these are found, then set the specially on
  443                   //the runtime state. Else, it is just a normal property,
  444                   //so put it in the UIDefaults associated with that runtime
  445                   //state
  446                   if ("backgroundPainter".equals(property)) {
  447                       rs.backgroundPainter = getPainter(defaults, key);
  448                   } else if ("foregroundPainter".equals(property)) {
  449                       rs.foregroundPainter = getPainter(defaults, key);
  450                   } else if ("borderPainter".equals(property)) {
  451                       rs.borderPainter = getPainter(defaults, key);
  452                   } else {
  453                       rs.defaults.put(property, defaults.get(key));
  454                   }
  455               }
  456           }
  457   
  458           //now that I've collected all the runtime states, I'll sort them based
  459           //on their integer "state" (see SynthState for how this works).
  460           Collections.sort(runtimeStates, STATE_COMPARATOR);
  461   
  462           //finally, set the array of runtime states on the values object
  463           values.states = runtimeStates.toArray(new RuntimeState[runtimeStates.size()]);
  464       }
  465   
  466       private Painter getPainter(Map<String, Object> defaults, String key) {
  467           Object p = defaults.get(key);
  468           if (p instanceof UIDefaults.LazyValue) {
  469               p = ((UIDefaults.LazyValue)p).createValue(UIManager.getDefaults());
  470           }
  471           return (p instanceof Painter ? (Painter)p : null);
  472       }
  473   
  474       /**
  475        * @inheritDoc
  476        *
  477        * Overridden to cause this style to populate itself with data from
  478        * UIDefaults, if necessary.
  479        */
  480       @Override public Insets getInsets(SynthContext ctx, Insets in) {
  481           if (in == null) {
  482               in = new Insets(0, 0, 0, 0);
  483           }
  484   
  485           Values v = getValues(ctx);
  486   
  487           if (v.contentMargins == null) {
  488               in.bottom = in.top = in.left = in.right = 0;
  489               return in;
  490           } else {
  491               in.bottom = v.contentMargins.bottom;
  492               in.top = v.contentMargins.top;
  493               in.left = v.contentMargins.left;
  494               in.right = v.contentMargins.right;
  495               // Account for scale
  496               // The key "JComponent.sizeVariant" is used to match Apple's LAF
  497               String scaleKey = (String)ctx.getComponent().getClientProperty(
  498                       "JComponent.sizeVariant");
  499               if (scaleKey != null){
  500                   if (LARGE_KEY.equals(scaleKey)){
  501                       in.bottom *= LARGE_SCALE;
  502                       in.top *= LARGE_SCALE;
  503                       in.left *= LARGE_SCALE;
  504                       in.right *= LARGE_SCALE;
  505                   } else if (SMALL_KEY.equals(scaleKey)){
  506                       in.bottom *= SMALL_SCALE;
  507                       in.top *= SMALL_SCALE;
  508                       in.left *= SMALL_SCALE;
  509                       in.right *= SMALL_SCALE;
  510                   } else if (MINI_KEY.equals(scaleKey)){
  511                       in.bottom *= MINI_SCALE;
  512                       in.top *= MINI_SCALE;
  513                       in.left *= MINI_SCALE;
  514                       in.right *= MINI_SCALE;
  515                   }
  516               }
  517               return in;
  518           }
  519       }
  520   
  521       /**
  522        * @inheritDoc
  523        *
  524        * <p>Overridden to cause this style to populate itself with data from
  525        * UIDefaults, if necessary.</p>
  526        *
  527        * <p>In addition, NimbusStyle handles ColorTypes slightly differently from
  528        * Synth.</p>
  529        * <ul>
  530        *  <li>ColorType.BACKGROUND will equate to the color stored in UIDefaults
  531        *      named "background".</li>
  532        *  <li>ColorType.TEXT_BACKGROUND will equate to the color stored in
  533        *      UIDefaults named "textBackground".</li>
  534        *  <li>ColorType.FOREGROUND will equate to the color stored in UIDefaults
  535        *      named "textForeground".</li>
  536        *  <li>ColorType.TEXT_FOREGROUND will equate to the color stored in
  537        *      UIDefaults named "textForeground".</li>
  538        * </ul>
  539        */
  540       @Override protected Color getColorForState(SynthContext ctx, ColorType type) {
  541           String key = null;
  542           if (type == ColorType.BACKGROUND) {
  543               key = "background";
  544           } else if (type == ColorType.FOREGROUND) {
  545               //map FOREGROUND as TEXT_FOREGROUND
  546               key = "textForeground";
  547           } else if (type == ColorType.TEXT_BACKGROUND) {
  548               key = "textBackground";
  549           } else if (type == ColorType.TEXT_FOREGROUND) {
  550               key = "textForeground";
  551           } else if (type == ColorType.FOCUS) {
  552               key = "focus";
  553           } else if (type != null) {
  554               key = type.toString();
  555           } else {
  556               return DEFAULT_COLOR;
  557           }
  558           Color c = (Color) get(ctx, key);
  559           //if all else fails, return a default color (which is a ColorUIResource)
  560           if (c == null) c = DEFAULT_COLOR;
  561           return c;
  562       }
  563   
  564       /**
  565        * @inheritDoc
  566        *
  567        * Overridden to cause this style to populate itself with data from
  568        * UIDefaults, if necessary. If a value named "font" is not found in
  569        * UIDefaults, then the "defaultFont" font in UIDefaults will be returned
  570        * instead.
  571        */
  572       @Override protected Font getFontForState(SynthContext ctx) {
  573           Font f = (Font)get(ctx, "font");
  574           if (f == null) f = UIManager.getFont("defaultFont");
  575   
  576           // Account for scale
  577           // The key "JComponent.sizeVariant" is used to match Apple's LAF
  578           String scaleKey = (String)ctx.getComponent().getClientProperty(
  579                   "JComponent.sizeVariant");
  580           if (scaleKey != null){
  581               if (LARGE_KEY.equals(scaleKey)){
  582                   f = f.deriveFont(Math.round(f.getSize2D()*LARGE_SCALE));
  583               } else if (SMALL_KEY.equals(scaleKey)){
  584                   f = f.deriveFont(Math.round(f.getSize2D()*SMALL_SCALE));
  585               } else if (MINI_KEY.equals(scaleKey)){
  586                   f = f.deriveFont(Math.round(f.getSize2D()*MINI_SCALE));
  587               }
  588           }
  589           return f;
  590       }
  591   
  592       /**
  593        * @inheritDoc
  594        *
  595        * Returns the SynthPainter for this style, which ends up delegating to
  596        * the Painters installed in this style.
  597        */
  598       @Override public SynthPainter getPainter(SynthContext ctx) {
  599           return painter;
  600       }
  601   
  602       /**
  603        * @inheritDoc
  604        *
  605        * Overridden to cause this style to populate itself with data from
  606        * UIDefaults, if necessary. If opacity is not specified in UI defaults,
  607        * then it defaults to being non-opaque.
  608        */
  609       @Override public boolean isOpaque(SynthContext ctx) {
  610           // Force Table CellRenderers to be opaque
  611           if ("Table.cellRenderer".equals(ctx.getComponent().getName())) {
  612               return true;
  613           }
  614           Boolean opaque = (Boolean)get(ctx, "opaque");
  615           return opaque == null ? false : opaque;
  616       }
  617   
  618       /**
  619        * @inheritDoc
  620        *
  621        * <p>Overridden to cause this style to populate itself with data from
  622        * UIDefaults, if necessary.</p>
  623        *
  624        * <p>Properties in UIDefaults may be specified in a chained manner. For
  625        * example:
  626        * <pre>
  627        * background
  628        * Button.opacity
  629        * Button.Enabled.foreground
  630        * Button.Enabled+Selected.background
  631        * </pre></p>
  632        *
  633        * <p>In this example, suppose you were in the Enabled+Selected state and
  634        * searched for "foreground". In this case, we first check for
  635        * Button.Enabled+Selected.foreground, but no such color exists. We then
  636        * fall back to the next valid state, in this case,
  637        * Button.Enabled.foreground, and have a match. So we return it.</p>
  638        *
  639        * <p>Again, if we were in the state Enabled and looked for "background", we
  640        * wouldn't find it in Button.Enabled, or in Button, but would at the top
  641        * level in UIManager. So we return that value.</p>
  642        *
  643        * <p>One special note: the "key" passed to this method could be of the form
  644        * "background" or "Button.background" where "Button" equals the prefix
  645        * passed to the NimbusStyle constructor. In either case, it looks for
  646        * "background".</p>
  647        *
  648        * @param ctx
  649        * @param key must not be null
  650        */
  651       @Override public Object get(SynthContext ctx, Object key) {
  652           Values v = getValues(ctx);
  653   
  654           // strip off the prefix, if there is one.
  655           String fullKey = key.toString();
  656           String partialKey = fullKey.substring(fullKey.indexOf(".") + 1);
  657   
  658           Object obj = null;
  659           int xstate = getExtendedState(ctx, v);
  660   
  661           // check the cache
  662           tmpKey.init(partialKey, xstate);
  663           obj = v.cache.get(tmpKey);
  664           boolean wasInCache = obj != null;
  665           if (!wasInCache){
  666               // Search exact matching states and then lesser matching states
  667               RuntimeState s = null;
  668               int[] lastIndex = new int[] {-1};
  669               while (obj == null &&
  670                       (s = getNextState(v.states, lastIndex, xstate)) != null) {
  671                   obj = s.defaults.get(partialKey);
  672               }
  673               // Search Region Defaults
  674               if (obj == null && v.defaults != null) {
  675                   obj = v.defaults.get(partialKey);
  676               }
  677               // return found object
  678               // Search UIManager Defaults
  679               if (obj == null) obj = UIManager.get(fullKey);
  680               // Search Synth Defaults for InputMaps
  681               if (obj == null && partialKey.equals("focusInputMap")) {
  682                   obj = super.get(ctx, fullKey);
  683               }
  684               // if all we got was a null, store this fact for later use
  685               v.cache.put(new CacheKey(partialKey, xstate),
  686                       obj == null ? NULL : obj);
  687           }
  688           // return found object
  689           return obj == NULL ? null : obj;
  690       }
  691   
  692       /**
  693        * Gets the appropriate background Painter, if there is one, for the state
  694        * specified in the given SynthContext. This method does appropriate
  695        * fallback searching, as described in #get.
  696        *
  697        * @param ctx The SynthContext. Must not be null.
  698        * @return The background painter associated for the given state, or null if
  699        * none could be found.
  700        */
  701       public Painter getBackgroundPainter(SynthContext ctx) {
  702           Values v = getValues(ctx);
  703           int xstate = getExtendedState(ctx, v);
  704           Painter p = null;
  705   
  706           // check the cache
  707           tmpKey.init("backgroundPainter$$instance", xstate);
  708           p = (Painter)v.cache.get(tmpKey);
  709           if (p != null) return p;
  710   
  711           // not in cache, so lookup and store in cache
  712           RuntimeState s = null;
  713           int[] lastIndex = new int[] {-1};
  714           while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
  715               if (s.backgroundPainter != null) {
  716                   p = s.backgroundPainter;
  717                   break;
  718               }
  719           }
  720           if (p == null) p = (Painter)get(ctx, "backgroundPainter");
  721           if (p != null) {
  722               v.cache.put(new CacheKey("backgroundPainter$$instance", xstate), p);
  723           }
  724           return p;
  725       }
  726   
  727       /**
  728        * Gets the appropriate foreground Painter, if there is one, for the state
  729        * specified in the given SynthContext. This method does appropriate
  730        * fallback searching, as described in #get.
  731        *
  732        * @param ctx The SynthContext. Must not be null.
  733        * @return The foreground painter associated for the given state, or null if
  734        * none could be found.
  735        */
  736       public Painter getForegroundPainter(SynthContext ctx) {
  737           Values v = getValues(ctx);
  738           int xstate = getExtendedState(ctx, v);
  739           Painter p = null;
  740   
  741           // check the cache
  742           tmpKey.init("foregroundPainter$$instance", xstate);
  743           p = (Painter)v.cache.get(tmpKey);
  744           if (p != null) return p;
  745   
  746           // not in cache, so lookup and store in cache
  747           RuntimeState s = null;
  748           int[] lastIndex = new int[] {-1};
  749           while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
  750               if (s.foregroundPainter != null) {
  751                   p = s.foregroundPainter;
  752                   break;
  753               }
  754           }
  755           if (p == null) p = (Painter)get(ctx, "foregroundPainter");
  756           if (p != null) {
  757               v.cache.put(new CacheKey("foregroundPainter$$instance", xstate), p);
  758           }
  759           return p;
  760       }
  761   
  762       /**
  763        * Gets the appropriate border Painter, if there is one, for the state
  764        * specified in the given SynthContext. This method does appropriate
  765        * fallback searching, as described in #get.
  766        *
  767        * @param ctx The SynthContext. Must not be null.
  768        * @return The border painter associated for the given state, or null if
  769        * none could be found.
  770        */
  771       public Painter getBorderPainter(SynthContext ctx) {
  772           Values v = getValues(ctx);
  773           int xstate = getExtendedState(ctx, v);
  774           Painter p = null;
  775   
  776           // check the cache
  777           tmpKey.init("borderPainter$$instance", xstate);
  778           p = (Painter)v.cache.get(tmpKey);
  779           if (p != null) return p;
  780   
  781           // not in cache, so lookup and store in cache
  782           RuntimeState s = null;
  783           int[] lastIndex = new int[] {-1};
  784           while ((s = getNextState(v.states, lastIndex, xstate)) != null) {
  785               if (s.borderPainter != null) {
  786                   p = s.borderPainter;
  787                   break;
  788               }
  789           }
  790           if (p == null) p = (Painter)get(ctx, "borderPainter");
  791           if (p != null) {
  792               v.cache.put(new CacheKey("borderPainter$$instance", xstate), p);
  793           }
  794           return p;
  795       }
  796   
  797       /**
  798        * Utility method which returns the proper Values based on the given
  799        * SynthContext. Ensures that parsing of the values has occurred, or
  800        * reoccurs as necessary.
  801        *
  802        * @param ctx The SynthContext
  803        * @return a non-null values reference
  804        */
  805       private Values getValues(SynthContext ctx) {
  806           validate();
  807           return values;
  808       }
  809   
  810       /**
  811        * Simple utility method that searchs the given array of Strings for the
  812        * given string. This method is only called from getExtendedState if
  813        * the developer has specified a specific state for the component to be
  814        * in (ie, has "wedged" the component in that state) by specifying
  815        * they client property "Nimbus.State".
  816        *
  817        * @param names a non-null array of strings
  818        * @param name the name to look for in the array
  819        * @return true or false based on whether the given name is in the array
  820        */
  821       private boolean contains(String[] names, String name) {
  822           assert name != null;
  823           for (int i=0; i<names.length; i++) {
  824               if (name.equals(names[i])) {
  825                   return true;
  826               }
  827           }
  828           return false;
  829       }
  830   
  831       /**
  832        * <p>Gets the extended state for a given synth context. Nimbus supports the
  833        * ability to define custom states. The algorithm used for choosing what
  834        * style information to use for a given state requires a single integer
  835        * bit string where each bit in the integer represents a different state
  836        * that the component is in. This method uses the componentState as
  837        * reported in the SynthContext, in addition to custom states, to determine
  838        * what this extended state is.</p>
  839        *
  840        * <p>In addition, this method checks the component in the given context
  841        * for a client property called "Nimbus.State". If one exists, then it will
  842        * decompose the String associated with that property to determine what
  843        * state to return. In this way, the developer can force a component to be
  844        * in a specific state, regardless of what the "real" state of the component
  845        * is.</p>
  846        *
  847        * <p>The string associated with "Nimbus.State" would be of the form:
  848        * <pre>Enabled+CustomState+MouseOver</pre></p>
  849        *
  850        * @param ctx
  851        * @param v
  852        * @return
  853        */
  854       private int getExtendedState(SynthContext ctx, Values v) {
  855           JComponent c = ctx.getComponent();
  856           int xstate = 0;
  857           int mask = 1;
  858           //check for the Nimbus.State client property
  859           //Performance NOTE: getClientProperty ends up inside a synchronized
  860           //block, so there is some potential for performance issues here, however
  861           //I'm not certain that there is one on a modern VM.
  862           Object property = c.getClientProperty("Nimbus.State");
  863           if (property != null) {
  864               String stateNames = property.toString();
  865               String[] states = stateNames.split("\\+");
  866               if (v.stateTypes == null){
  867                   // standard states only
  868                   for (String stateStr : states) {
  869                       State.StandardState s = State.getStandardState(stateStr);
  870                       if (s != null) xstate |= s.getState();
  871                   }
  872               } else {
  873                   // custom states
  874                   for (State s : v.stateTypes) {
  875                       if (contains(states, s.getName())) {
  876                           xstate |= mask;
  877                       }
  878                       mask <<= 1;
  879                   }
  880               }
  881           } else {
  882               //if there are no custom states defined, then simply return the
  883               //state that Synth reported
  884               if (v.stateTypes == null) return ctx.getComponentState();
  885   
  886               //there are custom states on this values, so I'll have to iterate
  887               //over them all and return a custom extended state
  888               int state = ctx.getComponentState();
  889               for (State s : v.stateTypes) {
  890                   if (s.isInState(c, state)) {
  891                       xstate |= mask;
  892                   }
  893                   mask <<= 1;
  894               }
  895           }
  896           return xstate;
  897       }
  898   
  899       /**
  900        * <p>Gets the RuntimeState that most closely matches the state in the given
  901        * context, but is less specific than the given "lastState". Essentially,
  902        * this allows you to search for the next best state.</p>
  903        *
  904        * <p>For example, if you had the following three states:
  905        * <pre>
  906        * Enabled
  907        * Enabled+Pressed
  908        * Disabled
  909        * </pre>
  910        * And you wanted to find the state that best represented
  911        * ENABLED+PRESSED+FOCUSED and <code>lastState</code> was null (or an
  912        * empty array, or an array with a single int with index == -1), then
  913        * Enabled+Pressed would be returned. If you then call this method again but
  914        * pass the index of Enabled+Pressed as the "lastState", then
  915        * Enabled would be returned. If you call this method a third time and pass
  916        * the index of Enabled in as the <code>lastState</code>, then null would be
  917        * returned.</p>
  918        *
  919        * <p>The actual code path for determining the proper state is the same as
  920        * in Synth.</p>
  921        *
  922        * @param ctx
  923        * @param lastState a 1 element array, allowing me to do pass-by-reference.
  924        * @return
  925        */
  926       private RuntimeState getNextState(RuntimeState[] states,
  927                                         int[] lastState,
  928                                         int xstate) {
  929           // Use the StateInfo with the most bits that matches that of state.
  930           // If there are none, then fallback to
  931           // the StateInfo with a state of 0, indicating it'll match anything.
  932   
  933           // Consider if we have 3 StateInfos a, b and c with states:
  934           // SELECTED, SELECTED | ENABLED, 0
  935           //
  936           // Input                          Return Value
  937           // -----                          ------------
  938           // SELECTED                       a
  939           // SELECTED | ENABLED             b
  940           // MOUSE_OVER                     c
  941           // SELECTED | ENABLED | FOCUSED   b
  942           // ENABLED                        c
  943   
  944           if (states != null && states.length > 0) {
  945               int bestCount = 0;
  946               int bestIndex = -1;
  947               int wildIndex = -1;
  948   
  949               //if xstate is 0, then search for the runtime state with component
  950               //state of 0. That is, find the exact match and return it.
  951               if (xstate == 0) {
  952                   for (int counter = states.length - 1; counter >= 0; counter--) {
  953                       if (states[counter].state == 0) {
  954                           lastState[0] = counter;
  955                           return states[counter];
  956                       }
  957                   }
  958                   //an exact match couldn't be found, so there was no match.
  959                   lastState[0] = -1;
  960                   return null;
  961               }
  962   
  963               //xstate is some value != 0
  964   
  965               //determine from which index to start looking. If lastState[0] is -1
  966               //then we know to start from the end of the state array. Otherwise,
  967               //we start at the lastIndex - 1.
  968               int lastStateIndex = lastState == null || lastState[0] == -1 ?
  969                   states.length : lastState[0];
  970   
  971               for (int counter = lastStateIndex - 1; counter >= 0; counter--) {
  972                   int oState = states[counter].state;
  973   
  974                   if (oState == 0) {
  975                       if (wildIndex == -1) {
  976                           wildIndex = counter;
  977                       }
  978                   } else if ((xstate & oState) == oState) {
  979                       // This is key, we need to make sure all bits of the
  980                       // StateInfo match, otherwise a StateInfo with
  981                       // SELECTED | ENABLED would match ENABLED, which we
  982                       // don't want.
  983   
  984                       // This comes from BigInteger.bitCnt
  985                       int bitCount = oState;
  986                       bitCount -= (0xaaaaaaaa & bitCount) >>> 1;
  987                       bitCount = (bitCount & 0x33333333) + ((bitCount >>> 2) &
  988                               0x33333333);
  989                       bitCount = bitCount + (bitCount >>> 4) & 0x0f0f0f0f;
  990                       bitCount += bitCount >>> 8;
  991                       bitCount += bitCount >>> 16;
  992                       bitCount = bitCount & 0xff;
  993                       if (bitCount > bestCount) {
  994                           bestIndex = counter;
  995                           bestCount = bitCount;
  996                       }
  997                   }
  998               }
  999               if (bestIndex != -1) {
 1000                   lastState[0] = bestIndex;
 1001                   return states[bestIndex];
 1002               }
 1003               if (wildIndex != -1) {
 1004                   lastState[0] = wildIndex;
 1005                   return states[wildIndex];
 1006               }
 1007           }
 1008           lastState[0] = -1;
 1009           return null;
 1010       }
 1011   
 1012       /**
 1013        * Contains values such as the UIDefaults and painters asssociated with
 1014        * a state. Whereas <code>State</code> represents a distinct state that a
 1015        * component can be in (such as Enabled), this class represents the colors,
 1016        * fonts, painters, etc associated with some state for this
 1017        * style.
 1018        */
 1019       private final class RuntimeState implements Cloneable {
 1020           int state;
 1021           Painter backgroundPainter;
 1022           Painter foregroundPainter;
 1023           Painter borderPainter;
 1024           String stateName;
 1025           UIDefaults defaults = new UIDefaults(10, .7f);
 1026   
 1027           private RuntimeState(int state, String stateName) {
 1028               this.state = state;
 1029               this.stateName = stateName;
 1030           }
 1031   
 1032           @Override
 1033           public String toString() {
 1034               return stateName;
 1035           }
 1036   
 1037           @Override
 1038           public RuntimeState clone() {
 1039               RuntimeState clone = new RuntimeState(state, stateName);
 1040               clone.backgroundPainter = backgroundPainter;
 1041               clone.foregroundPainter = foregroundPainter;
 1042               clone.borderPainter = borderPainter;
 1043               clone.defaults.putAll(defaults);
 1044               return clone;
 1045           }
 1046       }
 1047   
 1048       /**
 1049        * Essentially a struct of data for a style. A default instance of this
 1050        * class is used by NimbusStyle. Additional instances exist for each
 1051        * component that has overrides.
 1052        */
 1053       private static final class Values {
 1054           /**
 1055            * The list of State types. A State represents a type of state, such
 1056            * as Enabled, Default, WindowFocused, etc. These can be custom states.
 1057            */
 1058           State[] stateTypes = null;
 1059           /**
 1060            * The list of actual runtime state representations. These can represent things such
 1061            * as Enabled + Focused. Thus, they differ from States in that they contain
 1062            * several states together, and have associated properties, data, etc.
 1063            */
 1064           RuntimeState[] states = null;
 1065           /**
 1066            * The content margins for this region.
 1067            */
 1068           Insets contentMargins;
 1069           /**
 1070            * Defaults on the region/component level.
 1071            */
 1072           UIDefaults defaults = new UIDefaults(10, .7f);
 1073           /**
 1074            * Simple cache. After a value has been looked up, it is stored
 1075            * in this cache for later retrieval. The key is a concatenation of
 1076            * the property being looked up, two dollar signs, and the extended
 1077            * state. So for example:
 1078            *
 1079            * foo.bar$$2353
 1080            */
 1081           Map<CacheKey,Object> cache = new HashMap<CacheKey,Object>();
 1082       }
 1083   
 1084       /**
 1085        * This implementation presupposes that key is never null and that
 1086        * the two keys being checked for equality are never null
 1087        */
 1088       private static final class CacheKey {
 1089           private String key;
 1090           private int xstate;
 1091   
 1092           CacheKey(Object key, int xstate) {
 1093               init(key, xstate);
 1094           }
 1095   
 1096           void init(Object key, int xstate) {
 1097               this.key = key.toString();
 1098               this.xstate = xstate;
 1099           }
 1100   
 1101           @Override
 1102           public boolean equals(Object obj) {
 1103               final CacheKey other = (CacheKey) obj;
 1104               if (obj == null) return false;
 1105               if (this.xstate != other.xstate) return false;
 1106               if (!this.key.equals(other.key)) return false;
 1107               return true;
 1108           }
 1109   
 1110           @Override
 1111           public int hashCode() {
 1112               int hash = 3;
 1113               hash = 29 * hash + this.key.hashCode();
 1114               hash = 29 * hash + this.xstate;
 1115               return hash;
 1116           }
 1117       }
 1118   }

Home » openjdk-7 » javax.swing.plaf.nimbus » [javadoc | source]