//
// AnimationControlJ2D.java
//
/*
VisAD system for interactive analysis and visualization of numerical
data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/
package visad.java2d;
import visad.*;
import visad.browser.Convert;
import visad.util.Delay;
import java.rmi.*;
import java.util.StringTokenizer;
/**
AnimationControlJ2D is the VisAD class for controlling Animation
display scalars under Java2D.<P>
WLH - manipulate a list of VisADSwitch nodes in scene graph.<P>
*/
public class AnimationControlJ2D extends AVControlJ2D
implements Runnable, AnimationControl {
private int current = 0;
private boolean direction; // true = forward
private long step; // time in milliseconds for the current step
private long[] stepValues = {500}; // times in milliseconds between animation steps
private transient AnimationSetControlJ2D animationSet;
private ToggleControl animate;
private RealType real;
private boolean no_tick = false;
private boolean computeSet = true;
private transient VisADCanvasJ2D canvas;
/** AnimationControlJ2D is Serializable, mark as transient */
private transient Thread animationThread;
public AnimationControlJ2D(DisplayImplJ2D d, RealType r) {
super(d);
real = r;
current = 0;
direction = true;
step = 500;
stepValues = new long[] {step};
if (d != null) {
canvas = ((DisplayRendererJ2D) d.getDisplayRenderer()).getCanvas();
animationSet = new AnimationSetControlJ2D(d, this);
d.addControl(animationSet);
animate = new ToggleControl(d, this);
d.addControl(animate);
try {
animate.setOn(false);
}
catch (VisADException v) {
}
catch (RemoteException v) {
}
new Delay();
// initialize the stepValues array
try
{
Set set = animationSet.getSet();
if (set != null) stepValues = new long[set.getLength()];
}
catch (VisADException v) {;}
for (int i = 0; i<stepValues.length; i++)
{
stepValues[i] = step;
}
animationThread = new Thread(this);
animationThread.start();
}
}
public void nullControl() {
stop();
super.nullControl();
}
public void stop() {
animationThread = null;
}
public void run() {
Thread me = Thread.currentThread();
while (animationThread == me) {
try {
if (animate != null && animate.getOn() && !no_tick) {
takeStep();
}
}
catch (VisADException v) {
v.printStackTrace();
throw new VisADError("AnimationControlJ2D.run: " + v.toString());
}
catch (RemoteException v) {
v.printStackTrace();
throw new VisADError("AnimationControlJ2D.run: " + v.toString());
}
try {
synchronized (this) {
if (0 <= current && current < stepValues.length) {
wait(stepValues[current]);
}
else {
wait(500);
}
}
}
catch(InterruptedException e) {
// control doesn't normally come here
}
}
}
void setNoTick(boolean nt) {
no_tick = nt;
}
/** get the current ordinal step number */
public int getCurrent() {
return current;
}
/** set the current ordinal step number = c */
public void setCurrent(int c)
throws VisADException, RemoteException {
if (animationSet != null) {
current = animationSet.clipCurrent(c);
/* WLH 26 June 98
init();
*/
canvas.renderTrigger();
}
else {
current = 0;
}
// WLH 5 May 2000
// changeControl(true);
changeControl(false);
}
/** set the current step by the value of the RealType
mapped to Display.Animation */
public void setCurrent(double value)
throws VisADException, RemoteException {
if (animationSet != null) {
current = animationSet.getIndex(value);
canvas.renderTrigger();
}
else {
current = 0;
}
// WLH 5 May 2000
// changeControl(true);
changeControl(false);
}
/**
* Set the animation direction.
*
* @param dir true for forward, false for backward
*
* @throws VisADException Couldn't create necessary VisAD object. The
* direction remains unchanged.
* @throws RemoteException Java RMI exception
*/
public void setDirection(boolean dir)
throws VisADException, RemoteException {
direction = dir;
// WLH 5 May 2000
// changeControl(true);
changeControl(false);
}
/** Get the animation direction.
*
* @return true for forward, false for backward
*/
public boolean getDirection()
{
return direction;
}
/**
* Return the dwell time for the current step
*/
public long getStep() {
if (stepValues == null || current < 0 ||
stepValues.length <= current) return 500;
else return stepValues[current];
}
/**
* return an array of the dwell times for all the steps.
*/
public long[] getSteps()
{
return stepValues;
}
/**
* Set the dwell rate between animation steps to a constant value
*
* @param st dwell time in milliseconds
*
* @throws VisADException Couldn't create necessary VisAD object. The
* dwell time remains unchanged.
* @throws RemoteException Java RMI exception
*/
public void setStep(int st) throws VisADException, RemoteException {
if (st <= 0) {
throw new DisplayException("AnimationControlJ2D.setStep: " +
"step must be > 0");
}
step = st;
for (int i=0; i < stepValues.length; i++)
{
stepValues[i] = step;
}
// WLH 5 May 2000
// changeControl(true);
changeControl(false);
}
/**
* set the dwell time for individual steps.
*
* @param steps an array of dwell rates for each step in the animation
* If the length of the array is less than the number of
* frames in the animation, the subsequent step values will
* be set to the value of the last step.
*
* @throws VisADException Couldn't create necessary VisAD object. The
* dwell times remain unchanged.
* @throws RemoteException Java RMI exception
*/
public void setSteps(int[] steps)
throws VisADException, RemoteException
{
// verify that the values are valid
for (int i = 0; i < stepValues.length; i++)
{
stepValues[i] =
(i < steps.length) ? steps[i] : steps[steps.length-1];
if (stepValues[i] <= 0)
throw new DisplayException("AnimationControlJ2D.setSteps: " +
"step " + i + " must be > 0");
}
// WLH 5 May 2000
// changeControl(true);
changeControl(false);
}
/**
* advance one step (forward or backward)
*
* @throws VisADException Couldn't create necessary VisAD object. No
* step is taken.
* @throws RemoteException Java RMI exception
*/
public void takeStep() throws VisADException, RemoteException {
if (direction) current++;
else current--;
if (animationSet != null) {
current = animationSet.clipCurrent(current);
canvas.renderTrigger();
}
changeControl(false);
}
public void init() throws VisADException {
if (animationSet != null &&
animationSet.getSet() != null) {
double value = animationSet.getValue(current);
Set set = animationSet.getSet();
animation_string(real, set, value, current);
selectSwitches(value, set);
}
}
public Set getSet() {
if (animationSet != null) {
return animationSet.getSet();
}
else {
return null;
}
}
/**
* <p>Sets the set of times in this animation control. If the argument
* set is equal to the current set, then nothing is done.</p>
*
* @param s The set of times.
* @throws VisADException if a VisAD failure occurs.
* @throws RemoteException if a Java RMI failure occurs.
*/
public void setSet(Set s)
throws VisADException, RemoteException {
if (s == null && animationSet != null &&
animationSet.getSet() == null) return; // check for null/null
if (animationSet == null || s == null ||
(s != null && !s.equals(animationSet.getSet()))) {
setSet(s, false);
// have to do this i animationSet == null
if (s == null) {
stepValues = new long[] {step};
current = 0;
} else if (s.getLength() != stepValues.length) {
stepValues = new long[s.getLength()];
for (int i = 0; i < stepValues.length; i++)
{
stepValues[i] = step;
}
}
}
}
/**
* <p>Sets the set of times in this animation control. If the argument
* set is equal to the current set, then nothing is done.</p>
*
* @param s The set of times.
* @param noChange changeControl(!noChange) to not trigger
* re-transform, used by ScalarMap.setRange
* @throws VisADException if a VisAD failure occurs.
* @throws RemoteException if a Java RMI failure occurs.
*/
public void setSet(Set s, boolean noChange)
throws VisADException, RemoteException {
if (animationSet != null) {
if (s == null) {
stepValues = new long[] {step};
current = 0;
} else if (s.getLength() != stepValues.length) {
stepValues = new long[s.getLength()];
for (int i = 0; i < stepValues.length; i++)
{
stepValues[i] = step;
}
}
animationSet.setSet(s, noChange);
}
}
/** return true if automatic stepping is on */
public boolean getOn() {
if (animate != null) {
return animate.getOn();
}
else {
return false;
}
}
/**
* Set automatic stepping on or off.
*
* @param o true = turn stepping on, false = turn stepping off
*
* @throws VisADException Couldn't create necessary VisAD object. No
* change in automatic stepping occurs.
* @throws RemoteException Java RMI exception
*/
public void setOn(boolean o)
throws VisADException, RemoteException {
if (animate != null) {
animate.setOn(o);
}
}
/**
* toggle automatic stepping between off and on
*
* @throws VisADException Couldn't create necessary VisAD object. No
* change in automatic stepping occurs.
* @throws RemoteException Java RMI exception
*/
public void toggle()
throws VisADException, RemoteException {
if (animate != null) {
animate.setOn(!animate.getOn());
}
}
public void subSetTicks() {
if (animationSet != null) {
animationSet.setTicks();
}
if (animate != null) {
animate.setTicks();
}
}
public boolean subCheckTicks(DataRenderer r, DataDisplayLink link) {
boolean flag = false;
if (animationSet != null) {
flag |= animationSet.checkTicks(r, link);
}
if (animate != null) {
flag |= animate.checkTicks(r, link);
}
return flag;
}
public boolean subPeekTicks(DataRenderer r, DataDisplayLink link) {
boolean flag = false;
if (animationSet != null) {
flag |= animationSet.peekTicks(r, link);
}
if (animate != null) {
flag |= animate.peekTicks(r, link);
}
return flag;
}
public void subResetTicks() {
if (animationSet != null) {
animationSet.resetTicks();
}
if (animate != null) {
animate.resetTicks();
}
}
/** get a String that can be used to reconstruct this
AnimationControl later */
public String getSaveString() {
int numSteps;
long[] steps;
if (stepValues == null) {
numSteps = 1;
steps = new long[1];
steps[0] = 500;
}
else {
numSteps = stepValues.length;
steps = stepValues;
}
StringBuffer sb = new StringBuffer(35 + 12 * numSteps);
sb.append(animate != null && animate.getOn());
sb.append(' ');
sb.append(direction);
sb.append(' ');
sb.append(current);
sb.append(' ');
sb.append(numSteps);
for (int i=0; i<numSteps; i++) {
sb.append(' ');
sb.append((int) steps[i]);
}
sb.append(' ');
sb.append(computeSet);
return sb.toString();
}
/** reconstruct this AnimationControl using the specified save string */
public void setSaveString(String save)
throws VisADException, RemoteException
{
if (save == null) throw new VisADException("Invalid save string");
StringTokenizer st = new StringTokenizer(save);
int numTokens = st.countTokens();
if (numTokens < 4) throw new VisADException("Invalid save string");
// get animation settings
boolean on = Convert.getBoolean(st.nextToken());
boolean dir = Convert.getBoolean(st.nextToken());
int cur = Convert.getInt(st.nextToken());
int numSteps = Convert.getInt(st.nextToken());
if (numSteps <= 0) {
throw new VisADException("Number of steps is not positive");
}
if (numTokens < 4 + numSteps) {
throw new VisADException("Not enough step entries");
}
int[] steps = new int[numSteps];
for (int i=0; i<numSteps; i++) {
steps[i] = Convert.getInt(st.nextToken());
if (steps[i] <= 0) {
throw new VisADException("Step #" + (i + 1) + "is not positive");
}
}
boolean cs = st.hasMoreTokens() ? Convert.getBoolean(st.nextToken()) : getComputeSet();
// set values
setOn(on);
setDirection(dir);
setSteps(steps);
setCurrent(cur);
setComputeSet(cs);
}
/** copy the state of a remote control to this control */
public void syncControl(Control rmt)
throws VisADException
{
if (rmt == null) {
throw new VisADException("Cannot synchronize " + getClass().getName() +
" with null Control object");
}
if (!(rmt instanceof AnimationControlJ2D)) {
throw new VisADException("Cannot synchronize " + getClass().getName() +
" with " + rmt.getClass().getName());
}
AnimationControlJ2D ac = (AnimationControlJ2D )rmt;
boolean changed = false;
/* *** DON'T TRY TO SYNC CURRENT FRAME!!! *** */
// if (current != ac.current) {
// changed = true;
// if (animationSet != null) {
// current = animationSet.getIndex(ac.current);
// canvas.renderTrigger();
// } else {
// current = 0;
// }
// }
if (direction != ac.direction) {
changed = true;
direction = ac.direction;
}
if (animate != ac.animate) {
changed = true;
animate = ac.animate;
}
if (real != ac.real) {
changed = true;
real = ac.real;
}
if (no_tick != ac.no_tick) {
changed = true;
no_tick = ac.no_tick;
}
if (computeSet != ac.computeSet) {
changed = true;
computeSet = ac.computeSet;
}
if (changed) {
try {
// WLH 5 May 2000
// changeControl(true);
changeControl(false);
} catch (RemoteException re) {
throw new VisADException("Could not indicate that control" +
" changed: " + re.getMessage());
}
}
}
public boolean equals(Object o)
{
if (!super.equals(o)) {
return false;
}
AnimationControlJ2D ac = (AnimationControlJ2D )o;
/**** IGNORE FRAME POSITION ****/
// if (current != ac.current) {
// return false;
// }
if (direction != ac.direction) {
return false;
}
if (animate != ac.animate) {
return false;
}
if (real != ac.real) {
return false;
}
if (no_tick != ac.no_tick) {
return false;
}
if (computeSet != ac.computeSet) {
return false;
}
return true;
}
public String toString() {
return "AnimationControlJ2D: current = " + current +
" set = " + animationSet.getSet();
}
/**
* Set the flag to automatically compute the animation set if it is
* null
* @param compute false to allow application to control set computation
* if set is null.
*/
public void setComputeSet(boolean compute) {
computeSet = compute;
}
/**
* Get the flag to automatically compute the animation set if it is
* null
*
* @return true if should compute
*/
public boolean getComputeSet() {
return computeSet;
}
}