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 .
21 #include <com/sun/star/animations/XAnimate.hpp>
22 #include <com/sun/star/presentation/ParagraphTarget.hpp>
23 #include <com/sun/star/animations/AnimationFill.hpp>
24 #include <com/sun/star/animations/AnimationRestart.hpp>
25 #include <com/sun/star/presentation/EffectNodeType.hpp>
26 #include <com/sun/star/beans/XPropertySet.hpp>
28 #include <basenode.hxx>
29 #include <eventmultiplexer.hxx>
30 #include <basecontainernode.hxx>
31 #include <eventqueue.hxx>
32 #include <delayevent.hxx>
34 #include "nodetools.hxx"
35 #include "generateevent.hxx"
41 using namespace ::com::sun::star
;
48 typedef int StateTransitionTable
[17];
50 // State transition tables
51 // =========================================================================
53 const int* getStateTransitionTable( sal_Int16 nRestartMode
,
56 // TODO(F2): restart issues in below tables
58 // transition table for restart=NEVER, fill=REMOVE
59 static const StateTransitionTable stateTransitionTable_Never_Remove
= {
60 AnimationNode::INVALID
,
61 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
62 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
63 AnimationNode::INVALID
,
64 AnimationNode::ENDED
, // active successors for ACTIVE: no freeze here
65 AnimationNode::INVALID
,
66 AnimationNode::INVALID
,
67 AnimationNode::INVALID
,
68 AnimationNode::INVALID
, // active successors for FROZEN: this state is unreachable here
69 AnimationNode::INVALID
,
70 AnimationNode::INVALID
,
71 AnimationNode::INVALID
,
72 AnimationNode::INVALID
,
73 AnimationNode::INVALID
,
74 AnimationNode::INVALID
,
75 AnimationNode::INVALID
,
76 AnimationNode::ENDED
// active successors for ENDED: this state is a sink here (cannot restart)
79 // transition table for restart=WHEN_NOT_ACTIVE, fill=REMOVE
80 static const StateTransitionTable stateTransitionTable_NotActive_Remove
= {
81 AnimationNode::INVALID
,
82 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
83 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
84 AnimationNode::INVALID
,
85 AnimationNode::ENDED
, // active successors for ACTIVE: no freeze here
86 AnimationNode::INVALID
,
87 AnimationNode::INVALID
,
88 AnimationNode::INVALID
,
89 AnimationNode::INVALID
, // active successors for FROZEN:
90 // this state is unreachable here
91 AnimationNode::INVALID
,
92 AnimationNode::INVALID
,
93 AnimationNode::INVALID
,
94 AnimationNode::INVALID
,
95 AnimationNode::INVALID
,
96 AnimationNode::INVALID
,
97 AnimationNode::INVALID
,
98 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
// active successors for ENDED:
99 // restart possible when ended
102 // transition table for restart=ALWAYS, fill=REMOVE
103 static const StateTransitionTable stateTransitionTable_Always_Remove
= {
104 AnimationNode::INVALID
,
105 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
106 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
107 AnimationNode::INVALID
,
108 AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
, // active successors for ACTIVE: restart
109 AnimationNode::INVALID
,
110 AnimationNode::INVALID
,
111 AnimationNode::INVALID
,
112 AnimationNode::INVALID
, // active successors for FROZEN:
113 // this state is unreachable here
114 AnimationNode::INVALID
,
115 AnimationNode::INVALID
,
116 AnimationNode::INVALID
,
117 AnimationNode::INVALID
,
118 AnimationNode::INVALID
,
119 AnimationNode::INVALID
,
120 AnimationNode::INVALID
,
121 AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
// active successors for ENDED: restart
124 // transition table for restart=NEVER, fill=FREEZE
125 static const StateTransitionTable stateTransitionTable_Never_Freeze
= {
126 AnimationNode::INVALID
,
127 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
128 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
129 AnimationNode::INVALID
,
130 AnimationNode::FROZEN
|AnimationNode::ENDED
, // active successors for ACTIVE: freeze object
131 AnimationNode::INVALID
,
132 AnimationNode::INVALID
,
133 AnimationNode::INVALID
,
134 AnimationNode::ENDED
, // active successors for FROZEN: end
135 AnimationNode::INVALID
,
136 AnimationNode::INVALID
,
137 AnimationNode::INVALID
,
138 AnimationNode::INVALID
,
139 AnimationNode::INVALID
,
140 AnimationNode::INVALID
,
141 AnimationNode::INVALID
,
142 AnimationNode::ENDED
, // active successors for ENDED: this state is a sink here (cannot restart)
145 // transition table for restart=WHEN_NOT_ACTIVE, fill=FREEZE
146 static const StateTransitionTable stateTransitionTable_NotActive_Freeze
= {
147 AnimationNode::INVALID
,
148 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
149 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
150 AnimationNode::INVALID
,
151 AnimationNode::FROZEN
|AnimationNode::ENDED
, // active successors for ACTIVE: freeze object
152 AnimationNode::INVALID
,
153 AnimationNode::INVALID
,
154 AnimationNode::INVALID
,
155 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
, // active successors for FROZEN:
156 // restart possible when ended
157 AnimationNode::INVALID
,
158 AnimationNode::INVALID
,
159 AnimationNode::INVALID
,
160 AnimationNode::INVALID
,
161 AnimationNode::INVALID
,
162 AnimationNode::INVALID
,
163 AnimationNode::INVALID
,
164 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
// active successors for ENDED:
165 // restart possible when ended
168 // transition table for restart=ALWAYS, fill=FREEZE
169 static const StateTransitionTable stateTransitionTable_Always_Freeze
= {
170 AnimationNode::INVALID
,
171 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
172 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
173 AnimationNode::INVALID
,
174 AnimationNode::FROZEN
|AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
, // active successors for ACTIVE:
175 // end object, restart
176 AnimationNode::INVALID
,
177 AnimationNode::INVALID
,
178 AnimationNode::INVALID
,
179 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
, // active successors for FROZEN: restart possible
180 AnimationNode::INVALID
,
181 AnimationNode::INVALID
,
182 AnimationNode::INVALID
,
183 AnimationNode::INVALID
,
184 AnimationNode::INVALID
,
185 AnimationNode::INVALID
,
186 AnimationNode::INVALID
,
187 AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
// active successors for ENDED: restart
190 static const StateTransitionTable
* tableGuide
[] = {
191 &stateTransitionTable_Never_Remove
,
192 &stateTransitionTable_NotActive_Remove
,
193 &stateTransitionTable_Always_Remove
,
194 &stateTransitionTable_Never_Freeze
,
195 &stateTransitionTable_NotActive_Freeze
,
196 &stateTransitionTable_Always_Freeze
200 switch( nRestartMode
) {
202 case animations::AnimationRestart::DEFAULT
:
203 // same value: animations::AnimationRestart::INHERIT:
205 "getStateTransitionTable(): unexpected case for restart" );
207 case animations::AnimationRestart::NEVER
:
210 case animations::AnimationRestart::WHEN_NOT_ACTIVE
:
213 case animations::AnimationRestart::ALWAYS
:
219 switch( nFillMode
) {
221 case animations::AnimationFill::AUTO
:
222 case animations::AnimationFill::DEFAULT
:
223 // same value: animations::AnimationFill::INHERIT:
225 "getStateTransitionTable(): unexpected case for fill" );
227 case animations::AnimationFill::REMOVE
:
230 case animations::AnimationFill::FREEZE
:
231 case animations::AnimationFill::HOLD
:
232 case animations::AnimationFill::TRANSITION
:
237 return *tableGuide
[ 3*nFillValue
+ nRestartValue
];
240 /// Little helper predicate, to detect main sequence root node
241 bool isMainSequenceRootNode_(
242 const uno::Reference
< animations::XAnimationNode
>& xNode
)
244 // detect main sequence root node (need that for
245 // end-of-mainsequence signalling below)
246 beans::NamedValue
const aSearchKey(
248 uno::makeAny( presentation::EffectNodeType::MAIN_SEQUENCE
) );
250 uno::Sequence
<beans::NamedValue
> const userData(xNode
->getUserData());
251 return findNamedValue( userData
, aSearchKey
);
256 // BaseNode implementation
257 //=========================================================================
259 /** state transition handling
261 class BaseNode::StateTransition
264 enum Options
{ NONE
, FORCE
};
266 explicit StateTransition( BaseNode
* pNode
)
267 : mpNode(pNode
), meToState(INVALID
) {}
273 StateTransition(const StateTransition
&) = delete;
274 StateTransition
& operator=(const StateTransition
&) = delete;
276 bool enter( NodeState eToState
, int options
= NONE
)
278 OSL_ENSURE( meToState
== INVALID
,
279 "### commit() before enter()ing again!" );
280 if (meToState
!= INVALID
)
282 bool const bForce
= ((options
& FORCE
) != 0);
283 if (!bForce
&& !mpNode
->isTransition( mpNode
->meCurrState
, eToState
))
285 // recursion detection:
286 if ((mpNode
->meCurrentStateTransition
& eToState
) != 0)
287 return false; // already in wanted transition
289 mpNode
->meCurrentStateTransition
|= eToState
;
290 meToState
= eToState
;
291 return true; // in transition
295 OSL_ENSURE( meToState
!= INVALID
, "### nothing to commit!" );
296 if (meToState
!= INVALID
) {
297 mpNode
->meCurrState
= meToState
;
303 if (meToState
!= INVALID
) {
304 OSL_ASSERT( (mpNode
->meCurrentStateTransition
& meToState
) != 0 );
305 mpNode
->meCurrentStateTransition
&= ~meToState
;
311 BaseNode
*const mpNode
;
315 BaseNode::BaseNode( const uno::Reference
< animations::XAnimationNode
>& xNode
,
316 const BaseContainerNodeSharedPtr
& rParent
,
317 const NodeContext
& rContext
) :
318 maContext( rContext
.maContext
),
319 maDeactivatingListeners(),
320 mxAnimationNode( xNode
),
323 mpStateTransitionTable( nullptr ),
324 mnStartDelay( rContext
.mnStartDelay
),
325 meCurrState( UNRESOLVED
),
326 meCurrentStateTransition( 0 ),
328 mbIsMainSequenceRootNode( isMainSequenceRootNode_( xNode
) )
330 ENSURE_OR_THROW( mxAnimationNode
.is(),
331 "BaseNode::BaseNode(): Invalid XAnimationNode" );
333 // setup state transition table
334 mpStateTransitionTable
= getStateTransitionTable( getRestartMode(),
338 void BaseNode::dispose()
340 meCurrState
= INVALID
;
342 // discharge a loaded event, if any:
343 if (mpCurrentEvent
) {
344 mpCurrentEvent
->dispose();
345 mpCurrentEvent
.reset();
347 maDeactivatingListeners
.clear();
348 mxAnimationNode
.clear();
355 sal_Int16
BaseNode::getRestartMode()
357 const sal_Int16
nTmp( mxAnimationNode
->getRestart() );
358 return nTmp
!= animations::AnimationRestart::DEFAULT
359 ? nTmp
: getRestartDefaultMode();
362 sal_Int16
BaseNode::getFillMode()
364 const sal_Int16
nTmp( mxAnimationNode
->getFill() );
365 const sal_Int16
nFill(nTmp
!= animations::AnimationFill::DEFAULT
366 ? nTmp
: getFillDefaultMode());
368 // For AUTO fill mode, SMIL specifies that fill mode is FREEZE,
369 // if no explicit active duration is given
370 // (no duration, end, repeatCount or repeatDuration given),
371 // and REMOVE otherwise
372 if( nFill
== animations::AnimationFill::AUTO
) {
373 return (isIndefiniteTiming( mxAnimationNode
->getDuration() ) &&
374 isIndefiniteTiming( mxAnimationNode
->getEnd() ) &&
375 !mxAnimationNode
->getRepeatCount().hasValue() &&
376 isIndefiniteTiming( mxAnimationNode
->getRepeatDuration() ))
377 ? animations::AnimationFill::FREEZE
378 : animations::AnimationFill::REMOVE
;
385 sal_Int16
BaseNode::getFillDefaultMode() const
387 sal_Int16 nFillDefault
= mxAnimationNode
->getFillDefault();
388 if (nFillDefault
== animations::AnimationFill::DEFAULT
) {
389 nFillDefault
= (mpParent
!= nullptr
390 ? mpParent
->getFillDefaultMode()
391 : animations::AnimationFill::AUTO
);
396 sal_Int16
BaseNode::getRestartDefaultMode() const
398 sal_Int16 nRestartDefaultMode
= mxAnimationNode
->getRestartDefault();
399 if (nRestartDefaultMode
== animations::AnimationRestart::DEFAULT
) {
400 nRestartDefaultMode
= (mpParent
!= nullptr
401 ? mpParent
->getRestartDefaultMode()
402 : animations::AnimationRestart::ALWAYS
);
404 return nRestartDefaultMode
;
407 uno::Reference
<animations::XAnimationNode
> BaseNode::getXAnimationNode() const
409 return mxAnimationNode
;
412 bool BaseNode::init()
414 if (! checkValidNode())
416 meCurrState
= UNRESOLVED
;
417 // discharge a loaded event, if any:
418 if (mpCurrentEvent
) {
419 mpCurrentEvent
->dispose();
420 mpCurrentEvent
.reset();
422 return init_st(); // may call derived class
425 bool BaseNode::init_st()
430 bool BaseNode::resolve()
432 if (! checkValidNode())
435 OSL_ASSERT( meCurrState
!= RESOLVED
);
436 if (inStateOrTransition( RESOLVED
))
439 StateTransition
st(this);
440 if (st
.enter( RESOLVED
) &&
441 isTransition( RESOLVED
, ACTIVE
) &&
442 resolve_st() /* may call derived class */)
444 st
.commit(); // changing state
446 // discharge a loaded event, if any:
448 mpCurrentEvent
->dispose();
450 // schedule activation event:
452 // This method takes the NodeContext::mnStartDelay value into account,
453 // to cater for iterate container time shifts. We cannot put different
454 // iterations of the iterate container's children into different
455 // subcontainer (such as a 'DelayContainer', which delays resolving its
456 // children by a fixed amount), since all iterations' nodes must be
457 // resolved at the same time (otherwise, the delayed subset creation
458 // will not work, i.e. deactivate the subsets too late in the master
460 uno::Any
const aBegin( mxAnimationNode
->getBegin() );
461 if (aBegin
.hasValue()) {
463 mpCurrentEvent
= generateEvent(
464 aBegin
, [self
] () { self
->activate(); },
465 maContext
, mnStartDelay
);
468 // For some leaf nodes, PPT import yields empty begin time,
469 // although semantically, it should be 0.0
470 // TODO(F3): That should really be provided by the PPT import
472 // schedule delayed activation event. Take iterate node
473 // timeout into account
475 mpCurrentEvent
= makeDelay(
476 [self
] () { self
->activate(); },
478 "AnimationNode::activate with delay");
479 maContext
.mrEventQueue
.addEvent( mpCurrentEvent
);
487 bool BaseNode::resolve_st()
493 void BaseNode::activate()
495 if (! checkValidNode())
498 OSL_ASSERT( meCurrState
!= ACTIVE
);
499 if (inStateOrTransition( ACTIVE
))
502 StateTransition
st(this);
503 if (st
.enter( ACTIVE
)) {
505 activate_st(); // calling derived class
507 st
.commit(); // changing state
509 maContext
.mrEventMultiplexer
.notifyAnimationStart( mpSelf
);
513 void BaseNode::activate_st()
515 scheduleDeactivationEvent();
518 void BaseNode::scheduleDeactivationEvent( EventSharedPtr
const& pEvent
)
520 if (mpCurrentEvent
) {
521 mpCurrentEvent
->dispose();
522 mpCurrentEvent
.reset();
525 if (maContext
.mrEventQueue
.addEvent( pEvent
))
526 mpCurrentEvent
= pEvent
;
529 // This method need not take the
530 // NodeContext::mnStartDelay value into account,
531 // because the deactivation event is only scheduled
532 // when the effect is started: the timeout is then
533 // already respected.
536 // think about set node, anim base node!
537 // if anim base node has no activity, this is called to schedule deactivation,
538 // but what if it does not schedule anything?
540 // TODO(F2): Handle end time attribute, too
542 mpCurrentEvent
= generateEvent(
543 mxAnimationNode
->getDuration(),
544 [self
] () { self
->deactivate(); },
549 void BaseNode::deactivate()
551 if (inStateOrTransition( ENDED
| FROZEN
) || !checkValidNode())
554 if (isTransition( meCurrState
, FROZEN
, false /* no OSL_ASSERT */ )) {
555 // do transition to FROZEN:
556 StateTransition
st(this);
557 if (st
.enter( FROZEN
, StateTransition::FORCE
)) {
559 deactivate_st( FROZEN
);
562 notifyEndListeners();
564 // discharge a loaded event, before going on:
565 if (mpCurrentEvent
) {
566 mpCurrentEvent
->dispose();
567 mpCurrentEvent
.reset();
575 // state has changed either to FROZEN or ENDED
578 void BaseNode::deactivate_st( NodeState
)
584 bool const bIsFrozenOrInTransitionToFrozen
= inStateOrTransition( FROZEN
);
585 if (inStateOrTransition( ENDED
) || !checkValidNode())
588 // END must always be reachable. If not, that's an error in the
590 OSL_ENSURE( isTransition( meCurrState
, ENDED
),
591 "end state not reachable in transition table" );
593 StateTransition
st(this);
594 if (st
.enter( ENDED
, StateTransition::FORCE
)) {
596 deactivate_st( ENDED
);
597 st
.commit(); // changing state
599 // if is FROZEN or is to be FROZEN, then
600 // will/already notified deactivating listeners
601 if (!bIsFrozenOrInTransitionToFrozen
)
602 notifyEndListeners();
604 // discharge a loaded event, before going on:
605 if (mpCurrentEvent
) {
606 mpCurrentEvent
->dispose();
607 mpCurrentEvent
.reset();
612 void BaseNode::notifyDeactivating( const AnimationNodeSharedPtr
& rNotifier
)
614 OSL_ASSERT( rNotifier
->getState() == FROZEN
||
615 rNotifier
->getState() == ENDED
);
616 // TODO(F1): for end sync functionality, this might indeed be used some day
619 void BaseNode::notifyEndListeners() const
621 // notify all listeners
622 for( const auto& rListner
: maDeactivatingListeners
)
623 rListner
->notifyDeactivating( mpSelf
);
625 // notify state change
626 maContext
.mrEventMultiplexer
.notifyAnimationEnd( mpSelf
);
628 // notify main sequence end (iff we're the main
629 // sequence root node). This is because the main
630 // sequence determines the active duration of the
631 // slide. All other sequences are secondary, in that
632 // they don't prevent a slide change from happening,
633 // even if they have not been completed. In other
634 // words, all sequences except the main sequence are
635 // optional for the slide lifetime.
636 if (isMainSequenceRootNode())
637 maContext
.mrEventMultiplexer
.notifySlideAnimationsEnd();
640 AnimationNode::NodeState
BaseNode::getState() const
645 bool BaseNode::registerDeactivatingListener(
646 const AnimationNodeSharedPtr
& rNotifee
)
648 if (! checkValidNode())
651 ENSURE_OR_RETURN_FALSE(
653 "BaseNode::registerDeactivatingListener(): invalid notifee" );
654 maDeactivatingListeners
.push_back( rNotifee
);
659 void BaseNode::setSelf( const BaseNodeSharedPtr
& rSelf
)
661 ENSURE_OR_THROW( rSelf
.get() == this,
662 "BaseNode::setSelf(): got ptr to different object" );
663 ENSURE_OR_THROW( !mpSelf
,
664 "BaseNode::setSelf(): called multiple times" );
672 #if defined(DBG_UTIL)
673 void BaseNode::showState() const
675 const AnimationNode::NodeState
eNodeState( getState() );
677 if( eNodeState
== AnimationNode::INVALID
)
678 SAL_INFO("slideshow.verbose", "Node state: n" <<
679 debugGetNodeName(this) <<
682 "\",style=filled, fillcolor=\"0.5,0.2,0.5\"]");
684 SAL_INFO("slideshow.verbose", "Node state: n" <<
685 debugGetNodeName(this) <<
689 log(double(getState()))/4.0 <<
692 // determine additional node information
693 uno::Reference
<animations::XAnimate
> const xAnimate( mxAnimationNode
,
697 uno::Reference
< drawing::XShape
> xTargetShape( xAnimate
->getTarget(),
700 if( !xTargetShape
.is() )
702 css::presentation::ParagraphTarget aTarget
;
704 // no shape provided. Maybe a ParagraphTarget?
705 if( xAnimate
->getTarget() >>= aTarget
)
706 xTargetShape
= aTarget
.Shape
;
709 if( xTargetShape
.is() )
711 uno::Reference
< beans::XPropertySet
> xPropSet( xTargetShape
,
716 if( xPropSet
->getPropertyValue("Name") >>= aName
)
718 SAL_INFO("slideshow.verbose", "Node info: n" <<
719 debugGetNodeName(this) <<
728 const char* BaseNode::getDescription() const
735 } // namespace internal
736 } // namespace slideshow
738 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */