/*
* Copyright (c) 2009 Kathryn Huxtable and Kenneth Orr.
*
* This file is part of the SeaGlass Pluggable Look and Feel.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Id$
*/
package com.seaglasslookandfeel.state;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.plaf.synth.SynthConstants;
/**
* <p>Represents a built in, or custom, state in Sea Glass.</p>
*
* <p>Synth provides several built in states, which are:</p>
*
* <ul>
* <li>Enabled</li>
* <li>Mouse Over</li>
* <li>Pressed</li>
* <li>Disabled</li>
* <li>Focused</li>
* <li>Selected</li>
* <li>Default</li>
* </ul>
*
* <p>However, there are many more states that could be described in a
* LookAndFeel, and it would be nice to style components differently based on
* these different states. For example, a progress bar could be "indeterminate".
* It would be very convenient to allow this to be defined as a "state".</p>
*
* <p>This class, State, is intended to be used for such situations. Simply
* implement the abstract #isInState method. It returns true if the given
* JComponent is "in this state", false otherwise. This method will be called
* <em>many</em> times in <em>performance sensitive loops</em>. It must execute
* very quickly.</p>
*
* <p>For example, the following might be an implementation of a custom
* "Indeterminate" state for JProgressBars:</p>
*
* <pre>
* <code>
* public final class IndeterminateState extends State<JProgressBar> {
* public IndeterminateState() {
* super("Indeterminate");
* }
*
* @Override
* protected boolean isInState(JProgressBar c) {
* return c.isIndeterminate();
* }
* }
* </code>
* </pre>
*
* <p>Based on Nimbus's State class, which has too much package local stuff.</p>
*
*/
public abstract class State<T extends JComponent> {
static final Map<String, StandardState> standardStates = new HashMap<String, StandardState>(7);
/** Enabled state. */
public static final State Enabled = new StandardState(SynthConstants.ENABLED);
/** MouseOver state. */
public static final State MouseOver = new StandardState(SynthConstants.MOUSE_OVER);
/** Pressed state. */
public static final State Pressed = new StandardState(SynthConstants.PRESSED);
/** Disabled state. */
public static final State Disabled = new StandardState(SynthConstants.DISABLED);
/** Focused state. */
public static final State Focused = new StandardState(SynthConstants.FOCUSED);
/** Selected state. */
public static final State Selected = new StandardState(SynthConstants.SELECTED);
/** Default state. */
public static final State Default = new StandardState(SynthConstants.DEFAULT);
private String name;
/**
* <p>Create a new custom State. Specify the name for the state. The name
* should be unique within the states set for any one particular component.
* The name of the state should coincide with the name used in UIDefaults.
* </p>
*
* <p>For example, the following would be correct:</p>
*
* <pre>
* <code>
* defaults.put("Button.States", "Enabled, Foo, Disabled");
* defaults.put("Button.Foo", new FooState("Foo"));
* </code>
* </pre>
*
* @param name a simple user friendly name for the state, such as
* "Indeterminate" or "EmbeddedPanel" or "Blurred". It is
* customary to use camel case, with the first letter
* capitalized.
*/
protected State(String name) {
this.name = name;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return name;
}
/**
* <p>This is the main entry point, called by NimbusStyle.</p>
*
* <p>There are both custom states and standard states. Standard states
* correlate to the states defined in SynthConstants. When a UI delegate
* constructs a SynthContext, it specifies the state that the component is
* in according to the states defined in SynthConstants. Our NimbusStyle
* will then take this state, and query each State instance in the style
* asking whether isInState(c, s).</p>
*
* <p>Now, only the standard states care about the "s" param. So we have
* this odd arrangement:</p>
*
* <ul>
* <li>NimbusStyle calls State.isInState(c, s)</li>
* <li>State.isInState(c, s) simply delegates to State.isInState(c)</li>
* <li><em>EXCEPT</em>, StandardState overrides State.isInState(c, s) and
* returns directly from that method after checking its state, and does
* not call isInState(c) (since it is not needed for standard
* states).</li>
* </ul>
*
* @param c DOCUMENT ME!
* @param s DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public boolean isInState(T c, int s) {
return isInState(c);
}
/**
* <p>Gets whether the specified JComponent is in the custom state
* represented by this class. <em>This is an extremely performance sensitive
* loop.</em> Please take proper precautions to ensure that it executes
* quickly.</p>
*
* <p>Nimbus uses this method to help determine what state a JComponent is
* in. For example, a custom State could exist for JProgressBar such that it
* would return <code>true</code> when the progress bar is indeterminate.
* Such an implementation of this method would simply be:</p>
*
* <pre>
* <code> return c.isIndeterminate();</code>
* </pre>
*
* @param c the JComponent to test. This will never be null.
*
* @return true if <code>c</code> is in the custom state represented by this
* <code>State</code> instance
*/
public abstract boolean isInState(T c);
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public String getName() {
return name;
}
/**
* DOCUMENT ME!
*
* @param name DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public static boolean isStandardStateName(String name) {
return standardStates.containsKey(name);
}
/**
* DOCUMENT ME!
*
* @param name DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public static StandardState getStandardState(String name) {
return standardStates.get(name);
}
/**
* DOCUMENT ME!
*
* @author $author$
* @version $Revision$, $Date$
*/
public static final class StandardState extends State<JComponent> {
private int state;
/**
* Creates a new StandardState object.
*
* @param state DOCUMENT ME!
*/
private StandardState(int state) {
super(toString(state));
this.state = state;
standardStates.put(getName(), this);
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public int getState() {
return state;
}
/**
* @see com.seaglasslookandfeel.state.State#isInState(JComponent, int)
*/
public boolean isInState(JComponent c, int s) {
return (s & state) == state;
}
/**
* @see com.seaglasslookandfeel.state.State#isInState(JComponent)
*/
public boolean isInState(JComponent c) {
throw new AssertionError("This method should never be called");
}
/**
* DOCUMENT ME!
*
* @param state DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private static String toString(int state) {
StringBuffer buffer = new StringBuffer();
if ((state & SynthConstants.DEFAULT) == SynthConstants.DEFAULT) {
buffer.append("Default");
}
if ((state & SynthConstants.DISABLED) == SynthConstants.DISABLED) {
if (buffer.length() > 0)
buffer.append("+");
buffer.append("Disabled");
}
if ((state & SynthConstants.ENABLED) == SynthConstants.ENABLED) {
if (buffer.length() > 0)
buffer.append("+");
buffer.append("Enabled");
}
if ((state & SynthConstants.FOCUSED) == SynthConstants.FOCUSED) {
if (buffer.length() > 0)
buffer.append("+");
buffer.append("Focused");
}
if ((state & SynthConstants.MOUSE_OVER) == SynthConstants.MOUSE_OVER) {
if (buffer.length() > 0)
buffer.append("+");
buffer.append("MouseOver");
}
if ((state & SynthConstants.PRESSED) == SynthConstants.PRESSED) {
if (buffer.length() > 0)
buffer.append("+");
buffer.append("Pressed");
}
if ((state & SynthConstants.SELECTED) == SynthConstants.SELECTED) {
if (buffer.length() > 0)
buffer.append("+");
buffer.append("Selected");
}
return buffer.toString();
}
}
}