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 icy.math.SmoothMover.SmoothMoveType;
022
023import java.awt.event.ActionEvent;
024import java.awt.event.ActionListener;
025import java.util.ArrayList;
026
027import javax.swing.Timer;
028
029/**
030 * @author Stephane
031 */
032public class MultiSmoothMover implements ActionListener
033{
034    public static interface MultiSmoothMoverListener
035    {
036        public void moveStarted(MultiSmoothMover source, int index, double start, double end);
037
038        public void moveModified(MultiSmoothMover source, int index, double start, double end);
039
040        public void moveEnded(MultiSmoothMover source, int index, double value);
041
042        public void valueChanged(MultiSmoothMover source, int index, double newValue, int pourcent);
043    }
044
045    public static class MultiSmoothMoverAdapter implements MultiSmoothMoverListener
046    {
047        @Override
048        public void moveStarted(MultiSmoothMover source, int index, double start, double end)
049        {
050        }
051
052        @Override
053        public void moveModified(MultiSmoothMover source, int index, double start, double end)
054        {
055        }
056
057        @Override
058        public void moveEnded(MultiSmoothMover source, int index, double value)
059        {
060        }
061
062        @Override
063        public void valueChanged(MultiSmoothMover source, int index, double newValue, int pourcent)
064        {
065        }
066    }
067
068    /**
069     * current value
070     */
071    protected double[] currentValues;
072    /**
073     * smooth movement type
074     */
075    protected SmoothMoveType type;
076    /**
077     * time to do move (in ms)
078     */
079    protected int moveTime;
080
081    /**
082     * internals
083     */
084    protected final Timer timer;
085    protected boolean[] moving;
086    protected double[] destValues;
087    protected double[][] stepValues;
088    // private int stepIndex;
089    protected long[] startTime;
090    protected final ArrayList<MultiSmoothMoverListener> listeners;
091
092    public MultiSmoothMover(int size, SmoothMoveType type)
093    {
094        super();
095
096        currentValues = new double[size];
097        moving = new boolean[size];
098        destValues = new double[size];
099        startTime = new long[size];
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        // timer always running here
108        timer.start();
109        // default : 1 second to reach destination
110        moveTime = 1000;
111        // default
112        stepValues = new double[size][0];
113
114        listeners = new ArrayList<MultiSmoothMoverListener>();
115    }
116
117    public MultiSmoothMover(int size)
118    {
119        this(size, SmoothMoveType.LINEAR);
120    }
121
122    /**
123     * Move the value at specified index to 'value'
124     */
125    public void moveTo(int index, double value)
126    {
127        if (destValues[index] != value)
128        {
129            destValues[index] = value;
130            // start movement
131            start(index, System.currentTimeMillis());
132        }
133    }
134
135    /**
136     * Move all values
137     */
138    public void moveTo(double[] values)
139    {
140        final int maxInd = Math.min(values.length, destValues.length);
141
142        // first we check we have at least one value which had changed
143        boolean changed = false;
144        for (int index = 0; index < maxInd; index++)
145        {
146            if (destValues[index] != values[index])
147            {
148                changed = true;
149                break;
150            }
151        }
152
153        // value changed ?
154        if (changed)
155        {
156            // better synchronization for multiple changes
157            final long time = System.currentTimeMillis();
158
159            for (int index = 0; index < maxInd; index++)
160            {
161                destValues[index] = values[index];
162                // start movement
163                start(index, time);
164            }
165        }
166    }
167
168    public boolean isMoving(int index)
169    {
170        return moving[index];
171    }
172
173    public boolean isMoving()
174    {
175        for (boolean b : moving)
176            if (b)
177                return true;
178
179        return false;
180    }
181
182    protected void start(int index, long time)
183    {
184        final double current = currentValues[index];
185        final double dest = destValues[index];
186
187        // number of step to reach final value
188        final int size = Math.max(moveTime / timer.getDelay(), 1);
189
190        // calculate interpolation
191        switch (type)
192        {
193            case NONE:
194                stepValues[index] = new double[2];
195                stepValues[index][0] = current;
196                stepValues[index][1] = dest;
197                break;
198
199            case LINEAR:
200                stepValues[index] = Interpolator.doLinearInterpolation(current, dest, size);
201                break;
202
203            case LOG:
204                stepValues[index] = Interpolator.doLogInterpolation(current, dest, size);
205                break;
206
207            case EXP:
208                stepValues[index] = Interpolator.doExpInterpolation(current, dest, size);
209                break;
210        }
211
212        // notify and start
213        if (!isMoving(index))
214        {
215            moveStarted(index, time);
216            moving[index] = true;
217        }
218        else
219            moveModified(index, time);
220    }
221
222    /**
223     * Stop specified index
224     */
225    public void stop(int index)
226    {
227        // stop and notify
228        if (isMoving(index))
229        {
230            moving[index] = false;
231            moveEnded(index);
232        }
233    }
234
235    /**
236     * Stop all
237     */
238    public void stopAll()
239    {
240        // stop all
241        for (int index = 0; index < moving.length; index++)
242            if (moving[index])
243                moveEnded(index);
244    }
245
246    /**
247     * Shutdown the mover object (this actually stop internal timer and remove all listeners)
248     */
249    public void shutDown()
250    {
251        timer.stop();
252        timer.removeActionListener(this);
253        listeners.clear();
254    }
255
256    /**
257     * @return the update delay (in ms)
258     */
259    public int getUpdateDelay()
260    {
261        return timer.getDelay();
262    }
263
264    /**
265     * @param updateDelay
266     *        the update delay (in ms) to set
267     */
268    public void setUpdateDelay(int updateDelay)
269    {
270        timer.setDelay(updateDelay);
271    }
272
273    /**
274     * @return the smooth type
275     */
276    public SmoothMoveType getType()
277    {
278        return type;
279    }
280
281    /**
282     * @param type
283     *        the smooth type to set
284     */
285    public void setType(SmoothMoveType type)
286    {
287        this.type = type;
288    }
289
290    /**
291     * @return the moveTime
292     */
293    public int getMoveTime()
294    {
295        return moveTime;
296    }
297
298    /**
299     * @param moveTime
300     *        the moveTime to set
301     */
302    public void setMoveTime(int moveTime)
303    {
304        // can't be < 1
305        this.moveTime = Math.max(moveTime, 1);
306    }
307
308    /**
309     * Immediately set the value
310     */
311    public void setValue(int index, double value)
312    {
313        // stop current movement
314        stop(index);
315        // directly set value
316        destValues[index] = value;
317        setCurrentValue(index, value, 100);
318    }
319
320    /**
321     * Immediately set all values
322     */
323    public void setValues(double[] values)
324    {
325        final int maxInd = Math.min(values.length, destValues.length);
326
327        for (int index = 0; index < maxInd; index++)
328        {
329            final double value = values[index];
330            // stop current movement
331            stop(index);
332            // directly set value
333            destValues[index] = value;
334            setCurrentValue(index, value, 100);
335        }
336    }
337
338    /**
339     * @return the value
340     */
341    public double getValue(int index)
342    {
343        return currentValues[index];
344    }
345
346    /**
347     * @return the destValue
348     */
349    public double getDestValue(int index)
350    {
351        return destValues[index];
352    }
353
354    /**
355     * update current value from elapsed time
356     */
357    protected void updateCurrentValue(int index, long time)
358    {
359        final int elapsedMsTime = (int) (time - startTime[index]);
360
361        // move completed ?
362        if ((type == SmoothMoveType.NONE) || (elapsedMsTime >= moveTime))
363        {
364            setCurrentValue(index, destValues[index], 100);
365            // stop
366            stop(index);
367        }
368        else
369        {
370            final int len = stepValues[index].length;
371            final int ind = Math.min((elapsedMsTime * len) / moveTime, len - 2) + 1;
372
373            // set value
374            if ((ind >= 0) && (ind < stepValues[index].length))
375                setCurrentValue(index, stepValues[index][ind], (elapsedMsTime * 100) / moveTime);
376        }
377    }
378
379    protected void setCurrentValue(int index, double value, int pourcent)
380    {
381        if (currentValues[index] != value)
382        {
383            currentValues[index] = value;
384            // notify value changed
385            changed(index, value, pourcent);
386        }
387    }
388
389    public void addListener(MultiSmoothMoverListener listener)
390    {
391        listeners.add(listener);
392    }
393
394    public void removeListener(MultiSmoothMoverListener listener)
395    {
396        listeners.remove(listener);
397    }
398
399    /**
400     * Move started event
401     */
402    protected void moveStarted(int index, long time)
403    {
404        startTime[index] = time;
405
406        for (MultiSmoothMoverListener listener : listeners)
407            listener.moveStarted(this, index, currentValues[index], destValues[index]);
408    }
409
410    /**
411     * Move modified event.
412     */
413    protected void moveModified(int index, long time)
414    {
415        startTime[index] = time;
416
417        for (MultiSmoothMoverListener listener : listeners)
418            listener.moveModified(this, index, currentValues[index], destValues[index]);
419    }
420
421    /**
422     * Move ended event.
423     */
424    protected void moveEnded(int index)
425    {
426        for (MultiSmoothMoverListener listener : listeners)
427            listener.moveEnded(this, index, currentValues[index]);
428    }
429
430    /**
431     * Value changed event.
432     */
433    protected void changed(int index, double newValue, int pourcent)
434    {
435        for (MultiSmoothMoverListener listener : listeners)
436            listener.valueChanged(this, index, newValue, pourcent);
437    }
438
439    @Override
440    public void actionPerformed(ActionEvent e)
441    {
442        // better synchronization for multiple changes
443        final long time = System.currentTimeMillis();
444
445        // process only moving values
446        for (int index = 0; index < moving.length; index++)
447            if (moving[index])
448                updateCurrentValue(index, time);
449    }
450
451}