/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Icy 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.math;
import icy.math.SmoothMover.SmoothMoveType;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.Timer;
/**
* @author Stephane
*/
public class MultiSmoothMover implements ActionListener
{
public static interface MultiSmoothMoverListener
{
public void moveStarted(MultiSmoothMover source, int index, double start, double end);
public void moveModified(MultiSmoothMover source, int index, double start, double end);
public void moveEnded(MultiSmoothMover source, int index, double value);
public void valueChanged(MultiSmoothMover source, int index, double newValue, int pourcent);
}
public static class MultiSmoothMoverAdapter implements MultiSmoothMoverListener
{
@Override
public void moveStarted(MultiSmoothMover source, int index, double start, double end)
{
}
@Override
public void moveModified(MultiSmoothMover source, int index, double start, double end)
{
}
@Override
public void moveEnded(MultiSmoothMover source, int index, double value)
{
}
@Override
public void valueChanged(MultiSmoothMover source, int index, double newValue, int pourcent)
{
}
}
/**
* current value
*/
protected double[] currentValues;
/**
* smooth movement type
*/
protected SmoothMoveType type;
/**
* time to do move (in ms)
*/
protected int moveTime;
/**
* internals
*/
protected final Timer timer;
protected boolean[] moving;
protected double[] destValues;
protected double[][] stepValues;
// private int stepIndex;
protected long[] startTime;
protected final ArrayList<MultiSmoothMoverListener> listeners;
public MultiSmoothMover(int size, SmoothMoveType type)
{
super();
currentValues = new double[size];
moving = new boolean[size];
destValues = new double[size];
startTime = new long[size];
this.type = type;
// 60 updates per second by default
timer = new Timer(1000 / 60, this);
// no initial delay
timer.setInitialDelay(0);
timer.setRepeats(true);
// timer always running here
timer.start();
// default : 1 second to reach destination
moveTime = 1000;
// default
stepValues = new double[size][0];
listeners = new ArrayList<MultiSmoothMoverListener>();
}
public MultiSmoothMover(int size)
{
this(size, SmoothMoveType.LINEAR);
}
/**
* Move the value at specified index to 'value'
*/
public void moveTo(int index, double value)
{
if (destValues[index] != value)
{
destValues[index] = value;
// start movement
start(index, System.currentTimeMillis());
}
}
/**
* Move all values
*/
public void moveTo(double[] values)
{
final int maxInd = Math.min(values.length, destValues.length);
// first we check we have at least one value which had changed
boolean changed = false;
for (int index = 0; index < maxInd; index++)
{
if (destValues[index] != values[index])
{
changed = true;
break;
}
}
// value changed ?
if (changed)
{
// better synchronization for multiple changes
final long time = System.currentTimeMillis();
for (int index = 0; index < maxInd; index++)
{
destValues[index] = values[index];
// start movement
start(index, time);
}
}
}
public boolean isMoving(int index)
{
return moving[index];
}
public boolean isMoving()
{
for (boolean b : moving)
if (b)
return true;
return false;
}
protected void start(int index, long time)
{
final double current = currentValues[index];
final double dest = destValues[index];
// number of step to reach final value
final int size = Math.max(moveTime / timer.getDelay(), 1);
// calculate interpolation
switch (type)
{
case NONE:
stepValues[index] = new double[2];
stepValues[index][0] = current;
stepValues[index][1] = dest;
break;
case LINEAR:
stepValues[index] = Interpolator.doLinearInterpolation(current, dest, size);
break;
case LOG:
stepValues[index] = Interpolator.doLogInterpolation(current, dest, size);
break;
case EXP:
stepValues[index] = Interpolator.doExpInterpolation(current, dest, size);
break;
}
// notify and start
if (!isMoving(index))
{
moveStarted(index, time);
moving[index] = true;
}
else
moveModified(index, time);
}
/**
* Stop specified index
*/
public void stop(int index)
{
// stop and notify
if (isMoving(index))
{
moving[index] = false;
moveEnded(index);
}
}
/**
* Stop all
*/
public void stopAll()
{
// stop all
for (int index = 0; index < moving.length; index++)
if (moving[index])
moveEnded(index);
}
/**
* Shutdown the mover object (this actually stop internal timer and remove all listeners)
*/
public void shutDown()
{
timer.stop();
timer.removeActionListener(this);
listeners.clear();
}
/**
* @return the update delay (in ms)
*/
public int getUpdateDelay()
{
return timer.getDelay();
}
/**
* @param updateDelay
* the update delay (in ms) to set
*/
public void setUpdateDelay(int updateDelay)
{
timer.setDelay(updateDelay);
}
/**
* @return the smooth type
*/
public SmoothMoveType getType()
{
return type;
}
/**
* @param type
* the smooth type to set
*/
public void setType(SmoothMoveType type)
{
this.type = type;
}
/**
* @return the moveTime
*/
public int getMoveTime()
{
return moveTime;
}
/**
* @param moveTime
* the moveTime to set
*/
public void setMoveTime(int moveTime)
{
// can't be < 1
this.moveTime = Math.max(moveTime, 1);
}
/**
* Immediately set the value
*/
public void setValue(int index, double value)
{
// stop current movement
stop(index);
// directly set value
destValues[index] = value;
setCurrentValue(index, value, 100);
}
/**
* Immediately set all values
*/
public void setValues(double[] values)
{
final int maxInd = Math.min(values.length, destValues.length);
for (int index = 0; index < maxInd; index++)
{
final double value = values[index];
// stop current movement
stop(index);
// directly set value
destValues[index] = value;
setCurrentValue(index, value, 100);
}
}
/**
* @return the value
*/
public double getValue(int index)
{
return currentValues[index];
}
/**
* @return the destValue
*/
public double getDestValue(int index)
{
return destValues[index];
}
/**
* update current value from elapsed time
*/
protected void updateCurrentValue(int index, long time)
{
final int elapsedMsTime = (int) (time - startTime[index]);
// move completed ?
if ((type == SmoothMoveType.NONE) || (elapsedMsTime >= moveTime))
{
setCurrentValue(index, destValues[index], 100);
// stop
stop(index);
}
else
{
final int len = stepValues[index].length;
final int ind = Math.min((elapsedMsTime * len) / moveTime, len - 2) + 1;
// set value
if ((ind >= 0) && (ind < stepValues[index].length))
setCurrentValue(index, stepValues[index][ind], (elapsedMsTime * 100) / moveTime);
}
}
protected void setCurrentValue(int index, double value, int pourcent)
{
if (currentValues[index] != value)
{
currentValues[index] = value;
// notify value changed
changed(index, value, pourcent);
}
}
public void addListener(MultiSmoothMoverListener listener)
{
listeners.add(listener);
}
public void removeListener(MultiSmoothMoverListener listener)
{
listeners.remove(listener);
}
/**
* Move started event
*/
protected void moveStarted(int index, long time)
{
startTime[index] = time;
for (MultiSmoothMoverListener listener : listeners)
listener.moveStarted(this, index, currentValues[index], destValues[index]);
}
/**
* Move modified event.
*/
protected void moveModified(int index, long time)
{
startTime[index] = time;
for (MultiSmoothMoverListener listener : listeners)
listener.moveModified(this, index, currentValues[index], destValues[index]);
}
/**
* Move ended event.
*/
protected void moveEnded(int index)
{
for (MultiSmoothMoverListener listener : listeners)
listener.moveEnded(this, index, currentValues[index]);
}
/**
* Value changed event.
*/
protected void changed(int index, double newValue, int pourcent)
{
for (MultiSmoothMoverListener listener : listeners)
listener.valueChanged(this, index, newValue, pourcent);
}
@Override
public void actionPerformed(ActionEvent e)
{
// better synchronization for multiple changes
final long time = System.currentTimeMillis();
// process only moving values
for (int index = 0; index < moving.length; index++)
if (moving[index])
updateCurrentValue(index, time);
}
}