001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.math;
020
021import java.awt.event.ActionEvent;
022import java.awt.event.ActionListener;
023import java.util.ArrayList;
024
025import javax.swing.Timer;
026
027/**
028 * @author Stephane
029 */
030public class SmoothMover implements ActionListener
031{
032    public static interface SmoothMoverListener
033    {
034        public void moveStarted(SmoothMover source, double start, double end);
035
036        public void moveModified(SmoothMover source, double start, double end);
037
038        public void moveEnded(SmoothMover source, double value);
039
040        public void valueChanged(SmoothMover source, double newValue, int pourcent);
041    }
042
043    public static class SmoothMoverAdapter implements SmoothMoverListener
044    {
045        @Override
046        public void moveStarted(SmoothMover source, double start, double end)
047        {
048        }
049
050        @Override
051        public void moveModified(SmoothMover source, double start, double end)
052        {
053        }
054
055        @Override
056        public void moveEnded(SmoothMover source, double value)
057        {
058        }
059
060        @Override
061        public void valueChanged(SmoothMover source, double newValue, int pourcent)
062        {
063        }
064    }
065
066    public enum SmoothMoveType
067    {
068        NONE, LINEAR, LOG, EXP
069    };
070
071    /**
072     * current value
073     */
074    protected double currentValue;
075    /**
076     * smooth movement type
077     */
078    protected SmoothMoveType type;
079    /**
080     * time to do move (in ms)
081     */
082    protected int moveTime;
083
084    /**
085     * internals
086     */
087    protected final Timer timer;
088    protected double destValue;
089    protected double[] stepValues;
090    // private int stepIndex;
091    protected long startTime;
092    protected final ArrayList<SmoothMoverListener> listeners;
093
094    public SmoothMover(double initValue, SmoothMoveType type)
095    {
096        super();
097
098        currentValue = initValue;
099        destValue = initValue;
100
101        this.type = type;
102        // 60 updates per second by default
103        timer = new Timer(1000 / 60, this);
104        // no initial delay
105        timer.setInitialDelay(0);
106        timer.setRepeats(true);
107        // default : 1 second to reach destination
108        moveTime = 1000;
109        // default
110        stepValues = new double[0];
111
112        listeners = new ArrayList<SmoothMoverListener>();
113    }
114
115    public SmoothMover(double initValue)
116    {
117        this(initValue, SmoothMoveType.LINEAR);
118    }
119
120    /**
121     * Move to specified values v
122     */
123    public void moveTo(double value)
124    {
125        if (destValue != value)
126        {
127            destValue = value;
128            // start movement
129            start();
130        }
131    }
132
133    public boolean isMoving()
134    {
135        return timer.isRunning();
136    }
137
138    protected void start()
139    {
140        // number of step to reach final value
141        final int size = Math.max(moveTime / timer.getDelay(), 1);
142
143        // calculate interpolation
144        switch (type)
145        {
146            case NONE:
147                stepValues = new double[2];
148                stepValues[0] = currentValue;
149                stepValues[1] = destValue;
150                break;
151
152            case LINEAR:
153                stepValues = Interpolator.doLinearInterpolation(currentValue, destValue, size);
154                break;
155
156            case LOG:
157                stepValues = Interpolator.doLogInterpolation(currentValue, destValue, size);
158                break;
159
160            case EXP:
161                stepValues = Interpolator.doExpInterpolation(currentValue, destValue, size);
162                break;
163        }
164
165        // initialize index to 1 (ignore value 0 which is current value)
166        // stepIndex = 1;
167
168        if (!isMoving())
169        {
170            // notify and start
171            moveStarted();
172            timer.start();
173        }
174        else
175        {
176            // update current value
177            // updateCurrentValue();
178            // notify and restart
179            moveModified();
180            // timer.restart();
181        }
182    }
183
184    public void stop()
185    {
186        if (isMoving())
187        {
188            // stop and notify
189            timer.stop();
190            moveEnded();
191        }
192    }
193
194    /**
195     * Shutdown the mover object (this actually stop internal timer and remove all listeners)
196     */
197    public void shutDown()
198    {
199        timer.stop();
200        timer.removeActionListener(this);
201        listeners.clear();
202    }
203
204    /**
205     * @return the update delay (in ms)
206     */
207    public int getUpdateDelay()
208    {
209        return timer.getDelay();
210    }
211
212    /**
213     * @param updateDelay
214     *        the update delay (in ms) to set
215     */
216    public void setUpdateDelay(int updateDelay)
217    {
218        timer.setDelay(updateDelay);
219    }
220
221    /**
222     * @return the smooth type
223     */
224    public SmoothMoveType getType()
225    {
226        return type;
227    }
228
229    /**
230     * @param type
231     *        the smooth type to set
232     */
233    public void setType(SmoothMoveType type)
234    {
235        this.type = type;
236    }
237
238    /**
239     * @return the moveTime
240     */
241    public int getMoveTime()
242    {
243        return moveTime;
244    }
245
246    /**
247     * @param moveTime
248     *        the moveTime to set
249     */
250    public void setMoveTime(int moveTime)
251    {
252        // can't be < 1
253        this.moveTime = Math.max(moveTime, 1);
254    }
255
256    /**
257     * Immediately set the value
258     */
259    public void setValue(double value)
260    {
261        // stop current movement
262        stop();
263        // directly set value
264        destValue = value;
265        setCurrentValue(value, 100);
266    }
267
268    /**
269     * @return the value
270     */
271    public double getValue()
272    {
273        return currentValue;
274    }
275
276    /**
277     * @return the destValue
278     */
279    public double getDestValue()
280    {
281        return destValue;
282    }
283
284    public void addListener(SmoothMoverListener listener)
285    {
286        listeners.add(listener);
287    }
288
289    public void removeListener(SmoothMoverListener listener)
290    {
291        listeners.remove(listener);
292    }
293
294    /**
295     * Move started event
296     */
297    protected void moveStarted()
298    {
299        startTime = System.currentTimeMillis();
300
301        for (SmoothMoverListener listener : listeners)
302            listener.moveStarted(this, currentValue, destValue);
303    }
304
305    /**
306     * Move modified event
307     */
308    protected void moveModified()
309    {
310        startTime = System.currentTimeMillis();
311
312        for (SmoothMoverListener listener : listeners)
313            listener.moveModified(this, currentValue, destValue);
314    }
315
316    /**
317     * Move ended event
318     */
319    protected void moveEnded()
320    {
321        for (SmoothMoverListener listener : listeners)
322            listener.moveEnded(this, currentValue);
323    }
324
325    /**
326     * update current value from elapsed time
327     */
328    protected void updateCurrentValue()
329    {
330        final int elapsedMsTime = (int) (System.currentTimeMillis() - startTime);
331
332        // move completed ?
333        if ((type == SmoothMoveType.NONE) || (elapsedMsTime >= moveTime))
334        {
335            setCurrentValue(destValue, 100);
336            // stop
337            stop();
338        }
339        else
340        {
341            final int len = stepValues.length;
342            final int ind = Math.min((elapsedMsTime * len) / moveTime, len - 2);
343            // set value
344            setCurrentValue(stepValues[ind + 1], (elapsedMsTime * 100) / moveTime);
345        }
346    }
347
348    protected void setCurrentValue(double value, int pourcent)
349    {
350        if (currentValue != value)
351        {
352            currentValue = value;
353            // notify value changed
354            changed(value, pourcent);
355        }
356    }
357
358    /**
359     * Value changed event.
360     */
361    protected void changed(double newValue, int pourcent)
362    {
363        for (SmoothMoverListener listener : listeners)
364            listener.valueChanged(this, newValue, pourcent);
365    }
366
367    @Override
368    public void actionPerformed(ActionEvent e)
369    {
370        // process only if timer running
371        if (isMoving())
372            updateCurrentValue();
373    }
374}