/* -*- 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/debug.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <sal/log.hxx>
#include <com/sun/star/animations/AnimationNodeType.hpp>
#include <com/sun/star/animations/AnimateColor.hpp>
#include <com/sun/star/animations/AnimateMotion.hpp>
#include <com/sun/star/animations/AnimateSet.hpp>
#include <com/sun/star/animations/AnimationFill.hpp>
#include <com/sun/star/animations/Audio.hpp>
#include <com/sun/star/animations/Command.hpp>
#include <com/sun/star/animations/Event.hpp>
#include <com/sun/star/animations/EventTrigger.hpp>
#include <com/sun/star/animations/IterateContainer.hpp>
#include <com/sun/star/animations/ParallelTimeContainer.hpp>
#include <com/sun/star/animations/SequenceTimeContainer.hpp>
#include <com/sun/star/animations/XCommand.hpp>
#include <com/sun/star/animations/XIterateContainer.hpp>
#include <com/sun/star/animations/XAnimateTransform.hpp>
#include <com/sun/star/animations/XAnimateMotion.hpp>
#include <com/sun/star/animations/XAnimate.hpp>
#include <com/sun/star/animations/AnimationRestart.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/presentation/EffectNodeType.hpp>
#include <com/sun/star/presentation/EffectCommands.hpp>
#include <com/sun/star/presentation/EffectPresetClass.hpp>
#include <com/sun/star/presentation/ParagraphTarget.hpp>
#include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/util/XCloneable.hpp>
#include <com/sun/star/util/XChangesNotifier.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequence.hxx>
#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/i18n/BreakIterator.hpp>
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/presentation/TextAnimationType.hpp>
 
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
 
#include <algorithm>
#include <deque>
#include <numeric>
 
#include <cppuhelper/implbase.hxx>
 
#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <o3tl/safeint.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdpage.hxx>
#include <CustomAnimationEffect.hxx>
#include <CustomAnimationPreset.hxx>
#include <animations.hxx>
#include <utility>
 
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::presentation;
using namespace ::com::sun::star::animations;
 
using ::com::sun::star::container::XEnumerationAccess;
using ::com::sun::star::container::XEnumeration;
using ::com::sun::star::beans::NamedValue;
using ::com::sun::star::container::XChild;
using ::com::sun::star::drawing::XShape;
using ::com::sun::star::lang::XInitialization;
using ::com::sun::star::text::XText;
using ::com::sun::star::text::XTextRange;
using ::com::sun::star::beans::XPropertySet;
using ::com::sun::star::util::XCloneable;
using ::com::sun::star::lang::Locale;
using ::com::sun::star::util::XChangesNotifier;
using ::com::sun::star::util::XChangesListener;
 
namespace sd
{
class MainSequenceChangeGuard
{
public:
    explicit MainSequenceChangeGuard( EffectSequenceHelper* pSequence )
    {
        mpMainSequence = dynamic_cast< MainSequence* >( pSequence );
        if( mpMainSequence == nullptr )
        {
            InteractiveSequence* pI = dynamic_cast< InteractiveSequence* >( pSequence );
            if( pI )
                mpMainSequence = pI->mpMainSequence;
        }
        DBG_ASSERT( mpMainSequence, "sd::MainSequenceChangeGuard::MainSequenceChangeGuard(), no main sequence to guard!" );
 
        if( mpMainSequence )
            mpMainSequence->mbIgnoreChanges++;
    }
 
    ~MainSequenceChangeGuard()
    {
        if( mpMainSequence )
            mpMainSequence->mbIgnoreChanges++;
    }
 
private:
    MainSequence* mpMainSequence;
};
 
CustomAnimationEffect::CustomAnimationEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
:   mnNodeType(-1),
    mnPresetClass(-1),
    mnFill(AnimationFill::HOLD),
    mfBegin(-1.0),
    mfDuration(-1.0),
    mfAbsoluteDuration(-1.0),
    mnGroupId(-1),
    mnIterateType(0),
    mfIterateInterval(0.0),
    mnParaDepth( -1 ),
    mbHasText(false),
    mfAcceleration( 1.0 ),
    mfDecelerate( 1.0 ),
    mbAutoReverse(false),
    mnTargetSubItem(0),
    mnCommand(0),
    mpEffectSequence( nullptr ),
    mbHasAfterEffect(false),
    mbAfterEffectOnNextEffect(false)
{
    setNode( xNode );
}
 
void CustomAnimationEffect::setNode( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
{
    mxNode = xNode;
    mxAudio.clear();
    mnCommand = 0;
 
    const Sequence< NamedValue > aUserData( mxNode->getUserData() );
 
    for( const NamedValue& rProp : aUserData )
    {
        if ( rProp.Name == "node-type" )
        {
            rProp.Value >>= mnNodeType;
        }
        else if ( rProp.Name == "preset-id" )
        {
            rProp.Value >>= maPresetId;
        }
        else if ( rProp.Name == "preset-sub-type" )
        {
            rProp.Value >>= maPresetSubType;
        }
        else if ( rProp.Name == "preset-class" )
        {
            rProp.Value >>= mnPresetClass;
        }
        else if ( rProp.Name == "preset-property" )
        {
            rProp.Value >>= maProperty;
        }
        else if ( rProp.Name == "group-id" )
        {
            rProp.Value >>= mnGroupId;
        }
    }
 
    // get effect start time
    mxNode->getBegin() >>= mfBegin;
 
    mfAcceleration = mxNode->getAcceleration();
    mfDecelerate = mxNode->getDecelerate();
    mbAutoReverse = mxNode->getAutoReverse();
 
    mnFill = mxNode->getFill();
 
    // get iteration data
    Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
    if( xIter.is() )
    {
        mfIterateInterval = xIter->getIterateInterval();
        mnIterateType = xIter->getIterateType();
        maTarget = xIter->getTarget();
        mnTargetSubItem = xIter->getSubItem();
    }
    else
    {
        mfIterateInterval = 0.0f;
        mnIterateType = 0;
    }
 
    // calculate effect duration and get target shape
    Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
    if( xEnumerationAccess.is() )
    {
        Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
        if( xEnumeration.is() )
        {
            while( xEnumeration->hasMoreElements() )
            {
                Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY );
                if( !xChildNode.is() )
                    continue;
 
                if( xChildNode->getType() == AnimationNodeType::AUDIO )
                {
                    mxAudio.set( xChildNode, UNO_QUERY );
                }
                else if( xChildNode->getType() == AnimationNodeType::COMMAND )
                {
                    Reference< XCommand > xCommand( xChildNode, UNO_QUERY );
                    if( xCommand.is() )
                    {
                        mnCommand = xCommand->getCommand();
                        if( !maTarget.hasValue() )
                            maTarget = xCommand->getTarget();
                    }
                }
                else
                {
                    double fBegin = 0.0;
                    double fDuration = 0.0;
                    xChildNode->getBegin() >>= fBegin;
                    xChildNode->getDuration() >>= fDuration;
 
                    fDuration += fBegin;
                    if( fDuration > mfDuration )
                        mfDuration = fDuration;
 
                    // no target shape yet?
                    if( !maTarget.hasValue() )
                    {
                        // go get it boys!
                        Reference< XAnimate > xAnimate( xChildNode, UNO_QUERY );
                        if( xAnimate.is() )
                        {
                            maTarget = xAnimate->getTarget();
                            mnTargetSubItem = xAnimate->getSubItem();
                        }
                    }
                }
            }
        }
    }
 
    mfAbsoluteDuration = mfDuration;
    double fRepeatCount = 1.0;
    if( (mxNode->getRepeatCount()) >>= fRepeatCount )
        mfAbsoluteDuration *= fRepeatCount;
 
    checkForText();
}
 
sal_Int32 CustomAnimationEffect::getNumberOfSubitems( const Any& aTarget, sal_Int16 nIterateType )
{
    sal_Int32 nSubItems = 0;
 
    try
    {
        // first get target text
        sal_Int32 nOnlyPara = -1;
 
        Reference< XText > xShape;
        aTarget >>= xShape;
        if( !xShape.is() )
        {
            ParagraphTarget aParaTarget;
            if( aTarget >>= aParaTarget )
            {
                xShape.set( aParaTarget.Shape, UNO_QUERY );
                nOnlyPara = aParaTarget.Paragraph;
            }
        }
 
        // now use the break iterator to iterate over the given text
        // and count the sub items
 
        if( xShape.is() )
        {
            // TODO/LATER: Optimize this, don't create a break iterator each time
            const Reference< uno::XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
            Reference < i18n::XBreakIterator > xBI = i18n::BreakIterator::create(xContext);
 
            Reference< XEnumerationAccess > xEA( xShape, UNO_QUERY_THROW );
            Reference< XEnumeration > xEnumeration( xEA->createEnumeration(), UNO_SET_THROW );
            css::lang::Locale aLocale;
            static constexpr OUStringLiteral aStrLocaleName( u"CharLocale" );
            Reference< XTextRange > xParagraph;
 
            sal_Int32 nPara = 0;
            while( xEnumeration->hasMoreElements() )
            {
                xEnumeration->nextElement() >>= xParagraph;
 
                // skip this if it's not the only paragraph we want to count
                if( (nOnlyPara != -1) && (nOnlyPara != nPara ) )
                    continue;
 
                if( nIterateType == TextAnimationType::BY_PARAGRAPH )
                {
                    nSubItems++;
                }
                else
                {
                    const OUString aText( xParagraph->getString() );
                    Reference< XPropertySet > xSet( xParagraph, UNO_QUERY_THROW );
                    xSet->getPropertyValue( aStrLocaleName ) >>= aLocale;
 
                    sal_Int32 nPos;
                    const sal_Int32 nEndPos = aText.getLength();
 
                    if( nIterateType == TextAnimationType::BY_WORD )
                    {
                        for( nPos = 0; nPos < nEndPos; nPos++ )
                        {
                            nPos = xBI->getWordBoundary(aText, nPos, aLocale, i18n::WordType::ANY_WORD, true).endPos;
                            nSubItems++;
                        }
                        break;
                    }
                    else
                    {
                        sal_Int32 nDone;
                        for( nPos = 0; nPos < nEndPos; nPos++ )
                        {
                            nPos = xBI->nextCharacters(aText, nPos, aLocale, i18n::CharacterIteratorMode::SKIPCELL, 0, nDone);
                            nSubItems++;
                        }
                    }
                }
 
                if( nPara == nOnlyPara )
                    break;
 
                nPara++;
            }
        }
    }
    catch( Exception& )
    {
        nSubItems = 0;
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::getNumberOfSubitems(), exception caught!" );
    }
 
    return nSubItems;
}
 
CustomAnimationEffect::~CustomAnimationEffect()
{
}
 
CustomAnimationEffectPtr CustomAnimationEffect::clone() const
{
    Reference< XCloneable > xCloneable( mxNode, UNO_QUERY_THROW );
    Reference< XAnimationNode > xNode( xCloneable->createClone(), UNO_QUERY_THROW );
    CustomAnimationEffectPtr pEffect = std::make_shared<CustomAnimationEffect>( xNode );
    pEffect->setEffectSequence( getEffectSequence() );
    return pEffect;
}
 
sal_Int32 CustomAnimationEffect::get_node_type( const Reference< XAnimationNode >& xNode )
{
    sal_Int16 nNodeType = -1;
 
    if( xNode.is() )
    {
        const Sequence< NamedValue > aUserData( xNode->getUserData() );
        if( aUserData.hasElements() )
        {
            const NamedValue* pProp = std::find_if(aUserData.begin(), aUserData.end(),
                [](const NamedValue& rProp) { return rProp.Name == "node-type"; });
            if (pProp != aUserData.end())
                pProp->Value >>= nNodeType;
        }
    }
 
    return nNodeType;
}
 
void CustomAnimationEffect::setPresetClassAndId( sal_Int16 nPresetClass, const OUString& rPresetId )
{
    if( mnPresetClass == nPresetClass && maPresetId == rPresetId )
        return;
 
    mnPresetClass = nPresetClass;
    maPresetId = rPresetId;
    if( !mxNode.is() )
        return;
 
    // first try to find a "preset-class" entry in the user data
    // and change it
    Sequence< NamedValue > aUserData( mxNode->getUserData() );
    sal_Int32 nLength = aUserData.getLength();
    bool bFoundPresetClass = false;
    bool bFoundPresetId = false;
    if( nLength )
    {
        auto [begin, end] = asNonConstRange(aUserData);
        NamedValue* pProp = std::find_if(begin, end,
            [](const NamedValue& rProp) { return rProp.Name == "preset-class"; });
        if (pProp != end)
        {
            pProp->Value <<= mnPresetClass;
            bFoundPresetClass = true;
        }
 
        pProp = std::find_if(begin, end,
            [](const NamedValue& rProp) { return rProp.Name == "preset-id"; });
        if (pProp != end)
        {
            pProp->Value <<= mnPresetClass;
            bFoundPresetId = true;
        }
    }
 
    // no "preset-class" entry inside user data, so add it
    if( !bFoundPresetClass )
    {
        aUserData.realloc( nLength + 1);
        auto& el = aUserData.getArray()[nLength];
        el.Name = "preset-class";
        el.Value <<= mnPresetClass;
        ++nLength;
    }
 
    if( !bFoundPresetId && maPresetId.getLength() > 0 )
    {
        aUserData.realloc( nLength + 1);
        auto& el = aUserData.getArray()[nLength];
        el.Name = "preset-id";
        el.Value <<= maPresetId;
    }
 
    mxNode->setUserData( aUserData );
}
 
void CustomAnimationEffect::setNodeType( sal_Int16 nNodeType )
{
    if( mnNodeType == nNodeType )
        return;
 
    mnNodeType = nNodeType;
    if( !mxNode.is() )
        return;
 
    // first try to find a "node-type" entry in the user data
    // and change it
    Sequence< NamedValue > aUserData( mxNode->getUserData() );
    sal_Int32 nLength = aUserData.getLength();
    bool bFound = false;
    if( nLength )
    {
        auto [begin, end] = asNonConstRange(aUserData);
        NamedValue* pProp = std::find_if(begin, end,
            [](const NamedValue& rProp) { return rProp.Name == "node-type"; });
        if (pProp != end)
        {
            pProp->Value <<= mnNodeType;
            bFound = true;
        }
    }
 
    // no "node-type" entry inside user data, so add it
    if( !bFound )
    {
        aUserData.realloc( nLength + 1);
        auto& el = aUserData.getArray()[nLength];
        el.Name = "node-type";
        el.Value <<= mnNodeType;
    }
 
    mxNode->setUserData( aUserData );
}
 
void CustomAnimationEffect::setGroupId( sal_Int32 nGroupId )
{
    mnGroupId = nGroupId;
    if( !mxNode.is() )
        return;
 
    // first try to find a "group-id" entry in the user data
    // and change it
    Sequence< NamedValue > aUserData( mxNode->getUserData() );
    sal_Int32 nLength = aUserData.getLength();
    bool bFound = false;
    if( nLength )
    {
        auto [begin, end] = asNonConstRange(aUserData);
        NamedValue* pProp = std::find_if(begin, end,
            [](const NamedValue& rProp) { return rProp.Name == "group-id"; });
        if (pProp != end)
        {
            pProp->Value <<= mnGroupId;
            bFound = true;
        }
    }
 
    // no "group-id" entry inside user data, so add it
    if( !bFound )
    {
        aUserData.realloc( nLength + 1);
        auto& el = aUserData.getArray()[nLength];
        el.Name = "group-id";
        el.Value <<= mnGroupId;
    }
 
    mxNode->setUserData( aUserData );
}
 
/** checks if the text for this effect has changed and updates internal flags.
    returns true if something changed.
*/
bool CustomAnimationEffect::checkForText( const std::vector<sal_Int32>* paragraphNumberingLevel )
{
    bool bChange = false;
 
    Reference< XText > xText;
 
    if( maTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
    {
        // calc para depth
        ParagraphTarget aParaTarget;
        maTarget >>= aParaTarget;
 
        xText.set( aParaTarget.Shape, UNO_QUERY );
 
        // get paragraph
        if( xText.is() )
        {
            sal_Int32 nPara = aParaTarget.Paragraph;
 
            bool bHasText = false;
            sal_Int32 nParaDepth = 0;
 
            if ( paragraphNumberingLevel )
            {
                bHasText = !paragraphNumberingLevel->empty();
                if (nPara >= 0 && o3tl::make_unsigned(nPara) < paragraphNumberingLevel->size())
                    nParaDepth = paragraphNumberingLevel->at(nPara);
            }
            else
            {
                Reference< XEnumerationAccess > xEA( xText, UNO_QUERY );
                if( xEA.is() )
                {
                    Reference< XEnumeration > xEnumeration = xEA->createEnumeration();
                    if( xEnumeration.is() )
                    {
                        bHasText = xEnumeration->hasMoreElements();
 
                        while( xEnumeration->hasMoreElements() && nPara-- )
                            xEnumeration->nextElement();
 
                        if( xEnumeration->hasMoreElements() )
                        {
                            Reference< XPropertySet > xParaSet;
                            xEnumeration->nextElement() >>= xParaSet;
                            if( xParaSet.is() )
                            {
                                xParaSet->getPropertyValue( u"NumberingLevel"_ustr ) >>= nParaDepth;
                            }
                        }
                    }
                }
            }
 
            if( bHasText )
            {
                bChange |= bHasText != mbHasText;
                mbHasText = bHasText;
 
                bChange |= nParaDepth != mnParaDepth;
                mnParaDepth = nParaDepth;
            }
        }
    }
    else
    {
        maTarget >>= xText;
        bool bHasText = xText.is() && !xText->getString().isEmpty();
        bChange |= bHasText != mbHasText;
        mbHasText = bHasText;
    }
 
    bChange |= calculateIterateDuration();
    return bChange;
}
 
bool CustomAnimationEffect::calculateIterateDuration()
{
    bool bChange = false;
 
    // if we have an iteration, we must also calculate the
    // 'true' container duration, that is
    // ( ( is form animated ) ? [contained effects duration] : 0 ) +
    // ( [number of animated children] - 1 ) * [interval-delay] + [contained effects duration]
    Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
    if( xIter.is() )
    {
        double fDuration = mfDuration;
        const double fSubEffectDuration = mfDuration;
 
        if( mnTargetSubItem != ShapeAnimationSubType::ONLY_BACKGROUND ) // does not make sense for iterate container but better check
        {
            const sal_Int32 nSubItems = getNumberOfSubitems( maTarget, mnIterateType );
            if( nSubItems )
            {
                const double f = (nSubItems-1) * mfIterateInterval;
                fDuration += f;
            }
        }
 
        // if we also animate the form first, we have to add the
        // sub effect duration to the whole effect duration
        if( mnTargetSubItem == ShapeAnimationSubType::AS_WHOLE )
            fDuration += fSubEffectDuration;
 
        bChange |= fDuration != mfAbsoluteDuration;
        mfAbsoluteDuration = fDuration;
    }
 
    return bChange;
}
 
void CustomAnimationEffect::setTarget( const css::uno::Any& rTarget )
{
    try
    {
        maTarget = rTarget;
 
        // first, check special case for random node
        Reference< XInitialization > xInit( mxNode, UNO_QUERY );
        if( xInit.is() )
        {
            const Sequence< Any > aArgs( &maTarget, 1 );
            xInit->initialize( aArgs );
        }
        else
        {
            Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
            if( xIter.is() )
            {
                xIter->setTarget(maTarget);
            }
            else
            {
                Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
                if( xEnumerationAccess.is() )
                {
                    Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
                    if( xEnumeration.is() )
                    {
                        while( xEnumeration->hasMoreElements() )
                        {
                            const Any aElem( xEnumeration->nextElement() );
                            Reference< XAnimate > xAnimate( aElem, UNO_QUERY );
                            if( xAnimate.is() )
                                xAnimate->setTarget( rTarget );
                            else
                            {
                                Reference< XCommand > xCommand( aElem, UNO_QUERY );
                                if( xCommand.is() )
                                    xCommand->setTarget( rTarget );
                            }
                        }
                    }
                }
            }
        }
        checkForText();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setTarget()" );
    }
}
 
void CustomAnimationEffect::setTargetSubItem( sal_Int16 nSubItem )
{
    try
    {
        mnTargetSubItem = nSubItem;
 
        Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
        if( xIter.is() )
        {
            xIter->setSubItem(mnTargetSubItem);
        }
        else
        {
            Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
            if( xEnumerationAccess.is() )
            {
                Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
                if( xEnumeration.is() )
                {
                    while( xEnumeration->hasMoreElements() )
                    {
                        Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
                        if( xAnimate.is() )
                            xAnimate->setSubItem( mnTargetSubItem );
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setTargetSubItem()" );
    }
}
 
void CustomAnimationEffect::setDuration( double fDuration )
{
    if( (mfDuration == -1.0) || (mfDuration == fDuration) )
        return;
 
    try
    {
        double fScale = fDuration / mfDuration;
        mfDuration = fDuration;
        double fRepeatCount = 1.0;
        getRepeatCount() >>= fRepeatCount;
        mfAbsoluteDuration = mfDuration * fRepeatCount;
 
        // calculate effect duration and get target shape
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() )
                {
                    Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xChildNode.is() )
                        continue;
 
                    double fChildBegin = 0.0;
                    xChildNode->getBegin() >>= fChildBegin;
                    if(  fChildBegin != 0.0 )
                    {
                        fChildBegin *= fScale;
                        xChildNode->setBegin( Any( fChildBegin ) );
                    }
 
                    double fChildDuration = 0.0;
                    xChildNode->getDuration() >>= fChildDuration;
                    if( fChildDuration != 0.0 )
                    {
                        fChildDuration *= fScale;
                        xChildNode->setDuration( Any( fChildDuration ) );
                    }
                }
            }
        }
        calculateIterateDuration();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setDuration()" );
    }
}
 
void CustomAnimationEffect::setBegin( double fBegin )
{
    if( mxNode.is() ) try
    {
        mfBegin = fBegin;
        mxNode->setBegin( Any( fBegin ) );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setBegin()" );
    }
}
 
void CustomAnimationEffect::setAcceleration( double fAcceleration )
{
    if( mxNode.is() ) try
    {
        mfAcceleration = fAcceleration;
        mxNode->setAcceleration( fAcceleration );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setAcceleration()" );
    }
}
 
void CustomAnimationEffect::setDecelerate( double fDecelerate )
{
    if( mxNode.is() ) try
    {
        mfDecelerate = fDecelerate;
        mxNode->setDecelerate( fDecelerate );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setDecelerate()" );
    }
}
 
void CustomAnimationEffect::setAutoReverse( bool bAutoReverse )
{
    if( mxNode.is() ) try
    {
        mbAutoReverse = bAutoReverse;
        mxNode->setAutoReverse( bAutoReverse );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setAutoReverse()" );
    }
}
 
void CustomAnimationEffect::replaceNode( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
{
    sal_Int16 nNodeType = mnNodeType;
    Any aTarget = maTarget;
 
    sal_Int16 nFill = mnFill;
    double fBegin = mfBegin;
    double fDuration = mfDuration;
    double fAcceleration = mfAcceleration;
    double fDecelerate = mfDecelerate ;
    bool bAutoReverse = mbAutoReverse;
    Reference< XAudio > xAudio( mxAudio );
    sal_Int16 nIterateType = mnIterateType;
    double fIterateInterval = mfIterateInterval;
    sal_Int16 nSubItem = mnTargetSubItem;
 
    setNode( xNode );
 
    setAudio( xAudio );
    setNodeType( nNodeType );
    setTarget( aTarget );
    setTargetSubItem( nSubItem );
    setDuration( fDuration );
    setBegin( fBegin );
    setFill( nFill );
 
    setAcceleration( fAcceleration );
    setDecelerate( fDecelerate );
    setAutoReverse( bAutoReverse );
 
    if( nIterateType != mnIterateType )
        setIterateType( nIterateType );
 
    if( mnIterateType && ( fIterateInterval != mfIterateInterval ) )
        setIterateInterval( fIterateInterval );
}
 
Reference< XShape > CustomAnimationEffect::getTargetShape() const
{
    Reference< XShape > xShape;
    maTarget >>= xShape;
    if( !xShape.is() )
    {
        ParagraphTarget aParaTarget;
        if( maTarget >>= aParaTarget )
            xShape = aParaTarget.Shape;
    }
 
    return xShape;
}
 
Any CustomAnimationEffect::getRepeatCount() const
{
    if( mxNode.is() )
    {
        return mxNode->getRepeatCount();
    }
    else
    {
        Any aAny;
        return aAny;
    }
}
 
Any CustomAnimationEffect::getEnd() const
{
    if( mxNode.is() )
    {
        return mxNode->getEnd();
    }
    else
    {
        Any aAny;
        return aAny;
    }
}
 
void CustomAnimationEffect::setRepeatCount( const Any& rRepeatCount )
{
    if( mxNode.is() )
    {
        mxNode->setRepeatCount( rRepeatCount );
        double fRepeatCount = 1.0;
        rRepeatCount >>= fRepeatCount;
        mfAbsoluteDuration = mfDuration * fRepeatCount;
    }
}
 
void CustomAnimationEffect::setEnd( const Any& rEnd )
{
    if( mxNode.is() )
        mxNode->setEnd( rEnd );
}
 
void CustomAnimationEffect::setFill( sal_Int16 nFill )
{
    if (mxNode.is())
    {
        mnFill = nFill;
        mxNode->setFill( nFill );
    }
}
 
Reference< XAnimationNode > CustomAnimationEffect::createAfterEffectNode() const
{
    DBG_ASSERT( mbHasAfterEffect, "sd::CustomAnimationEffect::createAfterEffectNode(), this node has no after effect!" );
 
    const Reference< XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
 
    Reference< XAnimate > xAnimate;
    if( maDimColor.hasValue() )
        xAnimate = AnimateColor::create( xContext );
    else
        xAnimate = AnimateSet::create( xContext );
 
    Any aTo;
    OUString aAttributeName;
 
    if( maDimColor.hasValue() )
    {
        aTo = maDimColor;
        aAttributeName = "DimColor";
    }
    else
    {
        aTo <<= false;
        aAttributeName = "Visibility";
    }
 
    Any aBegin;
    if( !mbAfterEffectOnNextEffect ) // sameClick
    {
        Event aEvent;
 
        aEvent.Source <<= getNode();
        aEvent.Trigger = EventTrigger::END_EVENT;
        aEvent.Repeat = 0;
 
        aBegin <<= aEvent;
    }
    else
    {
        aBegin <<= 0.0;
    }
 
    xAnimate->setBegin( aBegin );
    xAnimate->setTo( aTo );
    xAnimate->setAttributeName( aAttributeName );
 
    xAnimate->setDuration( Any( 0.001 ) );
    xAnimate->setFill( AnimationFill::HOLD );
    xAnimate->setTarget( maTarget );
 
    return xAnimate;
}
 
void CustomAnimationEffect::setIterateType( sal_Int16 nIterateType )
{
    if( mnIterateType == nIterateType )
        return;
 
    try
    {
        // do we need to exchange the container node?
        if( (mnIterateType == 0) || (nIterateType == 0) )
        {
            sal_Int16 nTargetSubItem = mnTargetSubItem;
 
            const Reference< XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
            Reference< XTimeContainer > xNewContainer;
            if(nIterateType)
            {
                xNewContainer.set( IterateContainer::create( xContext ) );
            }
            else
                xNewContainer.set( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW );
 
            Reference< XTimeContainer > xOldContainer( mxNode, UNO_QUERY_THROW );
            Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
            Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
            while( xEnumeration->hasMoreElements() )
            {
                Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
                xOldContainer->removeChild( xChildNode );
                xNewContainer->appendChild( xChildNode );
            }
 
            xNewContainer->setBegin( mxNode->getBegin() );
            xNewContainer->setDuration( mxNode->getDuration() );
            xNewContainer->setEnd( mxNode->getEnd() );
            xNewContainer->setEndSync( mxNode->getEndSync() );
            xNewContainer->setRepeatCount( mxNode->getRepeatCount() );
            xNewContainer->setFill( mxNode->getFill() );
            xNewContainer->setFillDefault( mxNode->getFillDefault() );
            xNewContainer->setRestart( mxNode->getRestart() );
            xNewContainer->setRestartDefault( mxNode->getRestartDefault() );
            xNewContainer->setAcceleration( mxNode->getAcceleration() );
            xNewContainer->setDecelerate( mxNode->getDecelerate() );
            xNewContainer->setAutoReverse( mxNode->getAutoReverse() );
            xNewContainer->setRepeatDuration( mxNode->getRepeatDuration() );
            xNewContainer->setEndSync( mxNode->getEndSync() );
            xNewContainer->setRepeatCount( mxNode->getRepeatCount() );
            xNewContainer->setUserData( mxNode->getUserData() );
 
            mxNode = xNewContainer;
 
            Any aTarget;
            if( nIterateType )
            {
                Reference< XIterateContainer > xIter( mxNode, UNO_QUERY_THROW );
                xIter->setTarget(maTarget);
                xIter->setSubItem( nTargetSubItem );
            }
            else
            {
                aTarget = maTarget;
            }
 
            Reference< XEnumerationAccess > xEA( mxNode, UNO_QUERY_THROW );
            Reference< XEnumeration > xE( xEA->createEnumeration(), UNO_SET_THROW );
            while( xE->hasMoreElements() )
            {
                Reference< XAnimate > xAnimate( xE->nextElement(), UNO_QUERY );
                if( xAnimate.is() )
                {
                    xAnimate->setTarget( aTarget );
                    xAnimate->setSubItem( nTargetSubItem );
                }
            }
        }
 
        mnIterateType = nIterateType;
 
        // if we have an iteration container, we must set its type
        if( mnIterateType )
        {
            Reference< XIterateContainer > xIter( mxNode, UNO_QUERY_THROW );
            xIter->setIterateType( nIterateType );
        }
 
        checkForText();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setIterateType()" );
    }
}
 
void CustomAnimationEffect::setIterateInterval( double fIterateInterval )
{
    if( mfIterateInterval == fIterateInterval )
        return;
 
    Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
 
    DBG_ASSERT( xIter.is(), "sd::CustomAnimationEffect::setIterateInterval(), not an iteration node" );
    if( xIter.is() )
    {
        mfIterateInterval = fIterateInterval;
        xIter->setIterateInterval( fIterateInterval );
    }
 
    calculateIterateDuration();
}
 
OUString CustomAnimationEffect::getPath() const
{
    OUString aPath;
 
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimateMotion > xMotion( xEnumeration->nextElement(), UNO_QUERY );
            if( xMotion.is() )
            {
                xMotion->getPath() >>= aPath;
                break;
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::getPath()" );
    }
 
    return aPath;
}
 
void CustomAnimationEffect::setPath( const OUString& rPath )
{
    if( !mxNode.is() )
        return;
 
    try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimateMotion > xMotion( xEnumeration->nextElement(), UNO_QUERY );
            if( xMotion.is() )
            {
 
                MainSequenceChangeGuard aGuard( mpEffectSequence );
                xMotion->setPath( Any( rPath ) );
                break;
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setPath()" );
    }
}
 
Any CustomAnimationEffect::getProperty( sal_Int32 nNodeType, std::u16string_view rAttributeName, EValue eValue )
{
    Any aProperty;
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() && !aProperty.hasValue() )
                {
                    Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xAnimate.is() )
                        continue;
 
                    if( xAnimate->getType() == nNodeType )
                    {
                        if( xAnimate->getAttributeName() == rAttributeName )
                        {
                            switch( eValue )
                            {
                            case EValue::To:   aProperty = xAnimate->getTo(); break;
                            case EValue::By:   aProperty = xAnimate->getBy(); break;
                            }
                        }
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::getProperty()" );
    }
 
    return aProperty;
}
 
bool CustomAnimationEffect::setProperty( sal_Int32 nNodeType, std::u16string_view rAttributeName, EValue eValue, const Any& rValue )
{
    bool bChanged = false;
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() )
                {
                    Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xAnimate.is() )
                        continue;
 
                    if( xAnimate->getType() == nNodeType )
                    {
                        if( xAnimate->getAttributeName() == rAttributeName )
                        {
                            switch( eValue )
                            {
                            case EValue::To:
                                if( xAnimate->getTo() != rValue )
                                {
                                    xAnimate->setTo( rValue );
                                    bChanged = true;
                                }
                                break;
                            case EValue::By:
                                if( xAnimate->getTo() != rValue )
                                {
                                    xAnimate->setBy( rValue );
                                    bChanged = true;
                                }
                                break;
                            }
                        }
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setProperty()" );
    }
 
    return bChanged;
}
 
