1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
22 #include <canvas/debug.hxx>
23 #include <canvas/verbosetrace.hxx>
25 #include <com/sun/star/animations/XAnimate.hpp>
26 #include <com/sun/star/presentation/ParagraphTarget.hpp>
27 #include <com/sun/star/animations/AnimationFill.hpp>
28 #include <com/sun/star/animations/AnimationRestart.hpp>
29 #include <com/sun/star/presentation/EffectNodeType.hpp>
30 #include <com/sun/star/beans/XPropertySet.hpp>
32 #include "basenode.hxx"
33 #include "eventmultiplexer.hxx"
34 #include "basecontainernode.hxx"
35 #include "eventqueue.hxx"
36 #include "delayevent.hxx"
38 #include "nodetools.hxx"
39 #include "generateevent.hxx"
41 #include <boost/bind.hpp>
46 using namespace ::com::sun::star
;
53 typedef int StateTransitionTable
[17];
55 // State transition tables
56 // =========================================================================
58 const int* getStateTransitionTable( sal_Int16 nRestartMode
,
61 // TODO(F2): restart issues in below tables
63 // transition table for restart=NEVER, fill=REMOVE
64 static const StateTransitionTable stateTransitionTable_Never_Remove
= {
65 AnimationNode::INVALID
,
66 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
67 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
68 AnimationNode::INVALID
,
69 AnimationNode::ENDED
, // active successors for ACTIVE: no freeze here
70 AnimationNode::INVALID
,
71 AnimationNode::INVALID
,
72 AnimationNode::INVALID
,
73 AnimationNode::INVALID
, // active successors for FROZEN: this state is unreachable here
74 AnimationNode::INVALID
,
75 AnimationNode::INVALID
,
76 AnimationNode::INVALID
,
77 AnimationNode::INVALID
,
78 AnimationNode::INVALID
,
79 AnimationNode::INVALID
,
80 AnimationNode::INVALID
,
81 AnimationNode::ENDED
// active successors for ENDED: this state is a sink here (cannot restart)
84 // transition table for restart=WHEN_NOT_ACTIVE, fill=REMOVE
85 static const StateTransitionTable stateTransitionTable_NotActive_Remove
= {
86 AnimationNode::INVALID
,
87 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
88 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
89 AnimationNode::INVALID
,
90 AnimationNode::ENDED
, // active successors for ACTIVE: no freeze here
91 AnimationNode::INVALID
,
92 AnimationNode::INVALID
,
93 AnimationNode::INVALID
,
94 AnimationNode::INVALID
, // active successors for FROZEN:
95 // this state is unreachable here
96 AnimationNode::INVALID
,
97 AnimationNode::INVALID
,
98 AnimationNode::INVALID
,
99 AnimationNode::INVALID
,
100 AnimationNode::INVALID
,
101 AnimationNode::INVALID
,
102 AnimationNode::INVALID
,
103 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
// active successors for ENDED:
104 // restart possible when ended
107 // transition table for restart=ALWAYS, fill=REMOVE
108 static const StateTransitionTable stateTransitionTable_Always_Remove
= {
109 AnimationNode::INVALID
,
110 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
111 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
112 AnimationNode::INVALID
,
113 AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
, // active successors for ACTIVE: restart
114 AnimationNode::INVALID
,
115 AnimationNode::INVALID
,
116 AnimationNode::INVALID
,
117 AnimationNode::INVALID
, // active successors for FROZEN:
118 // this state is unreachable here
119 AnimationNode::INVALID
,
120 AnimationNode::INVALID
,
121 AnimationNode::INVALID
,
122 AnimationNode::INVALID
,
123 AnimationNode::INVALID
,
124 AnimationNode::INVALID
,
125 AnimationNode::INVALID
,
126 AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
// active successors for ENDED: restart
129 // transition table for restart=NEVER, fill=FREEZE
130 static const StateTransitionTable stateTransitionTable_Never_Freeze
= {
131 AnimationNode::INVALID
,
132 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
133 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
134 AnimationNode::INVALID
,
135 AnimationNode::FROZEN
|AnimationNode::ENDED
, // active successors for ACTIVE: freeze object
136 AnimationNode::INVALID
,
137 AnimationNode::INVALID
,
138 AnimationNode::INVALID
,
139 AnimationNode::ENDED
, // active successors for FROZEN: end
140 AnimationNode::INVALID
,
141 AnimationNode::INVALID
,
142 AnimationNode::INVALID
,
143 AnimationNode::INVALID
,
144 AnimationNode::INVALID
,
145 AnimationNode::INVALID
,
146 AnimationNode::INVALID
,
147 AnimationNode::ENDED
, // active successors for ENDED: this state is a sink here (cannot restart)
150 // transition table for restart=WHEN_NOT_ACTIVE, fill=FREEZE
151 static const StateTransitionTable stateTransitionTable_NotActive_Freeze
= {
152 AnimationNode::INVALID
,
153 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
154 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
155 AnimationNode::INVALID
,
156 AnimationNode::FROZEN
|AnimationNode::ENDED
, // active successors for ACTIVE: freeze object
157 AnimationNode::INVALID
,
158 AnimationNode::INVALID
,
159 AnimationNode::INVALID
,
160 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
, // active successors for FROZEN:
161 // restart possible when ended
162 AnimationNode::INVALID
,
163 AnimationNode::INVALID
,
164 AnimationNode::INVALID
,
165 AnimationNode::INVALID
,
166 AnimationNode::INVALID
,
167 AnimationNode::INVALID
,
168 AnimationNode::INVALID
,
169 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
// active successors for ENDED:
170 // restart possible when ended
173 // transition table for restart=ALWAYS, fill=FREEZE
174 static const StateTransitionTable stateTransitionTable_Always_Freeze
= {
175 AnimationNode::INVALID
,
176 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
177 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
178 AnimationNode::INVALID
,
179 AnimationNode::FROZEN
|AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
, // active successors for ACTIVE:
180 // end object, restart
181 AnimationNode::INVALID
,
182 AnimationNode::INVALID
,
183 AnimationNode::INVALID
,
184 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
, // active successors for FROZEN: restart possible
185 AnimationNode::INVALID
,
186 AnimationNode::INVALID
,
187 AnimationNode::INVALID
,
188 AnimationNode::INVALID
,
189 AnimationNode::INVALID
,
190 AnimationNode::INVALID
,
191 AnimationNode::INVALID
,
192 AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
// active successors for ENDED: restart
195 static const StateTransitionTable
* tableGuide
[] = {
196 &stateTransitionTable_Never_Remove
,
197 &stateTransitionTable_NotActive_Remove
,
198 &stateTransitionTable_Always_Remove
,
199 &stateTransitionTable_Never_Freeze
,
200 &stateTransitionTable_NotActive_Freeze
,
201 &stateTransitionTable_Always_Freeze
205 switch( nRestartMode
) {
207 case animations::AnimationRestart::DEFAULT
:
208 // same value: animations::AnimationRestart::INHERIT:
210 "getStateTransitionTable(): unexpected case for restart" );
211 // FALLTHROUGH intended
212 case animations::AnimationRestart::NEVER
:
215 case animations::AnimationRestart::WHEN_NOT_ACTIVE
:
218 case animations::AnimationRestart::ALWAYS
:
224 switch( nFillMode
) {
226 case animations::AnimationFill::AUTO
:
227 case animations::AnimationFill::DEFAULT
:
228 // same value: animations::AnimationFill::INHERIT:
230 "getStateTransitionTable(): unexpected case for fill" );
231 // FALLTHROUGH intended
232 case animations::AnimationFill::REMOVE
:
235 case animations::AnimationFill::FREEZE
:
236 case animations::AnimationFill::HOLD
:
237 case animations::AnimationFill::TRANSITION
:
242 return *tableGuide
[ 3*nFillValue
+ nRestartValue
];
245 /// Little helper predicate, to detect main sequence root node
246 bool isMainSequenceRootNode_(
247 const uno::Reference
< animations::XAnimationNode
>& xNode
)
249 // detect main sequence root node (need that for
250 // end-of-mainsequence signalling below)
251 beans::NamedValue
const aSearchKey(
252 OUString( "node-type" ),
253 uno::makeAny( presentation::EffectNodeType::MAIN_SEQUENCE
) );
255 uno::Sequence
<beans::NamedValue
> const userData(xNode
->getUserData());
256 return findNamedValue( userData
, aSearchKey
);
261 // BaseNode implementation
262 //=========================================================================
264 /** state transition handling
266 class BaseNode::StateTransition
: private boost::noncopyable
269 enum Options
{ NONE
, FORCE
};
271 explicit StateTransition( BaseNode
* pNode
)
272 : mpNode(pNode
), meToState(INVALID
) {}
278 bool enter( NodeState eToState
, int options
= NONE
)
280 OSL_ENSURE( meToState
== INVALID
,
281 "### commit() before enter()ing again!" );
282 if (meToState
!= INVALID
)
284 bool const bForce
= ((options
& FORCE
) != 0);
285 if (!bForce
&& !mpNode
->isTransition( mpNode
->meCurrState
, eToState
))
287 // recursion detection:
288 if ((mpNode
->meCurrentStateTransition
& eToState
) != 0)
289 return false; // already in wanted transition
291 mpNode
->meCurrentStateTransition
|= eToState
;
292 meToState
= eToState
;
293 return true; // in transition
297 OSL_ENSURE( meToState
!= INVALID
, "### nothing to commit!" );
298 if (meToState
!= INVALID
) {
299 mpNode
->meCurrState
= meToState
;
305 if (meToState
!= INVALID
) {
306 OSL_ASSERT( (mpNode
->meCurrentStateTransition
& meToState
) != 0 );
307 mpNode
->meCurrentStateTransition
&= ~meToState
;
313 BaseNode
*const mpNode
;
317 BaseNode::BaseNode( const uno::Reference
< animations::XAnimationNode
>& xNode
,
318 const BaseContainerNodeSharedPtr
& rParent
,
319 const NodeContext
& rContext
) :
320 maContext( rContext
.maContext
),
321 maDeactivatingListeners(),
322 mxAnimationNode( xNode
),
325 mpStateTransitionTable( NULL
),
326 mnStartDelay( rContext
.mnStartDelay
),
327 meCurrState( UNRESOLVED
),
328 meCurrentStateTransition( 0 ),
330 mbIsMainSequenceRootNode( isMainSequenceRootNode_( xNode
) )
332 ENSURE_OR_THROW( mxAnimationNode
.is(),
333 "BaseNode::BaseNode(): Invalid XAnimationNode" );
335 // setup state transition table
336 mpStateTransitionTable
= getStateTransitionTable( getRestartMode(),
340 void BaseNode::dispose()
342 meCurrState
= INVALID
;
344 // discharge a loaded event, if any:
345 if (mpCurrentEvent
) {
346 mpCurrentEvent
->dispose();
347 mpCurrentEvent
.reset();
349 maDeactivatingListeners
.clear();
350 mxAnimationNode
.clear();
357 sal_Int16
BaseNode::getRestartMode()
359 const sal_Int16
nTmp( mxAnimationNode
->getRestart() );
360 return (nTmp
!= animations::AnimationRestart::DEFAULT
&&
361 nTmp
!= animations::AnimationRestart::INHERIT
)
362 ? nTmp
: getRestartDefaultMode();
365 sal_Int16
BaseNode::getFillMode()
367 const sal_Int16
nTmp( mxAnimationNode
->getFill() );
368 const sal_Int16
nFill((nTmp
!= animations::AnimationFill::DEFAULT
&&
369 nTmp
!= animations::AnimationFill::INHERIT
)
370 ? nTmp
: getFillDefaultMode());
372 // For AUTO fill mode, SMIL specifies that fill mode is FREEZE,
373 // if no explicit active duration is given
374 // (no duration, end, repeatCount or repeatDuration given),
375 // and REMOVE otherwise
376 if( nFill
== animations::AnimationFill::AUTO
) {
377 return (isIndefiniteTiming( mxAnimationNode
->getDuration() ) &&
378 isIndefiniteTiming( mxAnimationNode
->getEnd() ) &&
379 !mxAnimationNode
->getRepeatCount().hasValue() &&
380 isIndefiniteTiming( mxAnimationNode
->getRepeatDuration() ))
381 ? animations::AnimationFill::FREEZE
382 : animations::AnimationFill::REMOVE
;
389 sal_Int16
BaseNode::getFillDefaultMode() const
391 sal_Int16 nFillDefault
= mxAnimationNode
->getFillDefault();
392 if (nFillDefault
== animations::AnimationFill::DEFAULT
) {
393 nFillDefault
= (mpParent
!= 0
394 ? mpParent
->getFillDefaultMode()
395 : animations::AnimationFill::AUTO
);
400 sal_Int16
BaseNode::getRestartDefaultMode() const
402 sal_Int16 nRestartDefaultMode
= mxAnimationNode
->getRestartDefault();
403 if (nRestartDefaultMode
== animations::AnimationRestart::DEFAULT
) {
404 nRestartDefaultMode
= (mpParent
!= 0
405 ? mpParent
->getRestartDefaultMode()
406 : animations::AnimationRestart::ALWAYS
);
408 return nRestartDefaultMode
;
411 uno::Reference
<animations::XAnimationNode
> BaseNode::getXAnimationNode() const
413 return mxAnimationNode
;
416 bool BaseNode::init()
418 if (! checkValidNode())
420 meCurrState
= UNRESOLVED
;
421 // discharge a loaded event, if any:
422 if (mpCurrentEvent
) {
423 mpCurrentEvent
->dispose();
424 mpCurrentEvent
.reset();
426 return init_st(); // may call derived class
429 bool BaseNode::init_st()
434 bool BaseNode::resolve()
436 if (! checkValidNode())
439 OSL_ASSERT( meCurrState
!= RESOLVED
);
440 if (inStateOrTransition( RESOLVED
))
443 StateTransition
st(this);
444 if (st
.enter( RESOLVED
) &&
445 isTransition( RESOLVED
, ACTIVE
) &&
446 resolve_st() /* may call derived class */)
448 st
.commit(); // changing state
450 // discharge a loaded event, if any:
452 mpCurrentEvent
->dispose();
454 // schedule activation event:
456 // This method takes the NodeContext::mnStartDelay value into account,
457 // to cater for iterate container time shifts. We cannot put different
458 // iterations of the iterate container's children into different
459 // subcontainer (such as a 'DelayContainer', which delays resolving its
460 // children by a fixed amount), since all iterations' nodes must be
461 // resolved at the same time (otherwise, the delayed subset creation
462 // will not work, i.e. deactivate the subsets too late in the master
464 uno::Any
const aBegin( mxAnimationNode
->getBegin() );
465 if (aBegin
.hasValue()) {
466 mpCurrentEvent
= generateEvent(
467 aBegin
, boost::bind( &AnimationNode::activate
, mpSelf
),
468 maContext
, mnStartDelay
);
471 // For some leaf nodes, PPT import yields empty begin time,
472 // although semantically, it should be 0.0
473 // TODO(F3): That should really be provided by the PPT import
475 // schedule delayed activation event. Take iterate node
476 // timeout into account
477 mpCurrentEvent
= makeDelay(
478 boost::bind( &AnimationNode::activate
, mpSelf
),
480 "AnimationNode::activate with delay");
481 maContext
.mrEventQueue
.addEvent( mpCurrentEvent
);
489 bool BaseNode::resolve_st()
495 bool BaseNode::activate()
497 if (! checkValidNode())
500 OSL_ASSERT( meCurrState
!= ACTIVE
);
501 if (inStateOrTransition( ACTIVE
))
504 StateTransition
st(this);
505 if (st
.enter( ACTIVE
)) {
507 activate_st(); // calling derived class
509 st
.commit(); // changing state
511 maContext
.mrEventMultiplexer
.notifyAnimationStart( mpSelf
);
519 void BaseNode::activate_st()
521 scheduleDeactivationEvent();
524 void BaseNode::scheduleDeactivationEvent( EventSharedPtr
const& pEvent
)
526 if (mpCurrentEvent
) {
527 mpCurrentEvent
->dispose();
528 mpCurrentEvent
.reset();
531 if (maContext
.mrEventQueue
.addEvent( pEvent
))
532 mpCurrentEvent
= pEvent
;
535 // This method need not take the
536 // NodeContext::mnStartDelay value into account,
537 // because the deactivation event is only scheduled
538 // when the effect is started: the timeout is then
539 // already respected.
542 // think about set node, anim base node!
543 // if anim base node has no activity, this is called to schedule deactivatiion,
544 // but what if it does not schedule anything?
546 // TODO(F2): Handle end time attribute, too
547 mpCurrentEvent
= generateEvent(
548 mxAnimationNode
->getDuration(),
549 boost::bind( &AnimationNode::deactivate
, mpSelf
),
554 void BaseNode::deactivate()
556 if (inStateOrTransition( ENDED
| FROZEN
) || !checkValidNode())
559 if (isTransition( meCurrState
, FROZEN
, false /* no OSL_ASSERT */ )) {
560 // do transition to FROZEN:
561 StateTransition
st(this);
562 if (st
.enter( FROZEN
, StateTransition::FORCE
)) {
564 deactivate_st( FROZEN
);
567 notifyEndListeners();
569 // discharge a loaded event, before going on:
570 if (mpCurrentEvent
) {
571 mpCurrentEvent
->dispose();
572 mpCurrentEvent
.reset();
580 // state has changed either to FROZEN or ENDED
583 void BaseNode::deactivate_st( NodeState
)
589 bool const bIsFrozenOrInTransitionToFrozen
= inStateOrTransition( FROZEN
);
590 if (inStateOrTransition( ENDED
) || !checkValidNode())
593 // END must always be reachable. If not, that's an error in the
595 OSL_ENSURE( isTransition( meCurrState
, ENDED
),
596 "end state not reachable in transition table" );
598 StateTransition
st(this);
599 if (st
.enter( ENDED
, StateTransition::FORCE
)) {
601 deactivate_st( ENDED
);
602 st
.commit(); // changing state
604 // if is FROZEN or is to be FROZEN, then
605 // will/already notified deactivating listeners
606 if (!bIsFrozenOrInTransitionToFrozen
)
607 notifyEndListeners();
609 // discharge a loaded event, before going on:
610 if (mpCurrentEvent
) {
611 mpCurrentEvent
->dispose();
612 mpCurrentEvent
.reset();
617 void BaseNode::notifyDeactivating( const AnimationNodeSharedPtr
& rNotifier
)
619 (void) rNotifier
; // avoid warning
620 OSL_ASSERT( rNotifier
->getState() == FROZEN
||
621 rNotifier
->getState() == ENDED
);
622 // TODO(F1): for end sync functionality, this might indeed be used some day
625 void BaseNode::notifyEndListeners() const
627 // notify all listeners
628 std::for_each( maDeactivatingListeners
.begin(),
629 maDeactivatingListeners
.end(),
630 boost::bind( &AnimationNode::notifyDeactivating
, _1
,
631 boost::cref(mpSelf
) ) );
633 // notify state change
634 maContext
.mrEventMultiplexer
.notifyAnimationEnd( mpSelf
);
636 // notify main sequence end (iff we're the main
637 // sequence root node). This is because the main
638 // sequence determines the active duration of the
639 // slide. All other sequences are secondary, in that
640 // they don't prevent a slide change from happening,
641 // even if they have not been completed. In other
642 // words, all sequences except the main sequence are
643 // optional for the slide lifetime.
644 if (isMainSequenceRootNode())
645 maContext
.mrEventMultiplexer
.notifySlideAnimationsEnd();
648 AnimationNode::NodeState
BaseNode::getState() const
653 bool BaseNode::registerDeactivatingListener(
654 const AnimationNodeSharedPtr
& rNotifee
)
656 if (! checkValidNode())
659 ENSURE_OR_RETURN_FALSE(
661 "BaseNode::registerDeactivatingListener(): invalid notifee" );
662 maDeactivatingListeners
.push_back( rNotifee
);
667 void BaseNode::setSelf( const BaseNodeSharedPtr
& rSelf
)
669 ENSURE_OR_THROW( rSelf
.get() == this,
670 "BaseNode::setSelf(): got ptr to different object" );
671 ENSURE_OR_THROW( !mpSelf
,
672 "BaseNode::setSelf(): called multiple times" );
680 #if OSL_DEBUG_LEVEL >= 2 && defined(DBG_UTIL)
681 void BaseNode::showState() const
683 const AnimationNode::NodeState
eNodeState( getState() );
685 if( eNodeState
== AnimationNode::INVALID
)
686 VERBOSE_TRACE( "Node state: n%p [label=\"%s\",style=filled,"
687 "fillcolor=\"0.5,0.2,0.5\"]",
688 (const char*)this+debugGetCurrentOffset(),
691 VERBOSE_TRACE( "Node state: n%p [label=\"%s\",style=filled,"
692 "fillcolor=\"%f,1.0,1.0\"]",
693 (const char*)this+debugGetCurrentOffset(),
695 log(double(getState()))/4.0 );
697 // determine additional node information
698 uno::Reference
<animations::XAnimate
> const xAnimate( mxAnimationNode
,
702 uno::Reference
< drawing::XShape
> xTargetShape( xAnimate
->getTarget(),
705 if( !xTargetShape
.is() )
707 ::com::sun::star::presentation::ParagraphTarget aTarget
;
709 // no shape provided. Maybe a ParagraphTarget?
710 if( (xAnimate
->getTarget() >>= aTarget
) )
711 xTargetShape
= aTarget
.Shape
;
714 if( xTargetShape
.is() )
716 uno::Reference
< beans::XPropertySet
> xPropSet( xTargetShape
,
721 if( (xPropSet
->getPropertyValue(
725 const OString
& rAsciiName(
726 OUStringToOString( aName
,
727 RTL_TEXTENCODING_ASCII_US
) );
729 VERBOSE_TRACE( "Node info: n%p, name \"%s\"",
730 (const char*)this+debugGetCurrentOffset(),
731 rAsciiName
.getStr() );
737 const char* BaseNode::getDescription() const
744 } // namespace internal
745 } // namespace slideshow
747 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */