/* -*- 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 <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/Timing.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/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/matrix/b2dhommatrix.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
 
#include <algorithm>
#include <deque>
 
#include <cppuhelper/implbase.hxx>
 
#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdpage.hxx>
#include <svx/unoapi.hxx>
#include <CustomAnimationEffect.hxx>
#include <CustomAnimationPreset.hxx>
#include <animations.hxx>
 
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),
    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();
 
    Sequence< NamedValue > aUserData( mxNode->getUserData() );
    sal_Int32 nLength = aUserData.getLength();
    const NamedValue* p = aUserData.getConstArray();
 
    while( nLength-- )
    {
        if ( p->Name == "node-type" )
        {
            p->Value >>= mnNodeType;
        }
        else if ( p->Name == "preset-id" )
        {
            p->Value >>= maPresetId;
        }
        else if ( p->Name == "preset-sub-type" )
        {
            p->Value >>= maPresetSubType;
        }
        else if ( p->Name == "preset-class" )
        {
            p->Value >>= mnPresetClass;
        }
        else if ( p->Name == "preset-property" )
        {
            p->Value >>= maProperty;
        }
        else if ( p->Name == "group-id" )
        {
            p->Value >>= mnGroupId;
        }
 
        p++;
    }
 
    // get effect start time
    mxNode->getBegin() >>= mfBegin;
 
    mfAcceleration = mxNode->getAcceleration();
    mfDecelerate = mxNode->getDecelerate();
    mbAutoReverse = mxNode->getAutoReverse();
 
    // 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(), UNO_QUERY );
        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
            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_QUERY_THROW );
            css::lang::Locale aLocale;
            const OUString aStrLocaleName( "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;
        OSL_FAIL( "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( new CustomAnimationEffect( xNode ) );
    pEffect->setEffectSequence( getEffectSequence() );
    return pEffect;
}
 
sal_Int32 CustomAnimationEffect::get_node_type( const Reference< XAnimationNode >& xNode )
{
    sal_Int16 nNodeType = -1;
 
    if( xNode.is() )
    {
        Sequence< NamedValue > aUserData( xNode->getUserData() );
        sal_Int32 nLength = aUserData.getLength();
        if( nLength )
        {
            const NamedValue* p = aUserData.getConstArray();
            while( nLength-- )
            {
                if ( p->Name == "node-type" )
                {
                    p->Value >>= nNodeType;
                    break;
                }
                p++;
            }
        }
    }
 
    return nNodeType;
}
 
void CustomAnimationEffect::setPresetClass( sal_Int16 nPresetClass )
{
    if( mnPresetClass != nPresetClass )
    {
        mnPresetClass = nPresetClass;
        if( mxNode.is() )
        {
            // 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 bFound = false;
            if( nLength )
            {
                NamedValue* p = aUserData.getArray();
                while( nLength-- )
                {
                    if ( p->Name == "preset-class" )
                    {
                        p->Value <<= mnPresetClass;
                        bFound = true;
                        break;
                    }
                    p++;
                }
            }
 
            // no "node-type" entry inside user data, so add it
            if( !bFound )
            {
                nLength = aUserData.getLength();
                aUserData.realloc( nLength + 1);
                aUserData[nLength].Name = "preset-class";
                aUserData[nLength].Value <<= mnPresetClass;
            }
 
            mxNode->setUserData( aUserData );
        }
    }
}
 
void CustomAnimationEffect::setNodeType( sal_Int16 nNodeType )
{
    if( mnNodeType != nNodeType )
    {
        mnNodeType = nNodeType;
        if( mxNode.is() )
        {
            // 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 )
            {
                NamedValue* p = aUserData.getArray();
                while( nLength-- )
                {
                    if ( p->Name == "node-type" )
                    {
                        p->Value <<= mnNodeType;
                        bFound = true;
                        break;
                    }
                    p++;
                }
            }
 
            // no "node-type" entry inside user data, so add it
            if( !bFound )
            {
                nLength = aUserData.getLength();
                aUserData.realloc( nLength + 1);
                aUserData[nLength].Name = "node-type";
                aUserData[nLength].Value <<= mnNodeType;
            }
 
            mxNode->setUserData( aUserData );
        }
    }
}
 
void CustomAnimationEffect::setGroupId( sal_Int32 nGroupId )
{
    mnGroupId = nGroupId;
    if( mxNode.is() )
    {
        // 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 )
        {
            NamedValue* p = aUserData.getArray();
            while( nLength-- )
            {
                if ( p->Name == "group-id" )
                {
                    p->Value <<= mnGroupId;
                    bFound = true;
                    break;
                }
                p++;
            }
        }
 
        // no "node-type" entry inside user data, so add it
        if( !bFound )
        {
            nLength = aUserData.getLength();
            aUserData.realloc( nLength + 1);
            aUserData[nLength].Name = "group-id";
            aUserData[nLength].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()
{
    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() )
        {
            Reference< XEnumerationAccess > xEA( xText, UNO_QUERY );
            if( xEA.is() )
            {
                Reference< XEnumeration > xEnumeration( xEA->createEnumeration(), UNO_QUERY );
                if( xEnumeration.is() )
                {
                    bool bHasText = xEnumeration->hasMoreElements();
                    bChange |= bHasText != mbHasText;
                    mbHasText = bHasText;
 
                    sal_Int32 nPara = aParaTarget.Paragraph;
 
                    while( xEnumeration->hasMoreElements() && nPara-- )
                        xEnumeration->nextElement();
 
                    if( xEnumeration->hasMoreElements() )
                    {
                        Reference< XPropertySet > xParaSet;
                        xEnumeration->nextElement() >>= xParaSet;
                        if( xParaSet.is() )
                        {
                            sal_Int32 nParaDepth = 0;
                            const OUString strNumberingLevel( "NumberingLevel" );
                            xParaSet->getPropertyValue( strNumberingLevel ) >>= nParaDepth;
                            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(), UNO_QUERY );
                    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& )
    {
        OSL_FAIL( "sd::CustomAnimationEffect::setTarget(), exception caught!" );
    }
}
 
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(), UNO_QUERY );
                if( xEnumeration.is() )
                {
                    while( xEnumeration->hasMoreElements() )
                    {
                        Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
                        if( xAnimate.is() )
                            xAnimate->setSubItem( mnTargetSubItem );
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        OSL_FAIL( "sd::CustomAnimationEffect::setTargetSubItem(), exception caught!" );
    }
}
 
void CustomAnimationEffect::setDuration( double fDuration )
{
    if( (mfDuration != -1.0) && (mfDuration != fDuration) ) 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(), UNO_QUERY );
            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( makeAny( fChildBegin ) );
                    }
 
                    double fChildDuration = 0.0;
                    xChildNode->getDuration() >>= fChildDuration;
                    if( fChildDuration != 0.0 )
                    {
                        fChildDuration *= fScale;
                        xChildNode->setDuration( makeAny( fChildDuration ) );
                    }
                }
            }
        }
        calculateIterateDuration();
    }
    catch( Exception& )
    {
        OSL_FAIL( "sd::CustomAnimationEffect::setDuration(), exception caught!" );
    }
}
 
void CustomAnimationEffect::setBegin( double fBegin )
{
    if( mxNode.is() ) try
    {
        mfBegin = fBegin;
        mxNode->setBegin( makeAny( fBegin ) );
    }
    catch( Exception& )
    {
        OSL_FAIL( "sd::CustomAnimationEffect::setBegin(), exception caught!" );
    }
}
 
void CustomAnimationEffect::setAcceleration( double fAcceleration )
{
    if( mxNode.is() ) try
    {
        mfAcceleration = fAcceleration;
        mxNode->setAcceleration( fAcceleration );
    }
    catch( Exception& )
    {
        OSL_FAIL( "sd::CustomAnimationEffect::setAcceleration(), exception caught!" );
    }
}
 
void CustomAnimationEffect::setDecelerate( double fDecelerate )
{
    if( mxNode.is() ) try
    {
        mfDecelerate = fDecelerate;
        mxNode->setDecelerate( fDecelerate );
    }
    catch( Exception& )
    {
        OSL_FAIL( "sd::CustomAnimationEffect::setDecelerate(), exception caught!" );
    }
}
 
void CustomAnimationEffect::setAutoReverse( bool bAutoReverse )
{
    if( mxNode.is() ) try
    {
        mbAutoReverse = bAutoReverse;
        mxNode->setAutoReverse( bAutoReverse );
    }
    catch( Exception& )
    {
        OSL_FAIL( "sd::CustomAnimationEffect::setAutoReverse(), exception caught!" );
    }
}
 
void CustomAnimationEffect::replaceNode( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
{
    sal_Int16 nNodeType = mnNodeType;
    Any aTarget = maTarget;
 
    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 );
 
    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;
    }
}
 
sal_Int16 CustomAnimationEffect::getFill() const
{
    if( mxNode.is() )
        return mxNode->getFill();
    else
        return 0;
}
 
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() )
        mxNode->setFill( nFill );
}
 
Reference< XAnimationNode > CustomAnimationEffect::createAfterEffectNode() const
{
    DBG_ASSERT( mbHasAfterEffect, "sd::CustomAnimationEffect::createAfterEffectNode(), this node has no after effect!" );
 
    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( makeAny( 0.001 ) );
    xAnimate->setFill( AnimationFill::HOLD );
    xAnimate->setTarget( maTarget );
 
    return xAnimate;
}
 
void CustomAnimationEffect::setIterateType( sal_Int16 nIterateType )
{
    if( mnIterateType != nIterateType ) try
    {
        // do we need to exchange the container node?
        if( (mnIterateType == 0) || (nIterateType == 0) )
        {
            sal_Int16 nTargetSubItem = mnTargetSubItem;
 
            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_QUERY_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_QUERY_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& )
    {
        OSL_FAIL( "sd::CustomAnimationEffect::setIterateType(), Exception caught!" );
    }
}
 
void CustomAnimationEffect::setIterateInterval( double fIterateInterval )
{
    if( mfIterateInterval != fIterateInterval )
    {
        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_QUERY_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimateMotion > xMotion( xEnumeration->nextElement(), UNO_QUERY );
            if( xMotion.is() )
            {
                xMotion->getPath() >>= aPath;
                break;
            }
        }
    }
    catch( Exception& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::getPath(), exception caught!" );
    }
 
    return aPath;
}
 
void CustomAnimationEffect::setPath( const OUString& rPath )
{
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimateMotion > xMotion( xEnumeration->nextElement(), UNO_QUERY );
            if( xMotion.is() )
            {
 
                MainSequenceChangeGuard aGuard( mpEffectSequence );
                xMotion->setPath( Any( rPath ) );
                break;
            }
        }
    }
    catch( Exception& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::setPath(), exception caught!" );
    }
}
 
Any CustomAnimationEffect::getProperty( sal_Int32 nNodeType, const OUString& rAttributeName, EValue eValue )
{
    Any aProperty;
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY );
            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& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::getProperty(), exception caught!" );
    }
 
    return aProperty;
}
 
bool CustomAnimationEffect::setProperty( sal_Int32 nNodeType, const OUString& 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(), UNO_QUERY );
            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& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::setProperty(), exception caught!" );
    }
 
    return bChanged;
}
 
static bool implIsColorAttribute( const OUString& rAttributeName )
{
    return rAttributeName == "FillColor" || rAttributeName == "LineColor" || rAttributeName == "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(), UNO_QUERY );
            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;
                        SAL_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& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::getColor(), exception caught!" );
    }
 
    return aColor;
}
 
void CustomAnimationEffect::setColor( sal_Int32 nIndex, const Any& rColor )
{
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY );
            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;
                        SAL_FALLTHROUGH;
                    case AnimationNodeType::ANIMATECOLOR:
                    {
                        Sequence<Any> aValues( xAnimate->getValues() );
                        if( aValues.hasElements() )
                        {
                            if( aValues.getLength() > nIndex )
                            {
                                aValues[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& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::setColor(), exception caught!" );
    }
}
 
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(), UNO_QUERY );
            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& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::getTransformationProperty(), exception caught!" );
    }
 
    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(), UNO_QUERY );
            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& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::setTransformationProperty(), exception caught!" );
    }
 
    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() ) try
    {
        Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
        Reference< XAudio > xAudio( Audio::create( xContext ) );
        xAudio->setSource( rSource );
        xAudio->setVolume( 1.0 );
        setAudio( xAudio );
    }
    catch( Exception& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::createAudio(), exception caught!" );
    }
}
 
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_QUERY_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& )
    {
        OSL_FAIL("sd::findCommandNode(), exception caught!" );
    }
 
    return xCommand;
}
 
void CustomAnimationEffect::removeAudio()
{
    try
    {
        Reference< XAnimationNode > xChild;
 
        if( mxAudio.is() )
        {
            xChild.set( mxAudio, UNO_QUERY );
            mxAudio.clear();
        }
        else if( mnCommand == EffectCommands::STOPAUDIO )
        {
            xChild.set( findCommandNode( mxNode ), UNO_QUERY );
            mnCommand = 0;
        }
 
        if( xChild.is() )
        {
            Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
            if( xContainer.is() )
                xContainer->removeChild( xChild );
        }
    }
    catch( Exception& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::removeAudio(), exception caught!" );
    }
 
}
 
void CustomAnimationEffect::setAudio( const Reference< css::animations::XAudio >& xAudio )
{
    if( mxAudio != xAudio ) try
    {
        removeAudio();
        mxAudio = xAudio;
        Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
        Reference< XAnimationNode > xChild( mxAudio, UNO_QUERY );
        if( xContainer.is() && xChild.is() )
            xContainer->appendChild( xChild );
    }
    catch( Exception& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::setAudio(), exception caught!" );
    }
}
 
void CustomAnimationEffect::setStopAudio()
{
    if( mnCommand != EffectCommands::STOPAUDIO ) try
    {
        if( mxAudio.is() )
            removeAudio();
 
        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& )
    {
        OSL_FAIL("sd::CustomAnimationEffect::setStopAudio(), exception caught!" );
    }
}
 
bool CustomAnimationEffect::getStopAudio() const
{
    return mnCommand == EffectCommands::STOPAUDIO;
}
 
SdrPathObj* CustomAnimationEffect::createSdrPathObjFromPath(SdrModel& rTargetModel)
{
    SdrPathObj * pPathObj = new SdrPathObj(rTargetModel, OBJ_PATHLINE);
    updateSdrPathObjFromPath( *pPathObj );
    return pPathObj;
}
 
void CustomAnimationEffect::updateSdrPathObjFromPath( SdrPathObj& rPathObj )
{
    ::basegfx::B2DPolyPolygon aPolyPoly;
    if( ::basegfx::utils::importFromSvgD( aPolyPoly, getPath(), true, nullptr ) )
    {
        SdrObject* pObj = 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 = GetSdrObjectFromXShape( getTargetShape() );
    if( pObj )
    {
        ::tools::Rectangle aBoundRect(0,0,0,0);
 
        const drawinglayer::primitive2d::Primitive2DContainer xPrimitives(pObj->GetViewContact().getViewIndependentPrimitive2DContainer());
        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( const css::uno::Reference< css::animations::XTimeContainer >& xSequenceRoot )
: mxSequenceRoot( xSequenceRoot ), mnSequenceType( EffectNodeType::DEFAULT )
{
    Reference< XAnimationNode > xNode( mxSequenceRoot, UNO_QUERY_THROW );
    create( xNode );
}
 
EffectSequenceHelper::~EffectSequenceHelper()
{
    reset();
}
 
void EffectSequenceHelper::reset()
{
    EffectSequence::iterator aIter( maEffects.begin() );
    EffectSequence::iterator aEnd( maEffects.end() );
    if( aIter != aEnd )
    {
        CustomAnimationEffectPtr pEffect = (*aIter++);
        pEffect->setEffectSequence(nullptr);
    }
    maEffects.clear();
}
 
Reference< XAnimationNode > EffectSequenceHelper::getRootNode()
{
    Reference< XAnimationNode > xRoot( mxSequenceRoot, UNO_QUERY );
    return xRoot;
}
 
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.get() )
    {
        Reference< XAnimationNode > xNode( pPreset->create( "" ) );
        if( xNode.is() )
        {
            // first, filter all only ui relevant user data
            std::vector< NamedValue > aNewUserData;
            Sequence< NamedValue > aUserData( xNode->getUserData() );
            sal_Int32 nLength = aUserData.getLength();
            const NamedValue* p = aUserData.getConstArray();
            bool bFilter = false;
 
            while( nLength-- )
            {
                if( p->Name != "text-only" && p->Name != "preset-property" )
                {
                    aNewUserData.push_back( *p );
                    bFilter = true;
                }
                p++;
            }
 
            if( bFilter )
            {
                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.reset( new 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.get(), "sd::EffectSequenceHelper::append(), failed!" );
    return pEffect;
}
 
CustomAnimationEffectPtr EffectSequenceHelper::append( const SdrPathObj& rPathObj, const Any& rTarget, double fDuration /* = -1.0 */ )
{
    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.reset( new CustomAnimationEffect( xEffectContainer ) );
        pEffect->setEffectSequence( this );
        pEffect->setTarget( rTarget );
        pEffect->setTargetSubItem( nSubItem );
        pEffect->setNodeType( css::presentation::EffectNodeType::ON_CLICK );
        pEffect->setPresetClass( css::presentation::EffectPresetClass::MOTIONPATH );
        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& )
    {
        OSL_FAIL( "sd::EffectSequenceHelper::append(), exception caught!" );
    }
 
    return pEffect;
}
 
