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}