/**
* Copyright (c) 2005-2006, Sun Microsystems, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jdesktop.animation.timing.interpolation;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTargetAdapter;
/**
* This class enables automating animation of object properties.
* The class is a TimingTarget, and should be used as a target of
* timing events from an Animator. These events will be used to
* change a specified property over time, according to how the
* PropertySetter is constructed.
* <p>
* For example, here is an animation of the "background" property
* of some object "obj" from blue to red over a period of one second:
* <pre>
* PropertySetter ps = new PropertySetter(obj, "background", Color.BLUE,
* Color.RED);
* Animator anim = new Animator(1000, ps);
* anim.start();
* </pre>
* Here is the same animation, created using one of the utility
* factory methods that returns an animator:
* <pre>
* Animator animator = PropertySetter.createAnimator(1000, obj, "background",
* Color.BLUE, Color.RED);
* anim.start();
* </pre>
* <p>
* More complex animations can be created by passing in multiple values
* for the property to take on, for example:
* <pre>
* Animator animator = PropertySetter.createAnimator(1000, obj, "background",
* Color.BLUE, Color.RED,
* Color.GREEN);
* anim.start();
* </pre>
* It is also possible to define more involved and tightly-controlled
* steps in the animation, including the times between the values and
* how the values are interpolated by using the constructor that takes
* a {@link KeyFrames} object. KeyFrames defines the fractional times at which
* an object takes on specific values, the values to assume at those times,
* and the method of interpolation between those values. For example,
* here is the same animation as above, specified through KeyFrames, where the
* RED color will be set 10% of the way through the animation (note that
* we are not setting an Interpolator, so the timing intervals will use the
* default LinearInterpolator):
* <pre>
* KeyValues vals = KeyValues.create(Color.BLUE, Color.RED, Color.GREEN);
* KeyTimes times = new KeyTimes(0.0f, .1f, 1.0f);
* KeyFrames frames = new KeyFrames(vals, times);
* Animator animator = PropertySetter.createAnimator(1000, obj, "background",
* frames);
* anim.start();
* </pre>
*
* @author Chet
*/
public class PropertySetter extends TimingTargetAdapter {
private Object object;
private String propertyName;
private KeyFrames keyFrames;
private Method propertySetter;
private Method propertyGetter;
/**
* Utility method that constructs a PropertySetter and an Animator using
* that PropertySetter and returns the Animator
* @param duration the duration, in milliseconds, of the animation
* @param object the object whose property will be animated
* @param propertyName the name of the property to be animated. For
* any propertyName "foo" there must be an accessible "setFoo" method
* on the object. If only one value is supplied in creating the
* KeyValues for the keyFrames, the animation
* will also need a "getFoo" method.
* @param keyFrames the fractional times, values, and interpolation
* to be used in calculating the values set on the object's property.
* @throws IllegalArgumentException if appropriate set/get methods
* cannot be found for propertyName.
*/
public static Animator createAnimator(int duration, Object object,
String propertyName, KeyFrames keyFrames) {
PropertySetter ps = new PropertySetter(object, propertyName, keyFrames);
Animator animator = new Animator(duration, ps);
return animator;
}
/**
* Utility method that constructs a PropertySetter and an Animator using
* that PropertySetter and returns the Animator
* @param duration the duration, in milliseconds, of the animation
* @param object the object whose property will be animated
* @param propertyName the name of the property to be animated. For
* any propertyName "foo" there must be an accessible "setFoo" method
* on the object. If only one value is supplied in creating the
* KeyValues for the keyFrames, the animation
* will also need a "getFoo" method.
* @param params the values that the object will take on during the
* animation. Internally, a KeyFrames object will be created that
* will use times that split the total duration evenly. Supplying
* only one value for params implies that this is a "to" animation
* whose intial value will be determined dynamically when the animation
* starts.
* @throws IllegalArgumentException if appropriate set/get methods
* cannot be found for propertyName.
*/
public static <T> Animator createAnimator(int duration,
Object object, String propertyName, T... params) {
PropertySetter ps = new PropertySetter(object, propertyName, params);
Animator animator = new Animator(duration, ps);
return animator;
}
/**
* Utility method that constructs a PropertySetter and an Animator using
* that PropertySetter and returns the Animator
*
* @param duration the duration, in milliseconds, of the animation
* @param object the object whose property will be animated
* @param propertyName the name of the property to be animated. For
* any propertyName "foo" there must be an accessible "setFoo" method
* on the object. If only one value is supplied in creating the
* KeyValues for the keyFrames, the animation
* will also need a "getFoo" method.
* @param evaluator KeyValues knows how to calculate intermediate values
* for many built-in types, but if you want to supply values in
* types not understood by KeyValues, you will need to supply your
* own Evaluator.
* @param params the values that the object will take on during the
* animation. Internally, a KeyFrames object will be created that
* will use times that split the total duration evenly. Supplying
* only one value for params implies that this is a "to" animation
* whose intial value will be determined dynamically when the animation
* starts.
* @throws IllegalArgumentException if appropriate set/get methods
* cannot be found for propertyName.
*/
public static <T> Animator createAnimator(int duration,
Object object, String propertyName,
Evaluator evaluator, T... params) {
PropertySetter ps = new PropertySetter(object, propertyName, evaluator,
params);
Animator animator = new Animator(duration, ps);
return animator;
}
/**
* Constructor for a PropertySetter where the values the propert
* takes on during the animation are specified in a {@link KeyFrames}
* object.
* @param object the object whose property will be animated
* @param propertyName the name of the property to be animated. For
* any propertyName "foo" there must be an accessible "setFoo" method
* on the object. If only one value is supplied in creating the
* KeyValues for the keyFrames, the animation
* will also need a "getFoo" method.
* @param keyFrames the fractional times, values, and interpolation
* to be used in calculating the values set on the object's property.
* @throws IllegalArgumentException if appropriate set/get methods
* cannot be found for propertyName.
*/
public PropertySetter(Object object, String propertyName,
KeyFrames keyFrames) {
this.object = object;
this.propertyName = propertyName;
this.keyFrames = keyFrames;
try {
setupMethodInfo();
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Bad property name (" +
propertyName +"): could not find " +
"an appropriate setter or getter method for that property");
}
}
/**
* Constructor for a PropertySetter where the values the propert
* takes on during the animation are specified in a {@link KeyFrames}
* object.
* @param object the object whose property will be animated
* @param propertyName the name of the property to be animated. For
* any propertyName "foo" there must be an accessible "setFoo" method
* on the object. If only one value is supplied in params, the animation
* will also need a "getFoo" method.
* @param params the values that the object will take on during the
* animation. Internally, a KeyFrames object will be created that
* will use times that split the total duration evenly. Supplying
* only one value for params implies that this is a "to" animation
* whose intial value will be determined dynamically when the animation
* starts.
* @throws IllegalArgumentException if appropriate set/get methods
* cannot be found for propertyName.
*/
public <T> PropertySetter(Object object, String propertyName, T... params) {
this(object, propertyName, new KeyFrames(KeyValues.create(params)));
}
/**
* Constructor for a PropertySetter where the values the propert
* takes on during the animation are specified in a {@link KeyFrames}
* object.
*
* @param object the object whose property will be animated
* @param propertyName the name of the property to be animated. For
* any propertyName "foo" there must be an accessible "setFoo" method
* on the object. If only one value is supplied in params, the animation
* will also need a "getFoo" method.
* @param evaluator KeyValues knows how to calculate intermediate values
* for many built-in types, but if you want to supply values in
* types not understood by KeyValues, you will need to supply your
* own Evaluator.
* @param params the values that the object will take on during the
* animation. Internally, a KeyFrames object will be created that
* will use times that split the total duration evenly. Supplying
* only one value for params implies that this is a "to" animation
* whose intial value will be determined dynamically when the animation
* starts.
* @throws IllegalArgumentException if appropriate set/get methods
* cannot be found for propertyName.
*/
public <T> PropertySetter(Object object, String propertyName,
Evaluator evaluator, T... params) {
this(object, propertyName,
new KeyFrames(KeyValues.create(evaluator, params)));
}
/**
* Translates the property name used in the PropertyRange object into
* the appropriate Method in the Object to be modified. This uses
* standard JavaBean naming convention (e.g., propertyName would
* become setPropertyName).
* @throws NoSuchMethodException if there is no method on the
* object with the appropriate name
* @throws SecurityException if the application does not have
* appropriate permissions to request access to the Method
*/
private void setupMethodInfo() throws NoSuchMethodException {
try {
String firstChar = propertyName.substring(0, 1);
String remainder = propertyName.substring(1);
Class propertyType = getType();
String propertySetterName = "set" + firstChar.toUpperCase() + remainder;
PropertyDescriptor prop = new PropertyDescriptor(propertyName, object.getClass(),
null, propertySetterName);
propertySetter = prop.getWriteMethod();
if (isToAnimation()) {
// Only need the getter for "to" animations
String propertyGetterName = "get" + firstChar.toUpperCase() +
remainder;
prop = new PropertyDescriptor(propertyName,
object.getClass(), propertyGetterName, null);
propertyGetter = prop.getReadMethod();
}
} catch (Exception e) {
throw new NoSuchMethodException("Cannot find property methods: " + e);
}
}
//
// TimingTargetAdapter overrides
//
/**
* Called by Animator to signal that the timer is about to start.
* The only operation performed in this method is setting an initial
* value for the animation if appropriate; this accounts
* for "to" animations, which need to start from the current value.
* <p>
* This method is not intended for use by application code.
*/
public void begin() {
if (isToAnimation()) {
try {
setStartValue(propertyGetter.invoke(object));
} catch (Exception e) {
System.out.println("Problem setting start value on object " +
object + ": " + e);
}
}
}
/**
* Called from Animator to signal a timing event. This
* causes PropertySetter to invoke the property-setting method (as
* specified by the propertyName in the constructor) with the
* appropriate value of the property given the range of values in the
* KeyValues object and the fraction of the timing cycle that has
* elapsed.
* <p>
* This method is not intended for use by application code.
*/
public void timingEvent(float fraction) {
try {
setValue(object, propertySetter, fraction);
} catch (Exception e) {
System.out.println("Problem calling setValue in " +
"PropertySetter.timingEvent: " + e);
}
}
private String getPropertyName() {
return propertyName;
}
/**
* Called during begin() if this is a "to" animation, to set the start
* value of the animation to whatever the current value is.
*/
private void setStartValue(Object object) {
keyFrames.getKeyValues().setStartValue(object);
}
/**
* Sets the appropriate value on the property given the current fraction
*/
private void setValue(Object object, Method method, float fraction) {
try {
method.invoke(object, keyFrames.getValue(fraction));
} catch (Exception e) {
System.out.println("Problem invoking method " +
propertySetter + " in object " + object +
" in setValue" + e);
}
}
/**
* Returns the type used in this property setter (defers to KeyFrames
* for this information).
*/
private Class getType() {
return keyFrames.getType();
}
/**
* Utility method for determining whether this is a "to" animation
* (true if the first value is null).
*/
private boolean isToAnimation() {
return (keyFrames.getKeyValues().isToAnimation());
}
}