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"
37 #include <sal/log.hxx>
43 using namespace ::com::sun::star
;
45 namespace slideshow::internal
{
49 typedef int StateTransitionTable
[17];
51 // State transition tables
52 // =========================================================================
54 const int* getStateTransitionTable( sal_Int16 nRestartMode
,
57 // TODO(F2): restart issues in below tables
59 // transition table for restart=NEVER, fill=REMOVE
60 static const StateTransitionTable stateTransitionTable_Never_Remove
= {
61 AnimationNode::INVALID
,
62 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
63 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
64 AnimationNode::INVALID
,
65 AnimationNode::ENDED
, // active successors for ACTIVE: no freeze here
66 AnimationNode::INVALID
,
67 AnimationNode::INVALID
,
68 AnimationNode::INVALID
,
69 AnimationNode::INVALID
, // active successors for FROZEN: this state is unreachable here
70 AnimationNode::INVALID
,
71 AnimationNode::INVALID
,
72 AnimationNode::INVALID
,
73 AnimationNode::INVALID
,
74 AnimationNode::INVALID
,
75 AnimationNode::INVALID
,
76 AnimationNode::INVALID
,
77 AnimationNode::ENDED
// active successors for ENDED: this state is a sink here (cannot restart)
80 // transition table for restart=WHEN_NOT_ACTIVE, fill=REMOVE
81 static const StateTransitionTable stateTransitionTable_NotActive_Remove
= {
82 AnimationNode::INVALID
,
83 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
84 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
85 AnimationNode::INVALID
,
86 AnimationNode::ENDED
, // active successors for ACTIVE: no freeze here
87 AnimationNode::INVALID
,
88 AnimationNode::INVALID
,
89 AnimationNode::INVALID
,
90 AnimationNode::INVALID
, // active successors for FROZEN:
91 // this state is unreachable here
92 AnimationNode::INVALID
,
93 AnimationNode::INVALID
,
94 AnimationNode::INVALID
,
95 AnimationNode::INVALID
,
96 AnimationNode::INVALID
,
97 AnimationNode::INVALID
,
98 AnimationNode::INVALID
,
99 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
// active successors for ENDED:
100 // restart possible when ended
103 // transition table for restart=ALWAYS, fill=REMOVE
104 static const StateTransitionTable stateTransitionTable_Always_Remove
= {
105 AnimationNode::INVALID
,
106 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
107 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
108 AnimationNode::INVALID
,
109 AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
, // active successors for ACTIVE: restart
110 AnimationNode::INVALID
,
111 AnimationNode::INVALID
,
112 AnimationNode::INVALID
,
113 AnimationNode::INVALID
, // active successors for FROZEN:
114 // this state is unreachable here
115 AnimationNode::INVALID
,
116 AnimationNode::INVALID
,
117 AnimationNode::INVALID
,
118 AnimationNode::INVALID
,
119 AnimationNode::INVALID
,
120 AnimationNode::INVALID
,
121 AnimationNode::INVALID
,
122 AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
// active successors for ENDED: restart
125 // transition table for restart=NEVER, fill=FREEZE
126 static const StateTransitionTable stateTransitionTable_Never_Freeze
= {
127 AnimationNode::INVALID
,
128 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
129 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
130 AnimationNode::INVALID
,
131 AnimationNode::FROZEN
|AnimationNode::ENDED
, // active successors for ACTIVE: freeze object
132 AnimationNode::INVALID
,
133 AnimationNode::INVALID
,
134 AnimationNode::INVALID
,
135 AnimationNode::ENDED
, // active successors for FROZEN: end
136 AnimationNode::INVALID
,
137 AnimationNode::INVALID
,
138 AnimationNode::INVALID
,
139 AnimationNode::INVALID
,
140 AnimationNode::INVALID
,
141 AnimationNode::INVALID
,
142 AnimationNode::INVALID
,
143 AnimationNode::ENDED
, // active successors for ENDED: this state is a sink here (cannot restart)
146 // transition table for restart=WHEN_NOT_ACTIVE, fill=FREEZE
147 static const StateTransitionTable stateTransitionTable_NotActive_Freeze
= {
148 AnimationNode::INVALID
,
149 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
150 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
151 AnimationNode::INVALID
,
152 AnimationNode::FROZEN
|AnimationNode::ENDED
, // active successors for ACTIVE: freeze object
153 AnimationNode::INVALID
,
154 AnimationNode::INVALID
,
155 AnimationNode::INVALID
,
156 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
, // active successors for FROZEN:
157 // restart possible when ended
158 AnimationNode::INVALID
,
159 AnimationNode::INVALID
,
160 AnimationNode::INVALID
,
161 AnimationNode::INVALID
,
162 AnimationNode::INVALID
,
163 AnimationNode::INVALID
,
164 AnimationNode::INVALID
,
165 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
// active successors for ENDED:
166 // restart possible when ended
169 // transition table for restart=ALWAYS, fill=FREEZE
170 static const StateTransitionTable stateTransitionTable_Always_Freeze
= {
171 AnimationNode::INVALID
,
172 AnimationNode::RESOLVED
|AnimationNode::ENDED
, // active successors for UNRESOLVED
173 AnimationNode::ACTIVE
|AnimationNode::ENDED
, // active successors for RESOLVED
174 AnimationNode::INVALID
,
175 AnimationNode::FROZEN
|AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
, // active successors for ACTIVE:
176 // end object, restart
177 AnimationNode::INVALID
,
178 AnimationNode::INVALID
,
179 AnimationNode::INVALID
,
180 AnimationNode::ENDED
|AnimationNode::RESOLVED
|AnimationNode::ACTIVE
, // active successors for FROZEN: restart possible
181 AnimationNode::INVALID
,
182 AnimationNode::INVALID
,
183 AnimationNode::INVALID
,
184 AnimationNode::INVALID
,
185 AnimationNode::INVALID
,
186 AnimationNode::INVALID
,
187 AnimationNode::INVALID
,
188 AnimationNode::ENDED
|AnimationNode::ACTIVE
|AnimationNode::RESOLVED
// active successors for ENDED: restart
191 static const StateTransitionTable
* tableGuide
[] = {
192 &stateTransitionTable_Never_Remove
,
193 &stateTransitionTable_NotActive_Remove
,
194 &stateTransitionTable_Always_Remove
,
195 &stateTransitionTable_Never_Freeze
,
196 &stateTransitionTable_NotActive_Freeze
,
197 &stateTransitionTable_Always_Freeze
201 switch( nRestartMode
) {
203 case animations::AnimationRestart::DEFAULT
:
204 // same value: animations::AnimationRestart::INHERIT:
206 "getStateTransitionTable(): unexpected case for restart" );
208 case animations::AnimationRestart::NEVER
:
211 case animations::AnimationRestart::WHEN_NOT_ACTIVE
:
214 case animations::AnimationRestart::ALWAYS
:
220 switch( nFillMode
) {
222 case animations::AnimationFill::AUTO
:
223 case animations::AnimationFill::DEFAULT
:
224 // same value: animations::AnimationFill::INHERIT:
226 "getStateTransitionTable(): unexpected case for fill" );
228 case animations::AnimationFill::REMOVE
:
231 case animations::AnimationFill::FREEZE
:
232 case animations::AnimationFill::HOLD
:
233 case animations::AnimationFill::TRANSITION
:
238 return *tableGuide
[ 3*nFillValue
+ nRestartValue
];
241 /// Little helper predicate, to detect main sequence root node
242 bool isMainSequenceRootNode_(
243 const uno::Reference
< animations::XAnimationNode
>& xNode
)
245 // detect main sequence root node (need that for
246 // end-of-mainsequence signalling below)
247 beans::NamedValue
const aSearchKey(
249 uno::Any( presentation::EffectNodeType::MAIN_SEQUENCE
) );
251 uno::Sequence
<beans::NamedValue
> const userData(xNode
->getUserData());
252 return findNamedValue( userData
, aSearchKey
);
257 // BaseNode implementation
258 //=========================================================================
260 /** state transition handling
262 class BaseNode::StateTransition
265 enum class Options
{ NONE
, FORCE
};
267 explicit StateTransition( BaseNode
* pNode
)
268 : mpNode(pNode
), meToState(INVALID
) {}
274 StateTransition(const StateTransition
&) = delete;
275 StateTransition
& operator=(const StateTransition
&) = delete;
277 bool enter( NodeState eToState
, Options options
= Options::NONE
)
279 OSL_ENSURE( meToState
== INVALID
,
280 "### commit() before enter()ing again!" );
281 if (meToState
!= INVALID
)
283 bool const bForce
= options
== Options::FORCE
;
284 if (!bForce
&& !mpNode
->isTransition( mpNode
->meCurrState
, eToState
))
286 // recursion detection:
287 if ((mpNode
->meCurrentStateTransition
& eToState
) != 0)
288 return false; // already in wanted transition
290 mpNode
->meCurrentStateTransition
|= eToState
;
291 meToState
= eToState
;
292 return true; // in transition
296 OSL_ENSURE( meToState
!= INVALID
, "### nothing to commit!" );
297 if (meToState
!= INVALID
) {
298 mpNode
->meCurrState
= meToState
;
304 if (meToState
!= INVALID
) {
305 OSL_ASSERT( (mpNode
->meCurrentStateTransition
& meToState
) != 0 );
306 mpNode
->meCurrentStateTransition
&= ~meToState
;
312 BaseNode
*const mpNode
;
316 BaseNode::BaseNode( const uno::Reference
< animations::XAnimationNode
>& xNode
,
317 BaseContainerNodeSharedPtr xParent
,
318 const NodeContext
& rContext
) :
319 maContext( rContext
.maContext
),
320 maDeactivatingListeners(),
321 mxAnimationNode( xNode
),
322 mpParent(std::move( xParent
)),
324 mpStateTransitionTable( nullptr ),
325 mnStartDelay( rContext
.mnStartDelay
),
326 meCurrState( UNRESOLVED
),
327 meCurrentStateTransition( 0 ),
329 mbIsMainSequenceRootNode( isMainSequenceRootNode_( xNode
) )
331 ENSURE_OR_THROW( mxAnimationNode
.is(),
332 "BaseNode::BaseNode(): Invalid XAnimationNode" );
334 // setup state transition table
335 mpStateTransitionTable
= getStateTransitionTable( getRestartMode(),
339 void BaseNode::dispose()
341 meCurrState
= INVALID
;
343 // discharge a loaded event, if any:
344 if (mpCurrentEvent
) {
345 mpCurrentEvent
->dispose();
346 mpCurrentEvent
.reset();
348 maDeactivatingListeners
.clear();
349 mxAnimationNode
.clear();
356 sal_Int16
BaseNode::getRestartMode()
358 const sal_Int16
nTmp( mxAnimationNode
->getRestart() );
359 return nTmp
!= animations::AnimationRestart::DEFAULT
360 ? nTmp
: getRestartDefaultMode();
363 sal_Int16
BaseNode::getFillMode()
365 const sal_Int16
nTmp( mxAnimationNode
->getFill() );
366 const sal_Int16
nFill(nTmp
!= animations::AnimationFill::DEFAULT
367 ? nTmp
: getFillDefaultMode());
369 // For AUTO fill mode, SMIL specifies that fill mode is FREEZE,
370 // if no explicit active duration is given
371 // (no duration, end, repeatCount or repeatDuration given),
372 // and REMOVE otherwise
373 if( nFill
== animations::AnimationFill::AUTO
) {
374 return (isIndefiniteTiming( mxAnimationNode
->getDuration() ) &&
375 isIndefiniteTiming( mxAnimationNode
->getEnd() ) &&
376 !mxAnimationNode
->getRepeatCount().hasValue() &&
377 isIndefiniteTiming( mxAnimationNode
->getRepeatDuration() ))
378 ? animations::AnimationFill::FREEZE
379 : animations::AnimationFill::REMOVE
;
386 sal_Int16
BaseNode::getFillDefaultMode() const
388 sal_Int16 nFillDefault
= mxAnimationNode
->getFillDefault();
389 if (nFillDefault
== animations::AnimationFill::DEFAULT
) {
390 nFillDefault
= (mpParent
!= nullptr
391 ? mpParent
->getFillDefaultMode()
392 : animations::AnimationFill::AUTO
);
397 sal_Int16
BaseNode::getRestartDefaultMode() const
399 sal_Int16 nRestartDefaultMode
= mxAnimationNode
->getRestartDefault();
400 if (nRestartDefaultMode
== animations::AnimationRestart::DEFAULT
) {
401 nRestartDefaultMode
= (mpParent
!= nullptr
402 ? mpParent
->getRestartDefaultMode()
403 : animations::AnimationRestart::ALWAYS
);
405 return nRestartDefaultMode
;
408 uno::Reference
<animations::XAnimationNode
> BaseNode::getXAnimationNode() const
410 return mxAnimationNode
;
413 bool BaseNode::init()
415 if (! checkValidNode())
417 meCurrState
= UNRESOLVED
;
418 // discharge a loaded event, if any:
419 if (mpCurrentEvent
) {
420 mpCurrentEvent
->dispose();
421 mpCurrentEvent
.reset();
423 return init_st(); // may call derived class
426 bool BaseNode::init_st()
431 bool BaseNode::resolve()
433 if (! checkValidNode())
436 OSL_ASSERT( meCurrState
!= RESOLVED
);
437 if (inStateOrTransition( RESOLVED
))
440 StateTransition
st(this);
441 if (st
.enter( RESOLVED
) &&
442 isTransition( RESOLVED
, ACTIVE
) &&
443 resolve_st() /* may call derived class */)
445 st
.commit(); // changing state
447 // discharge a loaded event, if any:
449 mpCurrentEvent
->dispose();
451 // schedule activation event:
453 // This method takes the NodeContext::mnStartDelay value into account,
454 // to cater for iterate container time shifts. We cannot put different
455 // iterations of the iterate container's children into different
456 // subcontainer (such as a 'DelayContainer', which delays resolving its
457 // children by a fixed amount), since all iterations' nodes must be
458 // resolved at the same time (otherwise, the delayed subset creation
459 // will not work, i.e. deactivate the subsets too late in the master
461 uno::Any
const aBegin( mxAnimationNode
->getBegin() );
462 if (aBegin
.hasValue()) {
464 mpCurrentEvent
= generateEvent(
465 aBegin
, [self
=std::move(self
)] () { self
->activate(); },
466 maContext
, mnStartDelay
);
469 // For some leaf nodes, PPT import yields empty begin time,
470 // although semantically, it should be 0.0
471 // TODO(F3): That should really be provided by the PPT import
473 // schedule delayed activation event. Take iterate node
474 // timeout into account
476 mpCurrentEvent
= makeDelay(
477 [self
=std::move(self
)] () { self
->activate(); },
479 u
"AnimationNode::activate with delay"_ustr
);
480 maContext
.mrEventQueue
.addEvent( mpCurrentEvent
);
488 bool BaseNode::resolve_st()
494 void BaseNode::activate()
496 if (! checkValidNode())
499 OSL_ASSERT( meCurrState
!= ACTIVE
);
500 if (inStateOrTransition( ACTIVE
))
503 StateTransition
st(this);
504 if (st
.enter( ACTIVE
)) {
506 activate_st(); // calling derived class
508 st
.commit(); // changing state
510 maContext
.mrEventMultiplexer
.notifyAnimationStart( mpSelf
);
514 void BaseNode::activate_st()
516 scheduleDeactivationEvent();
519 void BaseNode::scheduleDeactivationEvent( EventSharedPtr
const& pEvent
)
521 if (mpCurrentEvent
) {
522 mpCurrentEvent
->dispose();
523 mpCurrentEvent
.reset();
526 if (maContext
.mrEventQueue
.addEvent( pEvent
))
527 mpCurrentEvent
= pEvent
;
530 // This method need not take the
531 // NodeContext::mnStartDelay value into account,
532 // because the deactivation event is only scheduled
533 // when the effect is started: the timeout is then
534 // already respected.
537 // think about set node, anim base node!
538 // if anim base node has no activity, this is called to schedule deactivation,
539 // but what if it does not schedule anything?
541 if (mxAnimationNode
->getEnd().hasValue())
543 // TODO: We may need to calculate the duration if the end value is numeric.
544 // We expect that the end value contains EventTrigger::ON_NEXT here.
545 // LibreOffice does not generate numeric values, so we can leave it
546 // until we find a test case.
547 mpCurrentEvent
= generateEvent(
548 mxAnimationNode
->getEnd(),
549 [self
=mpSelf
] () { self
->deactivate(); },
555 mpCurrentEvent
= generateEvent(
556 mxAnimationNode
->getDuration(),
557 [self
=mpSelf
] () { self
->deactivate(); },
563 void BaseNode::deactivate()
565 if (inStateOrTransition( ENDED
| FROZEN
) || !checkValidNode())
568 if (isTransition( meCurrState
, FROZEN
, false /* no OSL_ASSERT */ )) {
569 // do transition to FROZEN:
570 StateTransition
st(this);
571 if (st
.enter( FROZEN
, StateTransition::Options::FORCE
)) {
573 deactivate_st( FROZEN
);
576 notifyEndListeners();
578 // discharge a loaded event, before going on:
579 if (mpCurrentEvent
) {
580 mpCurrentEvent
->dispose();
581 mpCurrentEvent
.reset();
589 // state has changed either to FROZEN or ENDED
592 void BaseNode::deactivate_st( NodeState
)
598 bool const bIsFrozenOrInTransitionToFrozen
= inStateOrTransition( FROZEN
);
599 if (inStateOrTransition( ENDED
) || !checkValidNode())
602 // END must always be reachable. If not, that's an error in the
604 OSL_ENSURE( isTransition( meCurrState
, ENDED
),
605 "end state not reachable in transition table" );
607 StateTransition
st(this);
608 if (!st
.enter( ENDED
, StateTransition::Options::FORCE
))
611 deactivate_st( ENDED
);
612 st
.commit(); // changing state
614 // if is FROZEN or is to be FROZEN, then
615 // will/already notified deactivating listeners
616 if (!bIsFrozenOrInTransitionToFrozen
)
617 notifyEndListeners();
619 // discharge a loaded event, before going on:
620 if (mpCurrentEvent
) {
621 mpCurrentEvent
->dispose();
622 mpCurrentEvent
.reset();
626 void BaseNode::notifyDeactivating( const AnimationNodeSharedPtr
& rNotifier
)
628 OSL_ASSERT( rNotifier
->getState() == FROZEN
||
629 rNotifier
->getState() == ENDED
);
630 // TODO(F1): for end sync functionality, this might indeed be used some day
633 void BaseNode::notifyEndListeners() const
635 // notify all listeners
636 for( const auto& rListener
: maDeactivatingListeners
)
637 rListener
->notifyDeactivating( mpSelf
);
639 // notify state change
640 maContext
.mrEventMultiplexer
.notifyAnimationEnd( mpSelf
);
642 // notify main sequence end (iff we're the main
643 // sequence root node). This is because the main
644 // sequence determines the active duration of the
645 // slide. All other sequences are secondary, in that
646 // they don't prevent a slide change from happening,
647 // even if they have not been completed. In other
648 // words, all sequences except the main sequence are
649 // optional for the slide lifetime.
650 if (isMainSequenceRootNode())
651 maContext
.mrEventMultiplexer
.notifySlideAnimationsEnd();
654 AnimationNode::NodeState
BaseNode::getState() const
659 bool BaseNode::registerDeactivatingListener(
660 const AnimationNodeSharedPtr
& rNotifee
)
662 if (! checkValidNode())
665 ENSURE_OR_RETURN_FALSE(
667 "BaseNode::registerDeactivatingListener(): invalid notifee" );
668 maDeactivatingListeners
.push_back( rNotifee
);
673 void BaseNode::setSelf( const BaseNodeSharedPtr
& rSelf
)
675 ENSURE_OR_THROW( rSelf
.get() == this,
676 "BaseNode::setSelf(): got ptr to different object" );
677 ENSURE_OR_THROW( !mpSelf
,
678 "BaseNode::setSelf(): called multiple times" );
686 #if defined(DBG_UTIL)
687 void BaseNode::showState() const
689 const AnimationNode::NodeState
eNodeState( getState() );
691 if( eNodeState
== AnimationNode::INVALID
)
692 SAL_INFO("slideshow.verbose", "Node state: n" <<
693 debugGetNodeName(this) <<
696 "\",style=filled, fillcolor=\"0.5,0.2,0.5\"]");
698 SAL_INFO("slideshow.verbose", "Node state: n" <<
699 debugGetNodeName(this) <<
703 log(double(getState()))/4.0 <<
706 // determine additional node information
707 uno::Reference
<animations::XAnimate
> const xAnimate( mxAnimationNode
,
712 uno::Reference
< drawing::XShape
> xTargetShape( xAnimate
->getTarget(),
715 if( !xTargetShape
.is() )
717 css::presentation::ParagraphTarget aTarget
;
719 // no shape provided. Maybe a ParagraphTarget?
720 if( xAnimate
->getTarget() >>= aTarget
)
721 xTargetShape
= aTarget
.Shape
;
724 if( !xTargetShape
.is() )
727 uno::Reference
< beans::XPropertySet
> xPropSet( xTargetShape
,
732 if( xPropSet
->getPropertyValue(u
"Name"_ustr
) >>= aName
)
734 SAL_INFO("slideshow.verbose", "Node info: n" <<
735 debugGetNodeName(this) <<
742 const char* BaseNode::getDescription() const
749 } // namespace slideshow
751 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */