1 /*
   2  * Copyright (c) 2005, 2006, 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 java.util.HashMap;
  28 import java.util.Map;
  29 import javax.swing.JComponent;
  30 import javax.swing.plaf.synth.SynthConstants;
  31 
  32 /**
  33  * <p>Represents a built in, or custom, state in Nimbus.</p>
  34  *
  35  * <p>Synth provides several built in states, which are:
  36  * <ul>
  37  *  <li>Enabled</li>
  38  *  <li>Mouse Over</li>
  39  *  <li>Pressed</li>
  40  *  <li>Disabled</li>
  41  *  <li>Focused</li>
  42  *  <li>Selected</li>
  43  *  <li>Default</li>
  44  * </ul>
  45  *
  46  * <p>However, there are many more states that could be described in a LookAndFeel, and it
  47  * would be nice to style components differently based on these different states.
  48  * For example, a progress bar could be "indeterminate". It would be very convenient
  49  * to allow this to be defined as a "state".</p>
  50  *
  51  * <p>This class, State, is intended to be used for such situations.
  52  * Simply implement the abstract #isInState method. It returns true if the given
  53  * JComponent is "in this state", false otherwise. This method will be called
  54  * <em>many</em> times in <em>performance sensitive loops</em>. It must execute
  55  * very quickly.</p>
  56  *
  57  * <p>For example, the following might be an implementation of a custom
  58  * "Indeterminate" state for JProgressBars:</p>
  59  *
  60  * <pre><code>
  61  *     public final class IndeterminateState extends State&lt;JProgressBar&gt; {
  62  *         public IndeterminateState() {
  63  *             super("Indeterminate");
  64  *         }
  65  *
  66  *         @Override
  67  *         protected boolean isInState(JProgressBar c) {
  68  *             return c.isIndeterminate();
  69  *         }
  70  *     }
  71  * </code></pre>
  72  */
  73 public abstract class State<T extends JComponent>{
  74     static final Map<String, StandardState> standardStates = new HashMap<String, StandardState>(7);
  75     static final State Enabled = new StandardState(SynthConstants.ENABLED);
  76     static final State MouseOver = new StandardState(SynthConstants.MOUSE_OVER);
  77     static final State Pressed = new StandardState(SynthConstants.PRESSED);
  78     static final State Disabled = new StandardState(SynthConstants.DISABLED);
  79     static final State Focused = new StandardState(SynthConstants.FOCUSED);
  80     static final State Selected = new StandardState(SynthConstants.SELECTED);
  81     static final State Default = new StandardState(SynthConstants.DEFAULT);
  82 
  83     private String name;
  84 
  85     /**
  86      * <p>Create a new custom State. Specify the name for the state. The name should
  87      * be unique within the states set for any one particular component.
  88      * The name of the state should coincide with the name used in UIDefaults.</p>
  89      *
  90      * <p>For example, the following would be correct:</p>
  91      * <pre><code>
  92      *     defaults.put("Button.States", "Enabled, Foo, Disabled");
  93      *     defaults.put("Button.Foo", new FooState("Foo"));
  94      * </code></pre>
  95      *
  96      * @param name a simple user friendly name for the state, such as "Indeterminate"
  97      *        or "EmbeddedPanel" or "Blurred". It is customary to use camel case,
  98      *        with the first letter capitalized.
  99      */
 100     protected State(String name) {
 101         this.name = name;
 102     }
 103 
 104     @Override public String toString() { return name; }
 105 
 106     /**
 107      * <p>This is the main entry point, called by NimbusStyle.</p>
 108      *
 109      * <p>There are both custom states and standard states. Standard states
 110      * correlate to the states defined in SynthConstants. When a UI delegate
 111      * constructs a SynthContext, it specifies the state that the component is
 112      * in according to the states defined in SynthConstants. Our NimbusStyle
 113      * will then take this state, and query each State instance in the style
 114      * asking whether isInState(c, s).</p>
 115      *
 116      * <p>Now, only the standard states care about the "s" param. So we have
 117      * this odd arrangement:</p>
 118      * <ul>
 119      *     <li>NimbusStyle calls State.isInState(c, s)</li>
 120      *     <li>State.isInState(c, s) simply delegates to State.isInState(c)</li>
 121      *     <li><em>EXCEPT</em>, StandardState overrides State.isInState(c, s) and
 122      *         returns directly from that method after checking its state, and
 123      *         does not call isInState(c) (since it is not needed for standard states).</li>
 124      * </ul>
 125      */
 126     boolean isInState(T c, int s) {
 127         return isInState(c);
 128     }
 129 
 130     /**
 131      * <p>Gets whether the specified JComponent is in the custom state represented
 132      * by this class. <em>This is an extremely performance sensitive loop.</em>
 133      * Please take proper precautions to ensure that it executes quickly.</p>
 134      *
 135      * <p>Nimbus uses this method to help determine what state a JComponent is
 136      * in. For example, a custom State could exist for JProgressBar such that
 137      * it would return <code>true</code> when the progress bar is indeterminate.
 138      * Such an implementation of this method would simply be:</p>
 139      *
 140      * <pre><code> return c.isIndeterminate();</code></pre>
 141      *
 142      * @param c the JComponent to test. This will never be null.
 143      * @return true if <code>c</code> is in the custom state represented by
 144      *         this <code>State</code> instance
 145      */
 146     protected abstract boolean isInState(T c);
 147 
 148     String getName() { return name; }
 149 
 150     static boolean isStandardStateName(String name) {
 151         return standardStates.containsKey(name);
 152     }
 153 
 154     static StandardState getStandardState(String name) {
 155         return standardStates.get(name);
 156     }
 157 
 158     static final class StandardState extends State<JComponent> {
 159         private int state;
 160 
 161         private StandardState(int state) {
 162             super(toString(state));
 163             this.state = state;
 164             standardStates.put(getName(), this);
 165         }
 166 
 167         public int getState() {
 168             return state;
 169         }
 170 
 171         @Override
 172         boolean isInState(JComponent c, int s) {
 173             return (s & state) == state;
 174         }
 175 
 176         @Override
 177         protected boolean isInState(JComponent c) {
 178             throw new AssertionError("This method should never be called");
 179         }
 180 
 181         private static String toString(int state) {
 182             StringBuffer buffer = new StringBuffer();
 183             if ((state & SynthConstants.DEFAULT) == SynthConstants.DEFAULT) {
 184                 buffer.append("Default");
 185             }
 186             if ((state & SynthConstants.DISABLED) == SynthConstants.DISABLED) {
 187                 if (buffer.length() > 0) buffer.append("+");
 188                 buffer.append("Disabled");
 189             }
 190             if ((state & SynthConstants.ENABLED) == SynthConstants.ENABLED) {
 191                 if (buffer.length() > 0) buffer.append("+");
 192                 buffer.append("Enabled");
 193             }
 194             if ((state & SynthConstants.FOCUSED) == SynthConstants.FOCUSED) {
 195                 if (buffer.length() > 0) buffer.append("+");
 196                 buffer.append("Focused");
 197             }
 198             if ((state & SynthConstants.MOUSE_OVER) == SynthConstants.MOUSE_OVER) {
 199                 if (buffer.length() > 0) buffer.append("+");
 200                 buffer.append("MouseOver");
 201             }
 202             if ((state & SynthConstants.PRESSED) == SynthConstants.PRESSED) {
 203                 if (buffer.length() > 0) buffer.append("+");
 204                 buffer.append("Pressed");
 205             }
 206             if ((state & SynthConstants.SELECTED) == SynthConstants.SELECTED) {
 207                 if (buffer.length() > 0) buffer.append("+");
 208                 buffer.append("Selected");
 209             }
 210             return buffer.toString();
 211         }
 212     }
 213 }