[Libreoffice-commits] .: 13 commits - filter/source sd/source sd/xml

Marco Cecchetti mcecchetti at kemper.freedesktop.org
Sun Jul 15 02:54:24 PDT 2012


 filter/source/svg/presentation_engine.js | 1252 ++++++++++++++++++++++++++-----
 sd/source/core/CustomAnimationEffect.cxx |   13 
 sd/xml/effects.xml                       |   12 
 3 files changed, 1102 insertions(+), 175 deletions(-)

New commits:
commit 821b01bf2c5046f6731fa537bc034a45c176f265
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Sun Jul 15 10:54:49 2012 +0200

    JavaScript engine: added support for skipping/rewinding an effect belonging to an interactive animation sequence.
    
    Moreover now when the mouse pointer is over an event-source shape, through which the
    user can start an interactive animation, the cursor appearance is changed to a hand

diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js
index cccbcd0..88d8de0 100644
--- a/filter/source/svg/presentation_engine.js
+++ b/filter/source/svg/presentation_engine.js
@@ -3006,7 +3006,7 @@ function skipEffects(dir)
 {
     if( dir == 1 )
     {
-        var bRet = aSlideShow.skipEffect();
+        var bRet = aSlideShow.skipPlayingOrNextEffect();
 
         if( !bRet )
         {
@@ -5145,6 +5145,7 @@ function BaseNode( aAnimElem, aParentNode, aNodeContext )
     this.aDuration = null;
     this.aEnd = null;
     this.bMainSequenceRootNode = false;
+    this.bInteractiveSequenceRootNode = false;
     this.eFillMode = FILL_MODE_FREEZE;
     this.eRestartMode = RESTART_MODE_NEVER;
     this.nReapeatCount = undefined;
@@ -5462,6 +5463,11 @@ BaseNode.prototype.isMainSequenceRootNode = function()
     return this.bMainSequenceRootNode;
 };
 
+BaseNode.prototype.isInteractiveSequenceRootNode = function()
+{
+    return this.bInteractiveSequenceRootNode;
+};
+
 BaseNode.prototype.makeDeactivationEvent = function( nDelay )
 {
     if( this.aDeactivationEvent )
@@ -5528,6 +5534,8 @@ BaseNode.prototype.notifyEndListeners = function()
     }
 
     this.aContext.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_END_EVENT, this.getId() );
+    if( this.getParentNode() && this.getParentNode().isMainSequenceRootNode() )
+        this.aContext.aEventMultiplexer.notifyNextEffectEndEvent();
 };
 
 BaseNode.prototype.getContext = function()
@@ -6123,6 +6131,7 @@ BaseContainerNode.prototype.parseElement= function()
     if( sNodeTypeAttr && aImpressNodeTypeInMap[ sNodeTypeAttr ] )
         this.eImpressNodeType = aImpressNodeTypeInMap[ sNodeTypeAttr ];
     this.bMainSequenceRootNode = ( this.eImpressNodeType == IMPRESS_MAIN_SEQUENCE_NODE );
+    this.bInteractiveSequenceRootNode = ( this.eImpressNodeType == IMPRESS_INTERACTIVE_SEQUENCE_NODE );
 
     // preset-class attribute
     this.ePresetClass =  undefined;
@@ -6554,6 +6563,7 @@ SequentialTimeContainer.prototype.rewindCurrentEffect = function( aChildNode )
         // resolve it again.
         aChildNode.init();
         this.resolveChild( aChildNode );
+        this.notifyRewindedEvent( aChildNode );
         this.bIsRewinding = false;
     }
     else
@@ -6582,12 +6592,17 @@ SequentialTimeContainer.prototype.rewindLastEffect = function( aChildNode )
         // immediately without increment the finished children counter and
         // resolve the next child.
         this.bIsRewinding = true;
-        // We end the current effect and remove any change it applies on the
-        // animated shape.
+        // We end the current effect.
         this.getContext().aTimerEventQueue.forceEmpty();
         this.getContext().aActivityQueue.clear();
         aChildNode.end();
-        aChildNode.removeEffect();
+        // Invoking the end method on the current child node that has not yet
+        // been activated should not lead to any change on the animated shape.
+        // However for safety we used to call the removeEffect method but
+        // lately we noticed that when interactive animation sequences are
+        // involved into the shape effect invoking such a method causes
+        // some issue.
+        //aChildNode.removeEffect();
 
         // As we rewind the previous effect we need to decrease the finished
         // children counter.
@@ -6603,6 +6618,7 @@ SequentialTimeContainer.prototype.rewindLastEffect = function( aChildNode )
         // in ENDED state now, On the contrary it cannot be resolved again later.
         aChildNode.init();
         this.resolveChild( aPreviousChildNode );
+        this.notifyRewindedEvent( aChildNode );
         this.bIsRewinding = false;
     }
     else
@@ -6629,29 +6645,50 @@ SequentialTimeContainer.prototype.resolveChild = function( aChildNode )
 {
     var bResolved = aChildNode.resolve();
 
-    if( bResolved && this.isMainSequenceRootNode() )
+    if( bResolved && ( this.isMainSequenceRootNode() || this.isInteractiveSequenceRootNode() ) )
     {
         if( this.aCurrentSkipEvent )
             this.aCurrentSkipEvent.dispose();
-
         this.aCurrentSkipEvent = makeEvent( bind2( SequentialTimeContainer.prototype.skipEffect, this, aChildNode ) );
-        this.aContext.aEventMultiplexer.registerSkipEffectEvent( this.aCurrentSkipEvent );
 
         if( this.aRewindCurrentEffectEvent )
             this.aRewindCurrentEffectEvent.dispose();
-
         this.aRewindCurrentEffectEvent = makeEvent( bind2( SequentialTimeContainer.prototype.rewindCurrentEffect, this, aChildNode ) );
-        this.aContext.aEventMultiplexer.registerRewindCurrentEffectEvent( this.aRewindCurrentEffectEvent );
 
         if( this.aRewindLastEffectEvent )
             this.aRewindLastEffectEvent.dispose();
-
         this.aRewindLastEffectEvent = makeEvent( bind2( SequentialTimeContainer.prototype.rewindLastEffect, this, aChildNode ) );
-        this.aContext.aEventMultiplexer.registerRewindLastEffectEvent( this.aRewindLastEffectEvent );
+
+        if( this.isMainSequenceRootNode() )
+        {
+            this.aContext.aEventMultiplexer.registerSkipEffectEvent( this.aCurrentSkipEvent );
+            this.aContext.aEventMultiplexer.registerRewindCurrentEffectEvent( this.aRewindCurrentEffectEvent );
+            this.aContext.aEventMultiplexer.registerRewindLastEffectEvent( this.aRewindLastEffectEvent );
+        }
+        else if( this.isInteractiveSequenceRootNode() )
+        {
+            this.aContext.aEventMultiplexer.registerSkipInteractiveEffectEvent( aChildNode.getId(), this.aCurrentSkipEvent );
+            this.aContext.aEventMultiplexer.registerRewindRunningInteractiveEffectEvent( aChildNode.getId(), this.aRewindCurrentEffectEvent );
+            this.aContext.aEventMultiplexer.registerRewindEndedInteractiveEffectEvent( aChildNode.getId(), this.aRewindLastEffectEvent );
+        }
     }
     return bResolved;
 };
 
+SequentialTimeContainer.prototype.notifyRewindedEvent = function( aChildNode )
+{
+    if( this.isInteractiveSequenceRootNode() )
+    {
+        this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( aChildNode.getId() );
+
+        var sId = aChildNode.getBegin().getEventBaseElementId();
+        if( sId )
+        {
+            this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( sId );
+        }
+    }
+};
+
 SequentialTimeContainer.prototype.dispose = function()
 {
     if( this.aCurrentSkipEvent )
@@ -8714,7 +8751,6 @@ AnimatedElement.prototype.restoreState = function( nAnimationNodeId )
     }
 
     ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').restoreState(' + nAnimationNodeId +')' );
-
     var aState = this.aStateSet[ nAnimationNodeId ];
     var bRet = this.setToElement( aState.aElement );
     if( bRet )
@@ -9670,6 +9706,8 @@ function registerEvent( nNodeId, aTiming, aEvent, aNodeContext )
                     {
                         case EVENT_TRIGGER_ON_CLICK:
                             aEventMultiplexer.registerEvent( eEventType, aSourceEventElement.getId(), aEvent );
+                            aEventMultiplexer.registerRewindedEffectHandler( aSourceEventElement.getId(),
+                                                                             bind2( aSourceEventElement.charge, aSourceEventElement ) );
                             bEventRegistered = true;
                             break;
                         default:
@@ -9681,6 +9719,11 @@ function registerEvent( nNodeId, aTiming, aEvent, aNodeContext )
                         var aEndEvent = aInteractiveAnimationSequenceMap[ nNodeId ].getEndEvent();
                         aEventMultiplexer.registerEvent( eEventType, aSourceEventElement.getId(), aStartEvent );
                         aEventMultiplexer.registerEvent( EVENT_TRIGGER_END_EVENT, nNodeId, aEndEvent );
+                        aEventMultiplexer.registerRewindedEffectHandler(
+                            nNodeId,
+                            bind2( InteractiveAnimationSequence.prototype.chargeEvents,
+                                   aInteractiveAnimationSequenceMap[ nNodeId ] )
+                        );
                     }
                 }
                 else  // no base event element present
@@ -9754,16 +9797,19 @@ SourceEventElement.prototype.getId = function()
 SourceEventElement.prototype.onMouseEnter = function()
 {
     this.bIsPointerOver = true;
+    this.setPointerCursor();
 };
 
 SourceEventElement.prototype.onMouseLeave = function()
 {
     this.bIsPointerOver = false;
+    this.setDefaultCursor();
 };
 
 SourceEventElement.prototype.charge = function()
 {
     this.bClickHandled = false;
+    this.setPointerCursor();
 };
 
 SourceEventElement.prototype.handleClick = function( aMouseEvent )
@@ -9771,14 +9817,27 @@ SourceEventElement.prototype.handleClick = function( aMouseEvent )
     if( !this.bIsPointerOver ) return false;
 
     if( this.bClickHandled )
-        return true;
+        return false;
 
     this.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_ON_CLICK, this.getId() );
     aSlideShow.update();
     this.bClickHandled = true;
+    this.setDefaultCursor();
     return true;
 };
 
+SourceEventElement.prototype.setPointerCursor = function()
+{
+    if( this.bClickHandled )
+        return;
+
+    this.aElement.setAttribute( 'style', 'cursor: pointer' );
+};
+
+SourceEventElement.prototype.setDefaultCursor = function()
+{
+    this.aElement.setAttribute( 'style', 'cursor: default' );
+};
 
 
 // ------------------------------------------------------------------------------------------ //
@@ -9876,10 +9935,15 @@ function EventMultiplexer( aTimerEventQueue )
 {
     this.aTimerEventQueue = aTimerEventQueue;
     this.aEventMap = new Object();
+    this.aSkipEffectEndHandlerSet = new Array();
     this.aMouseClickHandlerSet = new PriorityQueue( PriorityEntry.compare );
     this.aSkipEffectEvent = null;
     this.aRewindCurrentEffectEvent = null;
     this.aRewindLastEffectEvent = null;
+    this.aSkipInteractiveEffectEventSet = new Object();
+    this.aRewindRunningInteractiveEffectEventSet = new Object();
+    this.aRewindEndedInteractiveEffectEventSet = new Object();
+    this.aRewindedEffectHandlerSet = new Object();
 }
 
 EventMultiplexer.prototype.registerMouseClickHandler = function( aHandler, nPriority )
@@ -9932,6 +9996,21 @@ EventMultiplexer.prototype.notifyEvent = function( eEventType, aNotifierId )
     }
 };
 
+EventMultiplexer.prototype.registerNextEffectEndHandler = function( aHandler )
+{
+    this.aSkipEffectEndHandlerSet.push( aHandler );
+};
+
+EventMultiplexer.prototype.notifyNextEffectEndEvent = function()
+{
+    var nSize = this.aSkipEffectEndHandlerSet.length;
+    for( var i = 0; i < nSize; ++i )
+    {
+        (this.aSkipEffectEndHandlerSet[i])();
+    }
+    this.aSkipEffectEndHandlerSet = new Array();
+};
+
 EventMultiplexer.prototype.registerSkipEffectEvent = function( aEvent )
 {
     this.aSkipEffectEvent = aEvent;
@@ -9974,6 +10053,59 @@ EventMultiplexer.prototype.notifyRewindLastEffectEvent = function()
     }
 };
 
