summaryrefslogtreecommitdiffstats
path: root/slideshow/source/engine/animationnodes
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--slideshow/source/engine/animationnodes/animationaudionode.cxx203
-rw-r--r--slideshow/source/engine/animationnodes/animationaudionode.hxx70
-rw-r--r--slideshow/source/engine/animationnodes/animationbasenode.cxx489
-rw-r--r--slideshow/source/engine/animationnodes/animationbasenode.hxx99
-rw-r--r--slideshow/source/engine/animationnodes/animationcolornode.cxx121
-rw-r--r--slideshow/source/engine/animationnodes/animationcolornode.hxx54
-rw-r--r--slideshow/source/engine/animationnodes/animationcommandnode.cxx117
-rw-r--r--slideshow/source/engine/animationnodes/animationcommandnode.hxx60
-rw-r--r--slideshow/source/engine/animationnodes/animationnodefactory.cxx592
-rw-r--r--slideshow/source/engine/animationnodes/animationpathmotionnode.cxx52
-rw-r--r--slideshow/source/engine/animationnodes/animationpathmotionnode.hxx57
-rw-r--r--slideshow/source/engine/animationnodes/animationsetnode.cxx191
-rw-r--r--slideshow/source/engine/animationnodes/animationsetnode.hxx49
-rw-r--r--slideshow/source/engine/animationnodes/animationtransformnode.cxx97
-rw-r--r--slideshow/source/engine/animationnodes/animationtransformnode.hxx58
-rw-r--r--slideshow/source/engine/animationnodes/animationtransitionfilternode.cxx45
-rw-r--r--slideshow/source/engine/animationnodes/animationtransitionfilternode.hxx59
-rw-r--r--slideshow/source/engine/animationnodes/basecontainernode.cxx221
-rw-r--r--slideshow/source/engine/animationnodes/basenode.cxx751
-rw-r--r--slideshow/source/engine/animationnodes/generateevent.cxx237
-rw-r--r--slideshow/source/engine/animationnodes/generateevent.hxx55
-rw-r--r--slideshow/source/engine/animationnodes/nodetools.cxx84
-rw-r--r--slideshow/source/engine/animationnodes/nodetools.hxx71
-rw-r--r--slideshow/source/engine/animationnodes/paralleltimecontainer.cxx58
-rw-r--r--slideshow/source/engine/animationnodes/paralleltimecontainer.hxx55
-rw-r--r--slideshow/source/engine/animationnodes/propertyanimationnode.cxx100
-rw-r--r--slideshow/source/engine/animationnodes/propertyanimationnode.hxx50
-rw-r--r--slideshow/source/engine/animationnodes/sequentialtimecontainer.cxx122
-rw-r--r--slideshow/source/engine/animationnodes/sequentialtimecontainer.hxx65
-rw-r--r--slideshow/source/engine/animationnodes/setactivity.hxx144
30 files changed, 4426 insertions, 0 deletions
diff --git a/slideshow/source/engine/animationnodes/animationaudionode.cxx b/slideshow/source/engine/animationnodes/animationaudionode.cxx
new file mode 100644
index 000000000..c6eb61d17
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationaudionode.cxx
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/lang/NoSupportException.hpp>
+
+#include <eventqueue.hxx>
+#include "animationaudionode.hxx"
+#include <delayevent.hxx>
+
+using namespace com::sun::star;
+
+namespace slideshow::internal {
+
+AnimationAudioNode::AnimationAudioNode(
+ const uno::Reference< animations::XAnimationNode >& xNode,
+ const BaseContainerNodeSharedPtr& rParent,
+ const NodeContext& rContext )
+ : BaseNode( xNode, rParent, rContext ),
+ mxAudioNode( xNode, uno::UNO_QUERY_THROW ),
+ maSoundURL(),
+ mpPlayer()
+{
+ mxAudioNode->getSource() >>= maSoundURL;
+
+ OSL_ENSURE( !maSoundURL.isEmpty(),
+ "could not extract sound source URL/empty URL string" );
+
+ ENSURE_OR_THROW( getContext().mxComponentContext.is(),
+ "Invalid component context" );
+}
+
+void AnimationAudioNode::dispose()
+{
+ resetPlayer();
+ mxAudioNode.clear();
+ BaseNode::dispose();
+}
+
+void AnimationAudioNode::activate_st()
+{
+ createPlayer();
+
+ AnimationEventHandlerSharedPtr aHandler(
+ std::dynamic_pointer_cast<AnimationEventHandler>( getSelf() ) );
+ OSL_ENSURE( aHandler,
+ "could not cast self to AnimationEventHandler?" );
+ getContext().mrEventMultiplexer.addCommandStopAudioHandler( aHandler );
+
+ if (mpPlayer && mpPlayer->startPlayback())
+ {
+ // TODO(F2): Handle end time attribute, too
+ if( getXAnimationNode()->getDuration().hasValue() )
+ {
+ scheduleDeactivationEvent();
+ }
+ else
+ {
+ // no node duration. Take inherent media time. We have to recheck
+ // if the player is playing in case the duration isn't accurate
+ // or the progress fall behind.
+ auto self(getSelf());
+ scheduleDeactivationEvent(
+ makeDelay( [this] () { this->checkPlayingStatus(); },
+ mpPlayer->getDuration(),
+ "AnimationAudioNode::check if still playing with delay") );
+ }
+ }
+ else
+ {
+ // deactivate ASAP:
+ auto self(getSelf());
+ scheduleDeactivationEvent(
+ makeEvent( [self] () { self->deactivate(); },
+ "AnimationAudioNode::deactivate without delay") );
+ }
+}
+
+// TODO(F2): generate deactivation event, when sound
+// is over
+
+namespace {
+
+// libc++ and MSVC std::bind doesn't cut it here, and it's not possible to use
+// a lambda because the preprocessor thinks that comma in capture list
+// separates macro parameters
+struct NotifyAudioStopped
+{
+ EventMultiplexer & m_rEventMultiplexer;
+ ::std::shared_ptr<BaseNode> m_pSelf;
+ NotifyAudioStopped(EventMultiplexer & rEventMultiplexer,
+ ::std::shared_ptr<BaseNode> const& pSelf)
+ : m_rEventMultiplexer(rEventMultiplexer), m_pSelf(pSelf) { }
+
+ void operator()()
+ {
+ m_rEventMultiplexer.notifyAudioStopped(m_pSelf);
+ }
+};
+
+}
+
+void AnimationAudioNode::deactivate_st( NodeState /*eDestState*/ )
+{
+ AnimationEventHandlerSharedPtr aHandler(
+ std::dynamic_pointer_cast<AnimationEventHandler>( getSelf() ) );
+ OSL_ENSURE( aHandler,
+ "could not cast self to AnimationEventHandler?" );
+ getContext().mrEventMultiplexer.removeCommandStopAudioHandler( aHandler );
+
+ // force-end sound
+ if (mpPlayer)
+ {
+ mpPlayer->stopPlayback();
+ resetPlayer();
+ }
+
+ // notify _after_ state change:
+ getContext().mrEventQueue.addEvent(
+ makeEvent( NotifyAudioStopped(getContext().mrEventMultiplexer, getSelf()),
+ "AnimationAudioNode::notifyAudioStopped") );
+}
+
+bool AnimationAudioNode::hasPendingAnimation() const
+{
+ // force slide to use the animation framework
+ // (otherwise, a single sound on the slide would
+ // not be played).
+ return true;
+}
+
+void AnimationAudioNode::createPlayer() const
+{
+ if (mpPlayer)
+ return;
+
+ try
+ {
+ mpPlayer = SoundPlayer::create( getContext().mrEventMultiplexer,
+ maSoundURL,
+ getContext().mxComponentContext,
+ getContext().mrMediaFileManager);
+ }
+ catch( lang::NoSupportException& )
+ {
+ // catch possible exceptions from SoundPlayer,
+ // since being not able to playback the sound
+ // is not a hard error here (remainder of the
+ // animations should still work).
+ }
+}
+
+void AnimationAudioNode::resetPlayer() const
+{
+ if (mpPlayer)
+ {
+ mpPlayer->stopPlayback();
+ mpPlayer->dispose();
+ mpPlayer.reset();
+ }
+}
+
+bool AnimationAudioNode::handleAnimationEvent(
+ const AnimationNodeSharedPtr& /*rNode*/ )
+{
+ // TODO(F2): for now we support only STOPAUDIO events.
+ deactivate();
+ return true;
+}
+
+void AnimationAudioNode::checkPlayingStatus()
+{
+ auto self(getSelf());
+ double nDuration = mpPlayer->getDuration();
+ if (!mpPlayer->isPlaying() || nDuration < 0.0)
+ nDuration = 0.0;
+
+ scheduleDeactivationEvent(
+ makeDelay( [self] () { self->deactivate(); },
+ nDuration,
+ "AnimationAudioNode::deactivate with delay") );
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationaudionode.hxx b/slideshow/source/engine/animationnodes/animationaudionode.hxx
new file mode 100644
index 000000000..d1c08601c
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationaudionode.hxx
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONAUDIONODE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONAUDIONODE_HXX
+
+#include <basecontainernode.hxx>
+#include <soundplayer.hxx>
+#include <com/sun/star/animations/XAnimationNode.hpp>
+#include <com/sun/star/animations/XAudio.hpp>
+
+namespace slideshow {
+namespace internal {
+
+/** Audio node.
+
+ This animation node contains an audio effect. Duration and
+ start/stop behaviour is affected by the referenced audio
+ file.
+*/
+class AnimationAudioNode : public BaseNode, public AnimationEventHandler
+{
+public:
+ AnimationAudioNode(
+ css::uno::Reference<css::animations::XAnimationNode> const& xNode,
+ BaseContainerNodeSharedPtr const& pParent,
+ NodeContext const& rContext );
+
+protected:
+ virtual void dispose() override;
+
+private:
+ virtual void activate_st() override;
+ virtual void deactivate_st( NodeState eDestState ) override;
+ virtual bool hasPendingAnimation() const override;
+
+ /// overridden, because we need to deal with STOPAUDIO commands
+ virtual bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) override;
+
+private:
+ css::uno::Reference<css::animations::XAudio > mxAudioNode;
+ OUString maSoundURL;
+ mutable SoundPlayerSharedPtr mpPlayer;
+
+ void createPlayer() const;
+ void resetPlayer() const;
+ void checkPlayingStatus();
+};
+
+} // namespace internal
+} // namespace slideshow
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONAUDIONODE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationbasenode.cxx b/slideshow/source/engine/animationnodes/animationbasenode.cxx
new file mode 100644
index 000000000..4dcb64079
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationbasenode.cxx
@@ -0,0 +1,489 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <sal/log.hxx>
+#include <com/sun/star/presentation/ParagraphTarget.hpp>
+#include <com/sun/star/animations/Timing.hpp>
+#include <com/sun/star/animations/AnimationAdditiveMode.hpp>
+#include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
+
+#include "nodetools.hxx"
+#include <doctreenode.hxx>
+#include "animationbasenode.hxx"
+#include <delayevent.hxx>
+#include <framerate.hxx>
+
+#include <optional>
+#include <algorithm>
+
+using namespace com::sun::star;
+
+namespace slideshow::internal {
+
+AnimationBaseNode::AnimationBaseNode(
+ const uno::Reference< animations::XAnimationNode >& xNode,
+ const BaseContainerNodeSharedPtr& rParent,
+ const NodeContext& rContext )
+ : BaseNode( xNode, rParent, rContext ),
+ mxAnimateNode( xNode, uno::UNO_QUERY_THROW ),
+ maAttributeLayerHolder(),
+ maSlideSize( rContext.maSlideSize ),
+ mpActivity(),
+ mpShape(),
+ mpShapeSubset(),
+ mpSubsetManager(rContext.maContext.mpSubsettableShapeManager),
+ mbPreservedVisibility(true),
+ mbIsIndependentSubset( rContext.mbIsIndependentSubset )
+{
+ // extract native node targets
+ // ===========================
+
+ // plain shape target
+ uno::Reference< drawing::XShape > xShape( mxAnimateNode->getTarget(),
+ uno::UNO_QUERY );
+
+ // distinguish 5 cases:
+
+ // - plain shape target
+ // (NodeContext.mpMasterShapeSubset full set)
+
+ // - parent-generated subset (generate an
+ // independent subset)
+
+ // - parent-generated subset from iteration
+ // (generate a dependent subset)
+
+ // - XShape target at the XAnimatioNode (generate
+ // a plain shape target)
+
+ // - ParagraphTarget target at the XAnimationNode
+ // (generate an independent shape subset)
+ if( rContext.mpMasterShapeSubset )
+ {
+ if( rContext.mpMasterShapeSubset->isFullSet() )
+ {
+ // case 1: plain shape target from parent
+ mpShape = rContext.mpMasterShapeSubset->getSubsetShape();
+ }
+ else
+ {
+ // cases 2 & 3: subset shape
+ mpShapeSubset = rContext.mpMasterShapeSubset;
+ }
+ }
+ else
+ {
+ // no parent-provided shape, try to extract
+ // from XAnimationNode - cases 4 and 5
+
+ if( xShape.is() )
+ {
+ mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager,
+ xShape );
+ }
+ else
+ {
+ // no shape provided. Maybe a ParagraphTarget?
+ presentation::ParagraphTarget aTarget;
+
+ if( !(mxAnimateNode->getTarget() >>= aTarget) )
+ ENSURE_OR_THROW(
+ false, "could not extract any target information" );
+
+ xShape = aTarget.Shape;
+
+ ENSURE_OR_THROW( xShape.is(), "invalid shape in ParagraphTarget" );
+
+ mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager,
+ xShape );
+
+ // NOTE: For shapes with ParagraphTarget, we ignore
+ // the SubItem property. We implicitly assume that it
+ // is set to ONLY_TEXT.
+ OSL_ENSURE(
+ mxAnimateNode->getSubItem() ==
+ presentation::ShapeAnimationSubType::ONLY_TEXT ||
+ mxAnimateNode->getSubItem() ==
+ presentation::ShapeAnimationSubType::AS_WHOLE,
+ "ParagraphTarget given, but subitem not AS_TEXT or AS_WHOLE? "
+ "Make up your mind, I'll ignore the subitem." );
+
+ // okay, found a ParagraphTarget with a valid XShape. Does the shape
+ // provide the given paragraph?
+ if( aTarget.Paragraph >= 0 &&
+ mpShape->getTreeNodeSupplier().getNumberOfTreeNodes(
+ DocTreeNode::NodeType::LogicalParagraph) > aTarget.Paragraph )
+ {
+ const DocTreeNode& rTreeNode(
+ mpShape->getTreeNodeSupplier().getTreeNode(
+ aTarget.Paragraph,
+ DocTreeNode::NodeType::LogicalParagraph ) );
+
+ // CAUTION: the creation of the subset shape
+ // _must_ stay in the node constructor, since
+ // Slide::prefetchShow() initializes shape
+ // attributes right after animation import (or
+ // the Slide class must be changed).
+ mpShapeSubset =
+ std::make_shared<ShapeSubset>( mpShape,
+ rTreeNode,
+ mpSubsetManager );
+
+ // Override NodeContext, and flag this node as
+ // a special independent subset one. This is
+ // important when applying initial attributes:
+ // independent shape subsets must be setup
+ // when the slide starts, since they, as their
+ // name suggest, can have state independent to
+ // the master shape. The following example
+ // might illustrate that: a master shape has
+ // no effect, one of the text paragraphs
+ // within it has an appear effect. Now, the
+ // respective paragraph must be invisible when
+ // the slide is initially shown, and become
+ // visible only when the effect starts.
+ mbIsIndependentSubset = true;
+
+ // already enable subset right here, the
+ // setup of initial shape attributes of
+ // course needs the subset shape
+ // generated, to apply e.g. visibility
+ // changes.
+ mpShapeSubset->enableSubsetShape();
+ }
+ }
+ }
+}
+
+void AnimationBaseNode::dispose()
+{
+ if (mpActivity) {
+ mpActivity->dispose();
+ mpActivity.reset();
+ }
+
+ maAttributeLayerHolder.reset();
+ mxAnimateNode.clear();
+ mpShape.reset();
+ mpShapeSubset.reset();
+
+ BaseNode::dispose();
+}
+
+bool AnimationBaseNode::init_st()
+{
+ // if we've still got an old activity lying around, dispose it:
+ if (mpActivity) {
+ mpActivity->dispose();
+ mpActivity.reset();
+ }
+
+ // note: actually disposing the activity too early might cause problems,
+ // because on dequeued() it calls endAnimation(pAnim->end()), thus ending
+ // animation _after_ last screen update.
+ // review that end() is properly called (which calls endAnimation(), too).
+
+ try {
+ // TODO(F2): For restart functionality, we must regenerate activities,
+ // since they are not able to reset their state (or implement _that_)
+ mpActivity = createActivity();
+ }
+ catch (uno::Exception const&) {
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ // catch and ignore. We later handle empty activities, but for
+ // other nodes to function properly, the core functionality of
+ // this node must remain up and running.
+ }
+ return true;
+}
+
+bool AnimationBaseNode::resolve_st()
+{
+ // enable shape subset for automatically generated
+ // subsets. Independent subsets are already setup
+ // during construction time. Doing it only here
+ // saves us a lot of sprites and shapes lying
+ // around. This is especially important for
+ // character-wise iterations, since the shape
+ // content (e.g. thousands of characters) would
+ // otherwise be painted character-by-character.
+ if (isDependentSubsettedShape() && mpShapeSubset) {
+ mpShapeSubset->enableSubsetShape();
+ }
+ return true;
+}
+
+void AnimationBaseNode::activate_st()
+{
+ AttributableShapeSharedPtr const pShape(getShape());
+ mbPreservedVisibility = pShape->isVisible();
+
+ // create new attribute layer
+ maAttributeLayerHolder.createAttributeLayer(pShape);
+
+ ENSURE_OR_THROW( maAttributeLayerHolder.get(),
+ "Could not generate shape attribute layer" );
+
+ // TODO(Q2): This affects the way mpActivity
+ // works, but is performed here because of
+ // locality (we're fiddling with the additive mode
+ // here, anyway, and it's the only place where we
+ // do). OTOH, maybe the complete additive mode
+ // setup should be moved to the activities.
+
+ // for simple by-animations, the SMIL spec
+ // requires us to emulate "0,by-value" value list
+ // behaviour, with additive mode forced to "sum",
+ // no matter what the input is
+ // (http://www.w3.org/TR/smil20/animation.html#adef-by).
+ if( mxAnimateNode->getBy().hasValue() &&
+ !mxAnimateNode->getTo().hasValue() &&
+ !mxAnimateNode->getFrom().hasValue() )
+ {
+ // force attribute mode to REPLACE (note the
+ // subtle discrepancy to the paragraph above,
+ // where SMIL requires SUM. This is internally
+ // handled by the FromToByActivity, and is
+ // because otherwise DOM values would not be
+ // handled correctly: the activity cannot
+ // determine whether an
+ // Activity::getUnderlyingValue() yields the
+ // DOM value, or already a summed-up conglomerate)
+
+ // Note that this poses problems with our
+ // hybrid activity duration (time or min number of frames),
+ // since if activities
+ // exceed their duration, wrong 'by' start
+ // values might arise ('Laser effect')
+ maAttributeLayerHolder.get()->setAdditiveMode(
+ animations::AnimationAdditiveMode::REPLACE );
+ }
+ else
+ {
+ // apply additive mode to newly created Attribute layer
+ maAttributeLayerHolder.get()->setAdditiveMode(
+ mxAnimateNode->getAdditive() );
+ }
+
+ // fake normal animation behaviour, even if we
+ // show nothing. This is the appropriate way to
+ // handle errors on Activity generation, because
+ // maybe all other effects on the slide are
+ // correctly initialized (but won't run, if we
+ // signal an error here)
+ if (mpActivity) {
+ // supply Activity (and the underlying Animation) with
+ // it's AttributeLayer, to perform the animation on
+ mpActivity->setTargets( getShape(), maAttributeLayerHolder.get() );
+
+ // add to activities queue
+ getContext().mrActivitiesQueue.addActivity( mpActivity );
+ }
+ else {
+ // Actually, DO generate the event for empty activity,
+ // to keep the chain of animations running
+ BaseNode::scheduleDeactivationEvent();
+ }
+}
+
+void AnimationBaseNode::deactivate_st( NodeState eDestState )
+{
+ if (eDestState == FROZEN && mpActivity)
+ mpActivity->end();
+
+ if (isDependentSubsettedShape()) {
+ // for dependent subsets, remove subset shape
+ // from layer, re-integrate subsetted part
+ // back into original shape. For independent
+ // subsets, we cannot make any assumptions
+ // about subset attribute state relative to
+ // master shape, thus, have to keep it. This
+ // will effectively re-integrate the subsetted
+ // part into the original shape (whose
+ // animation will hopefully have ended, too)
+
+ // this statement will save a whole lot of
+ // sprites for iterated text effects, since
+ // those sprites will only exist during the
+ // actual lifetime of the effects
+ if (mpShapeSubset) {
+ mpShapeSubset->disableSubsetShape();
+ }
+ }
+
+ if (eDestState != ENDED)
+ return;
+
+ // no shape anymore, no layer needed:
+ maAttributeLayerHolder.reset();
+
+ if (! isDependentSubsettedShape()) {
+
+ // for all other shapes, removing the
+ // attribute layer quite possibly changes
+ // shape display. Thus, force update
+ AttributableShapeSharedPtr const pShape( getShape() );
+
+ // don't anybody dare to check against
+ // pShape->isVisible() here, removing the
+ // attribute layer might actually make the
+ // shape invisible!
+ getContext().mpSubsettableShapeManager->notifyShapeUpdate( pShape );
+ }
+
+ if (mpActivity) {
+ // kill activity, if still running
+ mpActivity->dispose();
+ mpActivity.reset();
+ }
+}
+
+void AnimationBaseNode::removeEffect()
+{
+ if (!isDependentSubsettedShape()) {
+ AttributableShapeSharedPtr const pShape(getShape());
+ pShape->setVisibility(!mbPreservedVisibility);
+ getContext().mpSubsettableShapeManager->notifyShapeUpdate( pShape );
+ pShape->setVisibility(mbPreservedVisibility);
+ }
+}
+
+bool AnimationBaseNode::hasPendingAnimation() const
+{
+ // TODO(F1): This might not always be true. Are there 'inactive'
+ // animation nodes?
+ return true;
+}
+
+#if defined(DBG_UTIL)
+void AnimationBaseNode::showState() const
+{
+ BaseNode::showState();
+
+ SAL_INFO( "slideshow.verbose", "AnimationBaseNode info: independent subset=" <<
+ (mbIsIndependentSubset ? "y" : "n") );
+}
+#endif
+
+ActivitiesFactory::CommonParameters
+AnimationBaseNode::fillCommonParameters() const
+{
+ double nDuration = 0.0;
+
+ // TODO(F3): Duration/End handling is barely there
+ if( !(mxAnimateNode->getDuration() >>= nDuration) ) {
+ mxAnimateNode->getEnd() >>= nDuration; // Wah.
+ }
+
+ // minimal duration we fallback to (avoid 0 here!)
+ nDuration = ::std::max( 0.001, nDuration );
+
+ const bool bAutoReverse( mxAnimateNode->getAutoReverse() );
+
+ std::optional<double> aRepeats;
+ double nRepeats = 0;
+ if( mxAnimateNode->getRepeatCount() >>= nRepeats ) {
+ aRepeats = nRepeats;
+ }
+ else {
+ if( mxAnimateNode->getRepeatDuration() >>= nRepeats ) {
+ // when repeatDuration is given,
+ // autoreverse does _not_ modify the
+ // active duration. Thus, calc repeat
+ // count with already adapted simple
+ // duration (twice the specified duration)
+
+ // convert duration back to repeat counts
+ if( bAutoReverse )
+ aRepeats = nRepeats / (2.0 * nDuration);
+ else
+ aRepeats = nRepeats / nDuration;
+ }
+ else
+ {
+ // no double value for both values - Timing::INDEFINITE?
+ animations::Timing eTiming;
+
+ if( !(mxAnimateNode->getRepeatDuration() >>= eTiming) ||
+ eTiming != animations::Timing_INDEFINITE )
+ {
+ if( !(mxAnimateNode->getRepeatCount() >>= eTiming) ||
+ eTiming != animations::Timing_INDEFINITE )
+ {
+ // no indefinite timing, no other values given -
+ // use simple run, i.e. repeat of 1.0
+ aRepeats = 1.0;
+ }
+ }
+ }
+ }
+
+ // calc accel/decel:
+ double nAcceleration = 0.0;
+ double nDeceleration = 0.0;
+ BaseNodeSharedPtr const pSelf( getSelf() );
+ for ( std::shared_ptr<BaseNode> pNode( pSelf );
+ pNode; pNode = pNode->getParentNode() )
+ {
+ uno::Reference<animations::XAnimationNode> const xAnimationNode(
+ pNode->getXAnimationNode() );
+ nAcceleration = std::max( nAcceleration,
+ xAnimationNode->getAcceleration() );
+ nDeceleration = std::max( nDeceleration,
+ xAnimationNode->getDecelerate() );
+ }
+
+ EventSharedPtr pEndEvent;
+ if (pSelf) {
+ pEndEvent = makeEvent( [pSelf] () {pSelf->deactivate(); },
+ "AnimationBaseNode::deactivate");
+ }
+
+ // Calculate the minimum frame count that depends on the duration and
+ // the minimum frame count.
+ const sal_Int32 nMinFrameCount (std::clamp<sal_Int32>(
+ basegfx::fround(nDuration * FrameRate::MinimumFramesPerSecond), 1, 10));
+
+ return ActivitiesFactory::CommonParameters(
+ pEndEvent,
+ getContext().mrEventQueue,
+ getContext().mrActivitiesQueue,
+ nDuration,
+ nMinFrameCount,
+ bAutoReverse,
+ aRepeats,
+ nAcceleration,
+ nDeceleration,
+ getShape(),
+ getSlideSize());
+}
+
+AttributableShapeSharedPtr const & AnimationBaseNode::getShape() const
+{
+ // any subsetting at all?
+ if (mpShapeSubset)
+ return mpShapeSubset->getSubsetShape();
+ else
+ return mpShape; // nope, plain shape always
+}
+
+} // namespace slideshow
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationbasenode.hxx b/slideshow/source/engine/animationnodes/animationbasenode.hxx
new file mode 100644
index 000000000..c8716706c
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationbasenode.hxx
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONBASENODE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONBASENODE_HXX
+
+#include <com/sun/star/animations/XAnimate.hpp>
+
+#include <basecontainernode.hxx>
+#include <activitiesfactory.hxx>
+#include <shapeattributelayerholder.hxx>
+#include <attributableshape.hxx>
+#include <shapesubset.hxx>
+
+namespace slideshow {
+namespace internal {
+
+/** Common base class for all leaf animation nodes.
+
+ This class basically holds the target shape
+*/
+class AnimationBaseNode : public BaseNode
+{
+public:
+ AnimationBaseNode(
+ css::uno::Reference<css::animations::XAnimationNode> const& xNode,
+ BaseContainerNodeSharedPtr const& pParent,
+ NodeContext const& rContext );
+
+#if defined(DBG_UTIL)
+ virtual void showState() const override;
+#endif
+ virtual void removeEffect() override;
+
+protected:
+ virtual void dispose() override;
+
+ css::uno::Reference<css::animations::XAnimate> const& getXAnimateNode() const
+ { return mxAnimateNode; }
+
+ /// Create parameter struct for ActivitiesFactory
+ ActivitiesFactory::CommonParameters fillCommonParameters() const;
+ ::basegfx::B2DVector const& getSlideSize() const { return maSlideSize; }
+ AttributableShapeSharedPtr const & getShape() const;
+
+private:
+ virtual bool hasPendingAnimation() const override;
+
+private: // state transition callbacks
+ virtual bool init_st() override;
+ virtual bool resolve_st() override;
+ virtual void activate_st() override;
+ virtual void deactivate_st( NodeState eDestState ) override;
+ virtual AnimationActivitySharedPtr createActivity() const = 0;
+
+private:
+ /** Returns true, if this is a subset animation, and
+ the subset is autogenerated (e.g. from an
+ iteration)
+ */
+ bool isDependentSubsettedShape() const
+ { return mpShapeSubset && !mbIsIndependentSubset; }
+
+private:
+ css::uno::Reference<css::animations::XAnimate> mxAnimateNode;
+ ShapeAttributeLayerHolder maAttributeLayerHolder;
+ ::basegfx::B2DVector maSlideSize;
+ AnimationActivitySharedPtr mpActivity;
+
+ /// When valid, this node has a plain target shape
+ AttributableShapeSharedPtr mpShape;
+ /// When valid, this is a subsetted target shape
+ ShapeSubsetSharedPtr mpShapeSubset;
+ SubsettableShapeManagerSharedPtr mpSubsetManager;
+ bool mbPreservedVisibility;
+ bool mbIsIndependentSubset;
+};
+
+} // namespace internal
+} // namespace presentation
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONBASENODE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationcolornode.cxx b/slideshow/source/engine/animationnodes/animationcolornode.cxx
new file mode 100644
index 000000000..39ca961c4
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationcolornode.cxx
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/animations/AnimationColorSpace.hpp>
+
+#include <coloranimation.hxx>
+#include <hslcoloranimation.hxx>
+#include "animationcolornode.hxx"
+#include <animationfactory.hxx>
+#include <activitiesfactory.hxx>
+
+using namespace com::sun::star;
+
+namespace slideshow::internal {
+
+namespace {
+/** Little wrapper for HSL to RGB mapping.
+
+ This class implements the HSLColorAnimation interface,
+ internally converting to RGB and forwarding to
+ ColorAnimation.
+*/
+class HSLWrapper : public HSLColorAnimation
+{
+public:
+ explicit HSLWrapper( const ColorAnimationSharedPtr& rAnimation )
+ : mpAnimation( rAnimation )
+ {
+ ENSURE_OR_THROW(
+ mpAnimation,
+ "HSLWrapper::HSLWrapper(): Invalid color animation delegate" );
+ }
+
+ virtual void prefetch() override
+ {}
+
+ virtual void start( const AnimatableShapeSharedPtr& rShape,
+ const ShapeAttributeLayerSharedPtr& rAttrLayer ) override
+ {
+ mpAnimation->start( rShape, rAttrLayer );
+ }
+
+ virtual void end() override
+ {
+ mpAnimation->end();
+ }
+
+ virtual bool operator()( const HSLColor& rColor ) override
+ {
+ return (*mpAnimation)( RGBColor( rColor ) );
+ }
+
+ virtual HSLColor getUnderlyingValue() const override
+ {
+ return HSLColor( mpAnimation->getUnderlyingValue() );
+ }
+
+private:
+ ColorAnimationSharedPtr mpAnimation;
+};
+
+} // anon namespace
+
+AnimationActivitySharedPtr AnimationColorNode::createActivity() const
+{
+ ActivitiesFactory::CommonParameters aParms( fillCommonParameters() );
+
+ switch( mxColorNode->getColorInterpolation() )
+ {
+ case animations::AnimationColorSpace::RGB:
+ return ActivitiesFactory::createAnimateActivity(
+ aParms,
+ AnimationFactory::createColorPropertyAnimation(
+ mxColorNode->getAttributeName(),
+ getShape(),
+ getContext().mpSubsettableShapeManager,
+ getSlideSize() ),
+ getXAnimateNode() );
+
+ case animations::AnimationColorSpace::HSL:
+ // Wrap a plain ColorAnimation with the HSL
+ // wrapper, which implements the HSLColorAnimation
+ // interface, and internally converts HSL to RGB color
+ return ActivitiesFactory::createAnimateActivity(
+ aParms,
+ std::make_shared<HSLWrapper>(
+ AnimationFactory::createColorPropertyAnimation(
+ mxColorNode->getAttributeName(),
+ getShape(),
+ getContext().mpSubsettableShapeManager,
+ getSlideSize() )),
+ mxColorNode );
+
+ default:
+ ENSURE_OR_THROW( false, "AnimationColorNode::createColorActivity(): "
+ "Unexpected color space" );
+ }
+
+ return AnimationActivitySharedPtr();
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationcolornode.hxx b/slideshow/source/engine/animationnodes/animationcolornode.hxx
new file mode 100644
index 000000000..6ae5f848c
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationcolornode.hxx
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOLORNODE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOLORNODE_HXX
+
+#include "animationbasenode.hxx"
+#include <com/sun/star/animations/XAnimateColor.hpp>
+
+namespace slideshow {
+namespace internal {
+
+class AnimationColorNode : public AnimationBaseNode
+{
+public:
+ AnimationColorNode(
+ css::uno::Reference<css::animations::XAnimationNode> const& xNode,
+ ::std::shared_ptr<BaseContainerNode> const& pParent,
+ NodeContext const& rContext )
+ : AnimationBaseNode( xNode, pParent, rContext ),
+ mxColorNode( xNode, css::uno::UNO_QUERY_THROW ) {}
+
+#if defined(DBG_UTIL)
+ virtual const char* getDescription() const override { return "AnimationColorNode"; }
+#endif
+
+private:
+ virtual AnimationActivitySharedPtr createActivity() const override;
+
+ css::uno::Reference<css::animations::XAnimateColor > mxColorNode;
+};
+
+} // namespace internal
+} // namespace presentation
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOLORNODE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationcommandnode.cxx b/slideshow/source/engine/animationnodes/animationcommandnode.cxx
new file mode 100644
index 000000000..2e8507e3a
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationcommandnode.cxx
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/presentation/EffectCommands.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+#include "animationcommandnode.hxx"
+#include <eventmultiplexer.hxx>
+#include <delayevent.hxx>
+
+
+using namespace com::sun::star;
+
+namespace slideshow::internal {
+
+namespace EffectCommands = css::presentation::EffectCommands;
+
+AnimationCommandNode::AnimationCommandNode( uno::Reference<animations::XAnimationNode> const& xNode,
+ ::std::shared_ptr<BaseContainerNode> const& pParent,
+ NodeContext const& rContext ) :
+ BaseNode( xNode, pParent, rContext ),
+ mpShape(),
+ mxCommandNode( xNode, css::uno::UNO_QUERY_THROW )
+{
+ uno::Reference< drawing::XShape > xShape( mxCommandNode->getTarget(),
+ uno::UNO_QUERY );
+ ShapeSharedPtr pShape( getContext().mpSubsettableShapeManager->lookupShape( xShape ) );
+ mpShape = ::std::dynamic_pointer_cast< IExternalMediaShapeBase >( pShape );
+}
+
+void AnimationCommandNode::dispose()
+{
+ mxCommandNode.clear();
+ mpShape.reset();
+ BaseNode::dispose();
+}
+
+void AnimationCommandNode::activate_st()
+{
+ switch( mxCommandNode->getCommand() ) {
+ // the command is user defined
+ case EffectCommands::CUSTOM: break;
+ // the command is an ole verb.
+ case EffectCommands::VERB: break;
+ // the command starts playing on a media object
+ case EffectCommands::PLAY:
+ {
+ double fMediaTime=0.0;
+ beans::PropertyValue aMediaTime;
+ if( (mxCommandNode->getParameter() >>= aMediaTime) && aMediaTime.Name == "MediaTime" )
+ {
+ aMediaTime.Value >>= fMediaTime;
+ }
+ if( mpShape )
+ {
+ mpShape->setMediaTime(fMediaTime/1000.0);
+ mpShape->play();
+ }
+ break;
+ }
+ // the command toggles the pause status on a media object
+ case EffectCommands::TOGGLEPAUSE:
+ {
+ if (mpShape)
+ {
+ if( mpShape->isPlaying() )
+ mpShape->pause();
+ else
+ mpShape->play();
+ }
+ break;
+ }
+ // the command stops the animation on a media object
+ case EffectCommands::STOP:
+ {
+ if( mpShape )
+ mpShape->stop();
+ break;
+ }
+ // the command stops all currently running sound effects
+ case EffectCommands::STOPAUDIO:
+ getContext().mrEventMultiplexer.notifyCommandStopAudio( getSelf() );
+ break;
+ }
+
+ // deactivate ASAP:
+ auto self(getSelf());
+ scheduleDeactivationEvent(
+ makeEvent( [self] () { self->deactivate(); },
+ "AnimationCommandNode::deactivate" ) );
+}
+
+bool AnimationCommandNode::hasPendingAnimation() const
+{
+ return mxCommandNode->getCommand() == EffectCommands::STOPAUDIO || mpShape;
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationcommandnode.hxx b/slideshow/source/engine/animationnodes/animationcommandnode.hxx
new file mode 100644
index 000000000..09dcf9eb1
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationcommandnode.hxx
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOMMANDNODE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOMMANDNODE_HXX
+
+#include <basecontainernode.hxx>
+#include <iexternalmediashapebase.hxx>
+#include <com/sun/star/animations/XCommand.hpp>
+
+namespace slideshow {
+namespace internal {
+
+/** Command node.
+
+ This animation node encapsulates a command. Not yet implemented:
+ verb & custom.
+*/
+class AnimationCommandNode : public BaseNode
+{
+public:
+ AnimationCommandNode(
+ css::uno::Reference<css::animations::XAnimationNode> const& xNode,
+ ::std::shared_ptr<BaseContainerNode> const& pParent,
+ NodeContext const& rContext );
+
+protected:
+ virtual void dispose() override;
+
+private:
+ virtual void activate_st() override;
+ virtual bool hasPendingAnimation() const override;
+
+private:
+ IExternalMediaShapeBaseSharedPtr mpShape;
+ css::uno::Reference<css::animations::XCommand > mxCommandNode;
+};
+
+} // namespace internal
+} // namespace slideshow
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONCOMMANDNODE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationnodefactory.cxx b/slideshow/source/engine/animationnodes/animationnodefactory.cxx
new file mode 100644
index 000000000..f9fa01b2f
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationnodefactory.cxx
@@ -0,0 +1,592 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/animations/AnimationNodeType.hpp>
+#include <com/sun/star/presentation/TextAnimationType.hpp>
+#include <com/sun/star/animations/XIterateContainer.hpp>
+#include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
+#include <com/sun/star/presentation/ParagraphTarget.hpp>
+#include <basegfx/numeric/ftools.hxx>
+#include <sal/log.hxx>
+
+#include <animationnodefactory.hxx>
+#include "paralleltimecontainer.hxx"
+#include "sequentialtimecontainer.hxx"
+#include "propertyanimationnode.hxx"
+#include "animationsetnode.hxx"
+#include "animationpathmotionnode.hxx"
+#include "animationcolornode.hxx"
+#include "animationtransformnode.hxx"
+#include "animationtransitionfilternode.hxx"
+#include "animationaudionode.hxx"
+#include "animationcommandnode.hxx"
+#include "nodetools.hxx"
+#include <tools.hxx>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal {
+
+namespace {
+
+// forward declaration needed by NodeCreator
+BaseNodeSharedPtr implCreateAnimationNode(
+ const uno::Reference< animations::XAnimationNode >& xNode,
+ const BaseContainerNodeSharedPtr& rParent,
+ const NodeContext& rContext );
+
+class NodeCreator
+{
+public:
+ NodeCreator( BaseContainerNodeSharedPtr& rParent,
+ const NodeContext& rContext )
+ : mrParent( rParent ), mrContext( rContext ) {}
+
+ void operator()(
+ const uno::Reference< animations::XAnimationNode >& xChildNode ) const
+ {
+ createChild( xChildNode, mrContext );
+ }
+
+protected:
+ void createChild(
+ const uno::Reference< animations::XAnimationNode >& xChildNode,
+ const NodeContext& rContext ) const
+ {
+ BaseNodeSharedPtr pChild( implCreateAnimationNode( xChildNode,
+ mrParent,
+ rContext ) );
+
+ OSL_ENSURE( pChild,
+ "NodeCreator::operator(): child creation failed" );
+
+ // TODO(Q1): This yields circular references, which, it seems, is
+ // unavoidable here
+ if( pChild )
+ mrParent->appendChildNode( pChild );
+ }
+
+ BaseContainerNodeSharedPtr& mrParent;
+ const NodeContext& mrContext;
+};
+
+/** Same as NodeCreator, only that NodeContext's
+ SubsetShape is cloned for every child node.
+
+ This is used for iterated animation node generation
+*/
+class CloningNodeCreator : private NodeCreator
+{
+public:
+ CloningNodeCreator( BaseContainerNodeSharedPtr& rParent,
+ const NodeContext& rContext )
+ : NodeCreator( rParent, rContext ) {}
+
+ void operator()(
+ const uno::Reference< animations::XAnimationNode >& xChildNode ) const
+ {
+ NodeContext aContext( mrContext );
+
+ // TODO(Q1): There's a catch here. If you clone a
+ // subset whose actual subsetting has already been
+ // realized (i.e. if enableSubsetShape() has been
+ // called already), and the original of your clone
+ // goes out of scope, then your subset will be
+ // gone (SubsettableShapeManager::revokeSubset() be
+ // called). As of now, this behaviour is not
+ // triggered here (we either clone, XOR we enable
+ // subset initially), but one might consider
+ // reworking DrawShape/ShapeSubset to avoid this.
+
+ // clone ShapeSubset, since each node needs their
+ // own version of the ShapeSubset (otherwise,
+ // e.g. activity counting does not work - subset
+ // would be removed after first animation node
+ // disables it).
+
+ // NOTE: this is only a problem for animation
+ // nodes that explicitly call
+ // disableSubsetShape(). Independent shape subsets
+ // (like those created for ParagraphTargets)
+ // solely rely on the ShapeSubset destructor to
+ // normalize things, which does the right thing
+ // here: the subset is only removed after _the
+ // last_ animation node releases the shared ptr.
+ aContext.mpMasterShapeSubset =
+ std::make_shared<ShapeSubset>( *aContext.mpMasterShapeSubset );
+
+ createChild( xChildNode, aContext );
+ }
+};
+
+/** Create animation nodes for text iterations
+
+ This method clones the animation nodes below xIterNode
+ for every iterated shape entity.
+*/
+bool implCreateIteratedNodes(
+ const uno::Reference< animations::XIterateContainer >& xIterNode,
+ BaseContainerNodeSharedPtr& rParent,
+ const NodeContext& rContext )
+{
+ ENSURE_OR_THROW( xIterNode.is(),
+ "implCreateIteratedNodes(): Invalid node" );
+
+ const double nIntervalTimeout( xIterNode->getIterateInterval() );
+
+ // valid iterate interval? We're ruling out monstrous
+ // values here, to avoid pseudo 'hangs' in the
+ // presentation
+ if( nIntervalTimeout < 0.0 ||
+ nIntervalTimeout > 1000.0 )
+ {
+ return false; // not an active iteration
+ }
+
+ if( ::basegfx::fTools::equalZero( nIntervalTimeout ) )
+ SAL_INFO("slideshow", "implCreateIteratedNodes(): "
+ "iterate interval close to zero, there's "
+ "no point in defining such an effect "
+ "(visually equivalent to whole-shape effect)" );
+
+ // Determine target shape (or subset)
+ // ==================================
+
+ // TODO(E1): I'm not too sure what to expect here...
+ ENSURE_OR_RETURN_FALSE(
+ xIterNode->getTarget().hasValue(),
+ "implCreateIteratedNodes(): no target on ITERATE node" );
+
+ uno::Reference< drawing::XShape > xTargetShape( xIterNode->getTarget(),
+ uno::UNO_QUERY );
+
+ presentation::ParagraphTarget aTarget;
+ sal_Int16 nSubItem( xIterNode->getSubItem() );
+ bool bParagraphTarget( false );
+
+ if( !xTargetShape.is() )
+ {
+ // no shape provided. Maybe a ParagraphTarget?
+ if( !(xIterNode->getTarget() >>= aTarget) )
+ ENSURE_OR_RETURN_FALSE(
+ false,
+ "implCreateIteratedNodes(): could not extract any "
+ "target information" );
+
+ xTargetShape = aTarget.Shape;
+
+ ENSURE_OR_RETURN_FALSE(
+ xTargetShape.is(),
+ "implCreateIteratedNodes(): invalid shape in ParagraphTarget" );
+
+ // we've a paragraph target to iterate over, thus,
+ // the whole animation container refers only to
+ // the text
+ nSubItem = presentation::ShapeAnimationSubType::ONLY_TEXT;
+
+ bParagraphTarget = true;
+ }
+
+ // Lookup shape, and fill NodeContext
+ // ==================================
+
+ AttributableShapeSharedPtr pTargetShape(
+ lookupAttributableShape( rContext.maContext.mpSubsettableShapeManager,
+ xTargetShape ) );
+
+ const DocTreeNodeSupplier& rTreeNodeSupplier(
+ pTargetShape->getTreeNodeSupplier() );
+
+ ShapeSubsetSharedPtr pTargetSubset;
+
+ NodeContext aContext( rContext );
+
+ // paragraph targets already need a subset as the
+ // master shape (they're representing only a single
+ // paragraph)
+ if( bParagraphTarget )
+ {
+ ENSURE_OR_RETURN_FALSE(
+ aTarget.Paragraph >= 0 &&
+ rTreeNodeSupplier.getNumberOfTreeNodes(
+ DocTreeNode::NodeType::LogicalParagraph ) > aTarget.Paragraph,
+ "implCreateIteratedNodes(): paragraph index out of range" );
+
+ pTargetSubset =
+ std::make_shared<ShapeSubset>(
+ pTargetShape,
+ // retrieve index aTarget.Paragraph of
+ // type PARAGRAPH from this shape
+ rTreeNodeSupplier.getTreeNode(
+ aTarget.Paragraph,
+ DocTreeNode::NodeType::LogicalParagraph ),
+ rContext.maContext.mpSubsettableShapeManager );
+
+ // iterate target is not the whole shape, but only
+ // the selected paragraph - subset _must_ be
+ // independent, to be able to affect visibility
+ // independent of master shape
+ aContext.mbIsIndependentSubset = true;
+
+ // already enable parent subset right here, to
+ // make potentially generated subsets subtract
+ // their content from the parent subset (and not
+ // the original shape). Otherwise, already
+ // subsetted parents (e.g. paragraphs) would not
+ // have their characters removed, when the child
+ // iterations start.
+ // Furthermore, the setup of initial shape
+ // attributes of course needs the subset shape
+ // generated, to apply e.g. visibility changes.
+ pTargetSubset->enableSubsetShape();
+ }
+ else
+ {
+ pTargetSubset =
+ std::make_shared<ShapeSubset>( pTargetShape,
+ rContext.maContext.mpSubsettableShapeManager );
+ }
+
+ aContext.mpMasterShapeSubset = pTargetSubset;
+ uno::Reference< animations::XAnimationNode > xNode( xIterNode,
+ uno::UNO_QUERY_THROW );
+
+ // Generate subsets
+ // ================
+
+ if( bParagraphTarget ||
+ nSubItem != presentation::ShapeAnimationSubType::ONLY_TEXT )
+ {
+ // prepend with animations for
+ // full Shape (will be subtracted
+ // from the subset parts within
+ // the Shape::createSubset()
+ // method). For ONLY_TEXT effects,
+ // we skip this part, to animate
+ // only the text.
+
+ // OR
+
+ // prepend with subset animation for full
+ // _paragraph_, from which the individual
+ // paragraph subsets are subtracted. Note that the
+ // subitem is superfluous here, we always assume
+ // ONLY_TEXT, if a paragraph is referenced as the
+ // master of an iteration effect.
+ NodeCreator aCreator( rParent, aContext );
+ if( !for_each_childNode( xNode, aCreator ) )
+ {
+ ENSURE_OR_RETURN_FALSE(
+ false,
+ "implCreateIteratedNodes(): iterated child node creation failed" );
+ }
+ }
+
+ // TODO(F2): This does not do the correct
+ // thing. Having nSubItem be set to ONLY_BACKGROUND
+ // should result in the text staying unanimated in the
+ // foreground, while the shape moves in the background
+ // (this behaviour is perfectly possible with the
+ // slideshow engine, only that the text won't be
+ // currently visible, because animations are always in
+ // the foreground)
+ if( nSubItem != presentation::ShapeAnimationSubType::ONLY_BACKGROUND )
+ {
+ // determine type of subitem iteration (logical
+ // text unit to animate)
+ DocTreeNode::NodeType eIterateNodeType(
+ DocTreeNode::NodeType::LogicalCharacterCell );
+
+ switch( xIterNode->getIterateType() )
+ {
+ case presentation::TextAnimationType::BY_PARAGRAPH:
+ eIterateNodeType = DocTreeNode::NodeType::LogicalParagraph;
+ break;
+
+ case presentation::TextAnimationType::BY_WORD:
+ eIterateNodeType = DocTreeNode::NodeType::LogicalWord;
+ break;
+
+ case presentation::TextAnimationType::BY_LETTER:
+ eIterateNodeType = DocTreeNode::NodeType::LogicalCharacterCell;
+ break;
+
+ default:
+ ENSURE_OR_THROW(
+ false, "implCreateIteratedNodes(): "
+ "Unexpected IterateType on XIterateContainer");
+ break;
+ }
+
+ if( bParagraphTarget &&
+ eIterateNodeType != DocTreeNode::NodeType::LogicalWord &&
+ eIterateNodeType != DocTreeNode::NodeType::LogicalCharacterCell )
+ {
+ // will not animate the whole paragraph, when
+ // only the paragraph is animated at all.
+ OSL_FAIL( "implCreateIteratedNodes(): Ignoring paragraph iteration for paragraph master" );
+ }
+ else
+ {
+ // setup iteration parameters
+
+
+ // iterate target is the whole shape (or the
+ // whole parent subshape), thus, can save
+ // loads of subset shapes by generating them
+ // only when the effects become active -
+ // before and after the effect active
+ // duration, all attributes are shared by
+ // master shape and subset (since the iterated
+ // effects are all the same).
+ aContext.mbIsIndependentSubset = false;
+
+ // determine number of nodes for given subitem
+ // type
+ sal_Int32 nTreeNodes( 0 );
+ if( bParagraphTarget )
+ {
+ // create the iterated subset _relative_ to
+ // the given paragraph index (i.e. animate the
+ // given subset type, but only when it's part
+ // of the given paragraph)
+ nTreeNodes = rTreeNodeSupplier.getNumberOfSubsetTreeNodes(
+ pTargetSubset->getSubset(),
+ eIterateNodeType );
+ }
+ else
+ {
+ // generate normal subset
+ nTreeNodes = rTreeNodeSupplier.getNumberOfTreeNodes(
+ eIterateNodeType );
+ }
+
+
+ // iterate node, generate copies of the children for each subset
+
+
+ // NodeContext::mnStartDelay contains additional node delay.
+ // This will make the duplicated nodes for each iteration start
+ // increasingly later.
+ aContext.mnStartDelay = nIntervalTimeout;
+
+ for( sal_Int32 i=0; i<nTreeNodes; ++i )
+ {
+ // create subset with the corresponding tree nodes
+ if( bParagraphTarget )
+ {
+ // create subsets relative to paragraph subset
+ aContext.mpMasterShapeSubset =
+ std::make_shared<ShapeSubset>(
+ pTargetSubset,
+ rTreeNodeSupplier.getSubsetTreeNode(
+ pTargetSubset->getSubset(),
+ i,
+ eIterateNodeType ) );
+ }
+ else
+ {
+ // create subsets from main shape
+ aContext.mpMasterShapeSubset =
+ std::make_shared<ShapeSubset>( pTargetSubset,
+ rTreeNodeSupplier.getTreeNode(
+ i,
+ eIterateNodeType ) );
+ }
+
+ CloningNodeCreator aCreator( rParent, aContext );
+ if( !for_each_childNode( xNode, aCreator ) )
+ {
+ ENSURE_OR_RETURN_FALSE(
+ false, "implCreateIteratedNodes(): "
+ "iterated child node creation failed" );
+ }
+
+ aContext.mnStartDelay += nIntervalTimeout;
+ }
+ }
+ }
+
+ // done with iterate child generation
+ return true;
+}
+
+BaseNodeSharedPtr implCreateAnimationNode(
+ const uno::Reference< animations::XAnimationNode >& xNode,
+ const BaseContainerNodeSharedPtr& rParent,
+ const NodeContext& rContext )
+{
+ ENSURE_OR_THROW( xNode.is(),
+ "implCreateAnimationNode(): invalid XAnimationNode" );
+
+ BaseNodeSharedPtr pCreatedNode;
+ BaseContainerNodeSharedPtr pCreatedContainer;
+
+ // create the internal node, corresponding to xNode
+ switch( xNode->getType() )
+ {
+ case animations::AnimationNodeType::CUSTOM:
+ OSL_FAIL( "implCreateAnimationNode(): "
+ "CUSTOM not yet implemented" );
+ return pCreatedNode;
+
+ case animations::AnimationNodeType::PAR:
+ pCreatedNode = pCreatedContainer =
+ std::make_shared<ParallelTimeContainer>( xNode, rParent, rContext );
+ break;
+
+ case animations::AnimationNodeType::ITERATE:
+ // map iterate container to ParallelTimeContainer.
+ // the iterating functionality is to be found
+ // below, (see method implCreateIteratedNodes)
+ pCreatedNode = pCreatedContainer =
+ std::make_shared<ParallelTimeContainer>( xNode, rParent, rContext );
+ break;
+
+ case animations::AnimationNodeType::SEQ:
+ pCreatedNode = pCreatedContainer =
+ std::make_shared<SequentialTimeContainer>( xNode, rParent, rContext );
+ break;
+
+ case animations::AnimationNodeType::ANIMATE:
+ pCreatedNode = std::make_shared<PropertyAnimationNode>(
+ xNode, rParent, rContext );
+ break;
+
+ case animations::AnimationNodeType::SET:
+ pCreatedNode = std::make_shared<AnimationSetNode>(
+ xNode, rParent, rContext );
+ break;
+
+ case animations::AnimationNodeType::ANIMATEMOTION:
+ pCreatedNode = std::make_shared<AnimationPathMotionNode>(
+ xNode, rParent, rContext );
+ break;
+
+ case animations::AnimationNodeType::ANIMATECOLOR:
+ pCreatedNode = std::make_shared<AnimationColorNode>(
+ xNode, rParent, rContext );
+ break;
+
+ case animations::AnimationNodeType::ANIMATETRANSFORM:
+ pCreatedNode = std::make_shared<AnimationTransformNode>(
+ xNode, rParent, rContext );
+ break;
+
+ case animations::AnimationNodeType::TRANSITIONFILTER:
+ pCreatedNode = std::make_shared<AnimationTransitionFilterNode>(
+ xNode, rParent, rContext );
+ break;
+
+ case animations::AnimationNodeType::AUDIO:
+ pCreatedNode = std::make_shared<AnimationAudioNode>(
+ xNode, rParent, rContext );
+ break;
+
+ case animations::AnimationNodeType::COMMAND:
+ pCreatedNode = std::make_shared<AnimationCommandNode>(
+ xNode, rParent, rContext );
+ break;
+
+ default:
+ OSL_FAIL( "implCreateAnimationNode(): "
+ "invalid AnimationNodeType" );
+ return pCreatedNode;
+ }
+
+ // TODO(Q1): This yields circular references, which, it seems, is
+ // unavoidable here
+
+ // HACK: node objects need shared_ptr to themselves,
+ // which we pass them here.
+ pCreatedNode->setSelf( pCreatedNode );
+
+ // if we've got a container node object, recursively add
+ // its children
+ if( pCreatedContainer )
+ {
+ uno::Reference< animations::XIterateContainer > xIterNode(
+ xNode, uno::UNO_QUERY );
+
+ // when this node is an XIterateContainer with
+ // active iterations, this method will generate
+ // the appropriate children
+ if( xIterNode.is() )
+ {
+ // note that implCreateIteratedNodes() might
+ // choose not to generate any child nodes
+ // (e.g. when the iterate timeout is outside
+ // sensible limits). Then, no child nodes are
+ // generated at all, since typically, child
+ // node attribute are incomplete for iteration
+ // children.
+ implCreateIteratedNodes( xIterNode,
+ pCreatedContainer,
+ rContext );
+ }
+ else
+ {
+ // no iterate subset node, just plain child generation now
+ NodeCreator aCreator( pCreatedContainer, rContext );
+ if( !for_each_childNode( xNode, aCreator ) )
+ {
+ OSL_FAIL( "implCreateAnimationNode(): "
+ "child node creation failed" );
+ return BaseNodeSharedPtr();
+ }
+ }
+ }
+
+ return pCreatedNode;
+}
+
+} // anon namespace
+
+AnimationNodeSharedPtr AnimationNodeFactory::createAnimationNode(
+ const uno::Reference< animations::XAnimationNode >& xNode,
+ const ::basegfx::B2DVector& rSlideSize,
+ const SlideShowContext& rContext )
+{
+ ENSURE_OR_THROW(
+ xNode.is(),
+ "AnimationNodeFactory::createAnimationNode(): invalid XAnimationNode" );
+
+ return implCreateAnimationNode(
+ xNode,
+ BaseContainerNodeSharedPtr(), // no parent
+ NodeContext( rContext,
+ rSlideSize ));
+}
+
+#if defined(DBG_UTIL)
+void AnimationNodeFactory::showTree( AnimationNodeSharedPtr const & pRootNode )
+{
+ if( pRootNode )
+ DEBUG_NODES_SHOWTREE( std::dynamic_pointer_cast<BaseContainerNode>(
+ pRootNode).get() );
+}
+#endif
+
+} // namespace slideshow
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationpathmotionnode.cxx b/slideshow/source/engine/animationnodes/animationpathmotionnode.cxx
new file mode 100644
index 000000000..2490b6e2c
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationpathmotionnode.cxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "animationpathmotionnode.hxx"
+#include <animationfactory.hxx>
+
+namespace slideshow::internal {
+
+void AnimationPathMotionNode::dispose()
+{
+ mxPathMotionNode.clear();
+ AnimationBaseNode::dispose();
+}
+
+AnimationActivitySharedPtr AnimationPathMotionNode::createActivity() const
+{
+ OUString aString;
+ ENSURE_OR_THROW( (mxPathMotionNode->getPath() >>= aString),
+ "no string-based SVG:d path found" );
+
+ ActivitiesFactory::CommonParameters const aParms( fillCommonParameters() );
+ return ActivitiesFactory::createSimpleActivity(
+ aParms,
+ AnimationFactory::createPathMotionAnimation(
+ aString,
+ mxPathMotionNode->getAdditive(),
+ getShape(),
+ getContext().mpSubsettableShapeManager,
+ getSlideSize(), 0 ),
+ true );
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationpathmotionnode.hxx b/slideshow/source/engine/animationnodes/animationpathmotionnode.hxx
new file mode 100644
index 000000000..a6a37a5b7
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationpathmotionnode.hxx
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONPATHMOTIONNODE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONPATHMOTIONNODE_HXX
+
+#include "animationbasenode.hxx"
+#include <com/sun/star/animations/XAnimateMotion.hpp>
+
+namespace slideshow {
+namespace internal {
+
+class AnimationPathMotionNode : public AnimationBaseNode
+{
+public:
+ AnimationPathMotionNode(
+ const css::uno::Reference<css::animations::XAnimationNode >& xNode,
+ const BaseContainerNodeSharedPtr& rParent,
+ const NodeContext& rContext )
+ : AnimationBaseNode( xNode, rParent, rContext ),
+ mxPathMotionNode( xNode, css::uno::UNO_QUERY_THROW ) {}
+
+#if defined(DBG_UTIL)
+ virtual const char* getDescription() const override
+ { return "AnimationPathMotionNode"; }
+#endif
+
+protected:
+ virtual void dispose() override;
+
+private:
+ virtual AnimationActivitySharedPtr createActivity() const override;
+
+ css::uno::Reference<css::animations::XAnimateMotion > mxPathMotionNode;
+};
+
+} // namespace internal
+} // namespace slideshow
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONPATHMOTIONNODE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationsetnode.cxx b/slideshow/source/engine/animationnodes/animationsetnode.cxx
new file mode 100644
index 000000000..350e80fbc
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationsetnode.cxx
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <animationfactory.hxx>
+#include "setactivity.hxx"
+#include "animationsetnode.hxx"
+#include "nodetools.hxx"
+#include <tools.hxx>
+#include <delayevent.hxx>
+
+using namespace com::sun::star;
+
+namespace slideshow::internal {
+
+AnimationActivitySharedPtr AnimationSetNode::createActivity() const
+{
+ ActivitiesFactory::CommonParameters aParms( fillCommonParameters() );
+ uno::Reference<animations::XAnimate> const xAnimateNode = getXAnimateNode();
+ OUString const attrName( xAnimateNode->getAttributeName() );
+ AttributableShapeSharedPtr const pShape( getShape() );
+
+ // make deactivation a two-step procedure. Normally, we
+ // could solely rely on
+ // BaseNode::scheduleDeactivationEvent() to deactivate()
+ // us. Unfortunately, that method on the one hand ignores
+ // indefinite timing, on the other hand generates
+ // zero-timeout delays, which might get fired _before_ our
+ // set activity has taken place. Therefore, we enforce
+ // sequentiality by letting only the set activity schedule
+ // the deactivation event (and AnimationBaseNode
+ // takes care for the fact when mpActivity should be zero).
+
+ // AnimationBaseNode::fillCommonParameters() has set up
+ // immediate deactivation as default when activity ends, but
+ if (! isIndefiniteTiming( xAnimateNode->getDuration() )) {
+ std::shared_ptr<AnimationSetNode> const pSelf(
+ std::dynamic_pointer_cast<AnimationSetNode>(getSelf()) );
+ ENSURE_OR_THROW(
+ pSelf, "cannot cast getSelf() to my type!" );
+ aParms.mpEndEvent = makeEvent(
+ [pSelf] () { pSelf->scheduleDeactivationEvent(); },
+ "AnimationSetNode::scheduleDeactivationEvent");
+ }
+
+ switch (AnimationFactory::classifyAttributeName( attrName )) {
+ default:
+ case AnimationFactory::CLASS_UNKNOWN_PROPERTY:
+ ENSURE_OR_THROW(
+ false, "AnimationSetNode::createSetActivity(): "
+ "Unexpected attribute class" );
+ break;
+
+ case AnimationFactory::CLASS_NUMBER_PROPERTY:
+ {
+ NumberAnimation::ValueType aValue;
+
+ ENSURE_OR_THROW(
+ extractValue( aValue,
+ xAnimateNode->getTo(),
+ pShape,
+ getSlideSize() ),
+ "AnimationSetNode::createSetActivity(): "
+ "Could not import numeric to value" );
+
+ return makeSetActivity(
+ aParms,
+ AnimationFactory::createNumberPropertyAnimation(
+ attrName,
+ pShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize(),
+ AnimationFactory::FLAG_NO_SPRITE ),
+ aValue );
+ }
+
+ case AnimationFactory::CLASS_ENUM_PROPERTY:
+ {
+ EnumAnimation::ValueType aValue;
+
+ ENSURE_OR_THROW(
+ extractValue( aValue,
+ xAnimateNode->getTo(),
+ pShape,
+ getSlideSize() ),
+ "AnimationSetNode::createSetActivity(): "
+ "Could not import enum to value" );
+
+ return makeSetActivity(
+ aParms,
+ AnimationFactory::createEnumPropertyAnimation(
+ attrName,
+ pShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize(),
+ AnimationFactory::FLAG_NO_SPRITE ),
+ aValue );
+ }
+
+ case AnimationFactory::CLASS_COLOR_PROPERTY:
+ {
+ ColorAnimation::ValueType aValue;
+
+ ENSURE_OR_THROW(
+ extractValue( aValue,
+ xAnimateNode->getTo(),
+ pShape,
+ getSlideSize() ),
+ "AnimationSetNode::createSetActivity(): "
+ "Could not import color to value" );
+
+ return makeSetActivity(
+ aParms,
+ AnimationFactory::createColorPropertyAnimation(
+ attrName,
+ pShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize(),
+ AnimationFactory::FLAG_NO_SPRITE ),
+ aValue );
+ }
+
+ case AnimationFactory::CLASS_STRING_PROPERTY:
+ {
+ StringAnimation::ValueType aValue;
+
+ ENSURE_OR_THROW(
+ extractValue( aValue,
+ xAnimateNode->getTo(),
+ pShape,
+ getSlideSize() ),
+ "AnimationSetNode::createSetActivity(): "
+ "Could not import string to value" );
+
+ return makeSetActivity(
+ aParms,
+ AnimationFactory::createStringPropertyAnimation(
+ attrName,
+ pShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize(),
+ AnimationFactory::FLAG_NO_SPRITE ),
+ aValue );
+ }
+
+ case AnimationFactory::CLASS_BOOL_PROPERTY:
+ {
+ BoolAnimation::ValueType aValue;
+
+ ENSURE_OR_THROW(
+ extractValue( aValue,
+ xAnimateNode->getTo(),
+ pShape,
+ getSlideSize() ),
+ "AnimationSetNode::createSetActivity(): "
+ "Could not import bool to value" );
+
+ return makeSetActivity(
+ aParms,
+ AnimationFactory::createBoolPropertyAnimation(
+ attrName,
+ pShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize(),
+ AnimationFactory::FLAG_NO_SPRITE ),
+ aValue );
+ }
+ }
+
+ return AnimationActivitySharedPtr();
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationsetnode.hxx b/slideshow/source/engine/animationnodes/animationsetnode.hxx
new file mode 100644
index 000000000..9c3937747
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationsetnode.hxx
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONSETNODE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONSETNODE_HXX
+
+#include "animationbasenode.hxx"
+
+namespace slideshow {
+namespace internal {
+
+class AnimationSetNode : public AnimationBaseNode
+{
+public:
+ AnimationSetNode(
+ css::uno::Reference<css::animations::XAnimationNode> const& xNode,
+ ::std::shared_ptr<BaseContainerNode> const& pParent,
+ NodeContext const& rContext )
+ : AnimationBaseNode( xNode, pParent, rContext ) {}
+
+#if defined(DBG_UTIL)
+ virtual const char* getDescription() const override { return "AnimationSetNode"; }
+#endif
+
+private:
+ virtual AnimationActivitySharedPtr createActivity() const override;
+};
+
+} // namespace internal
+} // namespace slideshow
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONSETNODE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationtransformnode.cxx b/slideshow/source/engine/animationnodes/animationtransformnode.cxx
new file mode 100644
index 000000000..b8019d77e
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationtransformnode.cxx
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/animations/AnimationTransformType.hpp>
+
+#include "animationtransformnode.hxx"
+#include <animationfactory.hxx>
+#include <activitiesfactory.hxx>
+
+using namespace com::sun::star;
+
+namespace slideshow::internal {
+
+void AnimationTransformNode::dispose()
+{
+ mxTransformNode.clear();
+ AnimationBaseNode::dispose();
+}
+
+AnimationActivitySharedPtr AnimationTransformNode::createActivity() const
+{
+ ActivitiesFactory::CommonParameters aParms( fillCommonParameters() );
+
+ const sal_Int16 nTransformType( mxTransformNode->getTransformType() );
+
+ const AttributableShapeSharedPtr& rShape( getShape() );
+
+ switch( nTransformType )
+ {
+ default:
+ ENSURE_OR_THROW(
+ false, "AnimationTransformNode::createTransformActivity(): "
+ "Unknown transform type" );
+
+ case animations::AnimationTransformType::TRANSLATE:
+ case animations::AnimationTransformType::SCALE:
+ return ActivitiesFactory::createAnimateActivity(
+ aParms,
+ AnimationFactory::createPairPropertyAnimation(
+ rShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize(),
+ nTransformType, 0 ),
+ getXAnimateNode() );
+
+ case animations::AnimationTransformType::ROTATE:
+ return ActivitiesFactory::createAnimateActivity(
+ aParms,
+ AnimationFactory::createNumberPropertyAnimation(
+ "Rotate",
+ rShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize() ),
+ getXAnimateNode() );
+
+ case animations::AnimationTransformType::SKEWX:
+ return ActivitiesFactory::createAnimateActivity(
+ aParms,
+ AnimationFactory::createNumberPropertyAnimation(
+ "SkewX",
+ rShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize() ),
+ getXAnimateNode() );
+
+ case animations::AnimationTransformType::SKEWY:
+ return ActivitiesFactory::createAnimateActivity(
+ aParms,
+ AnimationFactory::createNumberPropertyAnimation(
+ "SkewY",
+ rShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize() ),
+ getXAnimateNode() );
+ }
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationtransformnode.hxx b/slideshow/source/engine/animationnodes/animationtransformnode.hxx
new file mode 100644
index 000000000..b664fcb32
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationtransformnode.hxx
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSFORMNODE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSFORMNODE_HXX
+
+#include "animationbasenode.hxx"
+#include <com/sun/star/animations/XAnimateTransform.hpp>
+
+namespace slideshow {
+namespace internal {
+
+class AnimationTransformNode : public AnimationBaseNode
+{
+public:
+ AnimationTransformNode(
+ css::uno::Reference<css::animations::XAnimationNode> const& xNode,
+ ::std::shared_ptr<BaseContainerNode> const& pParent,
+ NodeContext const& rContext )
+ : AnimationBaseNode( xNode, pParent, rContext ),
+ mxTransformNode( xNode, css::uno::UNO_QUERY_THROW ) {}
+
+#if defined(DBG_UTIL)
+ virtual const char* getDescription() const override
+ { return "AnimationTransformNode"; }
+#endif
+
+protected:
+ virtual void dispose() override;
+
+private:
+ virtual AnimationActivitySharedPtr createActivity() const override;
+
+ css::uno::Reference<css::animations::XAnimateTransform > mxTransformNode;
+};
+
+} // namespace internal
+} // namespace slideshow
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSFORMNODE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationtransitionfilternode.cxx b/slideshow/source/engine/animationnodes/animationtransitionfilternode.cxx
new file mode 100644
index 000000000..13a62d89b
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationtransitionfilternode.cxx
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "animationtransitionfilternode.hxx"
+#include <transitionfactory.hxx>
+
+namespace slideshow::internal {
+
+void AnimationTransitionFilterNode::dispose()
+{
+ mxTransitionFilterNode.clear();
+ AnimationBaseNode::dispose();
+}
+
+AnimationActivitySharedPtr
+AnimationTransitionFilterNode::createActivity() const
+{
+ return TransitionFactory::createShapeTransition(
+ fillCommonParameters(),
+ getShape(),
+ getContext().mpSubsettableShapeManager,
+ getSlideSize(),
+ mxTransitionFilterNode );
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/animationtransitionfilternode.hxx b/slideshow/source/engine/animationnodes/animationtransitionfilternode.hxx
new file mode 100644
index 000000000..6790e9d22
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/animationtransitionfilternode.hxx
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSITIONFILTERNODE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSITIONFILTERNODE_HXX
+
+#include "animationbasenode.hxx"
+#include <com/sun/star/animations/XTransitionFilter.hpp>
+
+namespace slideshow {
+namespace internal {
+
+class AnimationTransitionFilterNode : public AnimationBaseNode
+{
+public:
+ AnimationTransitionFilterNode(
+ css::uno::Reference<css::animations::XAnimationNode> const& xNode,
+ ::std::shared_ptr<BaseContainerNode> const& pParent,
+ NodeContext const& rContext )
+ : AnimationBaseNode( xNode, pParent, rContext ),
+ mxTransitionFilterNode( xNode, css::uno::UNO_QUERY_THROW)
+ {}
+
+#if OSL_DEBUG_LEVEL >= 2
+ virtual const char* getDescription() const
+ { return "AnimationTransitionFilterNode"; }
+#endif
+
+protected:
+ virtual void dispose() override;
+
+private:
+ virtual AnimationActivitySharedPtr createActivity() const override;
+
+ css::uno::Reference<css::animations::XTransitionFilter> mxTransitionFilterNode;
+};
+
+} // namespace internal
+} // namespace slideshow
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_ANIMATIONTRANSITIONFILTERNODE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/basecontainernode.cxx b/slideshow/source/engine/animationnodes/basecontainernode.cxx
new file mode 100644
index 000000000..cde434f3d
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/basecontainernode.cxx
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <basecontainernode.hxx>
+#include <com/sun/star/animations/AnimationRestart.hpp>
+#include <com/sun/star/animations/AnimationFill.hpp>
+#include <eventqueue.hxx>
+#include "nodetools.hxx"
+#include <delayevent.hxx>
+#include <sal/log.hxx>
+
+#include <functional>
+#include <algorithm>
+
+using namespace com::sun::star;
+
+namespace slideshow::internal {
+namespace {
+bool isRepeatIndefinite(const uno::Reference<animations::XAnimationNode>& xNode)
+{
+ return xNode->getRepeatCount().hasValue() && isIndefiniteTiming(xNode->getRepeatCount());
+}
+
+bool isRestart(const uno::Reference<animations::XAnimationNode>& xNode)
+{
+ sal_Int16 nRestart = xNode->getRestart();
+ return nRestart == animations::AnimationRestart::WHEN_NOT_ACTIVE ||
+ nRestart == animations::AnimationRestart::ALWAYS;
+}
+}
+
+BaseContainerNode::BaseContainerNode(
+ const uno::Reference< animations::XAnimationNode >& xNode,
+ const BaseContainerNodeSharedPtr& rParent,
+ const NodeContext& rContext )
+ : BaseNode( xNode, rParent, rContext ),
+ maChildren(),
+ mnFinishedChildren(0),
+ mnLeftIterations(0),
+ mbRepeatIndefinite(isRepeatIndefinite(xNode)),
+ mbRestart(isRestart(xNode)),
+ mbDurationIndefinite( isIndefiniteTiming( xNode->getEnd() ) &&
+ isIndefiniteTiming( xNode->getDuration() ) )
+{
+}
+
+void BaseContainerNode::dispose()
+{
+ forEachChildNode( std::mem_fn(&Disposable::dispose), -1 );
+ maChildren.clear();
+ BaseNode::dispose();
+}
+
+bool BaseContainerNode::init_st()
+{
+ if( !(getXAnimationNode()->getRepeatCount() >>= mnLeftIterations) )
+ mnLeftIterations = 1.0;
+ return init_children();
+}
+
+bool BaseContainerNode::init_children()
+{
+ mnFinishedChildren = 0;
+
+ // initialize all children
+ return (std::count_if(
+ maChildren.begin(), maChildren.end(),
+ std::mem_fn(&AnimationNode::init) ) ==
+ static_cast<VectorOfNodes::difference_type>(maChildren.size()));
+}
+
+void BaseContainerNode::deactivate_st( NodeState eDestState )
+{
+ mnLeftIterations = 0; // in order to make skip effect work correctly
+ if (eDestState == FROZEN) {
+ // deactivate all children that are not FROZEN or ENDED:
+ forEachChildNode( std::mem_fn(&AnimationNode::deactivate),
+ ~(FROZEN | ENDED) );
+ }
+ else {
+ // end all children that are not ENDED:
+ forEachChildNode( std::mem_fn(&AnimationNode::end), ~ENDED );
+ }
+}
+
+bool BaseContainerNode::hasPendingAnimation() const
+{
+ // does any of our children returns "true" on
+ // AnimationNode::hasPendingAnimation()?
+ // If yes, we, too, return true
+ return std::any_of(
+ maChildren.begin(), maChildren.end(),
+ std::mem_fn(&AnimationNode::hasPendingAnimation) );
+}
+
+void BaseContainerNode::appendChildNode( AnimationNodeSharedPtr const& pNode )
+{
+ if (! checkValidNode())
+ return;
+
+ // register derived classes as end listeners at all children.
+ // this is necessary to control the children animation
+ // sequence, and to determine our own end event
+ if (pNode->registerDeactivatingListener( getSelf() )) {
+ maChildren.push_back( pNode );
+ }
+}
+
+bool BaseContainerNode::isChildNode( AnimationNodeSharedPtr const& pNode ) const
+{
+ // find given notifier in child vector
+ VectorOfNodes::const_iterator const iEnd( maChildren.end() );
+ VectorOfNodes::const_iterator const iFind(
+ std::find( maChildren.begin(), iEnd, pNode ) );
+ return (iFind != iEnd);
+}
+
+bool BaseContainerNode::notifyDeactivatedChild(
+ AnimationNodeSharedPtr const& pChildNode )
+{
+ OSL_ASSERT( pChildNode->getState() == FROZEN ||
+ pChildNode->getState() == ENDED );
+ // early exit on invalid nodes
+ OSL_ASSERT( getState() != INVALID );
+ if( getState() == INVALID )
+ return false;
+
+ if (! isChildNode(pChildNode)) {
+ OSL_FAIL( "unknown notifier!" );
+ return false;
+ }
+
+ std::size_t const nSize = maChildren.size();
+ OSL_ASSERT( mnFinishedChildren < nSize );
+ ++mnFinishedChildren;
+ bool bFinished = (mnFinishedChildren >= nSize);
+
+ // Handle repetition here.
+ if (bFinished) {
+ if(!mbRepeatIndefinite && mnLeftIterations >= 1.0)
+ {
+ mnLeftIterations -= 1.0;
+ }
+ if(mnLeftIterations >= 1.0 || mbRestart)
+ {
+ // SMIL spec said that "Accumulate" controls whether or not the animation
+ // is cumulative, but XTimeContainer do not have this attribute, so always
+ // remove the effect before next repeat.
+ forEachChildNode(std::mem_fn(&AnimationNode::removeEffect), -1);
+
+ if (mnLeftIterations >= 1.0)
+ bFinished = false;
+
+ EventSharedPtr aRepetitionEvent =
+ makeDelay( [this] () { this->repeat(); },
+ 0.0,
+ "BaseContainerNode::repeat");
+ getContext().mrEventQueue.addEvent( aRepetitionEvent );
+ }
+ else if (isDurationIndefinite())
+ {
+ if (getFillMode() == animations::AnimationFill::REMOVE)
+ forEachChildNode(std::mem_fn(&AnimationNode::removeEffect), -1);
+ deactivate();
+ }
+ }
+
+ return bFinished;
+}
+
+void BaseContainerNode::repeat()
+{
+ // Prevent repeat event scheduled before deactivation.
+ if (getState() == FROZEN || getState() == ENDED)
+ return;
+
+ forEachChildNode( std::mem_fn(&AnimationNode::end), ~ENDED );
+ bool bState = init_children();
+ if( bState )
+ activate_st();
+}
+
+#if defined(DBG_UTIL)
+void BaseContainerNode::showState() const
+{
+ for(const auto & i : maChildren)
+ {
+ BaseNodeSharedPtr pNode =
+ std::dynamic_pointer_cast<BaseNode>(i);
+ SAL_INFO("slideshow.verbose",
+ "Node connection: n" <<
+ debugGetNodeName(this) <<
+ " -> n" <<
+ debugGetNodeName(pNode.get()));
+ pNode->showState();
+ }
+
+ BaseNode::showState();
+}
+#endif
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/basenode.cxx b/slideshow/source/engine/animationnodes/basenode.cxx
new file mode 100644
index 000000000..1b8a6d7fd
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/basenode.cxx
@@ -0,0 +1,751 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/animations/XAnimate.hpp>
+#include <com/sun/star/presentation/ParagraphTarget.hpp>
+#include <com/sun/star/animations/AnimationFill.hpp>
+#include <com/sun/star/animations/AnimationRestart.hpp>
+#include <com/sun/star/presentation/EffectNodeType.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+
+#include <basenode.hxx>
+#include <eventmultiplexer.hxx>
+#include <basecontainernode.hxx>
+#include <eventqueue.hxx>
+#include <delayevent.hxx>
+#include <tools.hxx>
+#include "nodetools.hxx"
+#include "generateevent.hxx"
+
+#include <sal/log.hxx>
+
+#include <vector>
+#include <algorithm>
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal {
+
+namespace {
+
+typedef int StateTransitionTable[17];
+
+// State transition tables
+// =========================================================================
+
+const int* getStateTransitionTable( sal_Int16 nRestartMode,
+ sal_Int16 nFillMode )
+{
+ // TODO(F2): restart issues in below tables
+
+ // transition table for restart=NEVER, fill=REMOVE
+ static const StateTransitionTable stateTransitionTable_Never_Remove = {
+ AnimationNode::INVALID,
+ AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
+ AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
+ AnimationNode::INVALID,
+ AnimationNode::ENDED, // active successors for ACTIVE: no freeze here
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID, // active successors for FROZEN: this state is unreachable here
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::ENDED // active successors for ENDED: this state is a sink here (cannot restart)
+ };
+
+ // transition table for restart=WHEN_NOT_ACTIVE, fill=REMOVE
+ static const StateTransitionTable stateTransitionTable_NotActive_Remove = {
+ AnimationNode::INVALID,
+ AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
+ AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
+ AnimationNode::INVALID,
+ AnimationNode::ENDED, // active successors for ACTIVE: no freeze here
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID, // active successors for FROZEN:
+ // this state is unreachable here
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE // active successors for ENDED:
+ // restart possible when ended
+ };
+
+ // transition table for restart=ALWAYS, fill=REMOVE
+ static const StateTransitionTable stateTransitionTable_Always_Remove = {
+ AnimationNode::INVALID,
+ AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
+ AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
+ AnimationNode::INVALID,
+ AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED, // active successors for ACTIVE: restart
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID, // active successors for FROZEN:
+ // this state is unreachable here
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED // active successors for ENDED: restart
+ };
+
+ // transition table for restart=NEVER, fill=FREEZE
+ static const StateTransitionTable stateTransitionTable_Never_Freeze = {
+ AnimationNode::INVALID,
+ AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
+ AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
+ AnimationNode::INVALID,
+ AnimationNode::FROZEN|AnimationNode::ENDED, // active successors for ACTIVE: freeze object
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::ENDED, // active successors for FROZEN: end
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::ENDED, // active successors for ENDED: this state is a sink here (cannot restart)
+ };
+
+ // transition table for restart=WHEN_NOT_ACTIVE, fill=FREEZE
+ static const StateTransitionTable stateTransitionTable_NotActive_Freeze = {
+ AnimationNode::INVALID,
+ AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
+ AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
+ AnimationNode::INVALID,
+ AnimationNode::FROZEN|AnimationNode::ENDED, // active successors for ACTIVE: freeze object
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE, // active successors for FROZEN:
+ // restart possible when ended
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE // active successors for ENDED:
+ // restart possible when ended
+ };
+
+ // transition table for restart=ALWAYS, fill=FREEZE
+ static const StateTransitionTable stateTransitionTable_Always_Freeze = {
+ AnimationNode::INVALID,
+ AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
+ AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
+ AnimationNode::INVALID,
+ AnimationNode::FROZEN|AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED, // active successors for ACTIVE:
+ // end object, restart
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE, // active successors for FROZEN: restart possible
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::INVALID,
+ AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED // active successors for ENDED: restart
+ };
+
+ static const StateTransitionTable* tableGuide[] = {
+ &stateTransitionTable_Never_Remove,
+ &stateTransitionTable_NotActive_Remove,
+ &stateTransitionTable_Always_Remove,
+ &stateTransitionTable_Never_Freeze,
+ &stateTransitionTable_NotActive_Freeze,
+ &stateTransitionTable_Always_Freeze
+ };
+
+ int nRestartValue;
+ switch( nRestartMode ) {
+ default:
+ case animations::AnimationRestart::DEFAULT:
+ // same value: animations::AnimationRestart::INHERIT:
+ OSL_FAIL(
+ "getStateTransitionTable(): unexpected case for restart" );
+ [[fallthrough]];
+ case animations::AnimationRestart::NEVER:
+ nRestartValue = 0;
+ break;
+ case animations::AnimationRestart::WHEN_NOT_ACTIVE:
+ nRestartValue = 1;
+ break;
+ case animations::AnimationRestart::ALWAYS:
+ nRestartValue = 2;
+ break;
+ }
+
+ int nFillValue;
+ switch( nFillMode ) {
+ default:
+ case animations::AnimationFill::AUTO:
+ case animations::AnimationFill::DEFAULT:
+ // same value: animations::AnimationFill::INHERIT:
+ OSL_FAIL(
+ "getStateTransitionTable(): unexpected case for fill" );
+ [[fallthrough]];
+ case animations::AnimationFill::REMOVE:
+ nFillValue = 0;
+ break;
+ case animations::AnimationFill::FREEZE:
+ case animations::AnimationFill::HOLD:
+ case animations::AnimationFill::TRANSITION:
+ nFillValue = 1;
+ break;
+ }
+
+ return *tableGuide[ 3*nFillValue + nRestartValue ];
+}
+
+/// Little helper predicate, to detect main sequence root node
+bool isMainSequenceRootNode_(
+ const uno::Reference< animations::XAnimationNode >& xNode )
+{
+ // detect main sequence root node (need that for
+ // end-of-mainsequence signalling below)
+ beans::NamedValue const aSearchKey(
+ "node-type",
+ uno::makeAny( presentation::EffectNodeType::MAIN_SEQUENCE ) );
+
+ uno::Sequence<beans::NamedValue> const userData(xNode->getUserData());
+ return findNamedValue( userData, aSearchKey );
+}
+
+} // anon namespace
+
+// BaseNode implementation
+//=========================================================================
+
+/** state transition handling
+ */
+class BaseNode::StateTransition
+{
+public:
+ enum class Options { NONE, FORCE };
+
+ explicit StateTransition( BaseNode * pNode )
+ : mpNode(pNode), meToState(INVALID) {}
+
+ ~StateTransition() {
+ clear();
+ }
+
+ StateTransition(const StateTransition&) = delete;
+ StateTransition& operator=(const StateTransition&) = delete;
+
+ bool enter( NodeState eToState, Options options = Options::NONE )
+ {
+ OSL_ENSURE( meToState == INVALID,
+ "### commit() before enter()ing again!" );
+ if (meToState != INVALID)
+ return false;
+ bool const bForce = options == Options::FORCE;
+ if (!bForce && !mpNode->isTransition( mpNode->meCurrState, eToState ))
+ return false;
+ // recursion detection:
+ if ((mpNode->meCurrentStateTransition & eToState) != 0)
+ return false; // already in wanted transition
+ // mark transition:
+ mpNode->meCurrentStateTransition |= eToState;
+ meToState = eToState;
+ return true; // in transition
+ }
+
+ void commit() {
+ OSL_ENSURE( meToState != INVALID, "### nothing to commit!" );
+ if (meToState != INVALID) {
+ mpNode->meCurrState = meToState;
+ clear();
+ }
+ }
+
+ void clear() {
+ if (meToState != INVALID) {
+ OSL_ASSERT( (mpNode->meCurrentStateTransition & meToState) != 0 );
+ mpNode->meCurrentStateTransition &= ~meToState;
+ meToState = INVALID;
+ }
+ }
+
+private:
+ BaseNode *const mpNode;
+ NodeState meToState;
+};
+
+BaseNode::BaseNode( const uno::Reference< animations::XAnimationNode >& xNode,
+ const BaseContainerNodeSharedPtr& rParent,
+ const NodeContext& rContext ) :
+ maContext( rContext.maContext ),
+ maDeactivatingListeners(),
+ mxAnimationNode( xNode ),
+ mpParent( rParent ),
+ mpSelf(),
+ mpStateTransitionTable( nullptr ),
+ mnStartDelay( rContext.mnStartDelay ),
+ meCurrState( UNRESOLVED ),
+ meCurrentStateTransition( 0 ),
+ mpCurrentEvent(),
+ mbIsMainSequenceRootNode( isMainSequenceRootNode_( xNode ) )
+{
+ ENSURE_OR_THROW( mxAnimationNode.is(),
+ "BaseNode::BaseNode(): Invalid XAnimationNode" );
+
+ // setup state transition table
+ mpStateTransitionTable = getStateTransitionTable( getRestartMode(),
+ getFillMode() );
+}
+
+void BaseNode::dispose()
+{
+ meCurrState = INVALID;
+
+ // discharge a loaded event, if any:
+ if (mpCurrentEvent) {
+ mpCurrentEvent->dispose();
+ mpCurrentEvent.reset();
+ }
+ maDeactivatingListeners.clear();
+ mxAnimationNode.clear();
+ mpParent.reset();
+ mpSelf.reset();
+ maContext.dispose();
+}
+
+
+sal_Int16 BaseNode::getRestartMode()
+{
+ const sal_Int16 nTmp( mxAnimationNode->getRestart() );
+ return nTmp != animations::AnimationRestart::DEFAULT
+ ? nTmp : getRestartDefaultMode();
+}
+
+sal_Int16 BaseNode::getFillMode()
+{
+ const sal_Int16 nTmp( mxAnimationNode->getFill() );
+ const sal_Int16 nFill(nTmp != animations::AnimationFill::DEFAULT
+ ? nTmp : getFillDefaultMode());
+
+ // For AUTO fill mode, SMIL specifies that fill mode is FREEZE,
+ // if no explicit active duration is given
+ // (no duration, end, repeatCount or repeatDuration given),
+ // and REMOVE otherwise
+ if( nFill == animations::AnimationFill::AUTO ) {
+ return (isIndefiniteTiming( mxAnimationNode->getDuration() ) &&
+ isIndefiniteTiming( mxAnimationNode->getEnd() ) &&
+ !mxAnimationNode->getRepeatCount().hasValue() &&
+ isIndefiniteTiming( mxAnimationNode->getRepeatDuration() ))
+ ? animations::AnimationFill::FREEZE
+ : animations::AnimationFill::REMOVE;
+ }
+ else {
+ return nFill;
+ }
+}
+
+sal_Int16 BaseNode::getFillDefaultMode() const
+{
+ sal_Int16 nFillDefault = mxAnimationNode->getFillDefault();
+ if (nFillDefault == animations::AnimationFill::DEFAULT) {
+ nFillDefault = (mpParent != nullptr
+ ? mpParent->getFillDefaultMode()
+ : animations::AnimationFill::AUTO);
+ }
+ return nFillDefault;
+}
+
+sal_Int16 BaseNode::getRestartDefaultMode() const
+{
+ sal_Int16 nRestartDefaultMode = mxAnimationNode->getRestartDefault();
+ if (nRestartDefaultMode == animations::AnimationRestart::DEFAULT) {
+ nRestartDefaultMode = (mpParent != nullptr
+ ? mpParent->getRestartDefaultMode()
+ : animations::AnimationRestart::ALWAYS);
+ }
+ return nRestartDefaultMode;
+}
+
+uno::Reference<animations::XAnimationNode> BaseNode::getXAnimationNode() const
+{
+ return mxAnimationNode;
+}
+
+bool BaseNode::init()
+{
+ if (! checkValidNode())
+ return false;
+ meCurrState = UNRESOLVED;
+ // discharge a loaded event, if any:
+ if (mpCurrentEvent) {
+ mpCurrentEvent->dispose();
+ mpCurrentEvent.reset();
+ }
+ return init_st(); // may call derived class
+}
+
+bool BaseNode::init_st()
+{
+ return true;
+}
+
+bool BaseNode::resolve()
+{
+ if (! checkValidNode())
+ return false;
+
+ OSL_ASSERT( meCurrState != RESOLVED );
+ if (inStateOrTransition( RESOLVED ))
+ return true;
+
+ StateTransition st(this);
+ if (st.enter( RESOLVED ) &&
+ isTransition( RESOLVED, ACTIVE ) &&
+ resolve_st() /* may call derived class */)
+ {
+ st.commit(); // changing state
+
+ // discharge a loaded event, if any:
+ if (mpCurrentEvent)
+ mpCurrentEvent->dispose();
+
+ // schedule activation event:
+
+ // This method takes the NodeContext::mnStartDelay value into account,
+ // to cater for iterate container time shifts. We cannot put different
+ // iterations of the iterate container's children into different
+ // subcontainer (such as a 'DelayContainer', which delays resolving its
+ // children by a fixed amount), since all iterations' nodes must be
+ // resolved at the same time (otherwise, the delayed subset creation
+ // will not work, i.e. deactivate the subsets too late in the master
+ // shape).
+ uno::Any const aBegin( mxAnimationNode->getBegin() );
+ if (aBegin.hasValue()) {
+ auto self(mpSelf);
+ mpCurrentEvent = generateEvent(
+ aBegin, [self] () { self->activate(); },
+ maContext, mnStartDelay );
+ }
+ else {
+ // For some leaf nodes, PPT import yields empty begin time,
+ // although semantically, it should be 0.0
+ // TODO(F3): That should really be provided by the PPT import
+
+ // schedule delayed activation event. Take iterate node
+ // timeout into account
+ auto self(mpSelf);
+ mpCurrentEvent = makeDelay(
+ [self] () { self->activate(); },
+ mnStartDelay,
+ "AnimationNode::activate with delay");
+ maContext.mrEventQueue.addEvent( mpCurrentEvent );
+ }
+
+ return true;
+ }
+ return false;
+}
+
+bool BaseNode::resolve_st()
+{
+ return true;
+}
+
+
+void BaseNode::activate()
+{
+ if (! checkValidNode())
+ return;
+
+ OSL_ASSERT( meCurrState != ACTIVE );
+ if (inStateOrTransition( ACTIVE ))
+ return;
+
+ StateTransition st(this);
+ if (st.enter( ACTIVE )) {
+
+ activate_st(); // calling derived class
+
+ st.commit(); // changing state
+
+ maContext.mrEventMultiplexer.notifyAnimationStart( mpSelf );
+ }
+}
+
+void BaseNode::activate_st()
+{
+ scheduleDeactivationEvent();
+}
+
+void BaseNode::scheduleDeactivationEvent( EventSharedPtr const& pEvent )
+{
+ if (mpCurrentEvent) {
+ mpCurrentEvent->dispose();
+ mpCurrentEvent.reset();
+ }
+ if (pEvent) {
+ if (maContext.mrEventQueue.addEvent( pEvent ))
+ mpCurrentEvent = pEvent;
+ }
+ else {
+ // This method need not take the
+ // NodeContext::mnStartDelay value into account,
+ // because the deactivation event is only scheduled
+ // when the effect is started: the timeout is then
+ // already respected.
+
+ // xxx todo:
+ // think about set node, anim base node!
+ // if anim base node has no activity, this is called to schedule deactivation,
+ // but what if it does not schedule anything?
+
+ auto self(mpSelf);
+ if (mxAnimationNode->getEnd().hasValue())
+ {
+ // TODO: We may need to calculate the duration if the end value is numeric.
+ // We expect that the end value contains EventTrigger::ON_NEXT here.
+ // LibreOffice does not generate numeric values, so we can leave it
+ // until we find a test case.
+ mpCurrentEvent = generateEvent(
+ mxAnimationNode->getEnd(),
+ [self] () { self->deactivate(); },
+ maContext, 0.0 );
+
+ }
+ else
+ {
+ mpCurrentEvent = generateEvent(
+ mxAnimationNode->getDuration(),
+ [self] () { self->deactivate(); },
+ maContext, 0.0 );
+ }
+ }
+}
+
+void BaseNode::deactivate()
+{
+ if (inStateOrTransition( ENDED | FROZEN ) || !checkValidNode())
+ return;
+
+ if (isTransition( meCurrState, FROZEN, false /* no OSL_ASSERT */ )) {
+ // do transition to FROZEN:
+ StateTransition st(this);
+ if (st.enter( FROZEN, StateTransition::Options::FORCE )) {
+
+ deactivate_st( FROZEN );
+ st.commit();
+
+ notifyEndListeners();
+
+ // discharge a loaded event, before going on:
+ if (mpCurrentEvent) {
+ mpCurrentEvent->dispose();
+ mpCurrentEvent.reset();
+ }
+ }
+ }
+ else {
+ // use end instead:
+ end();
+ }
+ // state has changed either to FROZEN or ENDED
+}
+
+void BaseNode::deactivate_st( NodeState )
+{
+}
+
+void BaseNode::end()
+{
+ bool const bIsFrozenOrInTransitionToFrozen = inStateOrTransition( FROZEN );
+ if (inStateOrTransition( ENDED ) || !checkValidNode())
+ return;
+
+ // END must always be reachable. If not, that's an error in the
+ // transition tables
+ OSL_ENSURE( isTransition( meCurrState, ENDED ),
+ "end state not reachable in transition table" );
+
+ StateTransition st(this);
+ if (!st.enter( ENDED, StateTransition::Options::FORCE ))
+ return;
+
+ deactivate_st( ENDED );
+ st.commit(); // changing state
+
+ // if is FROZEN or is to be FROZEN, then
+ // will/already notified deactivating listeners
+ if (!bIsFrozenOrInTransitionToFrozen)
+ notifyEndListeners();
+
+ // discharge a loaded event, before going on:
+ if (mpCurrentEvent) {
+ mpCurrentEvent->dispose();
+ mpCurrentEvent.reset();
+ }
+}
+
+void BaseNode::notifyDeactivating( const AnimationNodeSharedPtr& rNotifier )
+{
+ OSL_ASSERT( rNotifier->getState() == FROZEN ||
+ rNotifier->getState() == ENDED );
+ // TODO(F1): for end sync functionality, this might indeed be used some day
+}
+
+void BaseNode::notifyEndListeners() const
+{
+ // notify all listeners
+ for( const auto& rListner : maDeactivatingListeners )
+ rListner->notifyDeactivating( mpSelf );
+
+ // notify state change
+ maContext.mrEventMultiplexer.notifyAnimationEnd( mpSelf );
+
+ // notify main sequence end (iff we're the main
+ // sequence root node). This is because the main
+ // sequence determines the active duration of the
+ // slide. All other sequences are secondary, in that
+ // they don't prevent a slide change from happening,
+ // even if they have not been completed. In other
+ // words, all sequences except the main sequence are
+ // optional for the slide lifetime.
+ if (isMainSequenceRootNode())
+ maContext.mrEventMultiplexer.notifySlideAnimationsEnd();
+}
+
+AnimationNode::NodeState BaseNode::getState() const
+{
+ return meCurrState;
+}
+
+bool BaseNode::registerDeactivatingListener(
+ const AnimationNodeSharedPtr& rNotifee )
+{
+ if (! checkValidNode())
+ return false;
+
+ ENSURE_OR_RETURN_FALSE(
+ rNotifee,
+ "BaseNode::registerDeactivatingListener(): invalid notifee" );
+ maDeactivatingListeners.push_back( rNotifee );
+
+ return true;
+}
+
+void BaseNode::setSelf( const BaseNodeSharedPtr& rSelf )
+{
+ ENSURE_OR_THROW( rSelf.get() == this,
+ "BaseNode::setSelf(): got ptr to different object" );
+ ENSURE_OR_THROW( !mpSelf,
+ "BaseNode::setSelf(): called multiple times" );
+
+ mpSelf = rSelf;
+}
+
+// Debug
+
+
+#if defined(DBG_UTIL)
+void BaseNode::showState() const
+{
+ const AnimationNode::NodeState eNodeState( getState() );
+
+ if( eNodeState == AnimationNode::INVALID )
+ SAL_INFO("slideshow.verbose", "Node state: n" <<
+ debugGetNodeName(this) <<
+ " [label=\"" <<
+ getDescription() <<
+ "\",style=filled, fillcolor=\"0.5,0.2,0.5\"]");
+ else
+ SAL_INFO("slideshow.verbose", "Node state: n" <<
+ debugGetNodeName(this) <<
+ " [label=\"" <<
+ getDescription() <<
+ "fillcolor=\"" <<
+ log(double(getState()))/4.0 <<
+ ",1.0,1.0\"]");
+
+ // determine additional node information
+ uno::Reference<animations::XAnimate> const xAnimate( mxAnimationNode,
+ uno::UNO_QUERY );
+ if( !xAnimate.is() )
+ return;
+
+ uno::Reference< drawing::XShape > xTargetShape( xAnimate->getTarget(),
+ uno::UNO_QUERY );
+
+ if( !xTargetShape.is() )
+ {
+ css::presentation::ParagraphTarget aTarget;
+
+ // no shape provided. Maybe a ParagraphTarget?
+ if( xAnimate->getTarget() >>= aTarget )
+ xTargetShape = aTarget.Shape;
+ }
+
+ if( !xTargetShape.is() )
+ return;
+
+ uno::Reference< beans::XPropertySet > xPropSet( xTargetShape,
+ uno::UNO_QUERY );
+
+ // read shape name
+ OUString aName;
+ if( xPropSet->getPropertyValue("Name") >>= aName )
+ {
+ SAL_INFO("slideshow.verbose", "Node info: n" <<
+ debugGetNodeName(this) <<
+ ", name \"" <<
+ aName <<
+ "\"");
+ }
+}
+
+const char* BaseNode::getDescription() const
+{
+ return "BaseNode";
+}
+
+#endif
+
+} // namespace slideshow
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/generateevent.cxx b/slideshow/source/engine/animationnodes/generateevent.cxx
new file mode 100644
index 000000000..6d61c0c25
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/generateevent.cxx
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <tools/diagnose_ex.h>
+
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/animations/XAnimationNode.hpp>
+#include <com/sun/star/animations/Timing.hpp>
+#include <com/sun/star/animations/EventTrigger.hpp>
+#include <com/sun/star/animations/Event.hpp>
+
+#include "generateevent.hxx"
+#include <subsettableshapemanager.hxx>
+#include <usereventqueue.hxx>
+#include <slideshowcontext.hxx>
+#include <delayevent.hxx>
+
+namespace slideshow::internal {
+
+using namespace com::sun::star;
+
+EventSharedPtr generateEvent(
+ uno::Any const& rEventDescription,
+ Delay::FunctorT const& rFunctor,
+ SlideShowContext const& rContext,
+ double nAdditionalDelay )
+{
+ EventSharedPtr pEvent;
+
+ if (! rEventDescription.hasValue())
+ return pEvent;
+
+ animations::Timing eTiming;
+ animations::Event aEvent;
+ uno::Sequence<uno::Any> aSequence;
+ double nDelay1 = 0;
+
+ if (rEventDescription >>= eTiming) {
+ switch (eTiming) {
+ case animations::Timing_INDEFINITE:
+ break; // don't schedule no event
+ case animations::Timing_MEDIA:
+ OSL_FAIL( "MEDIA timing not yet implemented!" );
+ break;
+ default:
+ ENSURE_OR_THROW( false, "unexpected case!" );
+ }
+ }
+ else if (rEventDescription >>= aEvent) {
+
+ // try to extract additional event delay
+ double nDelay2 = 0.0;
+ if (aEvent.Offset.hasValue() && !(aEvent.Offset >>= nDelay2)) {
+ OSL_FAIL( "offset values apart from DOUBLE not "
+ "recognized in animations::Event!" );
+ }
+
+ // common vars used inside switch
+ uno::Reference<animations::XAnimationNode> xNode;
+ uno::Reference<drawing::XShape> xShape;
+ ShapeSharedPtr pShape;
+
+ // TODO(F1): Respect aEvent.Repeat value
+
+ auto event2shape = [&] () {
+ if (aEvent.Source >>= xShape)
+ pShape = rContext.mpSubsettableShapeManager->lookupShape(xShape);
+ };
+
+ switch (aEvent.Trigger) {
+ default:
+ ENSURE_OR_THROW( false, "unexpected event trigger!" );
+ case animations::EventTrigger::NONE:
+ // no event at all
+ break;
+ case animations::EventTrigger::ON_BEGIN:
+ OSL_FAIL( "event trigger ON_BEGIN not yet implemented!" );
+ break;
+ case animations::EventTrigger::ON_END:
+ OSL_FAIL( "event trigger ON_END not yet implemented!" );
+ break;
+ case animations::EventTrigger::BEGIN_EVENT:
+ // try to extract XAnimationNode event source
+ if (aEvent.Source >>= xNode) {
+ pEvent = makeDelay( rFunctor,
+ nDelay2 + nAdditionalDelay,
+ "generateEvent, BEGIN_EVENT");
+ rContext.mrUserEventQueue.registerAnimationStartEvent(
+ pEvent, xNode );
+ }
+ else {
+ OSL_FAIL("could not extract source XAnimationNode "
+ "for BEGIN_EVENT!" );
+ }
+ break;
+ case animations::EventTrigger::END_EVENT:
+ // try to extract XAnimationNode event source
+ if (aEvent.Source >>= xNode) {
+ pEvent = makeDelay( rFunctor,
+ nDelay2 + nAdditionalDelay,
+ "generateEvent, END_EVENT");
+ rContext.mrUserEventQueue.registerAnimationEndEvent(
+ pEvent, xNode );
+ }
+ else {
+ OSL_FAIL( "could not extract source XAnimationNode "
+ "for END_EVENT!" );
+ }
+ break;
+ case animations::EventTrigger::ON_CLICK:
+ // try to extract XShape event source
+ event2shape();
+ if (pShape)
+ {
+ pEvent = makeDelay( rFunctor,
+ nDelay2 + nAdditionalDelay,
+ "generateEvent, ON_CLICK");
+ rContext.mrUserEventQueue.registerShapeClickEvent(
+ pEvent, pShape );
+ }
+ else {
+ OSL_FAIL( "could not extract source XAnimationNode "
+ "for ON_CLICK!" );
+ }
+ break;
+ case animations::EventTrigger::ON_DBL_CLICK:
+ // try to extract XShape event source
+ event2shape();
+ if (pShape)
+ {
+ pEvent = makeDelay( rFunctor,
+ nDelay2 + nAdditionalDelay,
+ "generateEvent, ON_DBL_CLICK");
+ rContext.mrUserEventQueue.registerShapeDoubleClickEvent(
+ pEvent, pShape );
+ }
+ else {
+ OSL_FAIL( "could not extract source XAnimationNode "
+ "for ON_DBL_CLICK!" );
+ }
+ break;
+ case animations::EventTrigger::ON_MOUSE_ENTER:
+ // try to extract XShape event source
+ event2shape();
+ if (pShape)
+ {
+ pEvent = makeDelay( rFunctor,
+ nDelay2 + nAdditionalDelay,
+ "generateEvent, ON_MOUSE_ENTER");
+ rContext.mrUserEventQueue.registerMouseEnterEvent(
+ pEvent, pShape );
+ }
+ else {
+ OSL_FAIL( "could not extract source XAnimationNode "
+ "for ON_MOUSE_ENTER!" );
+ }
+ break;
+ case animations::EventTrigger::ON_MOUSE_LEAVE:
+ // try to extract XShape event source
+ event2shape();
+ if (pShape)
+ {
+ pEvent = makeDelay( rFunctor,
+ nDelay2 + nAdditionalDelay,
+ "generateEvent, ON_MOUSE_LEAVE");
+ rContext.mrUserEventQueue.registerMouseLeaveEvent(
+ pEvent, pShape );
+ }
+ else {
+ OSL_FAIL( "could not extract source XAnimationNode "
+ "for ON_MOUSE_LEAVE!" );
+ }
+ break;
+ case animations::EventTrigger::ON_PREV:
+ OSL_FAIL( "event trigger ON_PREV not yet implemented, "
+ "mapped to ON_NEXT!" );
+ [[fallthrough]];
+ case animations::EventTrigger::ON_NEXT:
+ pEvent = makeDelay( rFunctor,
+ nDelay2 + nAdditionalDelay,
+ "generateEvent, ON_NEXT");
+ rContext.mrUserEventQueue.registerNextEffectEvent( pEvent );
+ break;
+ case animations::EventTrigger::ON_STOP_AUDIO:
+ // try to extract XAnimationNode event source
+ if (aEvent.Source >>= xNode) {
+ pEvent = makeDelay( rFunctor,
+ nDelay2 + nAdditionalDelay,
+ "generateEvent, ON_STOP_AUDIO");
+ rContext.mrUserEventQueue.registerAudioStoppedEvent(
+ pEvent, xNode );
+ }
+ else {
+ OSL_FAIL( "could not extract source XAnimationNode "
+ "for ON_STOP_AUDIO!" );
+ }
+ break;
+ case animations::EventTrigger::REPEAT:
+ OSL_FAIL( "event trigger REPEAT not yet implemented!" );
+ break;
+ }
+ }
+ else if (rEventDescription >>= aSequence) {
+ OSL_FAIL( "sequence of timing primitives "
+ "not yet implemented!" );
+ }
+ else if (rEventDescription >>= nDelay1) {
+ pEvent = makeDelay( rFunctor,
+ nDelay1 + nAdditionalDelay,
+ "generateEvent with delay");
+ // schedule delay event
+ rContext.mrEventQueue.addEvent( pEvent );
+ }
+
+ return pEvent;
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/generateevent.hxx b/slideshow/source/engine/animationnodes/generateevent.hxx
new file mode 100644
index 000000000..487989655
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/generateevent.hxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_GENERATEEVENT_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_GENERATEEVENT_HXX
+
+#include <slideshowcontext.hxx>
+#include <delayevent.hxx>
+#include <com/sun/star/uno/Any.hxx>
+
+namespace slideshow {
+namespace internal {
+
+/** Create an event for the given description, calling the given functor.
+
+ @param rEventDescription
+ Directly from API
+
+ @param rFunctor
+ Functor to call when event fires.
+
+ @param rContext
+ Context struct, to provide event queue
+
+ @param nAdditionalDelay
+ Additional delay, gets added on top of timeout.
+*/
+EventSharedPtr generateEvent(
+ css::uno::Any const& rEventDescription,
+ Delay::FunctorT const& rFunctor,
+ SlideShowContext const& rContext,
+ double nAdditionalDelay );
+
+} // namespace internal
+} // namespace slideshow
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_GENERATEEVENT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/nodetools.cxx b/slideshow/source/engine/animationnodes/nodetools.cxx
new file mode 100644
index 000000000..f509e680c
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/nodetools.cxx
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/animations/Timing.hpp>
+
+#include "nodetools.hxx"
+
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal
+{
+#if defined(DBG_UTIL)
+ static sal_Int32 lcl_nOffset = 0;
+
+ OUString debugGetNodeName( const BaseNode *pNode )
+ {
+ return OUString::number(lcl_nOffset) +
+ " - 0x" +
+ OUString::number(reinterpret_cast<sal_Int64>(pNode), 16);
+ }
+
+ void debugNodesShowTree( const BaseNode* pNode )
+ {
+ if( pNode )
+ pNode->showState();
+
+ ++lcl_nOffset;
+ }
+#endif
+
+ AttributableShapeSharedPtr lookupAttributableShape( const ShapeManagerSharedPtr& rShapeManager,
+ const uno::Reference< drawing::XShape >& xShape )
+ {
+ ENSURE_OR_THROW( rShapeManager,
+ "lookupAttributableShape(): invalid ShapeManager" );
+
+ ShapeSharedPtr pShape( rShapeManager->lookupShape( xShape ) );
+
+ ENSURE_OR_THROW( pShape,
+ "lookupAttributableShape(): no shape found for given XShape" );
+
+ AttributableShapeSharedPtr pRes(
+ ::std::dynamic_pointer_cast< AttributableShape >( pShape ) );
+
+ // TODO(E3): Cannot throw here, people might set animation info
+ // for non-animatable shapes from the API. AnimationNodes must catch
+ // the exception and handle that differently
+ ENSURE_OR_THROW( pRes,
+ "lookupAttributableShape(): shape found does not implement AttributableShape interface" );
+
+ return pRes;
+ }
+
+ bool isIndefiniteTiming( const uno::Any& rAny )
+ {
+ if( !rAny.hasValue() )
+ return true;
+
+ animations::Timing eTiming;
+
+ return (rAny >>= eTiming) && eTiming == animations::Timing_INDEFINITE;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/nodetools.hxx b/slideshow/source/engine/animationnodes/nodetools.hxx
new file mode 100644
index 000000000..f68f9dbdd
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/nodetools.hxx
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_NODETOOLS_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_NODETOOLS_HXX
+
+#include <com/sun/star/drawing/XShape.hpp>
+
+#include <shapemanager.hxx>
+#include <basenode.hxx>
+#include <attributableshape.hxx>
+
+
+#if defined(DBG_UTIL)
+# define DEBUG_NODES_SHOWTREE(a) debugNodesShowTree(a);
+#else
+# define DEBUG_NODES_SHOWTREE(a)
+#endif
+
+namespace slideshow
+{
+ namespace internal
+ {
+
+ // Tools
+
+
+#if defined(DBG_UTIL)
+ OUString debugGetNodeName( const BaseNode *pNode );
+ void debugNodesShowTree( const BaseNode* );
+#endif
+
+ /** Look up an AttributableShape from ShapeManager.
+
+ This method retrieves an AttributableShape pointer, given
+ an XShape and a LayerManager.
+
+ Throws a runtime exception if there's no such shape, or if
+ it does not implement the AttributableShape interface.
+ */
+ AttributableShapeSharedPtr lookupAttributableShape( const ShapeManagerSharedPtr& rShapeManager,
+ const css::uno::Reference< css::drawing::XShape >& xShape );
+
+ /** Predicate whether a Begin, Duration or End timing is
+ indefinite, i.e. either contains no value, or the
+ value Timing_INDEFINITE.
+ */
+ bool isIndefiniteTiming( const css::uno::Any& rAny );
+
+ }
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_NODETOOLS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/paralleltimecontainer.cxx b/slideshow/source/engine/animationnodes/paralleltimecontainer.cxx
new file mode 100644
index 000000000..f4c063716
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/paralleltimecontainer.cxx
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "paralleltimecontainer.hxx"
+#include <delayevent.hxx>
+
+#include <functional>
+
+namespace slideshow::internal {
+
+void ParallelTimeContainer::activate_st()
+{
+ // resolve all children:
+ std::size_t const nResolvedNodes =
+ static_cast<std::size_t>(std::count_if(
+ maChildren.begin(), maChildren.end(),
+ std::mem_fn(&AnimationNode::resolve) ));
+ OSL_ENSURE( nResolvedNodes == maChildren.size(),
+ "### resolving all children failed!" );
+
+ if (isDurationIndefinite() && maChildren.empty()) {
+ // deactivate ASAP:
+ auto self(getSelf());
+ scheduleDeactivationEvent(
+ makeEvent( [self] () { self->deactivate(); },
+ "ParallelTimeContainer::deactivate") );
+ }
+ else { // use default
+ scheduleDeactivationEvent();
+ }
+}
+
+void ParallelTimeContainer::notifyDeactivating(
+ AnimationNodeSharedPtr const& pChildNode )
+{
+ notifyDeactivatedChild( pChildNode );
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/paralleltimecontainer.hxx b/slideshow/source/engine/animationnodes/paralleltimecontainer.hxx
new file mode 100644
index 000000000..af3c6e1a9
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/paralleltimecontainer.hxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PARALLELTIMECONTAINER_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PARALLELTIMECONTAINER_HXX
+
+#include <basecontainernode.hxx>
+
+namespace slideshow {
+namespace internal {
+
+/** This class implements parallel node containers
+
+ All children of this node are played in parallel
+*/
+class ParallelTimeContainer : public BaseContainerNode
+{
+public:
+ ParallelTimeContainer(
+ const css::uno::Reference< css::animations::XAnimationNode >& xNode,
+ const BaseContainerNodeSharedPtr& rParent,
+ const NodeContext& rContext )
+ : BaseContainerNode( xNode, rParent, rContext ) {}
+
+#if defined(DBG_UTIL)
+ virtual const char* getDescription() const override
+ { return "ParallelTimeContainer"; }
+#endif
+
+private:
+ virtual void activate_st() override;
+ virtual void notifyDeactivating( AnimationNodeSharedPtr const& pChildNode ) override;
+};
+
+} // namespace internal
+} // namespace slideshow
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PARALLELTIMECONTAINER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/propertyanimationnode.cxx b/slideshow/source/engine/animationnodes/propertyanimationnode.cxx
new file mode 100644
index 000000000..2643c4403
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/propertyanimationnode.cxx
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "propertyanimationnode.hxx"
+#include <animationfactory.hxx>
+
+using namespace com::sun::star;
+
+namespace slideshow::internal {
+
+AnimationActivitySharedPtr PropertyAnimationNode::createActivity() const
+{
+ // Create AnimationActivity from common XAnimate parameters:
+ ActivitiesFactory::CommonParameters aParms( fillCommonParameters() );
+ uno::Reference<animations::XAnimate> const& xAnimateNode =getXAnimateNode();
+ OUString const attrName( xAnimateNode->getAttributeName() );
+ AttributableShapeSharedPtr const pShape( getShape() );
+
+ switch (AnimationFactory::classifyAttributeName( attrName )) {
+ default:
+ case AnimationFactory::CLASS_UNKNOWN_PROPERTY:
+ ENSURE_OR_THROW(
+ false,
+ "Unexpected attribute class (unknown or empty attribute name)" );
+ break;
+
+ case AnimationFactory::CLASS_NUMBER_PROPERTY:
+ return ActivitiesFactory::createAnimateActivity(
+ aParms,
+ AnimationFactory::createNumberPropertyAnimation(
+ attrName,
+ pShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize() ),
+ xAnimateNode );
+
+ case AnimationFactory::CLASS_ENUM_PROPERTY:
+ return ActivitiesFactory::createAnimateActivity(
+ aParms,
+ AnimationFactory::createEnumPropertyAnimation(
+ attrName,
+ pShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize(), 0 ),
+ xAnimateNode );
+
+ case AnimationFactory::CLASS_COLOR_PROPERTY:
+ return ActivitiesFactory::createAnimateActivity(
+ aParms,
+ AnimationFactory::createColorPropertyAnimation(
+ attrName,
+ pShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize() ),
+ xAnimateNode );
+
+ case AnimationFactory::CLASS_STRING_PROPERTY:
+ return ActivitiesFactory::createAnimateActivity(
+ aParms,
+ AnimationFactory::createStringPropertyAnimation(
+ attrName,
+ pShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize(), 0 ),
+ xAnimateNode );
+
+ case AnimationFactory::CLASS_BOOL_PROPERTY:
+ return ActivitiesFactory::createAnimateActivity(
+ aParms,
+ AnimationFactory::createBoolPropertyAnimation(
+ attrName,
+ pShape,
+ getContext().mpSubsettableShapeManager,
+ getSlideSize(), 0 ),
+ xAnimateNode );
+ }
+
+ return AnimationActivitySharedPtr();
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/propertyanimationnode.hxx b/slideshow/source/engine/animationnodes/propertyanimationnode.hxx
new file mode 100644
index 000000000..037fea61e
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/propertyanimationnode.hxx
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PROPERTYANIMATIONNODE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PROPERTYANIMATIONNODE_HXX
+
+#include "animationbasenode.hxx"
+
+namespace slideshow {
+namespace internal {
+
+class PropertyAnimationNode : public AnimationBaseNode
+{
+public:
+ PropertyAnimationNode(
+ css::uno::Reference<css::animations::XAnimationNode> const& xNode,
+ ::std::shared_ptr<BaseContainerNode> const& pParent,
+ NodeContext const& rContext )
+ : AnimationBaseNode( xNode, pParent, rContext ) {}
+
+#if defined(DBG_UTIL)
+ virtual const char* getDescription() const override
+ { return "PropertyAnimationNode"; }
+#endif
+
+private:
+ virtual AnimationActivitySharedPtr createActivity() const override;
+};
+
+} // namespace internal
+} // namespace slideshow
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_PROPERTYANIMATIONNODE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/sequentialtimecontainer.cxx b/slideshow/source/engine/animationnodes/sequentialtimecontainer.cxx
new file mode 100644
index 000000000..3f928593c
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/sequentialtimecontainer.cxx
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <delayevent.hxx>
+#include <eventqueue.hxx>
+#include <usereventqueue.hxx>
+#include "sequentialtimecontainer.hxx"
+
+namespace slideshow::internal {
+
+void SequentialTimeContainer::activate_st()
+{
+ // resolve first possible child, ignore
+ for ( ; mnFinishedChildren < maChildren.size(); ++mnFinishedChildren ) {
+ if (resolveChild( maChildren[mnFinishedChildren] ))
+ break;
+ else {
+ // node still UNRESOLVED, no need to deactivate or end...
+ OSL_FAIL( "### resolving child failed!" );
+ }
+ }
+
+ if (isDurationIndefinite() &&
+ (maChildren.empty() || mnFinishedChildren >= maChildren.size()))
+ {
+ // deactivate ASAP:
+ auto self(getSelf());
+ scheduleDeactivationEvent(
+ makeEvent( [self] () { self->deactivate(); },
+ "SequentialTimeContainer::deactivate") );
+ }
+ else // use default
+ scheduleDeactivationEvent();
+}
+
+void SequentialTimeContainer::dispose()
+{
+ BaseContainerNode::dispose();
+ if (mpCurrentSkipEvent) {
+ mpCurrentSkipEvent->dispose();
+ mpCurrentSkipEvent.reset();
+ }
+}
+
+void SequentialTimeContainer::skipEffect(
+ AnimationNodeSharedPtr const& pChildNode )
+{
+ if (isChildNode(pChildNode)) {
+ // empty all events ignoring timings => until next effect
+ getContext().mrEventQueue.forceEmpty();
+ getContext().mrEventQueue.addEvent(
+ makeEvent( [pChildNode] () { pChildNode->deactivate(); },
+ "SequentialTimeContainer::deactivate, skipEffect with delay") );
+ }
+ else
+ OSL_FAIL( "unknown notifier!" );
+}
+
+bool SequentialTimeContainer::resolveChild(
+ AnimationNodeSharedPtr const& pChildNode )
+{
+ bool const bResolved = pChildNode->resolve();
+ if (bResolved && isMainSequenceRootNode()) {
+ // discharge events:
+ if (mpCurrentSkipEvent)
+ mpCurrentSkipEvent->dispose();
+
+ // event that will deactivate the resolved/running child:
+ mpCurrentSkipEvent = makeEvent(
+ std::bind( &SequentialTimeContainer::skipEffect,
+ std::dynamic_pointer_cast<SequentialTimeContainer>( getSelf() ),
+ pChildNode ),
+ "SequentialTimeContainer::skipEffect, resolveChild");
+
+ // deactivate child node when skip event occurs:
+ getContext().mrUserEventQueue.registerSkipEffectEvent(
+ mpCurrentSkipEvent,
+ mnFinishedChildren+1<maChildren.size());
+ }
+ return bResolved;
+}
+
+void SequentialTimeContainer::notifyDeactivating(
+ AnimationNodeSharedPtr const& rNotifier )
+{
+ if (notifyDeactivatedChild( rNotifier ))
+ return;
+
+ OSL_ASSERT( mnFinishedChildren < maChildren.size() );
+ AnimationNodeSharedPtr const& pNextChild = maChildren[mnFinishedChildren];
+ OSL_ASSERT( pNextChild->getState() == UNRESOLVED );
+
+ if (! resolveChild( pNextChild )) {
+ // could not resolve child - since we risk to
+ // stall the chain of events here, play it safe
+ // and deactivate this node (only if we have
+ // indefinite duration - otherwise, we'll get a
+ // deactivation event, anyways).
+ deactivate();
+ }
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/sequentialtimecontainer.hxx b/slideshow/source/engine/animationnodes/sequentialtimecontainer.hxx
new file mode 100644
index 000000000..ad9dbb401
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/sequentialtimecontainer.hxx
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SEQUENTIALTIMECONTAINER_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SEQUENTIALTIMECONTAINER_HXX
+
+#include <basecontainernode.hxx>
+
+namespace slideshow {
+namespace internal {
+
+/** This class implements sequential node containers
+
+ All children of this node are played sequentially
+*/
+class SequentialTimeContainer : public BaseContainerNode
+{
+public:
+ SequentialTimeContainer(
+ css::uno::Reference<css::animations::XAnimationNode> const& xNode,
+ BaseContainerNodeSharedPtr const& pParent,
+ NodeContext const& rContext )
+ : BaseContainerNode( xNode, pParent, rContext ) {}
+
+#if defined(DBG_UTIL)
+ virtual const char* getDescription() const override
+ { return "SequentialTimeContainer"; }
+#endif
+
+protected:
+ virtual void dispose() override;
+
+private:
+ virtual void activate_st() override;
+ virtual void notifyDeactivating( AnimationNodeSharedPtr const& rNotifier ) override;
+
+ void skipEffect( AnimationNodeSharedPtr const& pChildNode );
+
+private:
+ bool resolveChild( AnimationNodeSharedPtr const& pChildNode );
+
+ EventSharedPtr mpCurrentSkipEvent;
+};
+
+} // namespace internal
+} // namespace slideshow
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SEQUENTIALTIMECONTAINER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/animationnodes/setactivity.hxx b/slideshow/source/engine/animationnodes/setactivity.hxx
new file mode 100644
index 000000000..37e3ff3a5
--- /dev/null
+++ b/slideshow/source/engine/animationnodes/setactivity.hxx
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SETACTIVITY_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SETACTIVITY_HXX
+
+#include <tools/diagnose_ex.h>
+
+#include <animationactivity.hxx>
+#include <animatableshape.hxx>
+#include <shapeattributelayer.hxx>
+#include <activitiesfactory.hxx>
+
+namespace slideshow {
+namespace internal {
+
+/** Templated setter for animation values
+
+ This template class implements the AnimationActivity
+ interface, but only the perform() and
+ setAttributeLayer() methods are functional. To be used for set animations.
+
+ @see AnimationSetNode.
+*/
+template <class AnimationT>
+class SetActivity : public AnimationActivity
+{
+public:
+ typedef ::std::shared_ptr< AnimationT > AnimationSharedPtrT;
+ typedef typename AnimationT::ValueType ValueT;
+
+ SetActivity( const ActivitiesFactory::CommonParameters& rParms,
+ const AnimationSharedPtrT& rAnimation,
+ const ValueT& rToValue )
+ : mpAnimation( rAnimation ),
+ mpShape(),
+ mpAttributeLayer(),
+ mpEndEvent( rParms.mpEndEvent ),
+ mrEventQueue( rParms.mrEventQueue ),
+ maToValue( rToValue ),
+ mbIsActive(true)
+ {
+ ENSURE_OR_THROW( mpAnimation, "Invalid animation" );
+ }
+
+ virtual void dispose() override
+ {
+ mbIsActive = false;
+ mpAnimation.reset();
+ mpShape.reset();
+ mpAttributeLayer.reset();
+ // discharge end event:
+ if (mpEndEvent && mpEndEvent->isCharged())
+ mpEndEvent->dispose();
+ mpEndEvent.reset();
+ }
+
+ virtual double calcTimeLag() const override
+ {
+ return 0.0;
+ }
+
+ virtual bool perform() override
+ {
+ if (! isActive())
+ return false;
+ // we're going inactive immediately:
+ mbIsActive = false;
+
+ if (mpAnimation && mpAttributeLayer && mpShape) {
+ mpAnimation->start( mpShape, mpAttributeLayer );
+ (*mpAnimation)(maToValue);
+ mpAnimation->end();
+ }
+ // fire end event, if any
+ if (mpEndEvent)
+ mrEventQueue.addEvent( mpEndEvent );
+
+ return false; // don't reinsert
+ }
+
+ virtual bool isActive() const override
+ {
+ return mbIsActive;
+ }
+
+ virtual void dequeued() override
+ {
+ }
+
+ virtual void end() override
+ {
+ perform();
+ }
+
+ virtual void setTargets( const AnimatableShapeSharedPtr& rShape,
+ const ShapeAttributeLayerSharedPtr& rAttrLayer ) override
+ {
+ ENSURE_OR_THROW( rShape, "Invalid shape" );
+ ENSURE_OR_THROW( rAttrLayer, "Invalid attribute layer" );
+
+ mpShape = rShape;
+ mpAttributeLayer = rAttrLayer;
+ }
+
+private:
+ AnimationSharedPtrT mpAnimation;
+ AnimatableShapeSharedPtr mpShape;
+ ShapeAttributeLayerSharedPtr mpAttributeLayer;
+ EventSharedPtr mpEndEvent;
+ EventQueue& mrEventQueue;
+ ValueT maToValue;
+ bool mbIsActive;
+};
+
+template <class AnimationT> AnimationActivitySharedPtr makeSetActivity(
+ const ActivitiesFactory::CommonParameters& rParms,
+ const ::std::shared_ptr< AnimationT >& rAnimation,
+ const typename AnimationT::ValueType& rToValue )
+{
+ return std::make_shared<SetActivity<AnimationT>>(rParms,rAnimation,rToValue);
+}
+
+} // namespace internal
+} // namespace presentation
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_ANIMATIONNODES_SETACTIVITY_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */