/*******************************************************************************
* Copyright (c) 2010-2015 Henshin developers. All rights reserved.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* TU Berlin, University of Luxembourg, SES S.A.
*******************************************************************************/
package de.tub.tfs.muvitor.animation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.draw2d.Animation;
import org.eclipse.draw2d.Animator;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
/**
* This is the main class users have to use in this GEF animation package. It
* implements a {@link Command} that can be given information about how some
* model elements (EMF {@link EObject}s) should be animated that are visualized
* as {@link IFigure}s in {@link GraphicalViewer} s. For this, you specify
* stepwise absolute ({@link Point}s) or relative (model elements) locations and
* optional size factors for the model elements to be animated as described
* below in detail.
* <hr/>
*
* <h3>Why not use GEF's animation mechanism?</h3>
* <p>
* The GEF already contains the classes {@link Animation} and {@link Animator}
* that can be used to animate figures. The main advantage of using this package
* is that you can specify more powerful animations more flexibly. More
* precisely, in addition to GEF you get this:
* <ul>
* <li>You can specify <b>which model</b> to animate instead of the model's
* figure, which will be determined automatically at animation runtime. This
* means that you can specify animations independently from actual viewers.</li>
* <li>The stepwise definition allows you to <b>animate several elements
* independently</b> along complex paths with flexible timing/speed (by
* interpolating intermediate steps).</li>
* <li>Each animated figure can be <b>zoomed smoothly on its path</b> according
* to absolute size factors. Each figure resizes independently of all other
* (possibly animated) figures in the same viewer!</li>
* <li>You can easily specify <b>parallel animations</b> in several viewers.</li>
* <li>AnimatingCommand will automatically take care of <b>reversing animations
* for undo operations</b>.</li>
* <li>With {@link AnimationPathModifier}s you have a convenient way to <b>alter
* the animation paths</b> to nice curves.</li>
* </ul>
* <hr/>
*
* <h3>Stepwise animation definition - an example</h3>
* To understand the principle of stepwise animation definition, we discuss the
* example time schedule matrix below. It represents the definition of an
* animation of three arbitrary model elements (<i>element1-3</i>). We have a
* column for each element to be animated and a row for each defined animation
* step, each with a positive integer value <i>d1-3</i> representing the
* duration of this step in msecs (except for the first, which is the starting
* position row):
* <table cellspacing="0" cellpadding="3" border="1">
* <tr>
* <td></td>
* <td><b>element1</b></td>
* <td><b>element2</b></td>
* <td><b>element3</b></td>
* </tr>
* <tr>
* <td><b>start</b></td>
* <td>element1</td>
* <td>Point(0,0)</td>
* <td>-/-</td>
* </tr>
* <tr>
* <td>d1</td>
* <td>element2</td>
* <td>-/-</td>
* <td>element1</td>
* </tr>
* <tr>
* <td>d2</td>
* <td>element3</td>
* <td>-/-</td>
* <td>element2</td>
* </tr>
* <tr>
* <td>d3</td>
* <td>element1</td>
* <td>Point(100,100)</td>
* <td>-/-</td>
* </tr>
* </table>
* <p>
* First, you have to understand what the performed animation will look like.
* After that you shall see how you can define this animation by calling the
* appropriate methods.
* <p>
* In short, each table entry tells when (after the sum of durations till this
* step) the figure of the column's element has to be where (absolute or
* relative location). For example, element1 is supposed to start at its own
* position, to move in d1 msecs to the position of element2, after that to move
* in d2 msecs to the position of element3, and to return in d3 seconds back to
* its initial position.<br>
* Note that when specifying relative locations, they will be resolved to the
* absolute locations that the related figure has <i>before the animation</i>!
* Thus, element1's figure will move to the locations of the figures of element2
* and element3 that they have before the animation, even if these will be
* animated themselves in this particular animation. <br>
* Of course, only those elements will be animated, for which a figure (being
* related to this element by some {@link GraphicalEditPart}) can be found.
* There is no need for element1-3 having figures in <i>the same</i> viewer at
* command-execution nor command-definition time, but only figures in open
* viewers that exist at execution-time will be animated. AnimatingCommand will
* not regard it as an error if no figure can be found for an element at all!
* <p>
* Let's continue with element2 to understand step interpolation: As you can see
* in the table, only an absolute starting position and a position after the
* third step has been specified. Generally, undefined steps between defined
* steps will be interpolated linearly. So, after d1 msecs element2's figure
* would reach the point (33,33), after d1+d2 msecs (66,66), and finally
* (100,100).<br>
* Note that an animation of a particular figure always starts at the first
* specified step and ends at the last specified step. Furthermore, this could
* lead to unexpected behavior if you specify a relative location by some other
* model element for which no figure location can be resolved! If no start or
* end location for the whole path of a model's figure can be resolved or
* interpolated, it will not be animated at all, but also no error message will
* be raised.
* <p>
* How element3's figure would be animated should be obvious by now. So let's
* see how we can specify this animation in some editor action. Suppose we have
* a container model element <code>parent</code>, which is the contents of the
* viewer that we expect to show, and we want to animate the figures for
* element1-3. <b>This viewer must implement {@link IGraphicalViewerProvider}
* (have a look there for an example) to be found by AnimatingCommand.</b> In
* addition we may have a {@link CompoundCommand} that manipulates our model
* before and possibly after the animation. The following shows the right code
* for this scenario (we will discuss another possibility for special purposes
* after that as well):<br>
*
* <pre>
* AnimatingCommand aniComm = new AnimatingCommand();
* // initialize objects for animation
* aniComm.initializeAnimatedElement(element1, parent, null);
* aniComm.initializeAnimatedElement(element2, parent, null);
* // specify starting position
* aniComm.specifyStep(element1, element1);
* aniComm.specifyStep(element2, new Point(0, 0));
* // finish "starting step" definition, declare step with duration d1
* aniComm.nextStep(d1);
* // specify step 1
* aniComm.specifyStep(element1, element2);
* aniComm.initializeAnimatedElement(element3, parent, null);
* aniComm.specifyStep(element3, element1);
* // finish definition of step 1, declare step with duration d2
* aniComm.nextStep(d2);
* // specify step 2
* aniComm.specifyStep(element1, element3);
* aniComm.specifyStep(element3, element2);
* // finish definition of step 2, declare step with duration d3
* aniComm.nextStep(d3);
* // specify step 3
* aniComm.specifyStep(element1, element1);
* aniComm.specifyStep(element2, new Point(100, 100));
* // queue the animating command in the compound command
* compoundCommand.add(aniComm);
* </pre>
*
* In this short code snippet, we first create a plain AnimatingCommand and
* declare element1 and element2 as to be animated in the viewer that has
* <code>parent</code> as contents. This must be done for an element before
* specifying any steps for it. We could have set some
* {@link AnimationPathModifier}s instead of the last <code>null</code>
* argument, but this is not mandatory.<br>
* Now, look at the matrix table above again: In the first step (which you have
* to imagine always as happening with the duration 0msec) we specify element1's
* and element2's figures to be at the location of the figure of element1 itself
* and at Point(0,0), respectively.<br>
* Note that we have not initialized element3 so far. We could have done so,
* though. It is not important in which step you initialize an element, as this
* does not change the animation at all, because AnimatingCommand will fill the
* specification for this element up to the current step with place holders. It
* is important that you must do this <i>sometime before</i> you call
* <code>specifyStep(...)</code> for this element!<br>
* With this we have completed the specification of the first step and can
* proceed to the next step by calling <code>nextStep(d1)</code>, which states
* that the next step should take <code>d1</code> msecs to complete.<br>
* The next row in the table says that in this step element1's figure should
* move to the original position of element2's figure, and that element3's
* figure should start its animation at the original position of element1's
* figure. For this we have to initialize element3 belatedly. <br>
* It should be clear by now, how the remaining lines correspond to the table
* entries. Note, that you do not have to finish the last step by calling
* <code>nextStep</code> a fourth time, however you would not know what duration
* to specify for this unused step. <br>
* By adding the AnimatingCommand to the compound command we are done!
*
* <h3>Composite figures as targets</h3>
* You should be aware that in fact not the figure of the target edit part is
* used to resolve the target location of an animation step, but the content
* pane of the target edit part is. <br>
* By default, an edit part returns its figure when asked for its content pane;
* so for simple figures or if you just want the center of the whole composite
* figure to be the target location, you do not have to worry about this. <br>
* But you can make use of this by overriding your
* {@link GraphicalEditPart#getContentPane()} to return a subfigure of your edit
* part's (composite) figure. So, you can let the center of the subfigure be the
* target location of each animation step that is specified with this edit part
* as target!
*
*
* If you have edit parts that are represented as composite figures
*
* <h3>Animations that resize figures on their path</h3>
* Optionally, you may specify a size factor for each step with
* {@link #specifyStep(Object, Object, double)} to highlight some element. The
* size factor is absolute, a value of 1 will set the animated figure to the
* original size (as does the convenience method
* {@link #specifyStep(Object, Object)}). A value of -1 will mark this element's
* size factor as to be interpolated for the current step, similar to the
* locations.
*
* <h3>Modifying the path of an animated figure</h3>
* If the figures' movement along a straight line bores you, you may choose from
* several alternative movements. For this you can call
* {@link #initializeAnimatedElement(Object, Object, AnimationPathModifier)}
* with one of the predefined {@link AnimationPathModifier}s that are accessible
* by static getters. Have a look there for the different available modifiers.
*
* <h3>Switch animations globally</h3>
* Sometimes you might just want to switch off all animations caused by
* AnimatingCommands. For this you just have to call
* {@link #setPerformAnimation(boolean)}. This packages provides a sample
* {@link ToggleAnimationAction}, which can be provided as a button in the
* editor's tool bar that switches this flag.
*
* <h3>Visualize the paths of animated elements (for debugging)</h3>
* With {@link #setDebug(boolean)} you can set a flag that forces the figures to
* mark their way as a red line, which will remain visible in the viewer after
* animation. So after the animation has been performed you can see and examine
* the paths of the figures.
*
* <h3>Initializing at execution time rather at definition time</h3>
* A remark about an slightly different way to define animations (i.e. by
* subclassing): AnimatingCommand features an empty method {@link #initialize()}
* , which will be called in {@link #execute()} and {@link #undo()} before doing
* anything else. With this, you may implement own AnimatingCommands and include
* the element initializations and step definitions in this subclasses by
* putting them in the overridden {@link #initialize()}. <br>
* The main purpose of this approach is to perform initialization at execution
* time rather than command-creation time! For example, consider you have a list
* of elements you want to animate, e.g. all nodes being shown in a viewer (like
* in {@link AnimatingCommand}), and you initialize them dynamically in a loop.
* Now imagine you want to create a complex action with several model changes,
* including creation and deletion of nodes, and moving around all nodes. The
* only way to initialize elements that are newly created in the surrounding
* compound command, is to subclass AnimatingCommand and to put the initializing
* loop in {@link #initialize()} so that all elements in the list are
* initialized <i>just before the animation is executed</i> when the compound
* command already created the new nodes, rather than when the compound command
* and the AnimatingCommand are being created and no model change has happened
* yet. Have a look at AnimatingDemoAction1 for an example.
*
* <h3>Refreshing viewers between animation series</h3>
* A word about refreshing the animated viewers: Depending on what you want to
* animate you may need to explicitly let the animated viewers be flushed before
* or after the animation. To achieve this you can experiment with setting the
* flags{@link #flushBefore} and {@link #flushAfter}. This could be needed
* especially in consecutive animations, though, I can not figure out a general
* rule for all scenarios.
*
*
* @see IGraphicalViewerProvider
* @see AnimationPathModifier
* @see ToggleAnimationAction
* @see AnimatedElement
* @see Localizer
* @see MultipleAnimation
* @author Tony Modica
*/
public class AnimatingCommand extends Command {
/**
* This flag may be used to display an animation in more detail for
* debugging. The paths of the animated figures will be shown as well.
*/
private static boolean debug = false;
/**
* Default duration for a single step is 400 msec.
*/
private static int DEFAULT_DURATION = 400;
private static final String LABEL = "Animating Command";
/**
* This flag may be used to generally disable animations.
*/
private static boolean performAnimation = true;
final static Set<GraphicalViewer> usedViewers = new HashSet<GraphicalViewer>();
/**
* Getter for the {@link #debug} flag.
*
* @return the {@link #debug} flag
*/
final static public boolean isDebug() {
return AnimatingCommand.debug;
}
/**
* Getter for the {@link #performAnimation} flag.
*
* @return the performAnimation
*/
final public static boolean isPerformAnimation() {
return performAnimation;
}
/**
* Setting this flag to <code>true</code> may be used to display an
* animation in more detail for debugging. The paths of the animated figures
* will be shown as well.
*
* @param debug
*/
final static public void setDebug(final boolean debug) {
AnimatingCommand.debug = debug;
}
/**
* @param msecs
*/
public static void setDefaultDuration(final int msecs) {
if (msecs == 0) {
DEFAULT_DURATION = 400;
} else {
DEFAULT_DURATION = msecs;
}
}
/**
* If this is set to <code>false</code> animations will be generally
* disabled until it is reset to <code>true</code>.
*
* @param performAnimation
*/
final static public void setPerformAnimation(final boolean performAnimation) {
AnimatingCommand.performAnimation = performAnimation;
}
/**
* Set this true to force viewers being flushed after this animation. You
* may have to experiment with this and {@link #flushAfter} to get the right
* animation behavior, especially in consecutive animations.
*/
public boolean flushAfter = false;
/**
* Set this true to force viewers being flushed when preparing animated
* elements. You may have to experiment with this and {@link #flushAfter} to
* get the right animation behavior, especially in consecutive animations.
*/
public boolean flushBefore = false;
/**
* A list that contains the absolute durations in msec for the animation
* steps. These are calculated in {@link #calculateDurations()} so that all
* absolute durations in this list will sum up to {@link #completeDuration}
* if set (>-1). Otherwise 1 relative time duration will be interpreted as 1
* {@link #DEFAULT_DURATION} .
*
* This is done in {@link #execute()} and {@link #undo()} once.
* {@link #setCompleteDuration(int)} will update this, but only if it has
* been calculated before, so that the duration can not be extended
* afterwards.
*/
private ArrayList<Integer> absoluteDurations;
/**
* A mapping from the models/animated figures to their
* {@link AnimatedElement}s to enable access to the initial/final
* coordinates by a model/figure.
*
* @see #getInitialLocation(Object)
* @see #getFinalLocation(Object)
*/
private final HashMap<Object, AnimatedElement> animatedElementsMap = new HashMap<Object, AnimatedElement>();
/**
* This stores how long the animation should be in total in msec. If it is
* set to -1 the {@link #DEFAULT_DURATION} will be regarded as length for 1
* relative time duration.
*
* @see #setCompleteDuration(int)
*/
private int completeDuration = -1;
/***************************************************************************
* Command methods
**************************************************************************/
/**
* This flag indicates that {@link #execute()} or {@link #undo()} have been
* called while {@link #performAnimation} was <code>true</code>. It is not
* related to {@link #initialize()} as overwriting this is optional!
*/
private boolean initialized;
/**
* This stores the relative durations that have been specified in
* {@link #nextStep(double)} well as to serve as a counter for the steps
* that have been defined so far.
*/
private final List<Double> relativeDurations = new ArrayList<Double>();
/**
* This is a list of animated elements that have got specifications for the
* current step. This is used to fill up unspecified animated elements with
* interpolated place holder specifications.
*/
private final List<AnimatedElement> specifiedElementsInActualStep = new ArrayList<AnimatedElement>();
/***************************************************************************
* Methods to specify animation
**************************************************************************/
/**
* Constructor, setting default label. The first step's duration is set to 0
* to let it happen immediately.
*/
public AnimatingCommand() {
this(LABEL);
}
/**
* Constructor for a peculiar label. The first step's duration is set to 0
* to let it happen immediately.
*
* @param label
*/
public AnimatingCommand(final String label) {
super(label);
// the first step will be performed immediately
relativeDurations.add(Double.valueOf(0.0));
}
/**
* If {@link #initialized} is true all lists and hash maps are cleared.
*
* @see org.eclipse.gef.commands.Command#dispose()
*/
@Override
final public void dispose() {
if (initialized) {
absoluteDurations.clear();
animatedElementsMap.clear();
relativeDurations.clear();
specifiedElementsInActualStep.clear();
}
}
/**
* Performs the specified animation. Calls the optional
* {@link #initialize()}, calculates the absolute durations, prepares the
* animation, performs the steps and finishes the animation. This may be
* overridden, but ensure to call super.execute()!
*
* @see org.eclipse.gef.commands.Command#execute()
* @see #calculateDurations()
* @see #prepareAnimation(boolean)
* @see #performStep(int, int)
* @see #animationDone()
*/
@Override
public void execute() {
if (performAnimation) {
if (!initialized) {
initialize();
initialized = true;
}
// calculate absolute durations only once
if (absoluteDurations == null) {
absoluteDurations = calculateDurations();
}
prepareAnimation(false);
// perform animation
for (int i = 0; i < getStepCounter(); i++) {
performStep(i, absoluteDurations.get(i).intValue());
}
animationDone();
}
}
/**
* This may be used to get the final bounds of a figure representing the
* model or for the animated figure itself. So the result of an animation
* can be preserved by setting these values via command to the model or
* figure.
*
* @param modelOrFigure
* The model/figure to get the last bounds after this animation
* for.
* @return The actual last bounds in the Localizer path of the
* {@link AnimatedElement} for modelOrFigure. May be
* <code>null</code> if animation (and thus resolving/interpolation)
* has not been performed properly.
*/
final public Point getFinalLocation(final Object modelOrFigure) {
final AnimatedElement animatedElement = animatedElementsMap.get(modelOrFigure);
if (animatedElement != null && animatedElement.finalLocation != null) {
return animatedElement.finalLocation.getCopy();
}
return null;
}
/**
* This may be used to get the initial bounds of a figure representing the
* model or for the animated figure itself. So the result of an animation
* can be preserved by setting these values via command to the model or
* figure and be reset with undo.
*
* @param modelOrFigure
* The model/figure to get the first bounds after this animation
* for.
* @return The cached first bounds in the Localizer path of the
* {@link AnimatedElement} for model. May be <code>null</code> if
* animation (and thus resolving/interpolation) has not been
* performed properly.
*/
final public Point getInitialLocation(final Object modelOrFigure) {
final AnimatedElement animatedElement = animatedElementsMap.get(modelOrFigure);
if (animatedElement != null && animatedElement.initialLocation != null) {
return animatedElement.initialLocation.getCopy();
}
return null;
}
/**
* @return the number of steps that have been defined so far
*/
final public int getStepCounter() {
return relativeDurations.size();
}
/**
* This will be called in {@link #execute()} or {@link #undo()} before the
* animation is performed. May be overwritten to specify animation at
* execution time instead of command creation time.
* <p>
* This is useful e.g. if the models to be animated have not been created
* yet and should be retrieved from some list when this command is being
* executed. So, instead of calling
* {@link #initializeAnimatedElement(Object, Object, AnimationPathModifier)}, {@link #nextStep(double)} and {@link #specifyStep(Object, Object)}
* directly on this command, this could be done here when the models are
* accessible.
* </p>
*/
public void initialize() {
}
/**
* Before specifying any steps for an element to animate, you have to
* initialize it with this method. If you already have specified some steps
* before, the {@link AnimatedElement} for the passed model will be filled
* up with place-holder steps up to the current step.
*
* Convenience method for standard paths.
*
* @param modelOrEditPartorFigure
* can be an {@link GraphicalEditPart} or {@link EObject}
* determining the model whose figure should be animated.
* Alternatively, you may pass a specific {@link IFigure} to be
* animated.
* @param viewerOrTop
* can be an {@link EObject} or {@link EditPartViewer}
* determining the viewer holding a figure of the model
*/
final public void initializeAnimatedElement(final Object modelOrEditPartorFigure,
final Object viewerOrTop) {
initializeAnimatedElement(modelOrEditPartorFigure, viewerOrTop, null);
}
/**
* Before specifying any steps for an element to animate, you have to
* initialize it with this method. If you already have specified some steps
* before, the {@link AnimatedElement} for the passed model will be filled
* up with place-holder steps up to the current step.
*
* @param modelOrEditPartOrFigure
* can be an {@link GraphicalEditPart} or {@link EObject}
* determining the model whose figure should be animated.v
* Alternatively, you may pass a specific {@link IFigure} to be
* animated.
* @param viewerOrTop
* can be an {@link EObject} or {@link EditPartViewer}
* determining the viewer holding a figure of the model
* @param pathModifier
* optional {@link AnimationPathModifier}
*/
final public void initializeAnimatedElement(final Object modelOrEditPartOrFigure,
final Object viewerOrTop, final AnimationPathModifier pathModifier) {
final AnimatedElement newElement = new AnimatedElement(modelOrEditPartOrFigure,
viewerOrTop, pathModifier);
if (newElement.animatedModel != null) {
animatedElementsMap.put(newElement.animatedModel, newElement);
} else if (newElement.animatedFigure != null) {
animatedElementsMap.put(newElement.animatedFigure, newElement);
}
// fill up animated element with place holders till actual step
for (int i = 0; i < getStepCounter() - 1; i++) {
newElement.addPlaceholderStep();
}
}
/**
* Convenience method that calls {@link #nextStep(double)} with a duration
* of 1.0.
*/
final public void nextStep() {
nextStep(1.0);
}
/**
* Completes the actual step definition by filling up the
* {@link AnimatedElement}s that have not been specified for this step with
* a place holder step. The next step is associated with the passed relative
* duration length.
*
* @param relativeDuration
* the relative duration length of the next step
*/
final public void nextStep(final double relativeDuration) {
/*
* fill up initialized animated elements that have not been extended by
* a step specification
*/
final List<AnimatedElement> unspecified = new ArrayList<AnimatedElement>(
animatedElementsMap.values());
unspecified.removeAll(specifiedElementsInActualStep);
for (final AnimatedElement animatedElement : unspecified) {
animatedElement.addPlaceholderStep();
}
// save specified relative duration for next step
if (relativeDuration <= 0) {
throw new IllegalArgumentException(
"A relative duration for a step must be greater than 0!");
}
relativeDurations.add(Double.valueOf(relativeDuration));
// prepare for next step
specifiedElementsInActualStep.clear();
}
/**
* You may use this to specify how long the animation should be in total in
* msec. If it is set to -1 the {@link #DEFAULT_DURATION} will be regarded
* as length for 1 relative time duration. Calling this method causes the
* {@link #absoluteDurations} to be recalculated by
* {@link #calculateDurations()}.
*
* @param duration
* the time in msec for the whole animation
*/
final public void setCompleteDuration(final int duration) {
completeDuration = duration;
// update absolute durations if animation has already been
// performed and thus no further steps will be added
if (absoluteDurations != null) {
absoluteDurations = calculateDurations();
}
}
/***************************************************************************
* Getter methods
**************************************************************************/
/**
* With this method you can specify the current animation step (the initial
* one or some subsequent one due to {@link #nextStep(double)}) so that the
* relative size factor will be 1. If the size is meant to be interpolated
* you should call {@link #specifyStep(Object, Object, double)} with -1 as
* size factor! </p>
*
* <p>
* A valid argument for the location object has to be an {@link EObject},
* {@link GraphicalEditPart}, {@link Point}, or <code>null</code>.
* </p>
*
* @param modelOrEditPart
* can be an {@link GraphicalEditPart} or {@link EObject}
* determining the model whose figure should be animated.
* @param locationObject
* If <code>null</code>, location and size will be interpolated
*/
final public void specifyStep(final Object modelOrEditPart, final Object locationObject) {
getAndPrepareAnimatedElement(modelOrEditPart).addStep(locationObject);
}
/**
* With this method you can specify the current animation step (the initial
* one or some subsequent one due to {@link #nextStep(double)}) with a
* relative size factor.
*
* <p>
* A valid argument for the location object has to be an {@link EObject},
* {@link GraphicalEditPart}, {@link Point}, or <code>null</code>.
* </p>
*
* @param modelOrEditPart
* can be an {@link GraphicalEditPart} or {@link EObject}
* determining the model whose figure should be animated.
* @param locationObject
* If <code>null</code>, the location will be interpolated
* @param sizeFactor
* If -1, the size will be interpolated
*/
final public void specifyStep(final Object modelOrEditPart, final Object locationObject,
final double sizeFactor) {
getAndPrepareAnimatedElement(modelOrEditPart).addStep(locationObject, sizeFactor);
}
/**
* Performs the specified animation like {@link #execute()}, but backwards.
* Calls the optional {@link #initialize()}, calculates the absolute
* durations, prepares the animation, performs the steps in reversed order
* and finishes the animation. This may be overridden, but ensure to call
* super.undo()!
*
* @see org.eclipse.gef.commands.Command#undo()
* @see #calculateDurations()
* @see #prepareAnimation(boolean)
* @see #performStep(int, int)
* @see #animationDone()
*/
@Override
public void undo() {
if (performAnimation) {
if (!initialized) {
initialize();
initialized = true;
}
/*
* Calculate absolute durations only once. This has not to be done
* already in execute() as the animation could just have been
* switched on for undo only.
*/
if (absoluteDurations == null) {
absoluteDurations = calculateDurations();
}
prepareAnimation(true);
// perform undo animation in reversed order
performStep(getStepCounter() - 1, 0);
for (int i = getStepCounter() - 2; i >= 0; i--) {
performStep(i, absoluteDurations.get(i + 1).intValue());
}
animationDone();
}
}
/***************************************************************************
* Internal methods
**************************************************************************/
/**
* Called after an animation has been performed in {@link #execute()} or
* {@link #undo()}.
*
* Calls {@link AnimatedElement#animationDone()} on all
* {@link AnimatedElement}s and clears the
* {@link AnimatedElement#resetViewers}.
*/
final private void animationDone() {
// call animationDone on the AnimatedElements in reversed order to
// restore the order of the figures in their original parents
final Object[] list = animatedElementsMap.values().toArray();
for (int i = list.length - 1; i >= 0; i--) {
((AnimatedElement) list[i]).animationDone();
}
// clean up used viewers after animation
if (flushAfter) {
for (final EditPartViewer viewer : usedViewers) {
viewer.flush();
}
usedViewers.clear();
}
}
/**
* Calculates a list that contains the absolute durations in msec for the
* animation steps. These are calculated so that all absolute durations in
* this list will sum up to {@link #completeDuration} if set (>-1).
* Otherwise 1 relative time duration will be interpreted as 1
* {@link #DEFAULT_DURATION}.
*
* This is done in {@link #execute()} and {@link #undo()} once.
* {@link #setCompleteDuration(int)} call this as well, but only if it has
* been done before, so that the duration can not be extended afterwards.
*
* @return A list that contains the absolute durations in msec for the
* animation steps.
*/
final private ArrayList<Integer> calculateDurations() {
// calculate sum of all relative step durations
double durationSum = 0.0;
for (final Double relativeDuration : relativeDurations) {
durationSum += relativeDuration.doubleValue();
}
/*
* if complete duration is not specified by user assume default duration
* for each relative duration
*/
if (completeDuration == -1) {
// count 1 relative duration as 1 default duration
completeDuration = (int) (DEFAULT_DURATION * durationSum);
}
// calculate absolute durations in msec
final ArrayList<Integer> newDurations = new ArrayList<Integer>();
for (int i = 0; i < relativeDurations.size(); i++) {
final double absoluteDuration = relativeDurations.get(i).doubleValue()
* completeDuration / durationSum;
newDurations.add(Integer.valueOf((int) absoluteDuration));
}
return newDurations;
}
/**
* Retrieves the {@link AnimatedElement} that has been created in
* {@link #initializeAnimatedElement(Object, Object, AnimationPathModifier)}
* for the passed model, edit part or figure. This animated element will be
* marked as specified for this step. This method ensures that a animated
* element gets at most one specification for the current step.
*
* @param modelOrEditPartOrFigure
* can be an {@link GraphicalEditPart} or {@link EObject}
* determining the model whose figure should be animated.
* Alternatively, this may be a specific {@link IFigure} to be
* animated.
* @return
*/
final private AnimatedElement getAndPrepareAnimatedElement(final Object modelOrEditPartOrFigure) {
final AnimatedElement animatedElement = getAnimatedElement(modelOrEditPartOrFigure);
// not initialized elements are being ignored
Assert.isTrue(!specifiedElementsInActualStep.contains(animatedElement),
"The current animation step has already been specified for model "
+ modelOrEditPartOrFigure);
/*
* we can add the selected animated to the list of specified elements
* element because this method will be called only when a step is being
* specified!
*/
specifiedElementsInActualStep.add(animatedElement);
return animatedElement;
}
/**
* Retrieves the {@link AnimatedElement} that has been created in
* {@link #initializeAnimatedElement(Object, Object, AnimationPathModifier)}
* for the passed model, edit part or figure.
*
* @param modelOrEditPartOrFigure
* can be an {@link GraphicalEditPart} or {@link EObject}
* determining the model whose figure should be animated.
* Alternatively, this may be a specific {@link IFigure} to be
* animated.
* @return
*/
private AnimatedElement getAnimatedElement(final Object modelOrEditPartOrFigure) {
AnimatedElement animatedElement = null;
if (modelOrEditPartOrFigure instanceof EObject
|| modelOrEditPartOrFigure instanceof IFigure) {
animatedElement = animatedElementsMap.get(modelOrEditPartOrFigure);
} else if (modelOrEditPartOrFigure instanceof GraphicalEditPart) {
animatedElement = animatedElementsMap.get(((GraphicalEditPart) modelOrEditPartOrFigure)
.getModel());
} else {
throw new IllegalArgumentException(
"A step has to be specified for an EObject, a GraphicalEditPart, or a IFigure!");
}
Assert.isNotNull(animatedElement, "No AnimatedElement has been initialized for "
+ modelOrEditPartOrFigure);
return animatedElement;
}
/**
* In this method the animation of the figures happens.
* {@link MultipleAnimation} is signaled to watch for changes on the
* observed layers. Each animated element is requested to prepare step i (by
* calling {@link AnimatedElement#prepareStep(int)}). Then
* {@link MultipleAnimation} is told to run animate the changes.
*
* @param i
* the index, indicating which of the specified steps should be
* performed
* @param duration
* the animation duration for this step in msec
*/
final private void performStep(final int i, final int duration) {
MultipleAnimation.markBegin();
for (final AnimatedElement element : animatedElementsMap.values()) {
element.prepareStep(i);
}
MultipleAnimation.run(duration);
}
/**
* Called by {@link #execute()} and {@link #undo()} after duration
* calculation and before performing the animation steps. For convenience,
* adds an interpolated place holder for all animated elements that have not
* been specified for the current (i.e. last) step. Then each animated
* elements is prepared for animation by calling
* {@link AnimatedElement#prepareForAnimation(boolean)}.
*
* @param isUndo
* set <code>true</code> if the element should be prepared for
* backwards undo animation
*
*/
final private void prepareAnimation(final boolean isUndo) {
// complete step definition if needed
if (!specifiedElementsInActualStep.isEmpty()) {
/*
* fill up initialized animated elements that have not been extended
* by a step specification
*/
final List<AnimatedElement> unspecified = new ArrayList<AnimatedElement>(
animatedElementsMap.values());
unspecified.removeAll(specifiedElementsInActualStep);
for (final AnimatedElement animatedElement : unspecified) {
animatedElement.addPlaceholderStep();
}
}
for (final AnimatedElement element : animatedElementsMap.values()) {
element.prepareForAnimation(isUndo, flushBefore);
}
}
}