static bool implIsColorAttribute( std::u16string_view rAttributeName )
{
    return rAttributeName == u"FillColor" || rAttributeName == u"LineColor" || rAttributeName == u"CharColor";
}
 
Any CustomAnimationEffect::getColor( sal_Int32 nIndex )
{
    Any aColor;
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() && !aColor.hasValue() )
                {
                    Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xAnimate.is() )
                        continue;
 
                    switch( xAnimate->getType() )
                    {
                    case AnimationNodeType::SET:
                    case AnimationNodeType::ANIMATE:
                        if( !implIsColorAttribute( xAnimate->getAttributeName() ) )
                            break;
                        [[fallthrough]];
                    case AnimationNodeType::ANIMATECOLOR:
                        Sequence<Any> aValues( xAnimate->getValues() );
                        if( aValues.hasElements() )
                        {
                            if( aValues.getLength() > nIndex )
                                aColor = aValues[nIndex];
                        }
                        else if( nIndex == 0 )
                            aColor = xAnimate->getFrom();
                        else
                            aColor = xAnimate->getTo();
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::getColor()" );
    }
 
    return aColor;
}
 
void CustomAnimationEffect::setColor( sal_Int32 nIndex, const Any& rColor )
{
    if( !mxNode.is() )
        return;
 
    try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() )
                {
                    Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xAnimate.is() )
                        continue;
 
                    switch( xAnimate->getType() )
                    {
                    case AnimationNodeType::SET:
                    case AnimationNodeType::ANIMATE:
                        if( !implIsColorAttribute( xAnimate->getAttributeName() ) )
                            break;
                        [[fallthrough]];
                    case AnimationNodeType::ANIMATECOLOR:
                    {
                        Sequence<Any> aValues( xAnimate->getValues() );
                        if( aValues.hasElements() )
                        {
                            if( aValues.getLength() > nIndex )
                            {
                                aValues.getArray()[nIndex] = rColor;
                                xAnimate->setValues( aValues );
                            }
                        }
                        else if( (nIndex == 0) && xAnimate->getFrom().hasValue() )
                            xAnimate->setFrom(rColor);
                        else if( (nIndex == 1) && xAnimate->getTo().hasValue() )
                            xAnimate->setTo(rColor);
                    }
                    break;
 
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setColor()" );
    }
}
 