+EventMultiplexer.prototype.registerSkipInteractiveEffectEvent = function( nNotifierId, aEvent )
+{
+    this.aSkipInteractiveEffectEventSet[ nNotifierId ] = aEvent;
+};
+
+EventMultiplexer.prototype.notifySkipInteractiveEffectEvent = function( nNotifierId )
+{
+    if( this.aSkipInteractiveEffectEventSet[ nNotifierId ] )
+    {
+        this.aTimerEventQueue.addEvent( this.aSkipInteractiveEffectEventSet[ nNotifierId ] );
+    }
+};
+
+EventMultiplexer.prototype.registerRewindRunningInteractiveEffectEvent = function( nNotifierId, aEvent )
+{
+    this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] = aEvent;
+};
+
+EventMultiplexer.prototype.notifyRewindRunningInteractiveEffectEvent = function( nNotifierId )
+{
+    if( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] )
+    {
+        this.aTimerEventQueue.addEvent( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] );
+    }
+};
+
+EventMultiplexer.prototype.registerRewindEndedInteractiveEffectEvent = function( nNotifierId, aEvent )
+{
+    this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] = aEvent;
+};
+
+EventMultiplexer.prototype.notifyRewindEndedInteractiveEffectEvent = function( nNotifierId )
+{
+    if( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] )
+    {
+        this.aTimerEventQueue.addEvent( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] );
+    }
+};
+
+EventMultiplexer.prototype.registerRewindedEffectHandler = function( aNotifierId, aHandler )
+{
+    this.aRewindedEffectHandlerSet[ aNotifierId ] = aHandler;
+};
+
+EventMultiplexer.prototype.notifyRewindedEffectEvent = function( aNotifierId )
+{
+    if( this.aRewindedEffectHandlerSet[ aNotifierId ] )
+    {
+        (this.aRewindedEffectHandlerSet[ aNotifierId ])();
+    }
+};
+
+
 EventMultiplexer.DEBUG = aEventMultiplexerDebugPrinter.isEnabled();
 
 EventMultiplexer.prototype.DBG = function( sMethodName, eEventType, aNotifierId, nTime )
@@ -11419,6 +11551,49 @@ var PREFERRED_FRAMES_PER_SECONDS        = 50;
 var PREFERRED_FRAME_RATE                = 1.0 / PREFERRED_FRAMES_PER_SECONDS;
 
 
+function Effect( nId )
+{
+    this.nId = ( typeof( nId ) === typeof( 1 ) ) ? nId : -1;
+    this.eState = Effect.NOT_STARTED;
+};
+
+Effect.NOT_STARTED = 0;
+Effect.PLAYING = 1;
+Effect.ENDED = 2;
+
+Effect.prototype.getId = function()
+{
+    return this.nId;
+};
+
+Effect.prototype.isMainEffect = function()
+{
+    return ( this.nId === -1 );
+};
+
+Effect.prototype.isPlaying = function()
+{
+    return ( this.eState === Effect.PLAYING );
+};
+
+Effect.prototype.isEnded = function()
+{
+    return ( this.eState === Effect.ENDED );
+};
+
+Effect.prototype.start = function()
+{
+    assert( this.eState === Effect.NOT_STARTED, 'Effect.start: wrong state.' );
+    this.eState = Effect.PLAYING;
+};
+
+Effect.prototype.end = function()
+{
+    assert( this.eState === Effect.PLAYING, 'Effect.end: wrong state.' );
+    this.eState = Effect.ENDED;
+};
+
+// ------------------------------------------------------------------------------------------ //
 
 function SlideShow()
 {
@@ -11435,18 +11610,21 @@ function SlideShow()
                                           this.aNextEffectEventArray,
                                           this.aInteractiveAnimationSequenceMap,
                                           this.aActivityQueue );
-    this.nCurrentEffect = 0;
-    this.eDirection = FORWARD;
-    this.nTotalInteracAnimSeqRunning = 0;
     this.bIsIdle = true;
     this.bIsEnabled = true;
+    this.bNoSlideTransition = false;
+
+    this.nCurrentEffect = 0;
+    this.bIsNextEffectRunning = false;
     this.bIsRewinding = false;
     this.bIsSkipping = false;
     this.bIsSkippingAll = false;
-    this.bNoSlideTransition = false;
+    this.nTotalInteractivePlayingEffects = 0;
+    this.aStartedEffectList = new Array();
+    this.aStartedEffectIndexMap = new Object();
+    this.aStartedEffectIndexMap[ -1 ] = undefined;
 }
 
-
 SlideShow.prototype.setSlideEvents = function( aNextEffectEventArray,
                                                aInteractiveAnimationSequenceMap,
                                                aEventMultiplexer )
@@ -11467,7 +11645,6 @@ SlideShow.prototype.setSlideEvents = function( aNextEffectEventArray,
     this.aContext.aEventMultiplexer = aEventMultiplexer;
     this.aEventMultiplexer = aEventMultiplexer;
     this.nCurrentEffect = 0;
-    this.nTotalInteracAnimSeqRunning = 0;
 };
 
 SlideShow.prototype.createSlideTransition = function( aSlideTransitionHandler, aLeavingSlide, aEnteringSlide, aTransitionEndEvent )
@@ -11512,30 +11689,74 @@ SlideShow.prototype.createSlideTransition = function( aSlideTransitionHandler, a
 
 };
 
+SlideShow.prototype.isEnabled = function()
+{
+    return this.bIsEnabled;
+};
+
 SlideShow.prototype.isRunning = function()
 {
     return !this.bIsIdle;
 };
 
-SlideShow.prototype.isInterAnimSeqRunning = function()
+SlideShow.prototype.isMainEffectPlaying = function()
 {
-    return ( this.nTotalInteracAnimSeqRunning > 0 );
+    return this.bIsNextEffectRunning;
 };
 
-SlideShow.prototype.isEnabled = function()
+SlideShow.prototype.isInteractiveEffectPlaying = function()
 {
-    return this.bIsEnabled;
+    return ( this.nTotalInteractivePlayingEffects > 0 );
+};
+
+SlideShow.prototype.isAnyEffectPlaying = function()
+{
+    return ( this.isMainEffectPlaying() || this.isInteractiveEffectPlaying() );
+};
+
+SlideShow.prototype.hasAnyEffectStarted = function()
+{
+    return ( this.aStartedEffectList.length > 0 );
 };
 
 SlideShow.prototype.notifyNextEffectStart = function()
 {
+    assert( !this.bIsNextEffectRunning,
+            'SlideShow.notifyNextEffectStart: an effect is already started.' );
+    this.bIsNextEffectRunning = true;
+    this.aEventMultiplexer.registerNextEffectEndHandler( bind2( SlideShow.prototype.notifyNextEffectEnd, this ) );
+    var aEffect = new Effect();
+    aEffect.start();
+    this.aStartedEffectIndexMap[ -1 ] = this.aStartedEffectList.length;
+    this.aStartedEffectList.push( aEffect );
+
+
     var aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nCurSlide].aSlideAnimationsHandler.aAnimatedElementMap;
     for( var sId in aAnimatedElementMap )
         aAnimatedElementMap[ sId ].notifyNextEffectStart( this.nCurrentEffect );
 };
 
+SlideShow.prototype.notifyNextEffectEnd = function()
+{
+     assert( this.bIsNextEffectRunning,
+            'SlideShow.notifyNextEffectEnd: effect already ended.' );
+    this.bIsNextEffectRunning = false;
+
+    this.aStartedEffectList[ this.aStartedEffectIndexMap[ -1 ] ].end();
+};
+
 SlideShow.prototype.notifySlideStart = function( nSlideIndex )
 {
+    this.nCurrentEffect = 0;
+    this.bIsRewinding = false;
+    this.bIsSkipping = false;
+    this.bIsSkippingAll = false;
+    this.nTotalInteractivePlayingEffects = 0;
+    this.aStartedEffectList = new Array();
+    this.aStartedEffectIndexMap = new Object();
+    this.aStartedEffectIndexMap[ -1 ] = undefined;
+
+
     var aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nSlideIndex].aSlideAnimationsHandler.aAnimatedElementMap;
     for( var sId in aAnimatedElementMap )
         aAnimatedElementMap[ sId ].notifySlideStart();
@@ -11556,88 +11777,108 @@ SlideShow.prototype.notifyTransitionEnd = function( nSlideIndex )
 
 SlideShow.prototype.notifyInteractiveAnimationSequenceStart = function( nNodeId )
 {
-    ++this.nTotalInteracAnimSeqRunning;
+    ++this.nTotalInteractivePlayingEffects;
+    var aEffect = new Effect( nNodeId );
+    aEffect.start();
+    this.aStartedEffectIndexMap[ nNodeId ] = this.aStartedEffectList.length;
+    this.aStartedEffectList.push( aEffect );
 };
 
 SlideShow.prototype.notifyInteractiveAnimationSequenceEnd = function( nNodeId )
 {
-    assert( this.nTotalInteracAnimSeqRunning > 0,
-            'SlideShow.notifyInteractiveAnimationSequenceStart: ' +
-            'the total number of running interactive application is zero' );
-    --this.nTotalInteracAnimSeqRunning;
+    assert( this.isInteractiveEffectPlaying(),
+            'SlideShow.notifyInteractiveAnimationSequenceEnd: no interactive effect playing.' )
+
+    this.aStartedEffectList[ this.aStartedEffectIndexMap[ nNodeId ] ].end();
+    --this.nTotalInteractivePlayingEffects;
 };
 
+/** nextEffect
+ *  Start the next effect belonging to the main animation sequence if any.
+ *  If there is an already playing effect belonging to any animation sequence
+ *  it is skipped.
+ *
+ *  @return {Boolean}
+ *      False if there is no more effect to start, true otherwise.
+ */
 SlideShow.prototype.nextEffect = function()
 {
     if( !this.isEnabled() )
         return false;
 
-    if( this.isInterAnimSeqRunning() )
-        return true;
-
-    if( this.isRunning() )
+    if( this.isAnyEffectPlaying() )
     {
-        this.skipCurrentEffect();
+        this.skipAllPlayingEffects();
         return true;
     }
 
     if( !this.aNextEffectEventArray )
         return false;
 
-    this.notifyNextEffectStart();
-
     if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
         return false;
 
-    this.eDirection = FORWARD;
+    this.notifyNextEffectStart();
+
     this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
     ++this.nCurrentEffect;
     this.update();
     return true;
 };
 
-/** skipCurrentEffect
- *  Skip the current playing effect.
+/** skipAllPlayingEffects
+ *  Skip all playing effect, independently to which animation sequence they
+ *  belong.
  *
  */
-SlideShow.prototype.skipCurrentEffect = function()
+SlideShow.prototype.skipAllPlayingEffects  = function()
 {
     if( this.bIsSkipping || this.bIsRewinding )
-        return;
+        return true;
 
     this.bIsSkipping = true;
-    this.aEventMultiplexer.notifySkipEffectEvent();
+    // TODO: The correct order should be based on the left playing time.
+    for( var i = 0; i < this.aStartedEffectList.length; ++i )
+    {
+        var aEffect = this.aStartedEffectList[i];
+        if( aEffect.isPlaying() )
+        {
+            if( aEffect.isMainEffect() )
+                this.aEventMultiplexer.notifySkipEffectEvent();
+            else
+                this.aEventMultiplexer.notifySkipInteractiveEffectEvent( aEffect.getId() );
+        }
+    }
     this.update();
     this.bIsSkipping = false;
+    return true;
 };
 
-/** skipEffect
- *  Skip the next effect to be played.
+/** skipNextEffect
+ *  Skip the next effect to be played (if any) that belongs to the main
+ *  animation sequence.
+ *  Require: no effect is playing.
  *
+ *  @return {Boolean}
+ *      False if there is no more effect to skip, true otherwise.
  */
-SlideShow.prototype.skipEffect = function()
+SlideShow.prototype.skipNextEffect = function()
 {
     if( this.bIsSkipping || this.bIsRewinding )
         return true;
 
-    if( this.isInterAnimSeqRunning() )
-        return true;
-
-    if( this.isRunning() )
-    {
-        this.skipCurrentEffect();
-        return true;
-    }
+    assert( !this.isAnyEffectPlaying(),
+            'SlideShow.skipNextEffect' );
 
     if( !this.aNextEffectEventArray )
         return false;
 
-    this.notifyNextEffectStart();
     if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
         return false;
 
+    this.notifyNextEffectStart();
+
     this.bIsSkipping = true;
-    this.eDirection = FORWARD;
     this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
     this.aEventMultiplexer.notifySkipEffectEvent();
     ++this.nCurrentEffect;
@@ -11646,8 +11887,25 @@ SlideShow.prototype.skipEffect = function()
     return true;
 };
 
+/** skipPlayingOrNextEffect
+ *  Skip the next effect to be played that belongs to the main animation
+ *  sequence  or all playing effects.
+ *
+ *  @return {Boolean}
+ *      False if there is no more effect to skip, true otherwise.
+ */
+SlideShow.prototype.skipPlayingOrNextEffect = function()
+{
+    if( this.isAnyEffectPlaying() )
+        return this.skipAllPlayingEffects();
+    else
+        return this.skipNextEffect();
+};
+
+
 /** skipAllEffects
- *  Skip all left effects on the current slide.
+ *  Skip all left effects that belongs to the main animation sequence and all
+ *  playing effects on the current slide.
  *
  *  @return {Boolean}
  *      True if it already skipping or when it has ended skipping,
@@ -11658,13 +11916,11 @@ SlideShow.prototype.skipAllEffects = function()
     if( this.bIsSkippingAll )
         return true;
 
-    if( this.isInterAnimSeqRunning() )
-        return true;
-
     this.bIsSkippingAll = true;
-    if( this.isRunning() )
+
+    if( this.isAnyEffectPlaying() )
     {
-        this.skipCurrentEffect();
+        this.skipAllPlayingEffects();
     }
     else if( !this.aNextEffectEventArray
                || ( this.nCurrentEffect >= this.aNextEffectEventArray.size() ) )
@@ -11681,14 +11937,16 @@ SlideShow.prototype.skipAllEffects = function()
     // aNextEffectEventArray will going on increasing after every skip action.
     while( this.nCurrentEffect < this.aNextEffectEventArray.size() )
     {
-        this.skipEffect();
+        this.skipNextEffect();
     }
     this.bIsSkippingAll = false;
     return true;
 };
 
 /** rewindEffect
- *  Rewind the current playing effect or the last played one.
+ *  Rewind all the effects started after at least one of the current playing
+ *  effects. If there is no playing effect, it rewinds the last played one,
+ *  both in case it belongs to the main or to an interactive animation sequence.
  *
  */
 SlideShow.prototype.rewindEffect = function()