void EffectSequenceHelper::replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pPreset, const OUString& rPresetSubType, double fDuration /* = -1.0 */ )
{
    if( pEffect.get() && pPreset.get() ) try
    {
        Reference< XAnimationNode > xNewNode( pPreset->create( rPresetSubType ) );
        if( xNewNode.is() )
        {
            pEffect->replaceNode( xNewNode );
            if( fDuration != -1.0 )
                pEffect->setDuration( fDuration );
        }
 
        rebuild();
    }
    catch( Exception& )
    {
        OSL_FAIL( "sd::EffectSequenceHelper::replace(), exception caught!" );
    }
}
 
void EffectSequenceHelper::replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pPreset, double fDuration /* = -1.0 */ )
{
    replace( pEffect, pPreset, "", fDuration );
}
 
void EffectSequenceHelper::remove( const CustomAnimationEffectPtr& pEffect )
{
    if( pEffect.get() )
    {
        pEffect->setEffectSequence( nullptr );
        maEffects.remove( 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_QUERY_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_QUERY_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( makeAny( 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( makeAny( 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.get() && (pEffect->getNodeType() == EffectNodeType::WITH_PREVIOUS) );
 
                    fBegin += fDuration;
                }
                while( pEffect.get() && (pEffect->getNodeType() != EffectNodeType::ON_CLICK) );
            }
            while( pEffect.get() );
 
            // 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( makeAny(0.0) );
        }
    }
    catch( Exception& )
    {
        OSL_FAIL( "sd::EffectSequenceHelper::rebuild(), exception caught!" );
    }
}
 
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_QUERY_THROW );
            Sequence< NamedValue > aUserData( rNode.mxNode->getUserData() );
            sal_Int32 nSize = aUserData.getLength();
            aUserData.realloc(nSize+1);
            aUserData[nSize].Name = "master-element";
            aUserData[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
            {
                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_QUERY_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( makeAny( 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( makeAny( aEvent ) );
 
                    xSequenceContainer->insertAfter( xNewClickContainer, xClickContainer );
 
                    xNextContainer.set( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW );
 
                    xNextContainer->setBegin( makeAny( 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_QUERY_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& )
    {
        OSL_FAIL( "ppt::stl_process_after_effect_node_func::operator(), exception caught!" );
    }
}
 
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( maEffects.begin() );
    for( ; aIter != maEffects.end(); ++aIter )
    {
        if( (*aIter)->getNode() == xNode )
        {
            pEffect = (*aIter);
            break;
        }
    }
 
    return pEffect;
}
 
sal_Int32 EffectSequenceHelper::getOffsetFromEffect( const CustomAnimationEffectPtr& xEffect ) const
{
    sal_Int32 nOffset = 0;
 
    EffectSequence::const_iterator aIter( maEffects.begin() );
    for( ; aIter != maEffects.end(); ++aIter, nOffset++ )
    {
        if( (*aIter) == xEffect )
            return nOffset;
    }
 
    return -1;
}
 
CustomAnimationEffectPtr EffectSequenceHelper::getEffectFromOffset( sal_Int32 nOffset ) const
{
    EffectSequence::const_iterator aIter( maEffects.begin() );
    while( nOffset-- && aIter != maEffects.end() )
        ++aIter;
 
    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 )
{
    EffectSequence::iterator aIter( maEffects.begin() );
    while( aIter != maEffects.end() )
    {
        if( (*aIter)->getTargetShape() == xShape )
            return true;
        ++aIter;
    }
 
    return false;
}
 
void EffectSequenceHelper::insertTextRange( const css::uno::Any& aTarget )
{
    bool bChanges = false;
 
    ParagraphTarget aParaTarget;
    if( !(aTarget >>= aParaTarget ) )
        return;
 
    EffectSequence::iterator aIter( maEffects.begin() );
    while( aIter != maEffects.end() )
    {
        if( (*aIter)->getTargetShape() == aParaTarget.Shape )
            bChanges |= (*aIter)->checkForText();
        ++aIter;
    }
 
    if( bChanges )
        rebuild();
}
 
void EffectSequenceHelper::disposeTextRange( const css::uno::Any& aTarget )
{
    ParagraphTarget aParaTarget;
    if( !(aTarget >>= aParaTarget ) )
        return;
 
    bool bChanges = false;
    bool bErased = false;
 
    EffectSequence::iterator aIter( maEffects.begin() );
    while( aIter != maEffects.end() )
    {
        Any aIterTarget( (*aIter)->getTarget() );
        if( aIterTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
        {
            ParagraphTarget aIterParaTarget;
            if( (aIterTarget >>= aIterParaTarget) && (aIterParaTarget.Shape == aParaTarget.Shape) )
            {
                if( aIterParaTarget.Paragraph == aParaTarget.Paragraph )
                {
                    // delete this effect if it targets the disposed paragraph directly
                    (*aIter)->setEffectSequence( nullptr );
                    aIter = maEffects.erase( aIter );
                    bChanges = true;
                    bErased = true;
                }
                else
                {
                    if( aIterParaTarget.Paragraph > aParaTarget.Paragraph )
                    {
                        // shift all paragraphs after disposed paragraph
                        aIterParaTarget.Paragraph--;
                        (*aIter)->setTarget( makeAny( aIterParaTarget ) );
                    }
                }
            }
        }
        else if( (*aIter)->getTargetShape() == aParaTarget.Shape )
        {
            bChanges |= (*aIter)->checkForText();
        }
 
        if( bErased )
            bErased = false;
        else
            ++aIter;
    }
 
    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
    EffectSequence::iterator aIter( maEffects.begin() );
    const EffectSequence::iterator aEnd( maEffects.end() );
    while( aIter != aEnd )
    {
        CustomAnimationEffectPtr pEffect( (*aIter++) );
 
        const sal_Int32 nGroupId = pEffect->getGroupId();
 
        if( nGroupId == -1 )
            continue; // trivial case, no group
 
        CustomAnimationTextGroupPtr pGroup = findGroup( nGroupId );
        if( !pGroup.get() )
        {
            pGroup.reset( new 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 finde 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( new 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( makeAny( 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 ) try
    {
        EffectSequence::iterator aInsertIter( find( pEffect ) );
 
        Reference< XEnumerationAccess > xText( xTarget, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xText->createEnumeration(), UNO_QUERY_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 = 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( makeAny( 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& )
    {
        OSL_FAIL("sd::EffectSequenceHelper::createTextGroup(), exception caught!" );
    }
}
 
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();
 
        EffectSequence::iterator aIter( aEffects.begin() );
        const EffectSequence::iterator aEnd( aEffects.end() );
        while( aIter != aEnd )
        {
            CustomAnimationEffectPtr pEffect( (*aIter++) );
 
            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();
 
        EffectSequence::iterator aIter( aEffects.begin() );
        const EffectSequence::iterator aEnd( aEffects.end() );
        while( aIter != aEnd )
        {
            CustomAnimationEffectPtr pEffect( (*aIter++) );
 
            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( makeAny( (*aIter)->getTargetShape() ) );
                pEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_BACKGROUND );
                maEffects.insert( aInsertIter, pEffect );
            }
 
            pTextGroup->addEffect( pEffect );
        }
 
        if( !bAnimateForm && (aEffects.size() == 1) )
        {
            CustomAnimationEffectPtr pEffect( (*aIter) );
            pEffect->setTarget( makeAny( (*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();
 
    EffectSequence::iterator aIter( aEffects.begin() );
    const EffectSequence::iterator aEnd( aEffects.end() );
    while( aIter != aEnd )
    {
        CustomAnimationEffectPtr pEffect( (*aIter++) );
 
        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();
}
 
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.size());
        std::copy( pTextGroup->maEffects.begin(), pTextGroup->maEffects.end(), aSortedVector.begin() );
        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 );
}
 
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() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
            createEffectsequence( xChildNode );
        }
    }
    catch( Exception& )
    {
        OSL_FAIL( "sd::EffectSequenceHelper::create(), exception caught!" );
    }
}
 
void EffectSequenceHelper::createEffectsequence( const Reference< XAnimationNode >& xNode )
{
    DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffectsequence(), illegal argument" );
 
    if( xNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
 
            createEffects( xChildNode );
        }
    }
    catch( Exception& )
    {
        OSL_FAIL( "sd::EffectSequenceHelper::createEffectsequence(), exception caught!" );
    }
}
 
void EffectSequenceHelper::createEffects( const Reference< XAnimationNode >& xNode )
{
    DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffects(), illegal argument" );
 
    if( xNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_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( new 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& )
    {
        OSL_FAIL( "sd::EffectSequenceHelper::createEffects(), exception caught!" );
    }
}
 
void EffectSequenceHelper::processAfterEffect( const Reference< XAnimationNode >& xNode )
{
    try
    {
        Reference< XAnimationNode > xMaster;
 
        Sequence< NamedValue > aUserData( xNode->getUserData() );
        sal_Int32 nLength = aUserData.getLength();
        const NamedValue* p = aUserData.getConstArray();
 
        while( nLength-- )
        {
            if ( p->Name == "master-element" )
            {
                p->Value >>= xMaster;
                break;
            }
            p++;
        }
 
        // 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.get() )
            {
                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& )
    {
        OSL_FAIL( "sd::EffectSequenceHelper::processAfterEffect(), exception caught!" );
    }
}
 
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()))
    , mbTimerMode(false)
    , mbRebuilding( false )
    , mnRebuildLockGuard( 0 )
    , mbPendingRebuildRequest( false )
    , mbIgnoreChanges( 0 )
{
    if( mxTimingRootNode.is() )
    {
        Sequence< css::beans::NamedValue > aUserData
            { { "node-type", css::uno::makeAny(css::presentation::EffectNodeType::MAIN_SEQUENCE) } };
        mxTimingRootNode->setUserData( aUserData );
    }
    init();
}
 
MainSequence::MainSequence( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
    : mxTimingRootNode( xNode, UNO_QUERY )
    , 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(500);
 
    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_QUERY_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( new 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
                { { "node-type", css::uno::makeAny(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( makeAny(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& )
    {
        OSL_FAIL( "sd::MainSequence::create(), exception caught!" );
        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
        { { "node-type", css::uno::makeAny(css::presentation::EffectNodeType::INTERACTIVE_SEQUENCE) } };
    xISRoot->setUserData( aUserData );
 
    Reference< XChild > xChild( mxSequenceRoot, UNO_QUERY_THROW );
    Reference< XTimeContainer > xParent( xChild->getParent(), UNO_QUERY_THROW );
    xParent->appendChild( xISRoot );
 
    pIS.reset( new 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.get() == nullptr )
    {
        for (auto const& interactiveSequence : maInteractiveSequenceVector)
        {
            pEffect = interactiveSequence->findEffect( xNode );
            if (pEffect.get())
                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 )
{
    bool bChanges = false;
 
    EffectSequence::iterator aIter;
    for( aIter = maEffects.begin(); aIter != maEffects.end(); ++aIter )
    {
        if( (*aIter)->getTargetShape() == xShape )
            bChanges |= (*aIter)->checkForText();
    }
 
    if( bChanges )
        EffectSequenceHelper::implRebuild();
}
 
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 (auto const& iteractiveSequence : maInteractiveSequenceVector)
        {
            InteractiveSequencePtr pIS(iteractiveSequence);
            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 after 1 second */
void MainSequence::startRecreateTimer()
{
    if( !mbRebuilding && (mbIgnoreChanges == 0) )
    {
        mbTimerMode = false;
        maTimer.Start();
    }
}
 
/** starts a timer that rebuilds the API core from the internal structure after 1 second */
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_QUERY_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& )
    {
        OSL_FAIL( "sd::InteractiveSequence::InteractiveSequence(), exception caught!" );
        return;
    }
}
 
void InteractiveSequence::rebuild()
{
    mpMainSequence->rebuild();
}
 
void InteractiveSequence::implRebuild()
{
    EffectSequenceHelper::implRebuild();
}
 
MainSequenceRebuildGuard::MainSequenceRebuildGuard( const MainSequencePtr& pMainSequence )
: mpMainSequence( pMainSequence )
{
    if( mpMainSequence.get() )
        mpMainSequence->lockRebuilds();
}
 
MainSequenceRebuildGuard::~MainSequenceRebuildGuard()
{
    if( mpMainSequence.get() )
        mpMainSequence->unlockRebuilds();
}
 
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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.