Any CustomAnimationEffect::getTransformationProperty( sal_Int32 nTransformType, EValue eValue )
{
    Any aProperty;
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() && !aProperty.hasValue() )
                {
                    Reference< XAnimateTransform > xTransform( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xTransform.is() )
                        continue;
 
                    if( xTransform->getTransformType() == nTransformType )
                    {
                        switch( eValue )
                        {
                        case EValue::To:   aProperty = xTransform->getTo(); break;
                        case EValue::By:   aProperty = xTransform->getBy(); break;
                        }
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::getTransformationProperty()" );
    }
 
    return aProperty;
}
 
bool CustomAnimationEffect::setTransformationProperty( sal_Int32 nTransformType, EValue eValue, const Any& rValue )
{
    bool bChanged = false;
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() )
                {
                    Reference< XAnimateTransform > xTransform( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xTransform.is() )
                        continue;
 
                    if( xTransform->getTransformType() == nTransformType )
                    {
                        switch( eValue )
                        {
                        case EValue::To:
                            if( xTransform->getTo() != rValue )
                            {
                                xTransform->setTo( rValue );
                                bChanged = true;
                            }
                            break;
                        case EValue::By:
                            if( xTransform->getBy() != rValue )
                            {
                                xTransform->setBy( rValue );
                                bChanged = true;
                            }
                            break;
                        }
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setTransformationProperty()" );
    }
 
    return bChanged;
}
 
void CustomAnimationEffect::createAudio( const css::uno::Any& rSource )
{
    DBG_ASSERT( !mxAudio.is(), "sd::CustomAnimationEffect::createAudio(), node already has an audio!" );
 
    if( mxAudio.is() )
        return;
 
    try
    {
        const Reference< XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
        Reference< XAudio > xAudio( Audio::create( xContext ) );
        xAudio->setSource( rSource );
        xAudio->setVolume( 1.0 );
        setAudio( xAudio );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::createAudio()" );
    }
}
 
static Reference< XCommand > findCommandNode( const Reference< XAnimationNode >& xRootNode )
{
    Reference< XCommand > xCommand;
 
    if( xRootNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( xRootNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( !xCommand.is() && xEnumeration->hasMoreElements() )
        {
            Reference< XAnimationNode > xNode( xEnumeration->nextElement(), UNO_QUERY );
            if( xNode.is() && (xNode->getType() == AnimationNodeType::COMMAND) )
                xCommand.set( xNode, UNO_QUERY_THROW );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::findCommandNode()" );
    }
 
    return xCommand;
}
 
void CustomAnimationEffect::removeAudio()
{
    try
    {
        Reference< XAnimationNode > xChild;
 
        if( mxAudio.is() )
        {
            xChild = mxAudio;
            mxAudio.clear();
        }
        else if( mnCommand == EffectCommands::STOPAUDIO )
        {
            xChild = findCommandNode( mxNode );
            mnCommand = 0;
        }
 
        if( xChild.is() )
        {
            Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
            if( xContainer.is() )
                xContainer->removeChild( xChild );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::removeAudio()" );
    }
 
}
 
void CustomAnimationEffect::setAudio( const Reference< css::animations::XAudio >& xAudio )
{
    if( mxAudio == xAudio )
        return;
 
    try
    {
        removeAudio();
        mxAudio = xAudio;
        Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
        if( xContainer.is() && mxAudio.is() )
            xContainer->appendChild( mxAudio );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setAudio()" );
    }
}
 
void CustomAnimationEffect::setStopAudio()
{
    if( mnCommand == EffectCommands::STOPAUDIO )
        return;
 
    try
    {
        if( mxAudio.is() )
            removeAudio();
 
        const Reference< XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
        Reference< XCommand > xCommand( Command::create( xContext ) );
 
        xCommand->setCommand( EffectCommands::STOPAUDIO );
 
        Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY_THROW );
        xContainer->appendChild( xCommand );
 
        mnCommand = EffectCommands::STOPAUDIO;
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::CustomAnimationEffect::setStopAudio()" );
    }
}
 
bool CustomAnimationEffect::getStopAudio() const
{
    return mnCommand == EffectCommands::STOPAUDIO;
}
 
rtl::Reference<SdrPathObj> CustomAnimationEffect::createSdrPathObjFromPath(SdrModel& rTargetModel)
{
    rtl::Reference<SdrPathObj> pPathObj = new SdrPathObj(rTargetModel, SdrObjKind::PathLine);
    updateSdrPathObjFromPath( *pPathObj );
    return pPathObj;
}
 
void CustomAnimationEffect::updateSdrPathObjFromPath( SdrPathObj& rPathObj )
{
    ::basegfx::B2DPolyPolygon aPolyPoly;
    if( ::basegfx::utils::importFromSvgD( aPolyPoly, getPath(), true, nullptr ) )
    {
        SdrObject* pObj = SdrObject::getSdrObjectFromXShape(getTargetShape());
        if( pObj )
        {
            SdrPage* pPage = pObj->getSdrPageFromSdrObject();
            if( pPage )
            {
                const Size aPageSize( pPage->GetSize() );
                aPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(static_cast<double>(aPageSize.Width()), static_cast<double>(aPageSize.Height())));
            }
 
            const ::tools::Rectangle aBoundRect( pObj->GetCurrentBoundRect() );
            const Point aCenter( aBoundRect.Center() );
            aPolyPoly.transform(basegfx::utils::createTranslateB2DHomMatrix(aCenter.X(), aCenter.Y()));
        }
    }
 
    rPathObj.SetPathPoly( aPolyPoly );
}
 
void CustomAnimationEffect::updatePathFromSdrPathObj( const SdrPathObj& rPathObj )
{
    ::basegfx::B2DPolyPolygon aPolyPoly( rPathObj.GetPathPoly() );
 
    SdrObject* pObj = SdrObject::getSdrObjectFromXShape(getTargetShape());
    if( pObj )
    {
        ::tools::Rectangle aBoundRect(0,0,0,0);
 
        drawinglayer::primitive2d::Primitive2DContainer xPrimitives;
        pObj->GetViewContact().getViewIndependentPrimitive2DContainer(xPrimitives);
        const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
        const basegfx::B2DRange aRange(xPrimitives.getB2DRange(aViewInformation2D));
 
        if(!aRange.isEmpty())
        {
            aBoundRect = ::tools::Rectangle(
                    static_cast<sal_Int32>(floor(aRange.getMinX())), static_cast<sal_Int32>(floor(aRange.getMinY())),
                    static_cast<sal_Int32>(ceil(aRange.getMaxX())), static_cast<sal_Int32>(ceil(aRange.getMaxY())));
        }
 
        const Point aCenter( aBoundRect.Center() );
 
        aPolyPoly.transform(basegfx::utils::createTranslateB2DHomMatrix(-aCenter.X(), -aCenter.Y()));
 
        SdrPage* pPage = pObj->getSdrPageFromSdrObject();
        if( pPage )
        {
            const Size aPageSize( pPage->GetSize() );
            aPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(
                1.0 / static_cast<double>(aPageSize.Width()), 1.0 / static_cast<double>(aPageSize.Height())));
        }
    }
 
    setPath( ::basegfx::utils::exportToSvgD( aPolyPoly, true, true, true) );
}
 
EffectSequenceHelper::EffectSequenceHelper()
: mnSequenceType( EffectNodeType::DEFAULT )
{
}
 
EffectSequenceHelper::EffectSequenceHelper( css::uno::Reference< css::animations::XTimeContainer > xSequenceRoot )
: mxSequenceRoot(std::move( xSequenceRoot )), mnSequenceType( EffectNodeType::DEFAULT )
{
    Reference< XAnimationNode > xNode( mxSequenceRoot, UNO_QUERY_THROW );
    create( xNode );
}
 
EffectSequenceHelper::~EffectSequenceHelper()
{
    reset();
}
 
void EffectSequenceHelper::reset()
{
    for( CustomAnimationEffectPtr& pEffect : maEffects )
    {
        pEffect->setEffectSequence(nullptr);
    }
    maEffects.clear();
}
 
Reference< XAnimationNode > EffectSequenceHelper::getRootNode()
{
    return mxSequenceRoot;
}
 
void EffectSequenceHelper::append( const CustomAnimationEffectPtr& pEffect )
{
    pEffect->setEffectSequence( this );
    maEffects.push_back(pEffect);
    rebuild();
}
 
CustomAnimationEffectPtr EffectSequenceHelper::append( const CustomAnimationPresetPtr& pPreset, const Any& rTarget, double fDuration /* = -1.0 */ )
{
    CustomAnimationEffectPtr pEffect;
 
    if( pPreset )
    {
        Reference< XAnimationNode > xNode( pPreset->create( u""_ustr ) );
        if( xNode.is() )
        {
            // first, filter all only ui relevant user data
            std::vector< NamedValue > aNewUserData;
            Sequence< NamedValue > aUserData( xNode->getUserData() );
 
            std::copy_if(std::cbegin(aUserData), std::cend(aUserData), std::back_inserter(aNewUserData),
                [](const NamedValue& rProp) { return rProp.Name != "text-only" && rProp.Name != "preset-property"; });
 
            if( !aNewUserData.empty() )
            {
                aUserData = ::comphelper::containerToSequence( aNewUserData );
                xNode->setUserData( aUserData );
            }
 
            // check target, maybe we need to force it to text
            sal_Int16 nSubItem = ShapeAnimationSubType::AS_WHOLE;
 
            if( rTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
            {
                nSubItem = ShapeAnimationSubType::ONLY_TEXT;
            }
            else if( pPreset->isTextOnly() )
            {
                Reference< XShape > xShape;
                rTarget >>= xShape;
                if( xShape.is() )
                {
                    // that's bad, we target a shape here but the effect is only for text
                    // so change subitem
                    nSubItem = ShapeAnimationSubType::ONLY_TEXT;
                }
            }
 
            // now create effect from preset
            pEffect = std::make_shared<CustomAnimationEffect>( xNode );
            pEffect->setEffectSequence( this );
            pEffect->setTarget( rTarget );
            pEffect->setTargetSubItem( nSubItem );
            if( fDuration != -1.0 )
                pEffect->setDuration( fDuration );
 
            maEffects.push_back(pEffect);
 
            rebuild();
        }
    }
 
    DBG_ASSERT( pEffect, "sd::EffectSequenceHelper::append(), failed!" );
    return pEffect;
}
 
CustomAnimationEffectPtr EffectSequenceHelper::append( const SdrPathObj& rPathObj, const Any& rTarget, double fDuration /* = -1.0 */, const OUString& rPresetId )
{
    CustomAnimationEffectPtr pEffect;
 
    if( fDuration <= 0.0 )
        fDuration = 2.0;
 
    try
    {
        Reference< XTimeContainer > xEffectContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
        Reference< XAnimationNode > xAnimateMotion( AnimateMotion::create( ::comphelper::getProcessComponentContext() ) );
 
        xAnimateMotion->setDuration( Any( fDuration ) );
        xAnimateMotion->setFill( AnimationFill::HOLD );
        xEffectContainer->appendChild( xAnimateMotion );
 
        sal_Int16 nSubItem = ShapeAnimationSubType::AS_WHOLE;
 
        if( rTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
            nSubItem = ShapeAnimationSubType::ONLY_TEXT;
 
        pEffect = std::make_shared<CustomAnimationEffect>( xEffectContainer );
        pEffect->setEffectSequence( this );
        pEffect->setTarget( rTarget );
        pEffect->setTargetSubItem( nSubItem );
        pEffect->setNodeType( css::presentation::EffectNodeType::ON_CLICK );
        pEffect->setPresetClassAndId( css::presentation::EffectPresetClass::MOTIONPATH, rPresetId );
        pEffect->setAcceleration( 0.5 );
        pEffect->setDecelerate( 0.5 );
        pEffect->setFill( AnimationFill::HOLD );
        pEffect->setBegin( 0.0 );
        pEffect->updatePathFromSdrPathObj( rPathObj );
        if( fDuration != -1.0 )
            pEffect->setDuration( fDuration );
 
        maEffects.push_back(pEffect);
 
        rebuild();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::append()" );
    }
 
    return pEffect;
}
 
void EffectSequenceHelper::replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pPreset, const OUString& rPresetSubType, double fDuration /* = -1.0 */ )
{
    if( !(pEffect && pPreset) )
        return;
 
    try
    {
        Reference< XAnimationNode > xNewNode( pPreset->create( rPresetSubType ) );
        if( xNewNode.is() )
        {
            pEffect->replaceNode( xNewNode );
            if( fDuration != -1.0 )
                pEffect->setDuration( fDuration );
        }
 
        rebuild();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::replace()" );
    }
}
 
void EffectSequenceHelper::replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pPreset, double fDuration /* = -1.0 */ )
{
    replace( pEffect, pPreset, u""_ustr, fDuration );
}
 
void EffectSequenceHelper::remove( const CustomAnimationEffectPtr& pEffect )
{
    if( pEffect )
    {
        pEffect->setEffectSequence( nullptr );
        maEffects.remove( pEffect );
    }
 
    rebuild();
}
 
void EffectSequenceHelper::moveToBeforeEffect( const CustomAnimationEffectPtr& pEffect, const CustomAnimationEffectPtr& pInsertBefore)
{
    if ( pEffect )
    {
        maEffects.remove( pEffect );
        EffectSequence::iterator aInsertIter( find( pInsertBefore ) );
 
        // aInsertIter being end() is OK: pInsertBefore could be null, so put at end.
        maEffects.insert( aInsertIter, pEffect );
 
        rebuild();
    }
}
 
void EffectSequenceHelper::rebuild()
{
    implRebuild();
}
 
void EffectSequenceHelper::implRebuild()
{
    try
    {
        // first we delete all time containers on the first two levels
        Reference< XEnumerationAccess > xEnumerationAccess( mxSequenceRoot, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
            Reference< XTimeContainer > xChildContainer( xChildNode, UNO_QUERY_THROW );
 
            Reference< XEnumerationAccess > xChildEnumerationAccess( xChildNode, UNO_QUERY_THROW );
            Reference< XEnumeration > xChildEnumeration( xChildEnumerationAccess->createEnumeration(), UNO_SET_THROW );
            while( xChildEnumeration->hasMoreElements() )
            {
                Reference< XAnimationNode > xNode( xChildEnumeration->nextElement(), UNO_QUERY_THROW );
                xChildContainer->removeChild( xNode );
            }
 
            mxSequenceRoot->removeChild( xChildNode );
        }
 
        // second, rebuild main sequence
        EffectSequence::iterator aIter( maEffects.begin() );
        EffectSequence::iterator aEnd( maEffects.end() );
        if( aIter != aEnd )
        {
            std::vector< sd::AfterEffectNode > aAfterEffects;
 
            CustomAnimationEffectPtr pEffect = *aIter++;
 
            bool bFirst = true;
            do
            {
                // create a par container for the next click node and all following with and after effects
                Reference< XTimeContainer > xOnClickContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
 
                Event aEvent;
                if( mxEventSource.is() )
                {
                    aEvent.Source <<= mxEventSource;
                    aEvent.Trigger = EventTrigger::ON_CLICK;
                }
                else
                {
                    aEvent.Trigger = EventTrigger::ON_NEXT;
                }
                aEvent.Repeat = 0;
 
                Any aBegin( aEvent );
                if( bFirst )
                {
                    // if the first node is not a click action, this click container
                    // must not have INDEFINITE begin but start at 0s
                    bFirst = false;
                    if( pEffect->getNodeType() != EffectNodeType::ON_CLICK )
                        aBegin <<= 0.0;
                }
 
                xOnClickContainer->setBegin( aBegin );
 
                mxSequenceRoot->appendChild( xOnClickContainer );
 
                double fBegin = 0.0;
 
                do
                {
                    // create a par container for the current click or after effect node and all following with effects
                    Reference< XTimeContainer > xWithContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
                    xWithContainer->setBegin( Any( fBegin ) );
                    xOnClickContainer->appendChild( xWithContainer );
 
                    double fDuration = 0.0;
                    do
                    {
                        Reference< XAnimationNode > xEffectNode( pEffect->getNode() );
                        xWithContainer->appendChild( xEffectNode );
 
                        if( pEffect->hasAfterEffect() )
                        {
                            Reference< XAnimationNode > xAfterEffect( pEffect->createAfterEffectNode() );
                            AfterEffectNode a( xAfterEffect, xEffectNode, pEffect->IsAfterEffectOnNext() );
                            aAfterEffects.push_back( a );
                        }
 
                        double fTemp = pEffect->getBegin() + pEffect->getAbsoluteDuration();
                        if( fTemp > fDuration )
                            fDuration = fTemp;
 
                        if( aIter != aEnd )
                            pEffect = *aIter++;
                        else
                            pEffect.reset();
                    }
                    while( pEffect && (pEffect->getNodeType() == EffectNodeType::WITH_PREVIOUS) );
 
                    fBegin += fDuration;
                }
                while( pEffect && (pEffect->getNodeType() != EffectNodeType::ON_CLICK) );
            }
            while( pEffect );
 
            // process after effect nodes
            std::for_each( aAfterEffects.begin(), aAfterEffects.end(), stl_process_after_effect_node_func );
 
            updateTextGroups();
 
            // reset duration, might have been altered (see below)
            mxSequenceRoot->setDuration( Any() );
        }
        else
        {
            // empty sequence, set duration to 0.0 explicitly
            // (otherwise, this sequence will never end)
            mxSequenceRoot->setDuration( Any(0.0) );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::rebuild()" );
    }
}
 
stl_CustomAnimationEffect_search_node_predict::stl_CustomAnimationEffect_search_node_predict( const css::uno::Reference< css::animations::XAnimationNode >& xSearchNode )
: mxSearchNode( xSearchNode )
{
}
 
bool stl_CustomAnimationEffect_search_node_predict::operator()( const CustomAnimationEffectPtr& pEffect ) const
{
    return pEffect->getNode() == mxSearchNode;
}
 
/// @throws Exception
static bool implFindNextContainer( Reference< XTimeContainer > const & xParent, Reference< XTimeContainer > const & xCurrent, Reference< XTimeContainer >& xNext )
{
    Reference< XEnumerationAccess > xEnumerationAccess( xParent, UNO_QUERY_THROW );
    Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration() );
    if( xEnumeration.is() )
    {
        Reference< XInterface > x;
        while( xEnumeration->hasMoreElements() && !xNext.is() )
        {
            if( (xEnumeration->nextElement() >>= x) && (x == xCurrent) )
            {
                if( xEnumeration->hasMoreElements() )
                    xEnumeration->nextElement() >>= xNext;
            }
        }
    }
    return xNext.is();
}
 
void stl_process_after_effect_node_func(AfterEffectNode const & rNode)
{
    try
    {
        if( rNode.mxNode.is() && rNode.mxMaster.is() )
        {
            // set master node
            Reference< XAnimationNode > xMasterNode( rNode.mxMaster, UNO_SET_THROW );
            Sequence< NamedValue > aUserData( rNode.mxNode->getUserData() );
            sal_Int32 nSize = aUserData.getLength();
            aUserData.realloc(nSize+1);
            auto pUserData = aUserData.getArray();
            pUserData[nSize].Name = "master-element";
            pUserData[nSize].Value <<= xMasterNode;
            rNode.mxNode->setUserData( aUserData );
 
            // insert after effect node into timeline
            Reference< XTimeContainer > xContainer( rNode.mxMaster->getParent(), UNO_QUERY_THROW );
 
            if( !rNode.mbOnNextEffect ) // sameClick
            {
                // insert the aftereffect after its effect is animated
                xContainer->insertAfter( rNode.mxNode, rNode.mxMaster );
            }
            else // nextClick
            {
                const Reference< XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
                // insert the aftereffect in the next group
 
                Reference< XTimeContainer > xClickContainer( xContainer->getParent(), UNO_QUERY_THROW );
                Reference< XTimeContainer > xSequenceContainer( xClickContainer->getParent(), UNO_QUERY_THROW );
 
                Reference< XTimeContainer > xNextContainer;
 
                // first try if we have an after effect container
                if( !implFindNextContainer( xClickContainer, xContainer, xNextContainer ) )
                {
                    Reference< XTimeContainer > xNextClickContainer;
                    // if not, try to find the next click effect container
                    if( implFindNextContainer( xSequenceContainer, xClickContainer, xNextClickContainer ) )
                    {
                        Reference< XEnumerationAccess > xEnumerationAccess( xNextClickContainer, UNO_QUERY_THROW );
                        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
                        if( xEnumeration->hasMoreElements() )
                        {
                            // the next container is the first child container
                            xEnumeration->nextElement() >>= xNextContainer;
                        }
                        else
                        {
                            // this does not yet have a child container, create one
                            xNextContainer.set( ParallelTimeContainer::create(xContext), UNO_QUERY_THROW );
 
                            xNextContainer->setBegin( Any( 0.0 ) );
                            xNextClickContainer->appendChild( xNextContainer );
                        }
                        DBG_ASSERT( xNextContainer.is(), "ppt::stl_process_after_effect_node_func::operator(), could not find/create container!" );
                    }
                }
 
                // if we don't have a next container, we add one to the sequence container
                if( !xNextContainer.is() )
                {
                    Reference< XTimeContainer > xNewClickContainer( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW );
 
                    Event aEvent;
                    aEvent.Trigger = EventTrigger::ON_NEXT;
                    aEvent.Repeat = 0;
                    xNewClickContainer->setBegin( Any( aEvent ) );
 
                    xSequenceContainer->insertAfter( xNewClickContainer, xClickContainer );
 
                    xNextContainer.set( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW );
 
                    xNextContainer->setBegin( Any( 0.0 ) );
                    xNewClickContainer->appendChild( xNextContainer );
                }
 
                if( xNextContainer.is() )
                {
                    // find begin time of first element
                    Reference< XEnumerationAccess > xEnumerationAccess( xNextContainer, UNO_QUERY_THROW );
                    Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
                    if( xEnumeration->hasMoreElements() )
                    {
                        Reference< XAnimationNode > xChild;
                        // the next container is the first child container
                        xEnumeration->nextElement() >>= xChild;
                        if( xChild.is() )
                        {
                            Any aBegin( xChild->getBegin() );
                            double fBegin = 0.0;
                            if( (aBegin >>= fBegin) && (fBegin >= 0.0))
                                rNode.mxNode->setBegin( aBegin );
                        }
                    }
 
                    xNextContainer->appendChild( rNode.mxNode );
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "ppt::stl_process_after_effect_node_func::operator()" );
    }
}
 
EffectSequence::iterator EffectSequenceHelper::find( const CustomAnimationEffectPtr& pEffect )
{
    return std::find( maEffects.begin(), maEffects.end(), pEffect );
}
 
CustomAnimationEffectPtr EffectSequenceHelper::findEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) const
{
    CustomAnimationEffectPtr pEffect;
 
    EffectSequence::const_iterator aIter = std::find_if(maEffects.begin(), maEffects.end(),
        [&xNode](const CustomAnimationEffectPtr& rxEffect) { return rxEffect->getNode() == xNode; });
    if (aIter != maEffects.end())
        pEffect = *aIter;
 
    return pEffect;
}
 
sal_Int32 EffectSequenceHelper::getOffsetFromEffect( const CustomAnimationEffectPtr& xEffect ) const
{
    auto aIter = std::find(maEffects.begin(), maEffects.end(), xEffect);
    if (aIter != maEffects.end())
        return static_cast<sal_Int32>(std::distance(maEffects.begin(), aIter));
 
    return -1;
}
 
CustomAnimationEffectPtr EffectSequenceHelper::getEffectFromOffset( sal_Int32 nOffset ) const
{
    EffectSequence::const_iterator aIter( maEffects.begin() );
    nOffset = std::min(nOffset, static_cast<sal_Int32>(maEffects.size()));
    std::advance(aIter, nOffset);
 
    CustomAnimationEffectPtr pEffect;
    if( aIter != maEffects.end() )
        pEffect = *aIter;
 
    return pEffect;
}
 
bool EffectSequenceHelper::disposeShape( const Reference< XShape >& xShape )
{
    bool bChanges = false;
 
    EffectSequence::iterator aIter( maEffects.begin() );
    while( aIter != maEffects.end() )
    {
        if( (*aIter)->getTargetShape() == xShape )
        {
            (*aIter)->setEffectSequence( nullptr );
            bChanges = true;
            aIter = maEffects.erase( aIter );
        }
        else
        {
            ++aIter;
        }
    }
 
    return bChanges;
}
 
bool EffectSequenceHelper::hasEffect( const css::uno::Reference< css::drawing::XShape >& xShape )
{
    return std::any_of(maEffects.begin(), maEffects.end(),
        [&xShape](const CustomAnimationEffectPtr& rxEffect) { return rxEffect->getTargetShape() == xShape; });
}
 
bool EffectSequenceHelper::getParagraphNumberingLevels( const Reference< XShape >& xShape, std::vector< sal_Int32 >& rParagraphNumberingLevel )
{
    rParagraphNumberingLevel.clear();
 
    if( !hasEffect( xShape ) )
        return false;
 
    Reference< XText > xText( xShape, UNO_QUERY );
    if( xText.is() )
    {
        Reference< XEnumerationAccess > xEA( xText, UNO_QUERY );
        if( xEA.is() )
        {
            Reference< XEnumeration > xEnumeration = xEA->createEnumeration();
 
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() )
                {
                    Reference< XPropertySet > xParaSet;
                    xEnumeration->nextElement() >>= xParaSet;
 
                    sal_Int32 nParaDepth = 0;
                    if( xParaSet.is() )
                    {
                        xParaSet->getPropertyValue( u"NumberingLevel"_ustr ) >>= nParaDepth;
                    }
 
                    rParagraphNumberingLevel.push_back( nParaDepth );
                }
            }
        }
    }
 
    return true;
}
 
void EffectSequenceHelper::insertTextRange( const css::uno::Any& aTarget )
{
    ParagraphTarget aParaTarget;
    if( !(aTarget >>= aParaTarget ) )
        return;
 
    // get map [paragraph index] -> [NumberingLevel]
    // for following reusage inside all animation effects
    std::vector< sal_Int32 > paragraphNumberingLevel;
    std::vector< sal_Int32 >* paragraphNumberingLevelParam = nullptr;
    if ( getParagraphNumberingLevels( aParaTarget.Shape, paragraphNumberingLevel ) )
        paragraphNumberingLevelParam = &paragraphNumberingLevel;
 
    // update internal flags for each animation effect
    const bool bChanges = std::accumulate(maEffects.begin(), maEffects.end(), false,
        [&aParaTarget, &paragraphNumberingLevelParam](const bool bCheck, const CustomAnimationEffectPtr& rxEffect) {
            bool bRes = bCheck;
            if (rxEffect->getTargetShape() == aParaTarget.Shape)
                bRes |= rxEffect->checkForText( paragraphNumberingLevelParam );
            return bRes;
        });
 
    if( bChanges )
        rebuild();
}
 
static bool isParagraphTargetTextEmpty( ParagraphTarget aParaTarget )
{
    // get paragraph
    Reference< XText > xText ( aParaTarget.Shape, UNO_QUERY );
    if( xText.is() )
    {
        Reference< XEnumerationAccess > xEA( xText, UNO_QUERY );
        if( xEA.is() )
        {
            Reference< XEnumeration > xEnumeration = xEA->createEnumeration();
            if( xEnumeration.is() )
            {
                // advance to the Nth paragraph
                sal_Int32 nPara = aParaTarget.Paragraph;
                while( xEnumeration->hasMoreElements() && nPara-- )
                    xEnumeration->nextElement();
 
                // get Nth paragraph's text and check if it's empty
                if( xEnumeration->hasMoreElements() )
                {
                    Reference< XTextRange > xRange( xEnumeration->nextElement(), UNO_QUERY );
                    if( xRange.is() )
                    {
                        OUString text = xRange->getString();
                        return text.isEmpty();
                    }
                }
            }
        }
    }
    return false;
}
 