@@ -11696,37 +11954,105 @@ SlideShow.prototype.rewindEffect = function()
     if( this.bIsSkipping || this.bIsRewinding )
         return;
 
-    if( this.isInterAnimSeqRunning() )
-        return true;
-
-    if( this.nCurrentEffect == 0 )
+    if( !this.hasAnyEffectStarted() )
     {
         this.rewindToPreviousSlide();
         return;
     }
 
     this.bIsRewinding = true;
-    if( this.isRunning() )
+
+    var nFirstPlayingEffectIndex = undefined;
+
+    var i = 0;
+    for( ; i < this.aStartedEffectList.length; ++i )
     {
-        this.aEventMultiplexer.notifyRewindCurrentEffectEvent();
+        var aEffect = this.aStartedEffectList[i];
+        if( aEffect.isPlaying() )
+        {
+            nFirstPlayingEffectIndex = i;
+            break;
+        }
     }
-    else
+
+    // There is at least one playing effect.
+    if( nFirstPlayingEffectIndex !== undefined )
+    {
+        i = this.aStartedEffectList.length - 1;
+        for( ; i >= nFirstPlayingEffectIndex; --i )
+        {
+            aEffect = this.aStartedEffectList[i];
+            if( aEffect.isPlaying() )
+            {
+                if( aEffect.isMainEffect() )
+                {
+                    this.aEventMultiplexer.notifyRewindCurrentEffectEvent();
+                    if( this.nCurrentEffect > 0 )
+                        --this.nCurrentEffect;
+                }
+                else
+                {
+                    this.aEventMultiplexer.notifyRewindRunningInteractiveEffectEvent( aEffect.getId() );
+                }
+            }
+            else if( aEffect.isEnded() )
+            {
+                if( aEffect.isMainEffect() )
+                {
+                    this.aEventMultiplexer.notifyRewindLastEffectEvent();
+                    if( this.nCurrentEffect > 0 )
+                        --this.nCurrentEffect;
+                }
+                else
+                {
+                    this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
+                }
+            }
+        }
+        this.update();
+
+        // Pay attention here: we need to remove all rewinded effects from
+        // the started effect list only after updating.
+        i = this.aStartedEffectList.length - 1;
+        for( ; i >= nFirstPlayingEffectIndex; --i )
+        {
+            aEffect = this.aStartedEffectList.pop();
+            if( !aEffect.isMainEffect() )
+                delete this.aStartedEffectIndexMap[ aEffect.getId() ];
+        }
+    }
+    else  // there is no playing effect
     {
-        this.aEventMultiplexer.notifyRewindLastEffectEvent();
+        aEffect = this.aStartedEffectList.pop();
+        if( !aEffect.isMainEffect() )
+            delete this.aStartedEffectIndexMap[ aEffect.getId() ];
+        if( aEffect.isEnded() )  // Well that is almost an assertion.
+        {
+            if( aEffect.isMainEffect() )
+            {
+                this.aEventMultiplexer.notifyRewindLastEffectEvent();
+                if( this.nCurrentEffect > 0 )
+                    --this.nCurrentEffect;
+            }
+            else
+            {
+                this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
+            }
+        }
+        this.update();
     }
-    if( this.nCurrentEffect > 0 )
-        --this.nCurrentEffect;
-    this.update();
+
     this.bIsRewinding = false;
 };
 
 /** rewindToPreviousSlide
- *  Displays the previous slide with all effects played.
+ *  Displays the previous slide with all effects, that belong to the main
+ *  animation sequence, played.
  *
  */
 SlideShow.prototype.rewindToPreviousSlide = function()
 {
-    if( this.isRunning() )
+    if( this.isAnyEffectPlaying() )
         return;
     var nNewSlide = nCurSlide - 1;
     this.displaySlide( nNewSlide, true );
@@ -11739,16 +12065,16 @@ SlideShow.prototype.rewindToPreviousSlide = function()
  */
 SlideShow.prototype.rewindAllEffects = function()
 {
-    if( this.nCurrentEffect == 0 )
+    if( !this.hasAnyEffectStarted() )
     {
         this.rewindToPreviousSlide();
         return;
     }
 
-     while( this.nCurrentEffect > 0 )
-     {
-         this.rewindEffect();
-     }
+    while( this.hasAnyEffectStarted() )
+    {
+        this.rewindEffect();
+    }
 };
 
 SlideShow.prototype.displaySlide = function( nNewSlide, bSkipSlideTransition )
commit c78e929b42e68d0ca21a1f9c5bb495ffbb716a6a
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Mon Jul 9 00:59:05 2012 +0200

    Added support for interactive animation sequence to the JavaScript engine.
    
    Now it is possible to start an effect by clicking on a given shape.

diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js
index a5a3a3f..cccbcd0 100644
--- a/filter/source/svg/presentation_engine.js
+++ b/filter/source/svg/presentation_engine.js
@@ -222,7 +222,7 @@ function getDefaultCharCodeDictionary()
 }
 
 
-function slideOnMouseDown( aEvt )
+function slideOnMouseUp( aEvt )
 {
     if (!aEvt)
         aEvt = window.event;
@@ -236,8 +236,12 @@ function slideOnMouseDown( aEvt )
 
     if( 0 != nOffset )
         dispatchEffects( nOffset );
+    return true; // the click has been handled
 }
 
+document.handleClick = slideOnMouseUp;
+
+
 /** Event handler for mouse wheel events in slide mode.
  *  based on http://adomas.org/javascript-mouse-wheel/
  *
@@ -309,7 +313,7 @@ function mouseHandlerDispatch( aEvt, anAction )
 }
 
 //Set mouse event handler.
-document.onmousedown = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_DOWN ); };
+document.onmouseup = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_UP ); };
 //document.onmousemove = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_MOVE ); };
 
 /** Function to supply the default mouse handler dictionary.
@@ -324,13 +328,17 @@ function getDefaultMouseHandlerDictionary()
     mouseHandlerDict[INDEX_MODE] = new Object();
 
     // slide mode
-    mouseHandlerDict[SLIDE_MODE][MOUSE_DOWN]
-        = function( aEvt ) { return slideOnMouseDown( aEvt ); };
+    mouseHandlerDict[SLIDE_MODE][MOUSE_UP]
+        //= function( aEvt ) { return slideOnMouseDown( aEvt ); };
+        = function( aEvt ) { return ( aSlideShow.aEventMultiplexer ) ?
+                                        aSlideShow.aEventMultiplexer.notifyMouseClick( aEvt )
+                                        : slideOnMouseUp( aEvt ); };
+
     mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL]
         = function( aEvt ) { return slideOnMouseWheel( aEvt ); };
 
     // index mode
-    mouseHandlerDict[INDEX_MODE][MOUSE_DOWN]
+    mouseHandlerDict[INDEX_MODE][MOUSE_UP]
         = function( aEvt ) { return toggleSlideIndex(); };
 //    mouseHandlerDict[INDEX_MODE][MOUSE_MOVE]
 //        = function( aEvt ) { return theSlideIndexPage.updateSelection( aEvt ); };
@@ -1433,7 +1441,7 @@ var NAVDBG = new DebugPrinter();
 NAVDBG.off();
 
 var ANIMDBG = new DebugPrinter();
-ANIMDBG.on();
+ANIMDBG.off();
 
 var aRegisterEventDebugPrinter = new DebugPrinter();
 aRegisterEventDebugPrinter.off();
@@ -5047,11 +5055,11 @@ NodeContext.prototype.makeSourceEventElement = function( sId, aEventBaseElem )
         return null;
     }
 
-    if( !this.aAnimationNodeMap[ sId ] )
+    if( !this.aSourceEventElementMap[ sId ] )
     {
-        this.aAnimationNodeMap[ sId ] = new SourceEventElement( aEventBaseElem, this.aContext.aEventMultiplexer );
+        this.aSourceEventElementMap[ sId ] = new SourceEventElement( sId, aEventBaseElem, this.aContext.aEventMultiplexer );
     }
-    return this.aAnimationNodeMap[ sId ];
+    return this.aSourceEventElementMap[ sId ];
 };
 
 
@@ -5323,7 +5331,7 @@ BaseNode.prototype.resolve = function()
         {
             this.aActivationEvent = makeDelay( bind( this, this.activate ), this.getBegin().getOffset() + this.nStartDelay );
         }
-        registerEvent( this.getBegin(), this.aActivationEvent, this.aNodeContext );
+        registerEvent( this.getId(), this.getBegin(), this.aActivationEvent, this.aNodeContext );
 
         return true;
     }
@@ -9378,6 +9386,7 @@ function SlideAnimations( aSlideShowContext )
     this.aAnimatedElementMap = new Object();
     this.aSourceEventElementMap = new Object();
     this.aNextEffectEventArray = new NextEffectEventArray();
+    this.aInteractiveAnimationSequenceMap = new Object();
     this.aEventMultiplexer = new EventMultiplexer( aSlideShowContext.aTimerEventQueue );
     this.aRootNode = null;
     this.bElementsParsed = false;
@@ -9385,6 +9394,11 @@ function SlideAnimations( aSlideShowContext )
     this.aContext.aAnimationNodeMap = this.aAnimationNodeMap;
     this.aContext.aAnimatedElementMap = this.aAnimatedElementMap;
     this.aContext.aSourceEventElementMap = this.aSourceEventElementMap;
+
+    // We set up a low priority for the invocation of document.handleClick
+    // in order to make clicks on shapes, that start interactive animation
+    // sequence (on click), have an higher priority.
+    this.aEventMultiplexer.registerMouseClickHandler( document, 100 );
 }
 
 
@@ -9433,7 +9447,12 @@ SlideAnimations.prototype.start = function()
     if( !this.bElementsParsed )
         return false;
 
-    aSlideShow.setSlideEvents( this.aNextEffectEventArray, this.aEventMultiplexer );
+    this.chargeSourceEvents();
+    this.chargeInterAnimEvents();
+
+    aSlideShow.setSlideEvents( this.aNextEffectEventArray,
+                               this.aInteractiveAnimationSequenceMap,
+                               this.aEventMultiplexer );
 
     if( this.aContext.bFirstRun == undefined )
         this.aContext.bFirstRun = true;
@@ -9489,7 +9508,21 @@ SlideAnimations.prototype.clearNextEffectEvents = function()
     this.aContext.bFirstRun = undefined;
 };
 
+SlideAnimations.prototype.chargeSourceEvents = function()
+{
+    for( var id in this.aSourceEventElementMap )
+    {
+        this.aSourceEventElementMap[id].charge();
+    }
+};
 
+SlideAnimations.prototype.chargeInterAnimEvents = function()
+{
+    for( var id in this.aInteractiveAnimationSequenceMap )
+    {
+        this.aInteractiveAnimationSequenceMap[id].chargeEvents();
+    }
+};
 
 /**********************************************************************************************
  *      Event classes and helper functions
@@ -9578,7 +9611,7 @@ function makeDelay( aFunctor, nTimeout )
 
 
 // ------------------------------------------------------------------------------------------ //
-function registerEvent( aTiming, aEvent, aNodeContext )
+function registerEvent( nNodeId, aTiming, aEvent, aNodeContext )
 {
     var aSlideShowContext = aNodeContext.aContext;
     var eTimingType = aTiming.getType();
@@ -9603,6 +9636,14 @@ function registerEvent( aTiming, aEvent, aNodeContext )
             log( 'registerEvent: next effect event array not initialized' );
             return;
         }
+        var aInteractiveAnimationSequenceMap =
+            aSlideShowContext.aInteractiveAnimationSequenceMap;
+        if( !aInteractiveAnimationSequenceMap )
+        {
+            log( 'registerEvent: interactive animation sequence map not initialized' );
+            return;
+        }
+
         switch( eTimingType )
         {
             case EVENT_TIMING:
@@ -9618,6 +9659,12 @@ function registerEvent( aTiming, aEvent, aNodeContext )
                     }
                     var aSourceEventElement = aNodeContext.makeSourceEventElement( sEventBaseElemId, aEventBaseElem );
 
+                    if( !aInteractiveAnimationSequenceMap[ nNodeId ] )
+                    {
+                        var aInteractiveAnimationSequence = new InteractiveAnimationSequence( nNodeId );
+                        aInteractiveAnimationSequenceMap[ nNodeId ] = aInteractiveAnimationSequence;
+                    }
+
                     var bEventRegistered = false;
                     switch( eEventType )
                     {
@@ -9629,7 +9676,12 @@ function registerEvent( aTiming, aEvent, aNodeContext )
                             log( 'generateEvent: not handled event type: ' + eEventType );
                     }
                     if( bEventRegistered )
-                        aSourceEventElement.addEventListener( eEventType  );
+                    {
+                        var aStartEvent = aInteractiveAnimationSequenceMap[ nNodeId ].getStartEvent();
+                        var aEndEvent = aInteractiveAnimationSequenceMap[ nNodeId ].getEndEvent();
+                        aEventMultiplexer.registerEvent( eEventType, aSourceEventElement.getId(), aStartEvent );
+                        aEventMultiplexer.registerEvent( EVENT_TRIGGER_END_EVENT, nNodeId, aEndEvent );
+                    }
                 }
                 else  // no base event element present
                 {
@@ -9680,82 +9732,173 @@ registerEvent.DBG = function( aTiming, nTime )
 
 
 // ------------------------------------------------------------------------------------------ //
-function SourceEventElement( aElement, aEventMultiplexer )
+function SourceEventElement( sId, aElement, aEventMultiplexer )
 {
-    this.nId = getUniqueId();
+    this.sId = sId;
     this.aElement = aElement;
     this.aEventMultiplexer = aEventMultiplexer;
-    this.aEventListenerStateArray = new Array();
-}
 
+    this.aEventMultiplexer.registerMouseClickHandler( this, 1000 );
+
+    this.bClickHandled = false;
+    this.bIsPointerOver = false;
+    this.aElement.addEventListener( 'mouseover', bind2( SourceEventElement.prototype.onMouseEnter, this), false );
+    this.aElement.addEventListener( 'mouseout', bind2( SourceEventElement.prototype.onMouseLeave, this), false );
+}
 
 SourceEventElement.prototype.getId = function()
 {
-    return this.nId;
+    return this.sId;
 };
 
-SourceEventElement.prototype.isEqualTo = function( aSourceEventElement )
+SourceEventElement.prototype.onMouseEnter = function()
 {
-    return ( this.getId() == aSourceEventElement.getId() );
+    this.bIsPointerOver = true;
 };
 
-SourceEventElement.prototype.onClick = function()
+SourceEventElement.prototype.onMouseLeave = function()
 {
-    this.aEventMultiplexer.notifyClickEvent( this );
+    this.bIsPointerOver = false;
 };
 
-SourceEventElement.prototype.isEventListenerRegistered = function( eEventType )
+SourceEventElement.prototype.charge = function()
 {
-    return this.aEventListenerStateArray[ eEventType ];
+    this.bClickHandled = false;
 };
 
-SourceEventElement.prototype.addEventListener = function( eEventType )
+SourceEventElement.prototype.handleClick = function( aMouseEvent )
 {
-    if( !this.aElement )
-        return false;
+    if( !this.bIsPointerOver ) return false;
 
-    this.aEventListenerStateArray[ eEventType ] = true;
-    switch( eEventType )
-    {
-        case EVENT_TRIGGER_ON_CLICK:
-            this.aElement.addEventListener( 'click', this.onClick, false );
-            break;
-        default:
-            log( 'SourceEventElement.addEventListener: not handled event type: ' + eEventType );
-            return false;
-    }
+    if( this.bClickHandled )
+        return true;
+
+    this.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_ON_CLICK, this.getId() );
+    aSlideShow.update();
+    this.bClickHandled = true;
     return true;
 };
 
-SourceEventElement.prototype.removeEventListener = function( eEventType )
+
+
+// ------------------------------------------------------------------------------------------ //
+function InteractiveAnimationSequence( nId )
 {
-    if( !this.aElement )
-        return false;
+    this.nId = nId;
+    this.bIsRunning = false;
+    this.aStartEvent = null;
+    this.aEndEvent = null;
+}
 
-    this.aEventListenerStateArray[ eEventType ] = false;
-    switch( eEventType )
+InteractiveAnimationSequence.prototype.getId = function()
+{
+    return this.nId;
+};
+
+InteractiveAnimationSequence.prototype.getStartEvent = function()
+{
+    if( !this.aStartEvent )
     {
-        case EVENT_TRIGGER_ON_CLICK:
-            this.aElement.removeEventListener( 'click', this.onClick, false );
-            break;
-        default:
-            log( 'SourceEventElement.removeEventListener: not handled event type: ' + eEventType );
-            return false;
+        this.aStartEvent =
+            makeEvent( bind2( InteractiveAnimationSequence.prototype.start, this ) );
     }
-    return true;
+    return this.aStartEvent;
 };
 
+InteractiveAnimationSequence.prototype.getEndEvent = function()
+{
+    if( !this.aEndEvent )
+    {
+        this.aEndEvent =
+            makeEvent( bind2( InteractiveAnimationSequence.prototype.end, this ) );
+    }
+    return this.aEndEvent;
+};
+
+InteractiveAnimationSequence.prototype.chargeEvents = function()
+{
+    if( this.aStartEvent )      this.aStartEvent.charge();
+    if( this.aEndEvent )        this.aEndEvent.charge();
+};
+
+InteractiveAnimationSequence.prototype.isRunning = function()
+{
+    return this.bIsRunning;
+};
+
+InteractiveAnimationSequence.prototype.start = function()
+{
+    aSlideShow.notifyInteractiveAnimationSequenceStart( this.getId() );
+    this.bIsRunning = true;
+};
+
+InteractiveAnimationSequence.prototype.end = function()
+{
+    aSlideShow.notifyInteractiveAnimationSequenceEnd( this.getId() );
+    this.bIsRunning = false;
+};
+
+// ------------------------------------------------------------------------------------------ //
+/** class PriorityEntry
+ *  It provides an entry type for priority queues.
+ *  Higher is the value of nPriority higher is the priority of the created entry.
+ *
+ *  @param aValue
+ *      The object to be prioritized.
+ *  @param nPriority
+ *      An integral number representing the object priority.
+ *
+ */
+function PriorityEntry( aValue, nPriority )
+{
+    this.aValue = aValue;
+    this.nPriority = nPriority;
+}
+
+/** EventEntry.compare
+ *  Compare priority of two entries.
+ *
+ *  @param aLhsEntry
+ *      An instance of type PriorityEntry.
+ *  @param aRhsEntry
+ *      An instance of type PriorityEntry.
+ *  @return {Boolean}
+ *      True if the first entry has higher priority of the second entry,
+ *      false otherwise.
+ */
+PriorityEntry.compare = function( aLhsEntry, aRhsEntry )
+{
+    return ( aLhsEntry.nPriority < aRhsEntry.nPriority );
+};
 
 // ------------------------------------------------------------------------------------------ //
 function EventMultiplexer( aTimerEventQueue )
 {
     this.aTimerEventQueue = aTimerEventQueue;
     this.aEventMap = new Object();
+    this.aMouseClickHandlerSet = new PriorityQueue( PriorityEntry.compare );
     this.aSkipEffectEvent = null;
     this.aRewindCurrentEffectEvent = null;
     this.aRewindLastEffectEvent = null;
 }
 
+EventMultiplexer.prototype.registerMouseClickHandler = function( aHandler, nPriority )
+{
+    var aHandlerEntry = new PriorityEntry( aHandler, nPriority );
+    this.aMouseClickHandlerSet.push( aHandlerEntry );
+};
+
+EventMultiplexer.prototype.notifyMouseClick = function( aMouseEvent )
+{
+    var aMouseClickHandlerSet = this.aMouseClickHandlerSet;
+    var nSize = aMouseClickHandlerSet.size();
+    for( var i = 0; i < nSize; ++i )
+    {
+        var aHandlerEntry = aMouseClickHandlerSet.aSequence[i];
+        if( aHandlerEntry.aValue.handleClick( aMouseEvent ) )
+            break;
+    }
+};
 
 EventMultiplexer.prototype.registerEvent = function( eEventType, aNotifierId, aEvent )
 {
@@ -11284,12 +11427,17 @@ function SlideShow()
     this.aTimerEventQueue = new TimerEventQueue( this.aTimer );
     this.aActivityQueue = new ActivityQueue( this.aTimer );
     this.aNextEffectEventArray = null;
+    this.aInteractiveAnimationSequenceMap = null;
     this.aEventMultiplexer = null;
 
-    this.aContext = new SlideShowContext( this.aTimerEventQueue, this.aEventMultiplexer,
-                                          this.aNextEffectEventArray, this.aActivityQueue );
+    this.aContext = new SlideShowContext( this.aTimerEventQueue,
+                                          this.aEventMultiplexer,
+                                          this.aNextEffectEventArray,
+                                          this.aInteractiveAnimationSequenceMap,
+                                          this.aActivityQueue );
     this.nCurrentEffect = 0;
     this.eDirection = FORWARD;
+    this.nTotalInteracAnimSeqRunning = 0;
     this.bIsIdle = true;
     this.bIsEnabled = true;
     this.bIsRewinding = false;
@@ -11299,19 +11447,27 @@ function SlideShow()
 }
 
 
-SlideShow.prototype.setSlideEvents = function( aNextEffectEventArray, aEventMultiplexer )
+SlideShow.prototype.setSlideEvents = function( aNextEffectEventArray,
+                                               aInteractiveAnimationSequenceMap,
+                                               aEventMultiplexer )
 {
     if( !aNextEffectEventArray )
         log( 'SlideShow.setSlideEvents: aNextEffectEventArray is not valid' );
 
+    if( !aInteractiveAnimationSequenceMap )
+        log( 'SlideShow.setSlideEvents:aInteractiveAnimationSequenceMap  is not valid' );
+
     if( !aEventMultiplexer )
         log( 'SlideShow.setSlideEvents: aEventMultiplexer is not valid' );
 
     this.aContext.aNextEffectEventArray = aNextEffectEventArray;
     this.aNextEffectEventArray = aNextEffectEventArray;
+    this.aContext.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
+    this.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
     this.aContext.aEventMultiplexer = aEventMultiplexer;
     this.aEventMultiplexer = aEventMultiplexer;
     this.nCurrentEffect = 0;
+    this.nTotalInteracAnimSeqRunning = 0;
 };
 
 SlideShow.prototype.createSlideTransition = function( aSlideTransitionHandler, aLeavingSlide, aEnteringSlide, aTransitionEndEvent )
@@ -11361,6 +11517,11 @@ SlideShow.prototype.isRunning = function()
     return !this.bIsIdle;
 };
 
+SlideShow.prototype.isInterAnimSeqRunning = function()
+{
+    return ( this.nTotalInteracAnimSeqRunning > 0 );
+};
+
 SlideShow.prototype.isEnabled = function()
 {
     return this.bIsEnabled;
@@ -11393,11 +11554,27 @@ SlideShow.prototype.notifyTransitionEnd = function( nSlideIndex )
     }
 };
 
+SlideShow.prototype.notifyInteractiveAnimationSequenceStart = function( nNodeId )
+{
+    ++this.nTotalInteracAnimSeqRunning;
+};
+
+SlideShow.prototype.notifyInteractiveAnimationSequenceEnd = function( nNodeId )
+{
+    assert( this.nTotalInteracAnimSeqRunning > 0,
+            'SlideShow.notifyInteractiveAnimationSequenceStart: ' +
+            'the total number of running interactive application is zero' );
+    --this.nTotalInteracAnimSeqRunning;
+};
+
 SlideShow.prototype.nextEffect = function()
 {
     if( !this.isEnabled() )
         return false;
 
+    if( this.isInterAnimSeqRunning() )
+        return true;
+
     if( this.isRunning() )
     {
         this.skipCurrentEffect();
@@ -11443,6 +11620,9 @@ SlideShow.prototype.skipEffect = function()
     if( this.bIsSkipping || this.bIsRewinding )
         return true;
 
+    if( this.isInterAnimSeqRunning() )
+        return true;
+
     if( this.isRunning() )
     {
         this.skipCurrentEffect();
@@ -11478,6 +11658,9 @@ SlideShow.prototype.skipAllEffects = function()
     if( this.bIsSkippingAll )
         return true;
 
+    if( this.isInterAnimSeqRunning() )
+        return true;
+
     this.bIsSkippingAll = true;
     if( this.isRunning() )
     {
@@ -11513,6 +11696,9 @@ SlideShow.prototype.rewindEffect = function()
     if( this.bIsSkipping || this.bIsRewinding )
         return;
 
+    if( this.isInterAnimSeqRunning() )
+        return true;
+
     if( this.nCurrentEffect == 0 )
     {
         this.rewindToPreviousSlide();
@@ -11717,11 +11903,12 @@ var aSlideShow = null;
 
 
 // ------------------------------------------------------------------------------------------ //
-function SlideShowContext( aTimerEventQueue, aEventMultiplexer, aNextEffectEventArray, aActivityQueue)
+function SlideShowContext( aTimerEventQueue, aEventMultiplexer, aNextEffectEventArray, aInteractiveAnimationSequenceMap, aActivityQueue)
 {
     this.aTimerEventQueue = aTimerEventQueue;
     this.aEventMultiplexer = aEventMultiplexer;
     this.aNextEffectEventArray = aNextEffectEventArray;
+    this.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
     this.aActivityQueue = aActivityQueue;
     this.bIsSkipping = false;
 }
commit ada5f5f507b2816b32280742e16d9e54d4b10b98
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Thu Jul 5 19:17:26 2012 +0200

    Bug fix: now the JavaScript engine parse the ‘counter-clockwise’ value correctly.
    Previously it expected ‘counterclockwise’.

diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js
index 984eaef..a5a3a3f 100644
--- a/filter/source/svg/presentation_engine.js
+++ b/filter/source/svg/presentation_engine.js
@@ -4117,9 +4117,9 @@ aColorSpaceOutMap = [ 'rgb', 'hsl' ];
 var CLOCKWISE               = 0;
 var COUNTERCLOCKWISE        = 1;
 
-aClockDirectionInMap = { 'clockwise': CLOCKWISE, 'counterclockwise': COUNTERCLOCKWISE };
+aClockDirectionInMap = { 'clockwise': CLOCKWISE, 'counter-clockwise': COUNTERCLOCKWISE };
 
-aClockDirectionOutMap = [ 'clockwise', 'counterclockwise' ];
+aClockDirectionOutMap = [ 'clockwise', 'counter-clockwise' ];
 
 
 // Attribute Value Types
commit b0b7d169a062bf5c3e8b51d85620acce0b546086
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Thu Jul 5 18:50:39 2012 +0200

    Bug fix: now the darken and lighten effects work in the right way.
    
    Edited the effects.xml and changed effect direction to counterclockwise.

diff --git a/sd/xml/effects.xml b/sd/xml/effects.xml
index be341ef..b555a32 100644
--- a/sd/xml/effects.xml
+++ b/sd/xml/effects.xml
@@ -1172,9 +1172,9 @@
     <anim:par smil:begin="indefinite" smil:fill="hold">
     <anim:par smil:begin="0" smil:fill="hold">
     <anim:par smil:begin="0" smil:fill="hold" pres:node-type="on-click" pres:preset-class="emphasis" pres:preset-id="ooo-emphasis-darken">
-        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="color" smil:by="hsl(0,-12%,-25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="clockwise"/>
-        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="fill-color" smil:by="hsl(0,-12%,-25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="clockwise"/>
-        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="stroke-color" smil:by="hsl(0,-12%,-25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="clockwise"/>
+        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="color" smil:by="hsl(0,-12%,-25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="counterclockwise"/>
+        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="fill-color" smil:by="hsl(0,-12%,-25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="counterclockwise"/>
+        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="stroke-color" smil:by="hsl(0,-12%,-25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="counterclockwise"/>
         <anim:set smil:dur="0.5" smil:fill="hold" smil:attributeName="fill" smil:to="solid"/>
     </anim:par>
     </anim:par>
@@ -1200,9 +1200,9 @@
     <anim:par smil:begin="indefinite" smil:fill="hold">
     <anim:par smil:begin="0" smil:fill="hold">
     <anim:par smil:begin="0" smil:fill="hold" pres:node-type="on-click" pres:preset-class="emphasis" pres:preset-id="ooo-emphasis-lighten">
-        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="color" smil:by="hsl(0,12%,25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="clockwise"/>
-        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="fill-color" smil:by="hsl(0,12%,25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="clockwise"/>
-        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="stroke-color" smil:by="hsl(0,12%,25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="clockwise"/>
+        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="color" smil:by="hsl(0,12%,25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="counterclockwise"/>
+        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="fill-color" smil:by="hsl(0,12%,25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="counterclockwise"/>
+        <anim:animateColor smil:dur="0.5" smil:fill="hold" smil:attributeName="stroke-color" smil:by="hsl(0,12%,25%)" anim:color-interpolation="hsl" anim:color-interpolation-direction="counterclockwise"/>
         <anim:set smil:dur="0.5" smil:fill="hold" smil:attributeName="fill" smil:to="solid"/>
     </anim:par>
     </anim:par>
commit aa50b3bd6b1282e6dff621df14997911b7a1f10f
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Thu Jul 5 16:35:02 2012 +0200

    Bug fix: now repeated animations and begin=’after effect ‘ works properly together.
    
    Modified the CustomAnimationEffect class in order to take into account the repeatCount
    attribute value when computing the node effect absolute duration.

diff --git a/sd/source/core/CustomAnimationEffect.cxx b/sd/source/core/CustomAnimationEffect.cxx
index 2ed6115..65fbb4a 100644
--- a/sd/source/core/CustomAnimationEffect.cxx
+++ b/sd/source/core/CustomAnimationEffect.cxx
@@ -281,6 +281,10 @@ void CustomAnimationEffect::setNode( const ::com::sun::star::uno::Reference< ::c
     }
 
     mfAbsoluteDuration = mfDuration;
+    double fRepeatCount = 1.0;
+    if( (mxNode->getRepeatCount()) >>= fRepeatCount )
+        mfAbsoluteDuration *= fRepeatCount;
+
     checkForText();
 }
 
@@ -755,7 +759,9 @@ void CustomAnimationEffect::setDuration( double fDuration )
     {
         double fScale = fDuration / mfDuration;
         mfDuration = fDuration;
-        mfAbsoluteDuration = mfDuration;
+        double fRepeatCount = 1.0;
+        getRepeatCount() >>= fRepeatCount;
+        mfAbsoluteDuration = mfDuration * fRepeatCount;
 
         // calculate effect duration and get target shape
         Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
@@ -954,7 +960,12 @@ sal_Int16 CustomAnimationEffect::getFill() const
 void CustomAnimationEffect::setRepeatCount( const Any& rRepeatCount )
 {
     if( mxNode.is() )
+    {
         mxNode->setRepeatCount( rRepeatCount );
+        double fRepeatCount = 1.0;
+        rRepeatCount >>= fRepeatCount;
+        mfAbsoluteDuration = mfDuration * fRepeatCount;
+    }
 }
 
 // --------------------------------------------------------------------
commit c1598303e4c458795c28d41408a00d2082396113
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Thu Jul 5 10:19:47 2012 +0200

    Bug fix: now a clip-based transition works correctly when mode=’out’ and reverse method involves flipping.

diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js
index f042263..984eaef 100644
--- a/filter/source/svg/presentation_engine.js
+++ b/filter/source/svg/presentation_engine.js
@@ -7933,8 +7933,10 @@ ClippingFunctor.prototype.perform = function( nT, nWidth, nHeight )
 {
     var aClipPoly = this.aParametricPolyPolygon.perform( this.bForwardParameterSweep ? nT : (1 - nT) );
 
-    if( this.bFlip )
-        aClipPoly.changeOrientation();
+    // Note: even if the reverse method involves flipping we don't need to
+    // change the clip-poly orientation because we utilize the 'nonzero'
+    // clip-rule.
+    // See: http://www.w3.org/TR/SVG11/painting.html#FillRuleProperty
 
     if( this.bSubtractPolygon )
     {
commit aaed0c6e4953941c232cb17fcedda92635ec5905
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Wed Jul 4 18:50:46 2012 +0200

    Provided support for rewinding to the previous slide and for skipping / rewinding all effects.
    
    The following method of the SlideShow class has been implemented:
    skipAllEffects, rewindToPreviousSlide, rewindAllEffects.
    Commented out the calls to the initAnimatedElement methods for ValueListActivity and
    FromToByActivity class as it is the source of a bug.

diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js
index 2e66561..f042263 100644
--- a/filter/source/svg/presentation_engine.js
+++ b/filter/source/svg/presentation_engine.js
@@ -150,13 +150,13 @@ function getDefaultKeyCodeDictionary()
     keyCodeDict[SLIDE_MODE][RIGHT_KEY]
         = function() { return dispatchEffects(1); };
     keyCodeDict[SLIDE_MODE][UP_KEY]
-        = function() { return skipEffects(-1); };
+        = function() { return aSlideShow.rewindEffect(); };
     keyCodeDict[SLIDE_MODE][DOWN_KEY]
         = function() { return skipEffects(1); };
     keyCodeDict[SLIDE_MODE][PAGE_UP_KEY]
-        = function() { return switchSlide( -1, true ); };
+        = function() { return aSlideShow.rewindAllEffects(); };
     keyCodeDict[SLIDE_MODE][PAGE_DOWN_KEY]
-        = function() { return switchSlide( 1, true ); };
+        = function() { return skipAllEffects(); };
     keyCodeDict[SLIDE_MODE][HOME_KEY]
         = function() { return aSlideShow.displaySlide( 0, true ); };
     keyCodeDict[SLIDE_MODE][END_KEY]
@@ -1433,7 +1433,7 @@ var NAVDBG = new DebugPrinter();
 NAVDBG.off();
 
 var ANIMDBG = new DebugPrinter();
-ANIMDBG.off();
+ANIMDBG.on();
 
 var aRegisterEventDebugPrinter = new DebugPrinter();
 aRegisterEventDebugPrinter.off();
@@ -2985,6 +2985,15 @@ function dispatchEffects(dir)
     }
 }
 
+function skipAllEffects()
+{
+    var bRet = aSlideShow.skipAllEffects();
+    if( !bRet )
+    {
+        switchSlide( 1, true );
+    }
+}
+
 function skipEffects(dir)
 {
     if( dir == 1 )
@@ -2993,12 +3002,12 @@ function skipEffects(dir)
 
         if( !bRet )
         {
-            switchSlide( 1, false );
+            switchSlide( 1, true );
         }
     }
     else
     {
-        switchSlide( dir, false );
+        switchSlide( dir, true );
     }
 }
 
@@ -10725,7 +10734,7 @@ function FromToByActivityTemplate( BaseType ) // template parameter
         this.nIteration = 0;
         this.bCumulative = bAccumulate;
 
-        this.initAnimatedElement();
+        //this.initAnimatedElement();
 
     }
     extend( FromToByActivity, BaseType );
@@ -10931,7 +10940,7 @@ function  ValueListActivityTemplate( BaseType ) // template parameter
         this.bCumulative = bAccumulate;
         this.aLastValue = this.aValueList[ this.aValueList.length - 1 ];
 
-        this.initAnimatedElement();
+        //this.initAnimatedElement();
     }
     extend( ValueListActivity, BaseType );
 
@@ -10943,7 +10952,7 @@ function  ValueListActivityTemplate( BaseType ) // template parameter
             ANIMDBG.print( 'createValueListActivity: value[' + i + '] = ' + this.aValueList[i] );
         }
 
-        this.initAnimatedElement();
+        //this.initAnimatedElement();
     };
 
     ValueListActivity.prototype.initAnimatedElement = function()
@@ -11283,6 +11292,7 @@ function SlideShow()
     this.bIsEnabled = true;
     this.bIsRewinding = false;
     this.bIsSkipping = false;
+    this.bIsSkippingAll = false;
     this.bNoSlideTransition = false;
 }
 
@@ -11422,30 +11432,6 @@ SlideShow.prototype.skipCurrentEffect = function()
     this.bIsSkipping = false;
 };
 
-/** rewindEffect
- *  Rewind the current playing effect or the last played one.
- *
- */
-SlideShow.prototype.rewindEffect = function()
-{
-    if( this.bIsSkipping || this.bIsRewinding )
-        return true;
-
-    this.bIsRewinding = true;
-    if( this.isRunning() )
-    {
-        this.aEventMultiplexer.notifyRewindCurrentEffectEvent();
-    }
-    else
-    {
-        this.aEventMultiplexer.notifyRewindLastEffectEvent();
-    }
-    if( this.nCurrentEffect > 0 )
-        --this.nCurrentEffect;
-    this.update();
-    this.bIsRewinding = false;
-};
-
 /** skipEffect
  *  Skip the next effect to be played.
  *
@@ -11478,6 +11464,105 @@ SlideShow.prototype.skipEffect = function()
     return true;
 };
 
+/** skipAllEffects
+ *  Skip all left effects on the current slide.
+ *
+ *  @return {Boolean}
+ *      True if it already skipping or when it has ended skipping,
+ *      false if the next slide needs to be displayed.
+ */
+SlideShow.prototype.skipAllEffects = function()
+{
+    if( this.bIsSkippingAll )
+        return true;
+
+    this.bIsSkippingAll = true;
+    if( this.isRunning() )
+    {
+        this.skipCurrentEffect();
+    }
+    else if( !this.aNextEffectEventArray
+               || ( this.nCurrentEffect >= this.aNextEffectEventArray.size() ) )
+    {
+        this.bIsSkippingAll = false;
+        return false;
+    }
+
+    // Pay attention here: a new next effect event is appended to
+    // aNextEffectEventArray only after the related animation node has been
+    // resolved, that is only after the animation node related to the previous
+    // effect has notified to be deactivated to the main sequence time container.
+    // So you should avoid any optimization here because the size of
+    // aNextEffectEventArray will going on increasing after every skip action.
+    while( this.nCurrentEffect < this.aNextEffectEventArray.size() )
+    {
+        this.skipEffect();
+    }
+    this.bIsSkippingAll = false;
+    return true;
+};
+
+/** rewindEffect
+ *  Rewind the current playing effect or the last played one.
+ *
+ */
+SlideShow.prototype.rewindEffect = function()
+{
+    if( this.bIsSkipping || this.bIsRewinding )
+        return;
+
+    if( this.nCurrentEffect == 0 )
+    {
+        this.rewindToPreviousSlide();
+        return;
+    }
+
+    this.bIsRewinding = true;
+    if( this.isRunning() )
+    {
+        this.aEventMultiplexer.notifyRewindCurrentEffectEvent();
+    }
+    else
+    {
+        this.aEventMultiplexer.notifyRewindLastEffectEvent();
+    }
+    if( this.nCurrentEffect > 0 )
+        --this.nCurrentEffect;
+    this.update();
+    this.bIsRewinding = false;
+};
+
+/** rewindToPreviousSlide
+ *  Displays the previous slide with all effects played.
+ *
+ */
+SlideShow.prototype.rewindToPreviousSlide = function()
+{
+    if( this.isRunning() )
+        return;
+    var nNewSlide = nCurSlide - 1;
+    this.displaySlide( nNewSlide, true );
+    this.skipAllEffects();
+};
+
+/** rewindAllEffects
+ *  Rewind all effects already played on the current slide.
+ *
+ */
+SlideShow.prototype.rewindAllEffects = function()
+{
+    if( this.nCurrentEffect == 0 )
+    {
+        this.rewindToPreviousSlide();
+        return;
+    }
+
+     while( this.nCurrentEffect > 0 )
+     {
+         this.rewindEffect();
+     }
+};
+
 SlideShow.prototype.displaySlide = function( nNewSlide, bSkipSlideTransition )
 {
     var aMetaDoc = theMetaDoc;
commit 9a786c8a1b76277e1926b19de9fc5732ae183a02
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Tue Jul 3 19:22:34 2012 +0200

    Performed some clean up and added documentation comments.

diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js
index 9b62dd7..2e66561 100644
--- a/filter/source/svg/presentation_engine.js
+++ b/filter/source/svg/presentation_engine.js
@@ -6281,6 +6281,7 @@ BaseContainerNode.prototype.removeEffect = function()
     var nChildrenCount = this.aChildrenArray.length;
     if( nChildrenCount == 0 )
         return;
+    // We remove effect in reverse order.
     for( var i = nChildrenCount - 1; i >= 0; --i )
     {
         if( ( this.aChildrenArray[i].getState() & ( FROZEN_NODE | ENDED_NODE ) ) == 0 )
@@ -6465,11 +6466,24 @@ SequentialTimeContainer.prototype.notifyDeactivating = function( aNotifier )
     }
 };
 
+/** skipEffect
+ *  Skip the current playing shape effect.
+ *  Requires: the current node is the main sequence root node.
+ *
+ *  @param aChildNode
+ *      An animation node representing the root node of the shape effect being
+ *      played.
+ */
 SequentialTimeContainer.prototype.skipEffect = function( aChildNode )
 {
     if( this.isChildNode( aChildNode ) )
     {
+        // First off we end all queued activities.
         this.getContext().aActivityQueue.endAll();
+        // We signal that we are going to skip all subsequent animations by
+        // setting the bIsSkipping flag to 'true', then all queued events are
+        // fired immediately. In such a way the correct order of the various
+        // events that belong to the animation time-line is preserved.
         this.getContext().bIsSkipping = true;
         this.getContext().aTimerEventQueue.forceEmpty();
         this.getContext().bIsSkipping = false;
@@ -6478,63 +6492,122 @@ SequentialTimeContainer.prototype.skipEffect = function( aChildNode )
     }
     else
     {
-        log( 'SequentialTimeContainer.skipEffect: unknown child: ' + aChildNode.getId() );
+        log( 'SequentialTimeContainer.skipEffect: unknown child: '
+                 + aChildNode.getId() );
     }
 };
 
+/** rewindCurrentEffect
+ *  Rewind a playing shape effect.
+ *  Requires: the current node is the main sequence root node.
+ *
+ *  @param aChildNode
+ *      An animation node representing the root node of the shape effect being
+ *      played
+ */
 SequentialTimeContainer.prototype.rewindCurrentEffect = function( aChildNode )
 {
     if( this.isChildNode( aChildNode ) )
     {
-        assert( !this.bIsRewinding, 'SequentialTimeContainer.rewindCurrentEffect: is already rewinding.' );
+        assert( !this.bIsRewinding,
+                'SequentialTimeContainer.rewindCurrentEffect: is already rewinding.' );
 
+        // We signal we are rewinding so the notifyDeactivating method returns
+        // immediately without increment the finished children counter and
+        // resolve the next child.
         this.bIsRewinding = true;
+        // First off we end all queued activities.
         this.getContext().aActivityQueue.endAll();
+        // We signal that we are going to skip all subsequent animations by
+        // setting the bIsSkipping flag to 'true', then all queued events are
+        // fired immediately. In such a way the correct order of the various
+        // events that belong to the animation time-line is preserved.
         this.getContext().bIsSkipping = true;
         this.getContext().aTimerEventQueue.forceEmpty();
         this.getContext().bIsSkipping = false;
+        // We end all new activities appended to the activity queue by
+        // the fired events.
         this.getContext().aActivityQueue.endAll();
 
+        // Now we perform a final 'end' and restore the animated shape to
+        // the state it was before the current effect was applied.
         aChildNode.end();
         aChildNode.removeEffect();
+        // Finally we place the child node to the 'unresolved' state and
+        // resolve it again.
         aChildNode.init();
         this.resolveChild( aChildNode );
         this.bIsRewinding = false;
     }
     else
     {
-        log( 'SequentialTimeContainer.rewindCurrentEffect: unknown child: ' + aChildNode.getId() );
+        log( 'SequentialTimeContainer.rewindCurrentEffect: unknown child: '
+                 + aChildNode.getId() );
     }
 };
 
+/** rewindLastEffect
+ *  Rewind the last ended effect.
+ *  Requires: the current node is the main sequence root node.
+ *
+ *  @param aChildNode
+ *      An animation node representing the root node of the next shape effect
+ *      to be played.
+ */
 SequentialTimeContainer.prototype.rewindLastEffect = function( aChildNode )
 {
     if( this.isChildNode( aChildNode ) )
     {
-        assert( !this.bIsRewinding, 'SequentialTimeContainer.rewindLastEffect: is already rewinding.' );
+        assert( !this.bIsRewinding,
+                'SequentialTimeContainer.rewindLastEffect: is already rewinding.' );
 
+        // We signal we are rewinding so the notifyDeactivating method returns
+        // immediately without increment the finished children counter and
+        // resolve the next child.
         this.bIsRewinding = true;
+        // We end the current effect and remove any change it applies on the
+        // animated shape.
         this.getContext().aTimerEventQueue.forceEmpty();
         this.getContext().aActivityQueue.clear();
-
         aChildNode.end();
         aChildNode.removeEffect();
+
+        // As we rewind the previous effect we need to decrease the finished
+        // children counter.
         --this.nFinishedChildren;
         var aPreviousChildNode = this.aChildrenArray[ this.nFinishedChildren ];
+        // No need to invoke the end method for the previous child as it is
+        // already in the ENDED state.
+
         aPreviousChildNode.removeEffect();
+        // We place the child node to the 'unresolved' state.
         aPreviousChildNode.init();
-        // We need to re-initialize it too, because it is in state ENDED now,
-        // and cannot be resolved again later.
+        // We need to re-initialize the old current child too, because it is
+        // in ENDED state now, On the contrary it cannot be resolved again later.
         aChildNode.init();
         this.resolveChild( aPreviousChildNode );
         this.bIsRewinding = false;
     }
     else
     {
-        log( 'SequentialTimeContainer.rewindLastEffect: unknown child: ' + aChildNode.getId() );
+        log( 'SequentialTimeContainer.rewindLastEffect: unknown child: '
+                 + aChildNode.getId() );
     }
 };
 
+/** resolveChild
+ *  Resolve the passed child.
+ *  In case this node is a main sequence root node events for skipping and
+ *  rewinding the effect related to the passed child node are created and
+ *  registered.
+ *
+ *  @param aChildNode
+ *      An animation node representing the root node of the next shape effect
+ *      to be played.
+ *  @return
+ *      It returns true if the passed child has been resolved successfully,
+ *      false otherwise.
+ */
 SequentialTimeContainer.prototype.resolveChild = function( aChildNode )
 {
     var bResolved = aChildNode.resolve();
@@ -6551,7 +6624,7 @@ SequentialTimeContainer.prototype.resolveChild = function( aChildNode )
             this.aRewindCurrentEffectEvent.dispose();
 
         this.aRewindCurrentEffectEvent = makeEvent( bind2( SequentialTimeContainer.prototype.rewindCurrentEffect, this, aChildNode ) );
-        this.aContext.aEventMultiplexer.registerRewindEffectEvent( this.aRewindCurrentEffectEvent );
+        this.aContext.aEventMultiplexer.registerRewindCurrentEffectEvent( this.aRewindCurrentEffectEvent );
 
         if( this.aRewindLastEffectEvent )
             this.aRewindLastEffectEvent.dispose();
@@ -8529,11 +8602,6 @@ AnimatedElement.prototype.getId = function()
     return this.aActiveElement.getAttribute( 'id' );
 };
 
-AnimatedElement.prototype.isUpdated = function()
-{
-    return this.bIsUpdated;
-};
-
 AnimatedElement.prototype.getAdditiveMode = function()
 {
     return this.eAdditiveMode;
@@ -8571,8 +8639,7 @@ AnimatedElement.prototype.notifySlideStart = function()
 
 AnimatedElement.prototype.notifyAnimationStart = function()
 {
-    this.DBG( '.notifyAnimationStart invoked' );
-    this.bIsUpdated = false;
+    // empty body
 };
 
 AnimatedElement.prototype.notifyAnimationEnd = function()
@@ -8582,28 +8649,16 @@ AnimatedElement.prototype.notifyAnimationEnd = function()
 
 AnimatedElement.prototype.notifyNextEffectStart = function( nEffectIndex )
 {
-//    assert( this.nCurrentState === nEffectIndex,
-//            'AnimatedElement(' + this.getId() + ').notifyNextEffectStart: assertion (current state == effect index) failed' );
-//
-//    if( this.isUpdated() )
-//    {
-//        if( !this.aElementArray[ nEffectIndex ] )
-//        {
-//            this.aElementArray[ nEffectIndex ] =  this.aElementArray[ this.nCurrentState ];
-//            this.DBG( '.notifyNextEffectStart(' + nEffectIndex + '): new state set to previous one ' );
-//        }
-//    }
-//    else
-//    {
-//        if( !this.aElementArray[ nEffectIndex ] )
-//        {
-//            this.aElementArray[ nEffectIndex ] = this.aActiveElement.cloneNode( true );
-//            this.DBG( '.notifyNextEffectStart(' + nEffectIndex + '): cloned active state ' );
-//        }
-//    }
-//    ++this.nCurrentState;
+    // empty body
 };
 
+/** saveState
+ *  Save the state of the managed animated element and append it to aStateSet
+ *  using the passed animation node id as key.
+ *
+ *  @param nAnimationNodeId
+ *      A non negative integer representing the unique id of an animation node.
+ */
 AnimatedElement.prototype.saveState = function( nAnimationNodeId )
 {
     ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').saveState(' + nAnimationNodeId +')' );
@@ -8620,6 +8675,16 @@ AnimatedElement.prototype.saveState = function( nAnimationNodeId )
 
 };
 
+/** restoreState
+ *  Restore the state of the managed animated element to the state with key
+ *  the passed animation node id.
+ *
+ *  @param nAnimationNodeId
+ *      A non negative integer representing the unique id of an animation node.
+ *
+ *  @return
+ *      True if the restoring operation is successful, false otherwise.
+ */
 AnimatedElement.prototype.restoreState = function( nAnimationNodeId )
 {
     if( !this.aStateSet[ nAnimationNodeId ] )
@@ -9676,7 +9741,7 @@ function EventMultiplexer( aTimerEventQueue )
     this.aTimerEventQueue = aTimerEventQueue;
     this.aEventMap = new Object();
     this.aSkipEffectEvent = null;
-    this.aRewindEffectEvent = null;
+    this.aRewindCurrentEffectEvent = null;
     this.aRewindLastEffectEvent = null;
 }
 
@@ -9727,17 +9792,17 @@ EventMultiplexer.prototype.notifySkipEffectEvent = function()
     }
 };
 
-EventMultiplexer.prototype.registerRewindEffectEvent = function( aEvent )
+EventMultiplexer.prototype.registerRewindCurrentEffectEvent = function( aEvent )
 {
-    this.aRewindEffectEvent = aEvent;
+    this.aRewindCurrentEffectEvent = aEvent;
 };
 
-EventMultiplexer.prototype.notifyRewindEffectEvent = function()
+EventMultiplexer.prototype.notifyRewindCurrentEffectEvent = function()
 {
-    if( this.aRewindEffectEvent )
+    if( this.aRewindCurrentEffectEvent )
     {
-        this.aTimerEventQueue.addEvent( this.aRewindEffectEvent );
-        this.aRewindEffectEvent = null;
+        this.aTimerEventQueue.addEvent( this.aRewindCurrentEffectEvent );
+        this.aRewindCurrentEffectEvent = null;
     }
 };
 
@@ -11342,6 +11407,10 @@ SlideShow.prototype.nextEffect = function()
     return true;
 };
 
+/** skipCurrentEffect
+ *  Skip the current playing effect.
+ *
+ */
 SlideShow.prototype.skipCurrentEffect = function()
 {
     if( this.bIsSkipping || this.bIsRewinding )
@@ -11353,6 +11422,10 @@ SlideShow.prototype.skipCurrentEffect = function()
     this.bIsSkipping = false;
 };
 
+/** rewindEffect
+ *  Rewind the current playing effect or the last played one.
+ *
+ */
 SlideShow.prototype.rewindEffect = function()
 {
     if( this.bIsSkipping || this.bIsRewinding )
@@ -11361,17 +11434,22 @@ SlideShow.prototype.rewindEffect = function()
     this.bIsRewinding = true;
     if( this.isRunning() )
     {
-        this.aEventMultiplexer.notifyRewindEffectEvent();
+        this.aEventMultiplexer.notifyRewindCurrentEffectEvent();
     }
     else
     {
         this.aEventMultiplexer.notifyRewindLastEffectEvent();
     }
-    --this.nCurrentEffect;
+    if( this.nCurrentEffect > 0 )
+        --this.nCurrentEffect;
     this.update();
     this.bIsRewinding = false;
 };
 
+/** skipEffect
+ *  Skip the next effect to be played.
+ *
+ */
 SlideShow.prototype.skipEffect = function()
 {
     if( this.bIsSkipping || this.bIsRewinding )
@@ -11400,16 +11478,6 @@ SlideShow.prototype.skipEffect = function()
     return true;
 };
 
-//SlideShow.prototype.previousEffect = function()
-//{
-//    if( this.nCurrentEffect <= 0 )
-//        return false;
-//    this.eDirection = BACKWARD;
-//    this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
-//    --this.nCurrentEffect;
-//    return true;
-//};
-
 SlideShow.prototype.displaySlide = function( nNewSlide, bSkipSlideTransition )
 {
     var aMetaDoc = theMetaDoc;
commit de912929b54d8b4ab1898a8f011233c340c2e080
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Tue Jul 3 15:39:32 2012 +0200

    Changed the rewindCurrentEffect method.
    
    The change was needed because rewinding a playing shape transition effect based on
    a clip-path doesn’t work properly on Firefox and Internet Explorer.

diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js
index d705658..9b62dd7 100644
--- a/filter/source/svg/presentation_engine.js
+++ b/filter/source/svg/presentation_engine.js
@@ -6489,8 +6489,11 @@ SequentialTimeContainer.prototype.rewindCurrentEffect = function( aChildNode )
         assert( !this.bIsRewinding, 'SequentialTimeContainer.rewindCurrentEffect: is already rewinding.' );
 
         this.bIsRewinding = true;
+        this.getContext().aActivityQueue.endAll();
+        this.getContext().bIsSkipping = true;
         this.getContext().aTimerEventQueue.forceEmpty();
-        this.getContext().aActivityQueue.clear();
+        this.getContext().bIsSkipping = false;
+        this.getContext().aActivityQueue.endAll();
 
         aChildNode.end();
         aChildNode.removeEffect();
commit ce5a16c4e34f50f101ec25839686f28feca42aa8
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Mon Jul 2 23:36:35 2012 +0200

    Skipping a single effect works in JavaScript even if the effect is rewinded (fill=remove).
    
    Added a bIsSkipping flag to the SlideShowContext class and modified the activate_st method
    of the AnimationBaseNode class. Now when we are skipping the managed activity is
    not appended to the activity class but the end method is executed immediately.
    In such a way the correct order of the various events that belong to the animation time-line
    is preserved.

diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js
index c71cad6..d705658 100644
--- a/filter/source/svg/presentation_engine.js
+++ b/filter/source/svg/presentation_engine.js
@@ -5745,7 +5745,14 @@ AnimationBaseNode.prototype.activate_st = function()
     {
         this.saveStateOfAnimatedElement();
         this.aActivity.setTargets( this.getAnimatedElement() );
-        this.getContext().aActivityQueue.addActivity( this.aActivity );
+        if( this.getContext().bIsSkipping  )
+        {
+            this.aActivity.end();
+        }
+        else
+        {
+            this.getContext().aActivityQueue.addActivity( this.aActivity );
+        }
     }
     else
     {
@@ -5821,8 +5828,7 @@ AnimationBaseNode.prototype.saveStateOfAnimatedElement = function()
 
 AnimationBaseNode.prototype.removeEffect = function()
 {
-    log( 'AnimationBaseNode.removeEffect invoked' );
-    this.getAnimatedElement().setTo( this.getId() );
+    this.getAnimatedElement().restoreState( this.getId() );
 };
 
 AnimationBaseNode.prototype.getTargetElement = function()
@@ -6175,7 +6181,7 @@ BaseContainerNode.prototype.deactivate_st = function( eDestState )
         // end all children that are not ENDED:
         this.forEachChildNode( mem_fn( 'end' ), ~ENDED_NODE );
         if( this.getFillMode() == FILL_MODE_REMOVE )
-            this.forEachChildNode( mem_fn( 'removeEffect' ), ENDED_NODE );
+            this.removeEffect();
     }
 };
 
@@ -6277,8 +6283,14 @@ BaseContainerNode.prototype.removeEffect = function()
         return;
     for( var i = nChildrenCount - 1; i >= 0; --i )
     {
-        if( ( this.aChildrenArray[i].getState() & FROZEN_NODE | ENDED_NODE ) == 0 )
+        if( ( this.aChildrenArray[i].getState() & ( FROZEN_NODE | ENDED_NODE ) ) == 0 )
+        {
+            log( 'BaseContainerNode.removeEffect: child(id:'
+                 + this.aChildrenArray[i].getId() + ') is neither frozen nor ended;'
+                 + ' state: '
+                 + aTransitionModeOutMap[ this.aChildrenArray[i].getState() ] );
             continue;
+        }
         this.aChildrenArray[i].removeEffect();
     }
 };
@@ -6458,7 +6470,9 @@ SequentialTimeContainer.prototype.skipEffect = function( aChildNode )
     if( this.isChildNode( aChildNode ) )
     {
         this.getContext().aActivityQueue.endAll();
+        this.getContext().bIsSkipping = true;
         this.getContext().aTimerEventQueue.forceEmpty();
+        this.getContext().bIsSkipping = false;
         var aEvent = makeEvent( bind2( aChildNode.deactivate, aChildNode ) );
         this.getContext().aTimerEventQueue.addEvent( aEvent );
     }
@@ -8589,6 +8603,7 @@ AnimatedElement.prototype.notifyNextEffectStart = function( nEffectIndex )
 
 AnimatedElement.prototype.saveState = function( nAnimationNodeId )
 {
+    ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').saveState(' + nAnimationNodeId +')' );
     if( !this.aStateSet[ nAnimationNodeId ] )
     {
         this.aStateSet[ nAnimationNodeId ] = new Object();
@@ -8602,15 +8617,17 @@ AnimatedElement.prototype.saveState = function( nAnimationNodeId )
 
 };
 
-AnimatedElement.prototype.setTo = function( nAnimationNodeId )
+AnimatedElement.prototype.restoreState = function( nAnimationNodeId )
 {
     if( !this.aStateSet[ nAnimationNodeId ] )
     {
-        log( 'AnimatedElement(' + this.getId() + ').setTo: state '
+        log( 'AnimatedElement(' + this.getId() + ').restoreState: state '
                  +nAnimationNodeId  + ' is not valid' );
         return false;
     }
 
+    ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').restoreState(' + nAnimationNodeId +')' );
+
     var aState = this.aStateSet[ nAnimationNodeId ];
     var bRet = this.setToElement( aState.aElement );
     if( bRet )
@@ -11196,8 +11213,8 @@ function SlideShow()
     this.eDirection = FORWARD;
     this.bIsIdle = true;
     this.bIsEnabled = true;
-    this.bIsSkipping = false;
     this.bIsRewinding = false;
+    this.bIsSkipping = false;
     this.bNoSlideTransition = false;
 }
 
@@ -11548,6 +11565,7 @@ function SlideShowContext( aTimerEventQueue, aEventMultiplexer, aNextEffectEvent
     this.aEventMultiplexer = aEventMultiplexer;
     this.aNextEffectEventArray = aNextEffectEventArray;
     this.aActivityQueue = aActivityQueue;
+    this.bIsSkipping = false;
 }
 
 
commit 159a45d06cc3d0e21b2b4e4b470591385db3503a
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Sat Jun 30 22:30:47 2012 +0200

    Implemented partial support for rewinding a single shape effect.
    Reworked the way saving/restoring the state of an animated shape is implemented.

diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js
index 0de9bea..c71cad6 100644
--- a/filter/source/svg/presentation_engine.js
+++ b/filter/source/svg/presentation_engine.js
@@ -146,7 +146,7 @@ function getDefaultKeyCodeDictionary()
 
     // slide mode
     keyCodeDict[SLIDE_MODE][LEFT_KEY]
-        = function() { return dispatchEffects(-1); };
+        = function() { return aSlideShow.rewindEffect(); };
     keyCodeDict[SLIDE_MODE][RIGHT_KEY]
         = function() { return dispatchEffects(1); };
     keyCodeDict[SLIDE_MODE][UP_KEY]
@@ -1433,7 +1433,7 @@ var NAVDBG = new DebugPrinter();
 NAVDBG.off();
 
 var ANIMDBG = new DebugPrinter();
-ANIMDBG.on();
+ANIMDBG.off();
 
 var aRegisterEventDebugPrinter = new DebugPrinter();
 aRegisterEventDebugPrinter.off();
@@ -3126,6 +3126,9 @@ function bind( aObject, aMethod )
 
 function bind2( aFunction )
 {
+    if( !aFunction  )
+        log( 'bind2: passed function is not valid.' );
+
     var aBoundArgList = arguments;
 
     var aResultFunction = null;
@@ -5272,6 +5275,7 @@ BaseNode.prototype.getParentNode = function()
 
 BaseNode.prototype.init = function()
 {
+    this.DBG( this.callInfo( 'init' ) );
     if( ! this.checkValidNode() )
         return false;
     if( this.aActivationEvent )
@@ -5652,7 +5656,6 @@ function AnimationBaseNode( aAnimElem, aParentNode, aNodeContext )
     this.bIsContainer = false;
     this.aTargetElement = null;
     this.aAnimatedElement = null;
-    this.nAnimatedElementOriginalState = 0;
     this.aActivity = null;
 
     this.nMinFrameCount = undefined;
@@ -5740,7 +5743,7 @@ AnimationBaseNode.prototype.activate_st = function()
 {
     if( this.aActivity )
     {
-        this.nAnimatedElementOriginalState = this.getAnimatedElement().getCurrentState();
+        this.saveStateOfAnimatedElement();
         this.aActivity.setTargets( this.getAnimatedElement() );
         this.getContext().aActivityQueue.addActivity( this.aActivity );
     }
@@ -5813,12 +5816,13 @@ AnimationBaseNode.prototype.hasPendingAnimation = function()
 
 AnimationBaseNode.prototype.saveStateOfAnimatedElement = function()
 {
-    this.getAnimatedElement().saveState();
+    this.getAnimatedElement().saveState( this.getId() );
 };
 
 AnimationBaseNode.prototype.removeEffect = function()
 {
-    this.getAnimatedElement().setTo( this.nAnimatedElementOriginalState );
+    log( 'AnimationBaseNode.removeEffect invoked' );
+    this.getAnimatedElement().setTo( this.getId() );
 };
 
 AnimationBaseNode.prototype.getTargetElement = function()
@@ -6268,7 +6272,15 @@ BaseContainerNode.prototype.repeat = function()
 
 BaseContainerNode.prototype.removeEffect = function()
 {
-    this.forEachChildNode( mem_fn( 'removeEffect' ), FROZEN_NODE | ENDED_NODE );
+    var nChildrenCount = this.aChildrenArray.length;
+    if( nChildrenCount == 0 )
+        return;
+    for( var i = nChildrenCount - 1; i >= 0; --i )
+    {
+        if( ( this.aChildrenArray[i].getState() & FROZEN_NODE | ENDED_NODE ) == 0 )
+            continue;
+        this.aChildrenArray[i].removeEffect();
+    }
 };
 
 BaseContainerNode.prototype.saveStateOfAnimatedElement = function()
@@ -6383,7 +6395,10 @@ function SequentialTimeContainer( aAnimElem, aParentNode, aNodeContext )
     SequentialTimeContainer.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
 
     this.sClassName = 'SequentialTimeContainer';
+    this.bIsRewinding = false;
     this.aCurrentSkipEvent = null;
+    this.aRewindCurrentEffectEvent = null;
+    this.aRewindLastEffectEvent = null;
 }
 extend( SequentialTimeContainer, BaseContainerNode );
 
@@ -6412,6 +6427,10 @@ SequentialTimeContainer.prototype.activate_st = function()
 
 SequentialTimeContainer.prototype.notifyDeactivating = function( aNotifier )
 {
+    // If we are rewinding we have not to resolve the next child.
+    if( this.bIsRewinding )
+        return;
+
     if( this.notifyDeactivatedChild( aNotifier ) )
         return;
 
@@ -6438,6 +6457,7 @@ SequentialTimeContainer.prototype.skipEffect = function( aChildNode )
 {
     if( this.isChildNode( aChildNode ) )
     {
+        this.getContext().aActivityQueue.endAll();
         this.getContext().aTimerEventQueue.forceEmpty();
         var aEvent = makeEvent( bind2( aChildNode.deactivate, aChildNode ) );
         this.getContext().aTimerEventQueue.addEvent( aEvent );
@@ -6448,9 +6468,54 @@ SequentialTimeContainer.prototype.skipEffect = function( aChildNode )
     }
 };
 
-SequentialTimeContainer.prototype.rewindEffect = function( aChildNode )
+SequentialTimeContainer.prototype.rewindCurrentEffect = function( aChildNode )
 {
-    // not implemented
+    if( this.isChildNode( aChildNode ) )
+    {
+        assert( !this.bIsRewinding, 'SequentialTimeContainer.rewindCurrentEffect: is already rewinding.' );
+
+        this.bIsRewinding = true;
+        this.getContext().aTimerEventQueue.forceEmpty();
+        this.getContext().aActivityQueue.clear();
+
+        aChildNode.end();
+        aChildNode.removeEffect();
+        aChildNode.init();
+        this.resolveChild( aChildNode );
+        this.bIsRewinding = false;
+    }
+    else
+    {
+        log( 'SequentialTimeContainer.rewindCurrentEffect: unknown child: ' + aChildNode.getId() );
+    }
+};
+
+SequentialTimeContainer.prototype.rewindLastEffect = function( aChildNode )
+{
+    if( this.isChildNode( aChildNode ) )
+    {
+        assert( !this.bIsRewinding, 'SequentialTimeContainer.rewindLastEffect: is already rewinding.' );
+
+        this.bIsRewinding = true;
+        this.getContext().aTimerEventQueue.forceEmpty();
+        this.getContext().aActivityQueue.clear();
+
+        aChildNode.end();
+        aChildNode.removeEffect();
+        --this.nFinishedChildren;
+        var aPreviousChildNode = this.aChildrenArray[ this.nFinishedChildren ];
+        aPreviousChildNode.removeEffect();
+        aPreviousChildNode.init();
+        // We need to re-initialize it too, because it is in state ENDED now,
+        // and cannot be resolved again later.
+        aChildNode.init();
+        this.resolveChild( aPreviousChildNode );
+        this.bIsRewinding = false;
+    }
+    else
+    {
+        log( 'SequentialTimeContainer.rewindLastEffect: unknown child: ' + aChildNode.getId() );
+    }
 };
 
 SequentialTimeContainer.prototype.resolveChild = function( aChildNode )
@@ -6459,13 +6524,23 @@ SequentialTimeContainer.prototype.resolveChild = function( aChildNode )
 
     if( bResolved && this.isMainSequenceRootNode() )
     {
-        aChildNode.saveStateOfAnimatedElement();
-
         if( this.aCurrentSkipEvent )
             this.aCurrentSkipEvent.dispose();
 
-        this.aCurrentSkipEvent = makeEvent( bind2( this.skipEffect, this, aChildNode ) );
+        this.aCurrentSkipEvent = makeEvent( bind2( SequentialTimeContainer.prototype.skipEffect, this, aChildNode ) );
         this.aContext.aEventMultiplexer.registerSkipEffectEvent( this.aCurrentSkipEvent );
+
+        if( this.aRewindCurrentEffectEvent )
+            this.aRewindCurrentEffectEvent.dispose();
+
+        this.aRewindCurrentEffectEvent = makeEvent( bind2( SequentialTimeContainer.prototype.rewindCurrentEffect, this, aChildNode ) );
+        this.aContext.aEventMultiplexer.registerRewindEffectEvent( this.aRewindCurrentEffectEvent );
+
+        if( this.aRewindLastEffectEvent )
+            this.aRewindLastEffectEvent.dispose();
+
+        this.aRewindLastEffectEvent = makeEvent( bind2( SequentialTimeContainer.prototype.rewindLastEffect, this, aChildNode ) );
+        this.aContext.aEventMultiplexer.registerRewindLastEffectEvent( this.aRewindLastEffectEvent );
     }
     return bResolved;
 };
@@ -8356,7 +8431,7 @@ function AnimatedElement( aElement )
     this.aClipPathContent = null;
 
     this.aPreviousElement = null;
-    this.aStateArray = new Array();
+    this.aStateSet = new Object();
 
     this.eAdditiveMode = ADDITIVE_MODE_REPLACE;
     this.bIsUpdated = true;
@@ -8366,7 +8441,6 @@ function AnimatedElement( aElement )
     this.aICTM = document.documentElement.createSVGMatrix();
 
     this.initElement();
-    //this.aElementArray[0] = this.aActiveElement.cloneNode( true );
 }
 
 AnimatedElement.prototype.initElement = function()
@@ -8377,7 +8451,6 @@ AnimatedElement.prototype.initElement = function()
     this.nScaleFactorY = 1.0;
     this.setCTM();
 
-    this.nCurrentState = -1;
     // add a transform attribute of type matrix
     this.aActiveElement.setAttribute( 'transform', makeMatrixString( 1, 0, 0, 1, 0, 0 ) );
 };
@@ -8439,11 +8512,6 @@ AnimatedElement.prototype.getId = function()
     return this.aActiveElement.getAttribute( 'id' );
 };
 
-AnimatedElement.prototype.getCurrentState = function()
-{
-    return this.nCurrentState;
-};
-
 AnimatedElement.prototype.isUpdated = function()
 {
     return this.bIsUpdated;
@@ -8519,46 +8587,34 @@ AnimatedElement.prototype.notifyNextEffectStart = function( nEffectIndex )
 //    ++this.nCurrentState;
 };
 
-AnimatedElement.prototype.saveState = function()
+AnimatedElement.prototype.saveState = function( nAnimationNodeId )
 {
-    ++this.nCurrentState;
-    if( !this.aStateArray[ this.nCurrentState ] )
+    if( !this.aStateSet[ nAnimationNodeId ] )
     {
-        this.aStateArray[ this.nCurrentState ] = new Object();
-        var aState = this.aStateArray[ this.nCurrentState ];
-        aState.aElement = this.aActiveElement.cloneNode( true );
-        aState.nCenterX = this.nCenterX;
-        aState.nCenterY = this.nCenterY;
-        aState.nScaleFactorX = this.nScaleFactorX;
-        aState.nScaleFactorY = this.nScaleFactorY;
+        this.aStateSet[ nAnimationNodeId ] = new Object();
     }
-};
+    var aState = this.aStateSet[ nAnimationNodeId ];
+    aState.aElement = this.aActiveElement.cloneNode( true );
+    aState.nCenterX = this.nCenterX;
+    aState.nCenterY = this.nCenterY;
+    aState.nScaleFactorX = this.nScaleFactorX;
+    aState.nScaleFactorY = this.nScaleFactorY;
 
-AnimatedElement.prototype.setToFirst = function()
-{
-    this.setTo( 0 );
 };
 
-AnimatedElement.prototype.setToLast = function()
+AnimatedElement.prototype.setTo = function( nAnimationNodeId )
 {
-    this.setTo( this.aStateArray.length - 1 );
-};
-
-AnimatedElement.prototype.setTo = function( nNewState )
-{
-    if( !this.aStateArray[ nNewState ] )
+    if( !this.aStateSet[ nAnimationNodeId ] )
     {
         log( 'AnimatedElement(' + this.getId() + ').setTo: state '
-                 + nNewState + ' is not valid' );
+                 +nAnimationNodeId  + ' is not valid' );
         return false;
     }
 
-    var aState = this.aStateArray[ nNewState ];
+    var aState = this.aStateSet[ nAnimationNodeId ];
     var bRet = this.setToElement( aState.aElement );
     if( bRet )
     {
-        this.nCurrentState = nNewState;
-
         this.nCenterX = aState.nCenterX;
         this.nCenterY = aState.nCenterY;
         this.nScaleFactorX = aState.nScaleFactorX;
@@ -9600,6 +9656,8 @@ function EventMultiplexer( aTimerEventQueue )
     this.aTimerEventQueue = aTimerEventQueue;
     this.aEventMap = new Object();
     this.aSkipEffectEvent = null;
+    this.aRewindEffectEvent = null;
+    this.aRewindLastEffectEvent = null;
 }
 
 
@@ -9649,6 +9707,34 @@ EventMultiplexer.prototype.notifySkipEffectEvent = function()
     }
 };
 
+EventMultiplexer.prototype.registerRewindEffectEvent = function( aEvent )
+{
+    this.aRewindEffectEvent = aEvent;
+};
+
+EventMultiplexer.prototype.notifyRewindEffectEvent = function()
+{
+    if( this.aRewindEffectEvent )
+    {
+        this.aTimerEventQueue.addEvent( this.aRewindEffectEvent );
+        this.aRewindEffectEvent = null;
+    }
+};
+
+EventMultiplexer.prototype.registerRewindLastEffectEvent = function( aEvent )
+{
+    this.aRewindLastEffectEvent = aEvent;
+};
+
+EventMultiplexer.prototype.notifyRewindLastEffectEvent = function()
+{
+    if( this.aRewindLastEffectEvent )
+    {
+        this.aTimerEventQueue.addEvent( this.aRewindLastEffectEvent );
+        this.aRewindLastEffectEvent = null;
+    }
+};
+
 EventMultiplexer.DEBUG = aEventMultiplexerDebugPrinter.isEnabled();
 
 EventMultiplexer.prototype.DBG = function( sMethodName, eEventType, aNotifierId, nTime )
@@ -11111,6 +11197,7 @@ function SlideShow()
     this.bIsIdle = true;
     this.bIsEnabled = true;
     this.bIsSkipping = false;
+    this.bIsRewinding = false;
     this.bNoSlideTransition = false;
 }
 
@@ -11237,7 +11324,7 @@ SlideShow.prototype.nextEffect = function()
 
 SlideShow.prototype.skipCurrentEffect = function()
 {
-    if( this.bIsSkipping )
+    if( this.bIsSkipping || this.bIsRewinding )
         return;
 
     this.bIsSkipping = true;
@@ -11246,9 +11333,28 @@ SlideShow.prototype.skipCurrentEffect = function()
     this.bIsSkipping = false;
 };
 
+SlideShow.prototype.rewindEffect = function()
+{
+    if( this.bIsSkipping || this.bIsRewinding )
+        return true;
+
+    this.bIsRewinding = true;
+    if( this.isRunning() )
+    {
+        this.aEventMultiplexer.notifyRewindEffectEvent();
+    }
+    else
+    {
+        this.aEventMultiplexer.notifyRewindLastEffectEvent();
+    }
+    --this.nCurrentEffect;
+    this.update();
+    this.bIsRewinding = false;
+};
+
 SlideShow.prototype.skipEffect = function()
 {
-    if( this.bIsSkipping )
+    if( this.bIsSkipping || this.bIsRewinding )
         return true;
 
     if( this.isRunning() )
@@ -11274,15 +11380,15 @@ SlideShow.prototype.skipEffect = function()
     return true;
 };
 
-SlideShow.prototype.previousEffect = function()
-{
-    if( this.nCurrentEffect <= 0 )
-        return false;
-    this.eDirection = BACKWARD;
-    this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
-    --this.nCurrentEffect;
-    return true;
-};
+//SlideShow.prototype.previousEffect = function()
+//{
+//    if( this.nCurrentEffect <= 0 )
+//        return false;
+//    this.eDirection = BACKWARD;
+//    this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
+//    --this.nCurrentEffect;
+//    return true;
+//};
 
 SlideShow.prototype.displaySlide = function( nNewSlide, bSkipSlideTransition )
 {
@@ -11516,12 +11622,12 @@ NextEffectEventArray.prototype.appendEvent = function( aEvent )
     {
         if( this.aEventArray[i].getId() == aEvent.getId() )
         {
-            aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event already present' );
+            aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') already present' );
             return false;
         }
     }
     this.aEventArray.push( aEvent );
-    aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event appended' );
+    aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') appended' );
     return true;
 };
 
@@ -11542,7 +11648,7 @@ function TimerEventQueue( aTimer )
 
 TimerEventQueue.prototype.addEvent = function( aEvent )
 {
-    this.DBG( 'TimerEventQueue.addEvent invoked' );
+    this.DBG( 'TimerEventQueue.addEvent event(' + aEvent.getId() + ') appended.' );
     if( !aEvent )
     {
         log( 'TimerEventQueue.addEvent: null event' );
@@ -11748,6 +11854,21 @@ ActivityQueue.prototype.clear = function()
     this.aCurrentActivityReinsertSet = new Array();
 };
 
+ActivityQueue.prototype.endAll = function()
+{
+    aActivityQueueDebugPrinter.print( 'ActivityQueue.endAll invoked' );
+    var nSize = this.aCurrentActivityWaitingSet.length;
+    var i;
+    for( i = 0; i < nSize; ++i )
+        this.aCurrentActivityWaitingSet[i].end();
+    this.aCurrentActivityWaitingSet = new Array();
+
+    nSize = this.aCurrentActivityReinsertSet.length;
+    for( i = 0; i < nSize; ++i )
+        this.aCurrentActivityReinsertSet[i].end();
+    this.aCurrentActivityReinsertSet = new Array();
+};
+
 ActivityQueue.prototype.getTimer = function()
 {
     return this.aTimer;
commit 6bfaabf4fe0a1ccf55ae238f690cebd641e6c425
Author: Marco Cecchetti <mrcekets at gmail.com>
Date:   Thu Jun 28 23:17:33 2012 +0200

    Now a skip effect command cannot be executed if one skip effect is already running.


... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list