void EffectSequenceHelper::disposeTextRange( const css::uno::Any& aTarget )
{
    ParagraphTarget aParaTarget;
    if( !(aTarget >>= aParaTarget ) )
        return;
 
    bool bChanges = false;
 
    // building list of effects for target shape; process effects not on target shape
    EffectSequence aTargetParagraphEffects;
    for( const auto &pEffect : maEffects )
    {
        Any aIterTarget( pEffect->getTarget() );
        if( aIterTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
        {
            ParagraphTarget aIterParaTarget;
            if( (aIterTarget >>= aIterParaTarget) && (aIterParaTarget.Shape == aParaTarget.Shape) )
            {
                aTargetParagraphEffects.push_back(pEffect);
            }
        }
        else if( pEffect->getTargetShape() == aParaTarget.Shape )
        {
            bChanges |= pEffect->checkForText();
        }
    }
 
    // select effect to delete:
    // if paragraph before target is blank, then delete its animation effect (if any) instead
    ParagraphTarget aPreviousParagraph = aParaTarget;
    --aPreviousParagraph.Paragraph;
    bool bIsPreviousParagraphEmpty = isParagraphTargetTextEmpty( aPreviousParagraph );
    sal_Int16 anParaNumToDelete = bIsPreviousParagraphEmpty ? aPreviousParagraph.Paragraph : aParaTarget.Paragraph;
 
    // update effects
    for( const auto &pEffect : aTargetParagraphEffects )
    {
        Any aIterTarget( pEffect->getTarget() );
 
        ParagraphTarget aIterParaTarget;
        aIterTarget >>= aIterParaTarget;
 
        // delete effect for target paragraph (may have effects in more than one text group)
        if( aIterParaTarget.Paragraph == anParaNumToDelete )
        {
            auto aItr = find( pEffect );
            DBG_ASSERT( aItr != maEffects.end(), "sd::EffectSequenceHelper::disposeTextRange(), Expected effect missing.");
            if( aItr != maEffects.end() )
            {
                (*aItr)->setEffectSequence( nullptr );
                maEffects.erase(aItr);
                bChanges = true;
            }
        }
 
        // shift all paragraphs after disposed paragraph
        if( aIterParaTarget.Paragraph > anParaNumToDelete )
        {
            --aIterParaTarget.Paragraph;
            pEffect->setTarget( Any( aIterParaTarget ) );
            bChanges = true;
        }
    }
 
    if( bChanges )
    {
        rebuild();
    }
}
 
CustomAnimationTextGroup::CustomAnimationTextGroup( const Reference< XShape >& rTarget, sal_Int32 nGroupId )
:   maTarget( rTarget ),
    mnGroupId( nGroupId )
{
    reset();
}
 
void CustomAnimationTextGroup::reset()
{
    mnTextGrouping = -1;
    mbAnimateForm = false;
    mbTextReverse = false;
    mfGroupingAuto = -1.0;
    mnLastPara = -1; // used to check for TextReverse
 
    for (sal_Int8 & rn : mnDepthFlags)
    {
        rn = 0;
    }
 
    maEffects.clear();
}
 
void CustomAnimationTextGroup::addEffect( CustomAnimationEffectPtr const & pEffect )
{
    maEffects.push_back( pEffect );
 
    Any aTarget( pEffect->getTarget() );
    if( aTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
    {
        // now look at the paragraph
        ParagraphTarget aParaTarget;
        aTarget >>= aParaTarget;
 
        if( mnLastPara != -1 )
            mbTextReverse = mnLastPara > aParaTarget.Paragraph;
 
        mnLastPara = aParaTarget.Paragraph;
 
        const sal_Int32 nParaDepth = pEffect->getParaDepth();
 
        // only look at the first PARA_LEVELS levels
        if( nParaDepth < PARA_LEVELS )
        {
            // our first paragraph with this level?
            if( mnDepthFlags[nParaDepth] == 0 )
            {
                // so set it to the first found
                mnDepthFlags[nParaDepth] = static_cast<sal_Int8>(pEffect->getNodeType());
            }
            else if( mnDepthFlags[nParaDepth] != pEffect->getNodeType() )
            {
                mnDepthFlags[nParaDepth] = -1;
            }
 
            if( pEffect->getNodeType() == EffectNodeType::AFTER_PREVIOUS )
                mfGroupingAuto = pEffect->getBegin();
 
            mnTextGrouping = PARA_LEVELS;
            while( (mnTextGrouping > 0)
                   && (mnDepthFlags[mnTextGrouping - 1] <= 0) )
                --mnTextGrouping;
        }
    }
    else
    {
        // if we have an effect with the shape as a target, we animate the background
        mbAnimateForm = pEffect->getTargetSubItem() != ShapeAnimationSubType::ONLY_TEXT;
    }
}
 
CustomAnimationTextGroupPtr EffectSequenceHelper::findGroup( sal_Int32 nGroupId )
{
    CustomAnimationTextGroupPtr aPtr;
 
    CustomAnimationTextGroupMap::iterator aIter( maGroupMap.find( nGroupId ) );
    if( aIter != maGroupMap.end() )
        aPtr = (*aIter).second;
 
    return aPtr;
}
 
void EffectSequenceHelper::updateTextGroups()
{
    maGroupMap.clear();
 
    // first create all the groups
    for( const CustomAnimationEffectPtr& pEffect : maEffects )
    {
        const sal_Int32 nGroupId = pEffect->getGroupId();
 
        if( nGroupId == -1 )
            continue; // trivial case, no group
 
        CustomAnimationTextGroupPtr pGroup = findGroup( nGroupId );
        if( !pGroup )
        {
            pGroup = std::make_shared<CustomAnimationTextGroup>( pEffect->getTargetShape(), nGroupId );
            maGroupMap[nGroupId] = pGroup;
        }
 
        pGroup->addEffect( pEffect );
    }
 
    // Now that all the text groups have been cleared up and rebuilt, we need to update its
    // text grouping. addEffect() already make mnTextGrouping the last possible level,
    // so just continue to find the last level that is not EffectNodeType::WITH_PREVIOUS.
    for(const auto &rGroupMapItem: maGroupMap)
    {
        const CustomAnimationTextGroupPtr &pGroup = rGroupMapItem.second;
        while(pGroup->mnTextGrouping > 0 && pGroup->mnDepthFlags[pGroup->mnTextGrouping - 1] == EffectNodeType::WITH_PREVIOUS)
            --pGroup->mnTextGrouping;
    }
}
 
CustomAnimationTextGroupPtr
EffectSequenceHelper::createTextGroup(const CustomAnimationEffectPtr& pEffect,
                                      sal_Int32 nTextGrouping, double fTextGroupingAuto,
                                      bool bAnimateForm, bool bTextReverse)
{
    // first find a free group-id
    sal_Int32 nGroupId = 0;
 
    CustomAnimationTextGroupMap::iterator aIter( maGroupMap.begin() );
    const CustomAnimationTextGroupMap::iterator aEnd( maGroupMap.end() );
    while( aIter != aEnd )
    {
        if( (*aIter).first == nGroupId )
        {
            nGroupId++;
            aIter = maGroupMap.begin();
        }
        else
        {
            ++aIter;
        }
    }
 
    Reference< XShape > xTarget( pEffect->getTargetShape() );
 
    CustomAnimationTextGroupPtr pTextGroup = std::make_shared<CustomAnimationTextGroup>( xTarget, nGroupId );
    maGroupMap[nGroupId] = pTextGroup;
 
    bool bUsed = false;
 
    // do we need to target the shape?
    if( (nTextGrouping == 0) || bAnimateForm )
    {
        sal_Int16 nSubItem;
        if( nTextGrouping == 0)
            nSubItem = bAnimateForm ? ShapeAnimationSubType::AS_WHOLE : ShapeAnimationSubType::ONLY_TEXT;
        else
            nSubItem = ShapeAnimationSubType::ONLY_BACKGROUND;
 
        pEffect->setTarget( Any( xTarget ) );
        pEffect->setTargetSubItem( nSubItem );
        pEffect->setEffectSequence( this );
        pEffect->setGroupId( nGroupId );
 
        pTextGroup->addEffect( pEffect );
        bUsed = true;
    }
 
    pTextGroup->mnTextGrouping = nTextGrouping;
    pTextGroup->mfGroupingAuto = fTextGroupingAuto;
    pTextGroup->mbTextReverse = bTextReverse;
 
    // now add an effect for each paragraph
    createTextGroupParagraphEffects( pTextGroup, pEffect, bUsed );
 
    notify_listeners();
 
    return pTextGroup;
}
 
void EffectSequenceHelper::createTextGroupParagraphEffects( const CustomAnimationTextGroupPtr& pTextGroup, const CustomAnimationEffectPtr& pEffect, bool bUsed )
{
    Reference< XShape > xTarget( pTextGroup->maTarget );
 
    sal_Int32 nTextGrouping = pTextGroup->mnTextGrouping;
    double fTextGroupingAuto = pTextGroup->mfGroupingAuto;
    bool bTextReverse = pTextGroup->mbTextReverse;
 
    // now add an effect for each paragraph
    if( nTextGrouping < 0 )
        return;
 
    try
    {
        EffectSequence::iterator aInsertIter( find( pEffect ) );
 
        Reference< XEnumerationAccess > xText( xTarget, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xText->createEnumeration(), UNO_SET_THROW );
 
        std::deque< sal_Int16 > aParaList;
        sal_Int16 nPara;
 
        // fill the list with all valid paragraphs
        for( nPara = 0; xEnumeration->hasMoreElements(); nPara++ )
        {
            Reference< XTextRange > xRange( xEnumeration->nextElement(), UNO_QUERY );
            if( xRange.is() && !xRange->getString().isEmpty() )
            {
                if( bTextReverse ) // sort them
                    aParaList.push_front( nPara );
                else
                    aParaList.push_back( nPara );
            }
        }
 
        ParagraphTarget aTarget;
        aTarget.Shape = std::move(xTarget);
 
        for( const auto i : aParaList )
        {
            aTarget.Paragraph = i;
 
            CustomAnimationEffectPtr pNewEffect;
            if( bUsed )
            {
                // clone a new effect from first effect
                pNewEffect = pEffect->clone();
                ++aInsertIter;
                aInsertIter = maEffects.insert( aInsertIter, pNewEffect );
            }
            else
            {
                // reuse first effect if it's not yet used
                pNewEffect = pEffect;
                bUsed = true;
                aInsertIter = find( pNewEffect );
            }
 
            // set target and group-id
            pNewEffect->setTarget( Any( aTarget ) );
            pNewEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_TEXT );
            pNewEffect->setGroupId( pTextGroup->mnGroupId );
            pNewEffect->setEffectSequence( this );
 
            // set correct node type
            if( pNewEffect->getParaDepth() < nTextGrouping )
            {
                if( fTextGroupingAuto == -1.0 )
                {
                    pNewEffect->setNodeType( EffectNodeType::ON_CLICK );
                    pNewEffect->setBegin( 0.0 );
                }
                else
                {
                    pNewEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
                    pNewEffect->setBegin( fTextGroupingAuto );
                }
            }
            else
            {
                pNewEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
                pNewEffect->setBegin( 0.0 );
            }
 
            pTextGroup->addEffect( pNewEffect );
        }
        notify_listeners();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::createTextGroup()" );
    }
}
 
void EffectSequenceHelper::setTextGrouping( const CustomAnimationTextGroupPtr& pTextGroup, sal_Int32 nTextGrouping )
{
    if( pTextGroup->mnTextGrouping == nTextGrouping )
    {
        // first case, trivial case, do nothing
    }
    else if( (pTextGroup->mnTextGrouping == -1) && (nTextGrouping >= 0) )
    {
        // second case, we need to add new effects for each paragraph
 
        CustomAnimationEffectPtr pEffect( pTextGroup->maEffects.front() );
 
        pTextGroup->mnTextGrouping = nTextGrouping;
        createTextGroupParagraphEffects( pTextGroup, pEffect, true );
        notify_listeners();
    }
    else if( (pTextGroup->mnTextGrouping >= 0) && (nTextGrouping == -1 ) )
    {
        // third case, we need to remove effects for each paragraph
 
        EffectSequence aEffects( pTextGroup->maEffects );
        pTextGroup->reset();
 
        for( const CustomAnimationEffectPtr& pEffect : aEffects )
        {
            if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
                remove( pEffect );
            else
                pTextGroup->addEffect( pEffect );
        }
        notify_listeners();
    }
    else
    {
        // fourth case, we need to change the node types for the text nodes
        double fTextGroupingAuto = pTextGroup->mfGroupingAuto;
 
        EffectSequence aEffects( pTextGroup->maEffects );
        pTextGroup->reset();
 
        for( CustomAnimationEffectPtr& pEffect : aEffects )
        {
            if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
            {
                // set correct node type
                if( pEffect->getParaDepth() < nTextGrouping )
                {
                    if( fTextGroupingAuto == -1.0 )
                    {
                        pEffect->setNodeType( EffectNodeType::ON_CLICK );
                        pEffect->setBegin( 0.0 );
                    }
                    else
                    {
                        pEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
                        pEffect->setBegin( fTextGroupingAuto );
                    }
                }
                else
                {
                    pEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
                    pEffect->setBegin( 0.0 );
                }
            }
 
            pTextGroup->addEffect( pEffect );
 
        }
        notify_listeners();
    }
}
 
void EffectSequenceHelper::setAnimateForm( const CustomAnimationTextGroupPtr& pTextGroup, bool bAnimateForm )
{
    if( pTextGroup->mbAnimateForm == bAnimateForm )
    {
        // trivial case, do nothing
    }
    else
    {
        EffectSequence aEffects( pTextGroup->maEffects );
        pTextGroup->reset();
 
        SAL_WARN_IF(aEffects.empty(), "sd", "EffectSequenceHelper::setAnimateForm effects empty" );
 
        if (aEffects.empty())
            return;
 
        EffectSequence::iterator aIter( aEffects.begin() );
        const EffectSequence::iterator aEnd( aEffects.end() );
 
        // first insert if we have to
        if( bAnimateForm )
        {
            EffectSequence::iterator aInsertIter( find( *aIter ) );
 
            CustomAnimationEffectPtr pEffect;
            if( (aEffects.size() == 1) && ((*aIter)->getTarget().getValueType() != ::cppu::UnoType<ParagraphTarget>::get() ) )
            {
                // special case, only one effect and that targets whole text,
                // convert this to target whole shape
                pEffect = *aIter++;
                pEffect->setTargetSubItem( ShapeAnimationSubType::AS_WHOLE );
            }
            else
            {
                pEffect = (*aIter)->clone();
                pEffect->setTarget( Any( (*aIter)->getTargetShape() ) );
                pEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_BACKGROUND );
                maEffects.insert( aInsertIter, pEffect );
            }
 
            pTextGroup->addEffect( pEffect );
        }
 
        if( !bAnimateForm && (aEffects.size() == 1) )
        {
            const CustomAnimationEffectPtr& pEffect( *aIter );
            pEffect->setTarget( Any( (*aIter)->getTargetShape() ) );
            pEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_TEXT );
            pTextGroup->addEffect( pEffect );
        }
        else
        {
            // read the rest to the group again
            while( aIter != aEnd )
            {
                CustomAnimationEffectPtr pEffect( *aIter++ );
 
                if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
                {
                    pTextGroup->addEffect( pEffect );
                }
                else
                {
                    DBG_ASSERT( !bAnimateForm, "sd::EffectSequenceHelper::setAnimateForm(), something is wrong here!" );
                    remove( pEffect );
                }
            }
        }
        notify_listeners();
    }
}
 
void EffectSequenceHelper::setTextGroupingAuto( const CustomAnimationTextGroupPtr& pTextGroup, double fTextGroupingAuto )
{
    sal_Int32 nTextGrouping = pTextGroup->mnTextGrouping;
 
    EffectSequence aEffects( pTextGroup->maEffects );
    pTextGroup->reset();
 
    for( CustomAnimationEffectPtr& pEffect : aEffects )
    {
        if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
        {
            // set correct node type
            if( pEffect->getParaDepth() < nTextGrouping )
            {
                if( fTextGroupingAuto == -1.0 )
                {
                    pEffect->setNodeType( EffectNodeType::ON_CLICK );
                    pEffect->setBegin( 0.0 );
                }
                else
                {
                    pEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
                    pEffect->setBegin( fTextGroupingAuto );
                }
            }
            else
            {
                pEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
                pEffect->setBegin( 0.0 );
            }
        }
 
        pTextGroup->addEffect( pEffect );
 
    }
    notify_listeners();
}
 
namespace {
 
struct ImplStlTextGroupSortHelper
{
    explicit ImplStlTextGroupSortHelper( bool bReverse ) : mbReverse( bReverse ) {};
    bool operator()( const CustomAnimationEffectPtr& p1, const CustomAnimationEffectPtr& p2 );
    bool mbReverse;
    sal_Int32 getTargetParagraph( const CustomAnimationEffectPtr& p1 );
};
 
}
 
sal_Int32 ImplStlTextGroupSortHelper::getTargetParagraph( const CustomAnimationEffectPtr& p1 )
{
    const Any aTarget(p1->getTarget());
    if( aTarget.hasValue() && aTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
    {
        ParagraphTarget aParaTarget;
        aTarget >>= aParaTarget;
        return aParaTarget.Paragraph;
    }
    else
    {
        return mbReverse ? 0x7fffffff : -1;
    }
}
 
bool ImplStlTextGroupSortHelper::operator()( const CustomAnimationEffectPtr& p1, const CustomAnimationEffectPtr& p2 )
{
    if( mbReverse )
    {
        return getTargetParagraph( p2 ) < getTargetParagraph( p1 );
    }
    else
    {
        return getTargetParagraph( p1 ) < getTargetParagraph( p2 );
    }
}
 
void EffectSequenceHelper::setTextReverse( const CustomAnimationTextGroupPtr& pTextGroup, bool bTextReverse )
{
    if( pTextGroup->mbTextReverse == bTextReverse )
    {
        // do nothing
    }
    else
    {
        std::vector< CustomAnimationEffectPtr > aSortedVector( pTextGroup->maEffects.begin(), pTextGroup->maEffects.end() );
        ImplStlTextGroupSortHelper aSortHelper( bTextReverse );
        std::sort( aSortedVector.begin(), aSortedVector.end(), aSortHelper );
 
        pTextGroup->reset();
 
        std::vector< CustomAnimationEffectPtr >::iterator aIter( aSortedVector.begin() );
        const std::vector< CustomAnimationEffectPtr >::iterator aEnd( aSortedVector.end() );
 
        if( aIter != aEnd )
        {
            pTextGroup->addEffect( *aIter );
            EffectSequence::iterator aInsertIter( find( *aIter++ ) );
            while( aIter != aEnd )
            {
                CustomAnimationEffectPtr pEffect( *aIter++ );
                maEffects.erase( find( pEffect ) );
                aInsertIter = maEffects.insert( ++aInsertIter, pEffect );
                pTextGroup->addEffect( pEffect );
            }
        }
        notify_listeners();
    }
}
 
void EffectSequenceHelper::addListener( ISequenceListener* pListener )
{
    if( std::find( maListeners.begin(), maListeners.end(), pListener ) == maListeners.end() )
        maListeners.push_back( pListener );
}
 
void EffectSequenceHelper::removeListener( ISequenceListener* pListener )
{
    maListeners.remove( pListener );
}
 
namespace {
 
struct stl_notify_listeners_func
{
    stl_notify_listeners_func() {}
    void operator()(ISequenceListener* pListener) { pListener->notify_change(); }
};
 
}
 
void EffectSequenceHelper::notify_listeners()
{
    stl_notify_listeners_func aFunc;
    std::for_each( maListeners.begin(), maListeners.end(), aFunc );
}
 
void EffectSequenceHelper::create( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
{
    DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::create(), illegal argument" );
 
    if( !xNode.is() )
        return;
 
    try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
            createEffectsequence( xChildNode );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::create()" );
    }
}
 
void EffectSequenceHelper::createEffectsequence( const Reference< XAnimationNode >& xNode )
{
    DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffectsequence(), illegal argument" );
 
    if( !xNode.is() )
        return;
 
    try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
 
            createEffects( xChildNode );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::createEffectsequence()" );
    }
}
 
void EffectSequenceHelper::createEffects( const Reference< XAnimationNode >& xNode )
{
    DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffects(), illegal argument" );
 
    if( !xNode.is() )
        return;
 
    try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
 
            switch( xChildNode->getType() )
            {
            // found an effect
            case AnimationNodeType::PAR:
            case AnimationNodeType::ITERATE:
                {
                    CustomAnimationEffectPtr pEffect = std::make_shared<CustomAnimationEffect>( xChildNode );
 
                    if( pEffect->mnNodeType != -1 )
                    {
                        pEffect->setEffectSequence( this );
                        maEffects.push_back(pEffect);
                    }
                }
                break;
 
            // found an after effect
            case AnimationNodeType::SET:
            case AnimationNodeType::ANIMATECOLOR:
                {
                    processAfterEffect( xChildNode );
                }
                break;
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::createEffects()" );
    }
}
 
void EffectSequenceHelper::processAfterEffect( const Reference< XAnimationNode >& xNode )
{
    try
    {
        Reference< XAnimationNode > xMaster;
 
        const Sequence< NamedValue > aUserData( xNode->getUserData() );
        const NamedValue* pProp = std::find_if(aUserData.begin(), aUserData.end(),
            [](const NamedValue& rProp) { return rProp.Name == "master-element"; });
 
        if (pProp != aUserData.end())
            pProp->Value >>= xMaster;
 
        // only process if this is a valid after effect
        if( xMaster.is() )
        {
            CustomAnimationEffectPtr pMasterEffect;
 
            // find the master effect
            stl_CustomAnimationEffect_search_node_predict aSearchPredict( xMaster );
            EffectSequence::iterator aIter( std::find_if( maEffects.begin(), maEffects.end(), aSearchPredict ) );
            if( aIter != maEffects.end() )
                pMasterEffect = *aIter;
 
            if( pMasterEffect )
            {
                pMasterEffect->setHasAfterEffect( true );
 
                // find out what kind of after effect this is
                if( xNode->getType() == AnimationNodeType::ANIMATECOLOR )
                {
                    // it's a dim
                    Reference< XAnimate > xAnimate( xNode, UNO_QUERY_THROW );
                    pMasterEffect->setDimColor( xAnimate->getTo() );
                    pMasterEffect->setAfterEffectOnNext( true );
                }
                else
                {
                    // it's a hide
                    pMasterEffect->setAfterEffectOnNext( xNode->getParent() != xMaster->getParent() );
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::processAfterEffect()" );
    }
}
 
namespace {
 
class AnimationChangeListener : public cppu::WeakImplHelper< XChangesListener >
{
public:
    explicit AnimationChangeListener( MainSequence* pMainSequence ) : mpMainSequence( pMainSequence ) {}
 
    virtual void SAL_CALL changesOccurred( const css::util::ChangesEvent& Event ) override;
    virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
private:
    MainSequence* mpMainSequence;
};
 
}
 
void SAL_CALL AnimationChangeListener::changesOccurred( const css::util::ChangesEvent& )
{
    if( mpMainSequence )
        mpMainSequence->startRecreateTimer();
}
 
void SAL_CALL AnimationChangeListener::disposing( const css::lang::EventObject& )
{
}
 
MainSequence::MainSequence()
    : mxTimingRootNode(SequenceTimeContainer::create(::comphelper::getProcessComponentContext()))
    , maTimer("sd MainSequence maTimer")
    , mbTimerMode(false)
    , mbRebuilding( false )
    , mnRebuildLockGuard( 0 )
    , mbPendingRebuildRequest( false )
    , mbIgnoreChanges( 0 )
{
    if( mxTimingRootNode.is() )
    {
        Sequence< css::beans::NamedValue > aUserData
            { { u"node-type"_ustr, css::uno::Any(css::presentation::EffectNodeType::MAIN_SEQUENCE) } };
        mxTimingRootNode->setUserData( aUserData );
    }
    init();
}
 
MainSequence::MainSequence( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
    : mxTimingRootNode( xNode, UNO_QUERY )
    , maTimer("sd MainSequence maTimer")
    , mbTimerMode( false )
    , mbRebuilding( false )
    , mnRebuildLockGuard( 0 )
    , mbPendingRebuildRequest( false )
    , mbIgnoreChanges( 0 )
{
    init();
}
 
MainSequence::~MainSequence()
{
    reset();
}
 
void MainSequence::init()
{
    mnSequenceType = EffectNodeType::MAIN_SEQUENCE;
 
    maTimer.SetInvokeHandler( LINK(this, MainSequence, onTimerHdl) );
    maTimer.SetTimeout(50);
 
    mxChangesListener.set( new AnimationChangeListener( this ) );
 
    createMainSequence();
}
 
void MainSequence::reset( const css::uno::Reference< css::animations::XAnimationNode >& xTimingRootNode )
{
    reset();
 
    mxTimingRootNode.set( xTimingRootNode, UNO_QUERY );
 
    createMainSequence();
}
 
Reference< css::animations::XAnimationNode > MainSequence::getRootNode()
{
    DBG_ASSERT( mnRebuildLockGuard == 0, "MainSequence::getRootNode(), rebuild is locked, is this really what you want?" );
 
    if( maTimer.IsActive() && mbTimerMode )
    {
        // force a rebuild NOW if one is pending
        maTimer.Stop();
        implRebuild();
    }
 
    return EffectSequenceHelper::getRootNode();
}
 
void MainSequence::createMainSequence()
{
    if( mxTimingRootNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxTimingRootNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
            sal_Int32 nNodeType = CustomAnimationEffect::get_node_type( xChildNode );
            if( nNodeType == EffectNodeType::MAIN_SEQUENCE )
            {
                mxSequenceRoot.set( xChildNode, UNO_QUERY );
                EffectSequenceHelper::create( xChildNode );
            }
            else if( nNodeType == EffectNodeType::INTERACTIVE_SEQUENCE )
            {
                Reference< XTimeContainer > xInteractiveRoot( xChildNode, UNO_QUERY_THROW );
                InteractiveSequencePtr pIS = std::make_shared<InteractiveSequence>( xInteractiveRoot, this );
                pIS->addListener( this );
                maInteractiveSequenceVector.push_back( pIS );
            }
        }
 
        // see if we have a mainsequence at all. if not, create one...
        if( !mxSequenceRoot.is() )
        {
            mxSequenceRoot = SequenceTimeContainer::create( ::comphelper::getProcessComponentContext() );
 
            uno::Sequence< css::beans::NamedValue > aUserData
                { { u"node-type"_ustr, css::uno::Any(css::presentation::EffectNodeType::MAIN_SEQUENCE) } };
            mxSequenceRoot->setUserData( aUserData );
 
            // empty sequence until now, set duration to 0.0
            // explicitly (otherwise, this sequence will never
            // end)
            mxSequenceRoot->setDuration( Any(0.0) );
 
            Reference< XAnimationNode > xMainSequenceNode( mxSequenceRoot, UNO_QUERY_THROW );
            mxTimingRootNode->appendChild( xMainSequenceNode );
        }
 
        updateTextGroups();
 
        notify_listeners();
 
        Reference< XChangesNotifier > xNotifier( mxTimingRootNode, UNO_QUERY );
        if( xNotifier.is() )
            xNotifier->addChangesListener( mxChangesListener );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::MainSequence::create()" );
        return;
    }
 
    DBG_ASSERT( mxSequenceRoot.is(), "sd::MainSequence::create(), found no main sequence!" );
}
 
void MainSequence::reset()
{
    EffectSequenceHelper::reset();
 
    for (auto const& interactiveSequence : maInteractiveSequenceVector)
        interactiveSequence->reset();
    maInteractiveSequenceVector.clear();
 
    try
    {
        Reference< XChangesNotifier > xNotifier( mxTimingRootNode, UNO_QUERY );
        if( xNotifier.is() )
            xNotifier->removeChangesListener( mxChangesListener );
    }
    catch( Exception& )
    {
 
    }
}
 
InteractiveSequencePtr MainSequence::createInteractiveSequence( const css::uno::Reference< css::drawing::XShape >& xShape )
{
    InteractiveSequencePtr pIS;
 
    // create a new interactive sequence container
    Reference< XTimeContainer > xISRoot = SequenceTimeContainer::create( ::comphelper::getProcessComponentContext() );
 
    uno::Sequence< css::beans::NamedValue > aUserData
        { { u"node-type"_ustr, css::uno::Any(css::presentation::EffectNodeType::INTERACTIVE_SEQUENCE) } };
    xISRoot->setUserData( aUserData );
    xISRoot->setRestart( css::animations::AnimationRestart::WHEN_NOT_ACTIVE );
 
    Reference< XChild > xChild( mxSequenceRoot, UNO_QUERY_THROW );
    Reference< XTimeContainer > xParent( xChild->getParent(), UNO_QUERY_THROW );
    xParent->appendChild( xISRoot );
 
    pIS = std::make_shared<InteractiveSequence>( xISRoot, this);
    pIS->setTriggerShape( xShape );
    pIS->addListener( this );
    maInteractiveSequenceVector.push_back( pIS );
    return pIS;
}
 
CustomAnimationEffectPtr MainSequence::findEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) const
{
    CustomAnimationEffectPtr pEffect = EffectSequenceHelper::findEffect( xNode );
 
    if( !pEffect )
    {
        for (auto const& interactiveSequence : maInteractiveSequenceVector)
        {
            pEffect = interactiveSequence->findEffect( xNode );
            if (pEffect)
                break;
        }
    }
    return pEffect;
}
 
sal_Int32 MainSequence::getOffsetFromEffect( const CustomAnimationEffectPtr& pEffect ) const
{
    sal_Int32 nOffset = EffectSequenceHelper::getOffsetFromEffect( pEffect );
 
    if( nOffset != -1 )
        return nOffset;
 
    nOffset = EffectSequenceHelper::getCount();
 
    for (auto const& interactiveSequence : maInteractiveSequenceVector)
    {
        sal_Int32 nTemp = interactiveSequence->getOffsetFromEffect( pEffect );
        if( nTemp != -1 )
            return nOffset + nTemp;
 
        nOffset += interactiveSequence->getCount();
    }
 
    return -1;
}
 
CustomAnimationEffectPtr MainSequence::getEffectFromOffset( sal_Int32 nOffset ) const
{
    if( nOffset >= 0 )
    {
        if( nOffset < getCount() )
            return EffectSequenceHelper::getEffectFromOffset( nOffset );
 
        nOffset -= getCount();
 
        auto aIter( maInteractiveSequenceVector.begin() );
 
        while( (aIter != maInteractiveSequenceVector.end()) && (nOffset > (*aIter)->getCount()) )
            nOffset -= (*aIter++)->getCount();
 
        if( (aIter != maInteractiveSequenceVector.end()) && (nOffset >= 0) )
            return (*aIter)->getEffectFromOffset( nOffset );
    }
 
    CustomAnimationEffectPtr pEffect;
    return pEffect;
}
 
bool MainSequence::disposeShape( const Reference< XShape >& xShape )
{
    bool bChanges = EffectSequenceHelper::disposeShape( xShape );
 
    for (auto const& iterativeSequence : maInteractiveSequenceVector)
    {
            bChanges |= iterativeSequence->disposeShape( xShape );
    }
 
    if( bChanges )
        startRebuildTimer();
 
    return bChanges;
}
 
bool MainSequence::hasEffect( const css::uno::Reference< css::drawing::XShape >& xShape )
{
    if( EffectSequenceHelper::hasEffect( xShape ) )
        return true;
 
    for (auto const& iterativeSequence : maInteractiveSequenceVector)
    {
        if( iterativeSequence->getTriggerShape() == xShape )
            return true;
 
        if( iterativeSequence->hasEffect( xShape ) )
            return true;
    }
 
    return false;
}
 
void MainSequence::insertTextRange( const css::uno::Any& aTarget )
{
    EffectSequenceHelper::insertTextRange( aTarget );
 
    for (auto const& iterativeSequence : maInteractiveSequenceVector)
    {
        iterativeSequence->insertTextRange( aTarget );
    }
}
 
void MainSequence::disposeTextRange( const css::uno::Any& aTarget )
{
    EffectSequenceHelper::disposeTextRange( aTarget );
 
    for (auto const& iterativeSequence : maInteractiveSequenceVector)
    {
        iterativeSequence->disposeTextRange( aTarget );
    }
}
 
/** callback from the sd::View when an object just left text edit mode */
void MainSequence::onTextChanged( const Reference< XShape >& xShape )
{
    EffectSequenceHelper::onTextChanged( xShape );
 
    for (auto const& iterativeSequence : maInteractiveSequenceVector)
    {
        iterativeSequence->onTextChanged( xShape );
    }
}
 
void EffectSequenceHelper::onTextChanged( const Reference< XShape >& xShape )
{
    // get map [paragraph index] -> [NumberingLevel]
    // for following reusage inside all animation effects
    std::vector< sal_Int32 > paragraphNumberingLevel;
    std::vector< sal_Int32 >* paragraphNumberingLevelParam = nullptr;
    if ( getParagraphNumberingLevels( xShape, paragraphNumberingLevel ) )
        paragraphNumberingLevelParam = &paragraphNumberingLevel;
 
    // update internal flags for each animation effect
    const bool bChanges = std::accumulate(maEffects.begin(), maEffects.end(), false,
        [&xShape, &paragraphNumberingLevelParam](const bool bCheck, const CustomAnimationEffectPtr& rxEffect) {
            bool bRes = bCheck;
            if (rxEffect->getTargetShape() == xShape)
                bRes |= rxEffect->checkForText( paragraphNumberingLevelParam );
            return bRes;
        });
 
    if( bChanges )
        rebuild();
}
 
void MainSequence::rebuild()
{
    startRebuildTimer();
}
 
void MainSequence::lockRebuilds()
{
    mnRebuildLockGuard++;
}
 
void MainSequence::unlockRebuilds()
{
    DBG_ASSERT( mnRebuildLockGuard, "sd::MainSequence::unlockRebuilds(), no corresponding lockRebuilds() call!" );
    if( mnRebuildLockGuard )
        mnRebuildLockGuard--;
 
    if( (mnRebuildLockGuard == 0) && mbPendingRebuildRequest )
    {
        mbPendingRebuildRequest = false;
        startRebuildTimer();
    }
}
 
void MainSequence::implRebuild()
{
    if( mnRebuildLockGuard )
    {
        mbPendingRebuildRequest = true;
        return;
    }
 
    mbRebuilding = true;
 
    EffectSequenceHelper::implRebuild();
 
    auto aIter( maInteractiveSequenceVector.begin() );
    while( aIter != maInteractiveSequenceVector.end() )
    {
        InteractiveSequencePtr pIS( *aIter );
        if( pIS->maEffects.empty() )
        {
            // remove empty interactive sequences
            aIter = maInteractiveSequenceVector.erase( aIter );
 
            Reference< XChild > xChild( mxSequenceRoot, UNO_QUERY_THROW );
            Reference< XTimeContainer > xParent( xChild->getParent(), UNO_QUERY_THROW );
            Reference< XAnimationNode > xISNode( pIS->mxSequenceRoot, UNO_QUERY_THROW );
            xParent->removeChild( xISNode );
        }
        else
        {
            pIS->implRebuild();
            ++aIter;
        }
    }
 
    notify_listeners();
    mbRebuilding = false;
}
 
void MainSequence::notify_change()
{
    notify_listeners();
}
 
bool MainSequence::setTrigger( const CustomAnimationEffectPtr& pEffect, const css::uno::Reference< css::drawing::XShape >& xTriggerShape )
{
    EffectSequenceHelper* pOldSequence = pEffect->getEffectSequence();
 
    EffectSequenceHelper* pNewSequence = nullptr;
    if( xTriggerShape.is() )
    {
        for (InteractiveSequencePtr const& pIS : maInteractiveSequenceVector)
        {
            if( pIS->getTriggerShape() == xTriggerShape )
            {
                pNewSequence = pIS.get();
                break;
            }
        }
 
        if( !pNewSequence )
            pNewSequence = createInteractiveSequence( xTriggerShape ).get();
    }
    else
    {
        pNewSequence = this;
    }
 
    if( pOldSequence != pNewSequence )
    {
        if( pOldSequence )
            pOldSequence->maEffects.remove( pEffect );
        if( pNewSequence )
            pNewSequence->maEffects.push_back( pEffect );
        pEffect->setEffectSequence( pNewSequence );
        return true;
    }
    else
    {
        return false;
    }
 
}
 
IMPL_LINK_NOARG(MainSequence, onTimerHdl, Timer *, void)
{
    if( mbTimerMode )
    {
        implRebuild();
    }
    else
    {
        reset();
        createMainSequence();
    }
}
 
/** starts a timer that recreates the internal structure from the API core */
void MainSequence::startRecreateTimer()
{
    if( !mbRebuilding && (mbIgnoreChanges == 0) )
    {
        mbTimerMode = false;
        maTimer.Start();
    }
}
 
/**
 * starts a timer that rebuilds the API core from the internal structure
 * This is used to reduce the number of screen redraws due to animation changes.
*/
void MainSequence::startRebuildTimer()
{
    mbTimerMode = true;
    maTimer.Start();
}
 
InteractiveSequence::InteractiveSequence( const Reference< XTimeContainer >& xSequenceRoot, MainSequence* pMainSequence )
: EffectSequenceHelper( xSequenceRoot ), mpMainSequence( pMainSequence )
{
    mnSequenceType = EffectNodeType::INTERACTIVE_SEQUENCE;
 
    try
    {
        if( mxSequenceRoot.is() )
        {
            Reference< XEnumerationAccess > xEnumerationAccess( mxSequenceRoot, UNO_QUERY_THROW );
            Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
            while( !mxEventSource.is() && xEnumeration->hasMoreElements() )
            {
                Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
 
                Event aEvent;
                if( (xChildNode->getBegin() >>= aEvent) && (aEvent.Trigger == EventTrigger::ON_CLICK) )
                    aEvent.Source >>= mxEventSource;
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::InteractiveSequence::InteractiveSequence()" );
        return;
    }
}
 
void InteractiveSequence::rebuild()
{
    mpMainSequence->rebuild();
}
 
void InteractiveSequence::implRebuild()
{
    EffectSequenceHelper::implRebuild();
}
 
MainSequenceRebuildGuard::MainSequenceRebuildGuard( MainSequencePtr pMainSequence )
: mpMainSequence(std::move( pMainSequence ))
{
    if( mpMainSequence )
        mpMainSequence->lockRebuilds();
}
 
MainSequenceRebuildGuard::~MainSequenceRebuildGuard()
{
    if( mpMainSequence )
        mpMainSequence->unlockRebuilds();
}
 
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V1053 Calling the 'reset' virtual function in the destructor may lead to unexpected result at runtime.

V614 The 'pEffect' smart pointer is utilized immediately after being declared or reset. It is suspicious that no value was assigned to it.

V1001 The 'aTarget' variable is assigned but is not used by the end of the function.