Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sd/source/core/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 113 kB image not shown  

Quelle  CustomAnimationEffect.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <tools/debug.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <sal/log.hxx>
#include <com/sun/star/animations/AnimationNodeType.hpp>
#include <com/sun/star/animations/AnimateColor.hpp>
#include <com/sun/star/animations/AnimateMotion.hpp>
#include <com/sun/star/animations/AnimateSet.hpp>
#include <com/sun/star/animations/AnimationFill.hpp>
#include <com/sun/star/animations/Audio.hpp>
#include <com/sun/star/animations/Command.hpp>
#include <com/sun/star/animations/Event.hpp>
#include <com/sun/star/animations/EventTrigger.hpp>
#include <com/sun/star/animations/IterateContainer.hpp>
#include <com/sun/star/animations/ParallelTimeContainer.hpp>
#include <com/sun/star/animations/SequenceTimeContainer.hpp>
#include <com/sun/star/animations/XCommand.hpp>
#include <com/sun/star/animations/XIterateContainer.hpp>
#include <com/sun/star/animations/XAnimateTransform.hpp>
#include <com/sun/star/animations/XAnimateMotion.hpp>
#include <com/sun/star/animations/XAnimate.hpp>
#include <com/sun/star/animations/AnimationRestart.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/presentation/EffectNodeType.hpp>
#include <com/sun/star/presentation/EffectCommands.hpp>
#include <com/sun/star/presentation/EffectPresetClass.hpp>
#include <com/sun/star/presentation/ParagraphTarget.hpp>
#include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/util/XCloneable.hpp>
#include <com/sun/star/util/XChangesNotifier.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequence.hxx>
#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/i18n/BreakIterator.hpp>
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/presentation/TextAnimationType.hpp>

#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>

#include <algorithm>
#include <deque>
#include <numeric>

#include <cppuhelper/implbase.hxx>

#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <o3tl/safeint.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdpage.hxx>
#include <CustomAnimationEffect.hxx>
#include <CustomAnimationPreset.hxx>
#include <animations.hxx>
#include <utility>

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::presentation;
using namespace ::com::sun::star::animations;

using ::com::sun::star::container::XEnumerationAccess;
using ::com::sun::star::container::XEnumeration;
using ::com::sun::star::beans::NamedValue;
using ::com::sun::star::container::XChild;
using ::com::sun::star::drawing::XShape;
using ::com::sun::star::lang::XInitialization;
using ::com::sun::star::text::XText;
using ::com::sun::star::text::XTextRange;
using ::com::sun::star::beans::XPropertySet;
using ::com::sun::star::util::XCloneable;
using ::com::sun::star::lang::Locale;
using ::com::sun::star::util::XChangesNotifier;
using ::com::sun::star::util::XChangesListener;

namespace sd
{
class MainSequenceChangeGuard
{
public:
    explicit MainSequenceChangeGuard( EffectSequenceHelper* pSequence )
    {
        mpMainSequence = dynamic_cast< MainSequence* >( pSequence );
        if( mpMainSequence == nullptr )
        {
            InteractiveSequence* pI = dynamic_cast< InteractiveSequence* >( pSequence );
            if( pI )
                mpMainSequence = pI->mpMainSequence;
        }
        DBG_ASSERT( mpMainSequence, "sd::MainSequenceChangeGuard::MainSequenceChangeGuard(), no main sequence to guard!" );

        if( mpMainSequence )
            mpMainSequence->mbIgnoreChanges++;
    }

    ~MainSequenceChangeGuard()
    {
        if( mpMainSequence )
            mpMainSequence->mbIgnoreChanges++;
    }

private:
    MainSequence* mpMainSequence;
};

CustomAnimationEffect::CustomAnimationEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
:   mnNodeType(-1),
    mnPresetClass(-1),
    mnFill(AnimationFill::HOLD),
    mfBegin(-1.0),
    mfDuration(-1.0),
    mfAbsoluteDuration(-1.0),
    mnGroupId(-1),
    mnIterateType(0),
    mfIterateInterval(0.0),
    mnParaDepth( -1 ),
    mbHasText(false),
    mfAcceleration( 1.0 ),
    mfDecelerate( 1.0 ),
    mbAutoReverse(false),
    mnTargetSubItem(0),
    mnCommand(0),
    mpEffectSequence( nullptr ),
    mbHasAfterEffect(false),
    mbAfterEffectOnNextEffect(false)
{
    setNode( xNode );
}

void CustomAnimationEffect::setNode( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
{
    mxNode = xNode;
    mxAudio.clear();
    mnCommand = 0;

    const Sequence< NamedValue > aUserData( mxNode->getUserData() );

    forconst NamedValue& rProp : aUserData )
    {
        if ( rProp.Name == "node-type" )
        {
            rProp.Value >>= mnNodeType;
        }
        else if ( rProp.Name == "preset-id" )
        {
            rProp.Value >>= maPresetId;
        }
        else if ( rProp.Name == "preset-sub-type" )
        {
            rProp.Value >>= maPresetSubType;
        }
        else if ( rProp.Name == "preset-class" )
        {
            rProp.Value >>= mnPresetClass;
        }
        else if ( rProp.Name == "preset-property" )
        {
            rProp.Value >>= maProperty;
        }
        else if ( rProp.Name == "group-id" )
        {
            rProp.Value >>= mnGroupId;
        }
    }

    // get effect start time
    mxNode->getBegin() >>= mfBegin;

    mfAcceleration = mxNode->getAcceleration();
    mfDecelerate = mxNode->getDecelerate();
    mbAutoReverse = mxNode->getAutoReverse();

    mnFill = mxNode->getFill();

    // get iteration data
    Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
    if( xIter.is() )
    {
        mfIterateInterval = xIter->getIterateInterval();
        mnIterateType = xIter->getIterateType();
        maTarget = xIter->getTarget();
        mnTargetSubItem = xIter->getSubItem();
    }
    else
    {
        mfIterateInterval = 0.0f;
        mnIterateType = 0;
    }

    // calculate effect duration and get target shape
    Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
    if( xEnumerationAccess.is() )
    {
        Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
        if( xEnumeration.is() )
        {
            while( xEnumeration->hasMoreElements() )
            {
                Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY );
                if( !xChildNode.is() )
                    continue;

                if( xChildNode->getType() == AnimationNodeType::AUDIO )
                {
                    mxAudio.set( xChildNode, UNO_QUERY );
                }
                else if( xChildNode->getType() == AnimationNodeType::COMMAND )
                {
                    Reference< XCommand > xCommand( xChildNode, UNO_QUERY );
                    if( xCommand.is() )
                    {
                        mnCommand = xCommand->getCommand();
                        if( !maTarget.hasValue() )
                            maTarget = xCommand->getTarget();
                    }
                }
                else
                {
                    double fBegin = 0.0;
                    double fDuration = 0.0;
                    xChildNode->getBegin() >>= fBegin;
                    xChildNode->getDuration() >>= fDuration;

                    fDuration += fBegin;
                    if( fDuration > mfDuration )
                        mfDuration = fDuration;

                    // no target shape yet?
                    if( !maTarget.hasValue() )
                    {
                        // go get it boys!
                        Reference< XAnimate > xAnimate( xChildNode, UNO_QUERY );
                        if( xAnimate.is() )
                        {
                            maTarget = xAnimate->getTarget();
                            mnTargetSubItem = xAnimate->getSubItem();
                        }
                    }
                }
            }
        }
    }

    mfAbsoluteDuration = mfDuration;
    double fRepeatCount = 1.0;
    if( (mxNode->getRepeatCount()) >>= fRepeatCount )
        mfAbsoluteDuration *= fRepeatCount;

    checkForText();
}

sal_Int32 CustomAnimationEffect::getNumberOfSubitems( const Any& aTarget, sal_Int16 nIterateType )
{
    sal_Int32 nSubItems = 0;

    try
    {
        // first get target text
        sal_Int32 nOnlyPara = -1;

        Reference< XText > xShape;
        aTarget >>= xShape;
        if( !xShape.is() )
        {
            ParagraphTarget aParaTarget;
            if( aTarget >>= aParaTarget )
            {
                xShape.set( aParaTarget.Shape, UNO_QUERY );
                nOnlyPara = aParaTarget.Paragraph;
            }
        }

        // now use the break iterator to iterate over the given text
        // and count the sub items

        if( xShape.is() )
        {
            // TODO/LATER: Optimize this, don't create a break iterator each time
            const Reference< uno::XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
            Reference < i18n::XBreakIterator > xBI = i18n::BreakIterator::create(xContext);

            Reference< XEnumerationAccess > xEA( xShape, UNO_QUERY_THROW );
            Reference< XEnumeration > xEnumeration( xEA->createEnumeration(), UNO_SET_THROW );
            css::lang::Locale aLocale;
            static constexpr OUStringLiteral aStrLocaleName( u"CharLocale" );
            Reference< XTextRange > xParagraph;

            sal_Int32 nPara = 0;
            while( xEnumeration->hasMoreElements() )
            {
                xEnumeration->nextElement() >>= xParagraph;

                // skip this if it's not the only paragraph we want to count
                if( (nOnlyPara != -1) && (nOnlyPara != nPara ) )
                    continue;

                if( nIterateType == TextAnimationType::BY_PARAGRAPH )
                {
                    nSubItems++;
                }
                else
                {
                    const OUString aText( xParagraph->getString() );
                    Reference< XPropertySet > xSet( xParagraph, UNO_QUERY_THROW );
                    xSet->getPropertyValue( aStrLocaleName ) >>= aLocale;

                    sal_Int32 nPos;
                    const sal_Int32 nEndPos = aText.getLength();

                    if( nIterateType == TextAnimationType::BY_WORD )
                    {
                        for( nPos = 0; nPos < nEndPos; nPos++ )
                        {
                            nPos = xBI->getWordBoundary(aText, nPos, aLocale, i18n::WordType::ANY_WORD, true).endPos;
                            nSubItems++;
                        }
                        break;
                    }
                    else
                    {
                        sal_Int32 nDone;
                        for( nPos = 0; nPos < nEndPos; nPos++ )
                        {
                            nPos = xBI->nextCharacters(aText, nPos, aLocale, i18n::CharacterIteratorMode::SKIPCELL, 0, nDone);
                            nSubItems++;
                        }
                    }
                }

                if( nPara == nOnlyPara )
                    break;

                nPara++;
            }
        }
    }
    catch( Exception& )
    {
        nSubItems = 0;
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::getNumberOfSubitems(), exception caught!" );
    }

    return nSubItems;
}

CustomAnimationEffect::~CustomAnimationEffect()
{
}

CustomAnimationEffectPtr CustomAnimationEffect::clone() const
{
    Reference< XCloneable > xCloneable( mxNode, UNO_QUERY_THROW );
    Reference< XAnimationNode > xNode( xCloneable->createClone(), UNO_QUERY_THROW );
    CustomAnimationEffectPtr pEffect = std::make_shared<CustomAnimationEffect>( xNode );
    pEffect->setEffectSequence( getEffectSequence() );
    return pEffect;
}

sal_Int32 CustomAnimationEffect::get_node_type( const Reference< XAnimationNode >& xNode )
{
    sal_Int16 nNodeType = -1;

    if( xNode.is() )
    {
        const Sequence< NamedValue > aUserData( xNode->getUserData() );
        if( aUserData.hasElements() )
        {
            const NamedValue* pProp = std::find_if(aUserData.begin(), aUserData.end(),
                [](const NamedValue& rProp) { return rProp.Name == "node-type"; });
            if (pProp != aUserData.end())
                pProp->Value >>= nNodeType;
        }
    }

    return nNodeType;
}

void CustomAnimationEffect::setPresetClassAndId( sal_Int16 nPresetClass, const OUString& rPresetId )
{
    if( mnPresetClass == nPresetClass && maPresetId == rPresetId )
        return;

    mnPresetClass = nPresetClass;
    maPresetId = rPresetId;
    if( !mxNode.is() )
        return;

    // first try to find a "preset-class" entry in the user data
    // and change it
    Sequence< NamedValue > aUserData( mxNode->getUserData() );
    sal_Int32 nLength = aUserData.getLength();
    bool bFoundPresetClass = false;
    bool bFoundPresetId = false;
    if( nLength )
    {
        auto [begin, end] = asNonConstRange(aUserData);
        NamedValue* pProp = std::find_if(begin, end,
            [](const NamedValue& rProp) { return rProp.Name == "preset-class"; });
        if (pProp != end)
        {
            pProp->Value <<= mnPresetClass;
            bFoundPresetClass = true;
        }

        pProp = std::find_if(begin, end,
            [](const NamedValue& rProp) { return rProp.Name == "preset-id"; });
        if (pProp != end)
        {
            pProp->Value <<= mnPresetClass;
            bFoundPresetId = true;
        }
    }

    // no "preset-class" entry inside user data, so add it
    if( !bFoundPresetClass )
    {
        aUserData.realloc( nLength + 1);
        auto& el = aUserData.getArray()[nLength];
        el.Name = "preset-class";
        el.Value <<= mnPresetClass;
        ++nLength;
    }

    if( !bFoundPresetId && maPresetId.getLength() > 0 )
    {
        aUserData.realloc( nLength + 1);
        auto& el = aUserData.getArray()[nLength];
        el.Name = "preset-id";
        el.Value <<= maPresetId;
    }

    mxNode->setUserData( aUserData );
}

void CustomAnimationEffect::setNodeType( sal_Int16 nNodeType )
{
    if( mnNodeType == nNodeType )
        return;

    mnNodeType = nNodeType;
    if( !mxNode.is() )
        return;

    // first try to find a "node-type" entry in the user data
    // and change it
    Sequence< NamedValue > aUserData( mxNode->getUserData() );
    sal_Int32 nLength = aUserData.getLength();
    bool bFound = false;
    if( nLength )
    {
        auto [begin, end] = asNonConstRange(aUserData);
        NamedValue* pProp = std::find_if(begin, end,
            [](const NamedValue& rProp) { return rProp.Name == "node-type"; });
        if (pProp != end)
        {
            pProp->Value <<= mnNodeType;
            bFound = true;
        }
    }

    // no "node-type" entry inside user data, so add it
    if( !bFound )
    {
        aUserData.realloc( nLength + 1);
        auto& el = aUserData.getArray()[nLength];
        el.Name = "node-type";
        el.Value <<= mnNodeType;
    }

    mxNode->setUserData( aUserData );
}

void CustomAnimationEffect::setGroupId( sal_Int32 nGroupId )
{
    mnGroupId = nGroupId;
    if( !mxNode.is() )
        return;

    // first try to find a "group-id" entry in the user data
    // and change it
    Sequence< NamedValue > aUserData( mxNode->getUserData() );
    sal_Int32 nLength = aUserData.getLength();
    bool bFound = false;
    if( nLength )
    {
        auto [begin, end] = asNonConstRange(aUserData);
        NamedValue* pProp = std::find_if(begin, end,
            [](const NamedValue& rProp) { return rProp.Name == "group-id"; });
        if (pProp != end)
        {
            pProp->Value <<= mnGroupId;
            bFound = true;
        }
    }

    // no "group-id" entry inside user data, so add it
    if( !bFound )
    {
        aUserData.realloc( nLength + 1);
        auto& el = aUserData.getArray()[nLength];
        el.Name = "group-id";
        el.Value <<= mnGroupId;
    }

    mxNode->setUserData( aUserData );
}

/** checks if the text for this effect has changed and updates internal flags.
    returns true if something changed.
*/

bool CustomAnimationEffect::checkForText( const std::vector<sal_Int32>* paragraphNumberingLevel )
{
    bool bChange = false;

    Reference< XText > xText;

    if( maTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
    {
        // calc para depth
        ParagraphTarget aParaTarget;
        maTarget >>= aParaTarget;

        xText.set( aParaTarget.Shape, UNO_QUERY );

        // get paragraph
        if( xText.is() )
        {
            sal_Int32 nPara = aParaTarget.Paragraph;

            bool bHasText = false;
            sal_Int32 nParaDepth = 0;

            if ( paragraphNumberingLevel )
            {
                bHasText = !paragraphNumberingLevel->empty();
                if (nPara >= 0 && o3tl::make_unsigned(nPara) < paragraphNumberingLevel->size())
                    nParaDepth = paragraphNumberingLevel->at(nPara);
            }
            else
            {
                Reference< XEnumerationAccess > xEA( xText, UNO_QUERY );
                if( xEA.is() )
                {
                    Reference< XEnumeration > xEnumeration = xEA->createEnumeration();
                    if( xEnumeration.is() )
                    {
                        bHasText = xEnumeration->hasMoreElements();

                        while( xEnumeration->hasMoreElements() && nPara-- )
                            xEnumeration->nextElement();

                        if( xEnumeration->hasMoreElements() )
                        {
                            Reference< XPropertySet > xParaSet;
                            xEnumeration->nextElement() >>= xParaSet;
                            if( xParaSet.is() )
                            {
                                xParaSet->getPropertyValue( u"NumberingLevel"_ustr ) >>= nParaDepth;
                            }
                        }
                    }
                }
            }

            if( bHasText )
            {
                bChange |= bHasText != mbHasText;
                mbHasText = bHasText;

                bChange |= nParaDepth != mnParaDepth;
                mnParaDepth = nParaDepth;
            }
        }
    }
    else
    {
        maTarget >>= xText;
        bool bHasText = xText.is() && !xText->getString().isEmpty();
        bChange |= bHasText != mbHasText;
        mbHasText = bHasText;
    }

    bChange |= calculateIterateDuration();
    return bChange;
}

bool CustomAnimationEffect::calculateIterateDuration()
{
    bool bChange = false;

    // if we have an iteration, we must also calculate the
    // 'true' container duration, that is
    // ( ( is form animated ) ? [contained effects duration] : 0 ) +
    // ( [number of animated children] - 1 ) * [interval-delay] + [contained effects duration]
    Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
    if( xIter.is() )
    {
        double fDuration = mfDuration;
        const double fSubEffectDuration = mfDuration;

        if( mnTargetSubItem != ShapeAnimationSubType::ONLY_BACKGROUND ) // does not make sense for iterate container but better check
        {
            const sal_Int32 nSubItems = getNumberOfSubitems( maTarget, mnIterateType );
            if( nSubItems )
            {
                const double f = (nSubItems-1) * mfIterateInterval;
                fDuration += f;
            }
        }

        // if we also animate the form first, we have to add the
        // sub effect duration to the whole effect duration
        if( mnTargetSubItem == ShapeAnimationSubType::AS_WHOLE )
            fDuration += fSubEffectDuration;

        bChange |= fDuration != mfAbsoluteDuration;
        mfAbsoluteDuration = fDuration;
    }

    return bChange;
}

void CustomAnimationEffect::setTarget( const css::uno::Any& rTarget )
{
    try
    {
        maTarget = rTarget;

        // first, check special case for random node
        Reference< XInitialization > xInit( mxNode, UNO_QUERY );
        if( xInit.is() )
        {
            const Sequence< Any > aArgs( &maTarget, 1 );
            xInit->initialize( aArgs );
        }
        else
        {
            Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
            if( xIter.is() )
            {
                xIter->setTarget(maTarget);
            }
            else
            {
                Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
                if( xEnumerationAccess.is() )
                {
                    Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
                    if( xEnumeration.is() )
                    {
                        while( xEnumeration->hasMoreElements() )
                        {
                            const Any aElem( xEnumeration->nextElement() );
                            Reference< XAnimate > xAnimate( aElem, UNO_QUERY );
                            if( xAnimate.is() )
                                xAnimate->setTarget( rTarget );
                            else
                            {
                                Reference< XCommand > xCommand( aElem, UNO_QUERY );
                                if( xCommand.is() )
                                    xCommand->setTarget( rTarget );
                            }
                        }
                    }
                }
            }
        }
        checkForText();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setTarget()" );
    }
}

void CustomAnimationEffect::setTargetSubItem( sal_Int16 nSubItem )
{
    try
    {
        mnTargetSubItem = nSubItem;

        Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
        if( xIter.is() )
        {
            xIter->setSubItem(mnTargetSubItem);
        }
        else
        {
            Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
            if( xEnumerationAccess.is() )
            {
                Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
                if( xEnumeration.is() )
                {
                    while( xEnumeration->hasMoreElements() )
                    {
                        Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
                        if( xAnimate.is() )
                            xAnimate->setSubItem( mnTargetSubItem );
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setTargetSubItem()" );
    }
}

void CustomAnimationEffect::setDuration( double fDuration )
{
    if( (mfDuration == -1.0) || (mfDuration == fDuration) )
        return;

    try
    {
        double fScale = fDuration / mfDuration;
        mfDuration = fDuration;
        double fRepeatCount = 1.0;
        getRepeatCount() >>= fRepeatCount;
        mfAbsoluteDuration = mfDuration * fRepeatCount;

        // calculate effect duration and get target shape
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() )
                {
                    Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xChildNode.is() )
                        continue;

                    double fChildBegin = 0.0;
                    xChildNode->getBegin() >>= fChildBegin;
                    if(  fChildBegin != 0.0 )
                    {
                        fChildBegin *= fScale;
                        xChildNode->setBegin( Any( fChildBegin ) );
                    }

                    double fChildDuration = 0.0;
                    xChildNode->getDuration() >>= fChildDuration;
                    if( fChildDuration != 0.0 )
                    {
                        fChildDuration *= fScale;
                        xChildNode->setDuration( Any( fChildDuration ) );
                    }
                }
            }
        }
        calculateIterateDuration();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setDuration()" );
    }
}

void CustomAnimationEffect::setBegin( double fBegin )
{
    if( mxNode.is() ) try
    {
        mfBegin = fBegin;
        mxNode->setBegin( Any( fBegin ) );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setBegin()" );
    }
}

void CustomAnimationEffect::setAcceleration( double fAcceleration )
{
    if( mxNode.is() ) try
    {
        mfAcceleration = fAcceleration;
        mxNode->setAcceleration( fAcceleration );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setAcceleration()" );
    }
}

void CustomAnimationEffect::setDecelerate( double fDecelerate )
{
    if( mxNode.is() ) try
    {
        mfDecelerate = fDecelerate;
        mxNode->setDecelerate( fDecelerate );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setDecelerate()" );
    }
}

void CustomAnimationEffect::setAutoReverse( bool bAutoReverse )
{
    if( mxNode.is() ) try
    {
        mbAutoReverse = bAutoReverse;
        mxNode->setAutoReverse( bAutoReverse );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setAutoReverse()" );
    }
}

void CustomAnimationEffect::replaceNode( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
{
    sal_Int16 nNodeType = mnNodeType;
    Any aTarget = maTarget;

    sal_Int16 nFill = mnFill;
    double fBegin = mfBegin;
    double fDuration = mfDuration;
    double fAcceleration = mfAcceleration;
    double fDecelerate = mfDecelerate ;
    bool bAutoReverse = mbAutoReverse;
    Reference< XAudio > xAudio( mxAudio );
    sal_Int16 nIterateType = mnIterateType;
    double fIterateInterval = mfIterateInterval;
    sal_Int16 nSubItem = mnTargetSubItem;

    setNode( xNode );

    setAudio( xAudio );
    setNodeType( nNodeType );
    setTarget( aTarget );
    setTargetSubItem( nSubItem );
    setDuration( fDuration );
    setBegin( fBegin );
    setFill( nFill );

    setAcceleration( fAcceleration );
    setDecelerate( fDecelerate );
    setAutoReverse( bAutoReverse );

    if( nIterateType != mnIterateType )
        setIterateType( nIterateType );

    if( mnIterateType && ( fIterateInterval != mfIterateInterval ) )
        setIterateInterval( fIterateInterval );
}

Reference< XShape > CustomAnimationEffect::getTargetShape() const
{
    Reference< XShape > xShape;
    maTarget >>= xShape;
    if( !xShape.is() )
    {
        ParagraphTarget aParaTarget;
        if( maTarget >>= aParaTarget )
            xShape = aParaTarget.Shape;
    }

    return xShape;
}

Any CustomAnimationEffect::getRepeatCount() const
{
    if( mxNode.is() )
    {
        return mxNode->getRepeatCount();
    }
    else
    {
        Any aAny;
        return aAny;
    }
}

Any CustomAnimationEffect::getEnd() const
{
    if( mxNode.is() )
    {
        return mxNode->getEnd();
    }
    else
    {
        Any aAny;
        return aAny;
    }
}

void CustomAnimationEffect::setRepeatCount( const Any& rRepeatCount )
{
    if( mxNode.is() )
    {
        mxNode->setRepeatCount( rRepeatCount );
        double fRepeatCount = 1.0;
        rRepeatCount >>= fRepeatCount;
        mfAbsoluteDuration = mfDuration * fRepeatCount;
    }
}

void CustomAnimationEffect::setEnd( const Any& rEnd )
{
    if( mxNode.is() )
        mxNode->setEnd( rEnd );
}

void CustomAnimationEffect::setFill( sal_Int16 nFill )
{
    if (mxNode.is())
    {
        mnFill = nFill;
        mxNode->setFill( nFill );
    }
}

Reference< XAnimationNode > CustomAnimationEffect::createAfterEffectNode() const
{
    DBG_ASSERT( mbHasAfterEffect, "sd::CustomAnimationEffect::createAfterEffectNode(), this node has no after effect!" );

    const Reference< XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );

    Reference< XAnimate > xAnimate;
    if( maDimColor.hasValue() )
        xAnimate = AnimateColor::create( xContext );
    else
        xAnimate = AnimateSet::create( xContext );

    Any aTo;
    OUString aAttributeName;

    if( maDimColor.hasValue() )
    {
        aTo = maDimColor;
        aAttributeName = "DimColor";
    }
    else
    {
        aTo <<= false;
        aAttributeName = "Visibility";
    }

    Any aBegin;
    if( !mbAfterEffectOnNextEffect ) // sameClick
    {
        Event aEvent;

        aEvent.Source <<= getNode();
        aEvent.Trigger = EventTrigger::END_EVENT;
        aEvent.Repeat = 0;

        aBegin <<= aEvent;
    }
    else
    {
        aBegin <<= 0.0;
    }

    xAnimate->setBegin( aBegin );
    xAnimate->setTo( aTo );
    xAnimate->setAttributeName( aAttributeName );

    xAnimate->setDuration( Any( 0.001 ) );
    xAnimate->setFill( AnimationFill::HOLD );
    xAnimate->setTarget( maTarget );

    return xAnimate;
}

void CustomAnimationEffect::setIterateType( sal_Int16 nIterateType )
{
    if( mnIterateType == nIterateType )
        return;

    try
    {
        // do we need to exchange the container node?
        if( (mnIterateType == 0) || (nIterateType == 0) )
        {
            sal_Int16 nTargetSubItem = mnTargetSubItem;

            const Reference< XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
            Reference< XTimeContainer > xNewContainer;
            if(nIterateType)
            {
                xNewContainer.set( IterateContainer::create( xContext ) );
            }
            else
                xNewContainer.set( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW );

            Reference< XTimeContainer > xOldContainer( mxNode, UNO_QUERY_THROW );
            Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
            Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
            while( xEnumeration->hasMoreElements() )
            {
                Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
                xOldContainer->removeChild( xChildNode );
                xNewContainer->appendChild( xChildNode );
            }

            xNewContainer->setBegin( mxNode->getBegin() );
            xNewContainer->setDuration( mxNode->getDuration() );
            xNewContainer->setEnd( mxNode->getEnd() );
            xNewContainer->setEndSync( mxNode->getEndSync() );
            xNewContainer->setRepeatCount( mxNode->getRepeatCount() );
            xNewContainer->setFill( mxNode->getFill() );
            xNewContainer->setFillDefault( mxNode->getFillDefault() );
            xNewContainer->setRestart( mxNode->getRestart() );
            xNewContainer->setRestartDefault( mxNode->getRestartDefault() );
            xNewContainer->setAcceleration( mxNode->getAcceleration() );
            xNewContainer->setDecelerate( mxNode->getDecelerate() );
            xNewContainer->setAutoReverse( mxNode->getAutoReverse() );
            xNewContainer->setRepeatDuration( mxNode->getRepeatDuration() );
            xNewContainer->setEndSync( mxNode->getEndSync() );
            xNewContainer->setRepeatCount( mxNode->getRepeatCount() );
            xNewContainer->setUserData( mxNode->getUserData() );

            mxNode = xNewContainer;

            Any aTarget;
            if( nIterateType )
            {
                Reference< XIterateContainer > xIter( mxNode, UNO_QUERY_THROW );
                xIter->setTarget(maTarget);
                xIter->setSubItem( nTargetSubItem );
            }
            else
            {
                aTarget = maTarget;
            }

            Reference< XEnumerationAccess > xEA( mxNode, UNO_QUERY_THROW );
            Reference< XEnumeration > xE( xEA->createEnumeration(), UNO_SET_THROW );
            while( xE->hasMoreElements() )
            {
                Reference< XAnimate > xAnimate( xE->nextElement(), UNO_QUERY );
                if( xAnimate.is() )
                {
                    xAnimate->setTarget( aTarget );
                    xAnimate->setSubItem( nTargetSubItem );
                }
            }
        }

        mnIterateType = nIterateType;

        // if we have an iteration container, we must set its type
        if( mnIterateType )
        {
            Reference< XIterateContainer > xIter( mxNode, UNO_QUERY_THROW );
            xIter->setIterateType( nIterateType );
        }

        checkForText();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setIterateType()" );
    }
}

void CustomAnimationEffect::setIterateInterval( double fIterateInterval )
{
    if( mfIterateInterval == fIterateInterval )
        return;

    Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );

    DBG_ASSERT( xIter.is(), "sd::CustomAnimationEffect::setIterateInterval(), not an iteration node" );
    if( xIter.is() )
    {
        mfIterateInterval = fIterateInterval;
        xIter->setIterateInterval( fIterateInterval );
    }

    calculateIterateDuration();
}

OUString CustomAnimationEffect::getPath() const
{
    OUString aPath;

    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimateMotion > xMotion( xEnumeration->nextElement(), UNO_QUERY );
            if( xMotion.is() )
            {
                xMotion->getPath() >>= aPath;
                break;
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::getPath()" );
    }

    return aPath;
}

void CustomAnimationEffect::setPath( const OUString& rPath )
{
    if( !mxNode.is() )
        return;

    try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimateMotion > xMotion( xEnumeration->nextElement(), UNO_QUERY );
            if( xMotion.is() )
            {

                MainSequenceChangeGuard aGuard( mpEffectSequence );
                xMotion->setPath( Any( rPath ) );
                break;
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setPath()" );
    }
}

Any CustomAnimationEffect::getProperty( sal_Int32 nNodeType, std::u16string_view rAttributeName, EValue eValue )
{
    Any aProperty;
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() && !aProperty.hasValue() )
                {
                    Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xAnimate.is() )
                        continue;

                    if( xAnimate->getType() == nNodeType )
                    {
                        if( xAnimate->getAttributeName() == rAttributeName )
                        {
                            switch( eValue )
                            {
                            case EValue::To:   aProperty = xAnimate->getTo(); break;
                            case EValue::By:   aProperty = xAnimate->getBy(); break;
                            }
                        }
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::getProperty()" );
    }

    return aProperty;
}

bool CustomAnimationEffect::setProperty( sal_Int32 nNodeType, std::u16string_view rAttributeName, EValue eValue, const Any& rValue )
{
    bool bChanged = false;
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() )
                {
                    Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xAnimate.is() )
                        continue;

                    if( xAnimate->getType() == nNodeType )
                    {
                        if( xAnimate->getAttributeName() == rAttributeName )
                        {
                            switch( eValue )
                            {
                            case EValue::To:
                                if( xAnimate->getTo() != rValue )
                                {
                                    xAnimate->setTo( rValue );
                                    bChanged = true;
                                }
                                break;
                            case EValue::By:
                                if( xAnimate->getTo() != rValue )
                                {
                                    xAnimate->setBy( rValue );
                                    bChanged = true;
                                }
                                break;
                            }
                        }
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setProperty()" );
    }

    return bChanged;
}

static bool implIsColorAttribute( std::u16string_view rAttributeName )
{
    return rAttributeName == u"FillColor" || rAttributeName == u"LineColor" || rAttributeName == u"CharColor";
}

Any CustomAnimationEffect::getColor( sal_Int32 nIndex )
{
    Any aColor;
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() && !aColor.hasValue() )
                {
                    Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xAnimate.is() )
                        continue;

                    switch( xAnimate->getType() )
                    {
                    case AnimationNodeType::SET:
                    case AnimationNodeType::ANIMATE:
                        if( !implIsColorAttribute( xAnimate->getAttributeName() ) )
                            break;
                        [[fallthrough]];
                    case AnimationNodeType::ANIMATECOLOR:
                        Sequence<Any> aValues( xAnimate->getValues() );
                        if( aValues.hasElements() )
                        {
                            if( aValues.getLength() > nIndex )
                                aColor = aValues[nIndex];
                        }
                        else if( nIndex == 0 )
                            aColor = xAnimate->getFrom();
                        else
                            aColor = xAnimate->getTo();
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::getColor()" );
    }

    return aColor;
}

void CustomAnimationEffect::setColor( sal_Int32 nIndex, const Any& rColor )
{
    if( !mxNode.is() )
        return;

    try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() )
                {
                    Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xAnimate.is() )
                        continue;

                    switch( xAnimate->getType() )
                    {
                    case AnimationNodeType::SET:
                    case AnimationNodeType::ANIMATE:
                        if( !implIsColorAttribute( xAnimate->getAttributeName() ) )
                            break;
                        [[fallthrough]];
                    case AnimationNodeType::ANIMATECOLOR:
                    {
                        Sequence<Any> aValues( xAnimate->getValues() );
                        if( aValues.hasElements() )
                        {
                            if( aValues.getLength() > nIndex )
                            {
                                aValues.getArray()[nIndex] = rColor;
                                xAnimate->setValues( aValues );
                            }
                        }
                        else if( (nIndex == 0) && xAnimate->getFrom().hasValue() )
                            xAnimate->setFrom(rColor);
                        else if( (nIndex == 1) && xAnimate->getTo().hasValue() )
                            xAnimate->setTo(rColor);
                    }
                    break;

                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setColor()" );
    }
}

Any CustomAnimationEffect::getTransformationProperty( sal_Int32 nTransformType, EValue eValue )
{
    Any aProperty;
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() && !aProperty.hasValue() )
                {
                    Reference< XAnimateTransform > xTransform( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xTransform.is() )
                        continue;

                    if( xTransform->getTransformType() == nTransformType )
                    {
                        switch( eValue )
                        {
                        case EValue::To:   aProperty = xTransform->getTo(); break;
                        case EValue::By:   aProperty = xTransform->getBy(); break;
                        }
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::getTransformationProperty()" );
    }

    return aProperty;
}

bool CustomAnimationEffect::setTransformationProperty( sal_Int32 nTransformType, EValue eValue, const Any& rValue )
{
    bool bChanged = false;
    if( mxNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
        if( xEnumerationAccess.is() )
        {
            Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
            if( xEnumeration.is() )
            {
                while( xEnumeration->hasMoreElements() )
                {
                    Reference< XAnimateTransform > xTransform( xEnumeration->nextElement(), UNO_QUERY );
                    if( !xTransform.is() )
                        continue;

                    if( xTransform->getTransformType() == nTransformType )
                    {
                        switch( eValue )
                        {
                        case EValue::To:
                            if( xTransform->getTo() != rValue )
                            {
                                xTransform->setTo( rValue );
                                bChanged = true;
                            }
                            break;
                        case EValue::By:
                            if( xTransform->getBy() != rValue )
                            {
                                xTransform->setBy( rValue );
                                bChanged = true;
                            }
                            break;
                        }
                    }
                }
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setTransformationProperty()" );
    }

    return bChanged;
}

void CustomAnimationEffect::createAudio( const css::uno::Any& rSource )
{
    DBG_ASSERT( !mxAudio.is(), "sd::CustomAnimationEffect::createAudio(), node already has an audio!" );

    if( mxAudio.is() )
        return;

    try
    {
        const Reference< XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
        Reference< XAudio > xAudio( Audio::create( xContext ) );
        xAudio->setSource( rSource );
        xAudio->setVolume( 1.0 );
        setAudio( xAudio );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::createAudio()" );
    }
}

static Reference< XCommand > findCommandNode( const Reference< XAnimationNode >& xRootNode )
{
    Reference< XCommand > xCommand;

    if( xRootNode.is() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( xRootNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( !xCommand.is() && xEnumeration->hasMoreElements() )
        {
            Reference< XAnimationNode > xNode( xEnumeration->nextElement(), UNO_QUERY );
            if( xNode.is() && (xNode->getType() == AnimationNodeType::COMMAND) )
                xCommand.set( xNode, UNO_QUERY_THROW );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::findCommandNode()" );
    }

    return xCommand;
}

void CustomAnimationEffect::removeAudio()
{
    try
    {
        Reference< XAnimationNode > xChild;

        if( mxAudio.is() )
        {
            xChild = mxAudio;
            mxAudio.clear();
        }
        else if( mnCommand == EffectCommands::STOPAUDIO )
        {
            xChild = findCommandNode( mxNode );
            mnCommand = 0;
        }

        if( xChild.is() )
        {
            Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
            if( xContainer.is() )
                xContainer->removeChild( xChild );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::removeAudio()" );
    }

}

void CustomAnimationEffect::setAudio( const Reference< css::animations::XAudio >& xAudio )
{
    if( mxAudio == xAudio )
        return;

    try
    {
        removeAudio();
        mxAudio = xAudio;
        Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
        if( xContainer.is() && mxAudio.is() )
            xContainer->appendChild( mxAudio );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setAudio()" );
    }
}

void CustomAnimationEffect::setStopAudio()
{
    if( mnCommand == EffectCommands::STOPAUDIO )
        return;

    try
    {
        if( mxAudio.is() )
            removeAudio();

        const Reference< XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );
        Reference< XCommand > xCommand( Command::create( xContext ) );

        xCommand->setCommand( EffectCommands::STOPAUDIO );

        Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY_THROW );
        xContainer->appendChild( xCommand );

        mnCommand = EffectCommands::STOPAUDIO;
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::CustomAnimationEffect::setStopAudio()" );
    }
}

bool CustomAnimationEffect::getStopAudio() const
{
    return mnCommand == EffectCommands::STOPAUDIO;
}

rtl::Reference<SdrPathObj> CustomAnimationEffect::createSdrPathObjFromPath(SdrModel&&nbsp;rTargetModel)
{
    rtl::Reference<SdrPathObj> pPathObj = new SdrPathObj(rTargetModel, SdrObjKind::PathLine);
    updateSdrPathObjFromPath( *pPathObj );
    return pPathObj;
}

void CustomAnimationEffect::updateSdrPathObjFromPath( SdrPathObj& rPathObj )
{
    ::basegfx::B2DPolyPolygon aPolyPoly;
    if( ::basegfx::utils::importFromSvgD( aPolyPoly, getPath(), true, nullptr ) )
    {
        SdrObject* pObj = SdrObject::getSdrObjectFromXShape(getTargetShape());
        if( pObj )
        {
            SdrPage* pPage = pObj->getSdrPageFromSdrObject();
            if( pPage )
            {
                const Size aPageSize( pPage->GetSize() );
                aPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(static_cast<double>(aPageSize.Width()), static_cast<double>(aPageSize.Height())));
            }

            const ::tools::Rectangle aBoundRect( pObj->GetCurrentBoundRect() );
            const Point aCenter( aBoundRect.Center() );
            aPolyPoly.translate(aCenter.X(), aCenter.Y());
        }
    }

    rPathObj.SetPathPoly( aPolyPoly );
}

void CustomAnimationEffect::updatePathFromSdrPathObj( const SdrPathObj& rPathObj )
{
    ::basegfx::B2DPolyPolygon aPolyPoly( rPathObj.GetPathPoly() );

    SdrObject* pObj = SdrObject::getSdrObjectFromXShape(getTargetShape());
    if( pObj )
    {
        ::tools::Rectangle aBoundRect(0,0,0,0);

        drawinglayer::primitive2d::Primitive2DContainer xPrimitives;
        pObj->GetViewContact().getViewIndependentPrimitive2DContainer(xPrimitives);
        const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
        const basegfx::B2DRange aRange(xPrimitives.getB2DRange(aViewInformation2D));

        if(!aRange.isEmpty())
        {
            aBoundRect = ::tools::Rectangle(
                    static_cast<sal_Int32>(floor(aRange.getMinX())), static_cast<sal_Int32>(floor(aRange.getMinY())),
                    static_cast<sal_Int32>(ceil(aRange.getMaxX())), static_cast<sal_Int32>(ceil(aRange.getMaxY())));
        }

        const Point aCenter( aBoundRect.Center() );

        aPolyPoly.translate(-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, truetruetrue) );
}

EffectSequenceHelper::EffectSequenceHelper()
: mnSequenceType( EffectNodeType::DEFAULT )
{
}

EffectSequenceHelper::EffectSequenceHelper( css::uno::Reference< css::animations::XTimeContainer > xSequenceRoot )
: mxSequenceRoot(std::move( xSequenceRoot )), mnSequenceType( EffectNodeType::DEFAULT )
{
    Reference< XAnimationNode > xNode( mxSequenceRoot, UNO_QUERY_THROW );
    create( xNode );
}

EffectSequenceHelper::~EffectSequenceHelper()
{
    reset();
}

void EffectSequenceHelper::reset()
{
    for( CustomAnimationEffectPtr& pEffect : maEffects )
    {
        pEffect->setEffectSequence(nullptr);
    }
    maEffects.clear();
}

Reference< XAnimationNode > EffectSequenceHelper::getRootNode()
{
    return mxSequenceRoot;
}

void EffectSequenceHelper::append( const CustomAnimationEffectPtr& pEffect )
{
    pEffect->setEffectSequence( this );
    maEffects.push_back(pEffect);
    rebuild();
}

CustomAnimationEffectPtr EffectSequenceHelper::append( const CustomAnimationPresetPtr& pPreset, const Any& rTarget, double fDuration /* = -1.0 */ )
{
    CustomAnimationEffectPtr pEffect;

    if( pPreset )
    {
        Reference< XAnimationNode > xNode( pPreset->create( u""_ustr ) );
        if( xNode.is() )
        {
            // first, filter all only ui relevant user data
            std::vector< NamedValue > aNewUserData;
            Sequence< NamedValue > aUserData( xNode->getUserData() );

            std::copy_if(std::cbegin(aUserData), std::cend(aUserData), std::back_inserter(aNewUserData),
                [](const NamedValue& rProp) { return rProp.Name != "text-only" && rProp.Name != "preset-property"; });

            if( !aNewUserData.empty() )
            {
                aUserData = ::comphelper::containerToSequence( aNewUserData );
                xNode->setUserData( aUserData );
            }

            // check target, maybe we need to force it to text
            sal_Int16 nSubItem = ShapeAnimationSubType::AS_WHOLE;

            if( rTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
            {
                nSubItem = ShapeAnimationSubType::ONLY_TEXT;
            }
            else if( pPreset->isTextOnly() )
            {
                Reference< XShape > xShape;
                rTarget >>= xShape;
                if( xShape.is() )
                {
                    // that's bad, we target a shape here but the effect is only for text
                    // so change subitem
                    nSubItem = ShapeAnimationSubType::ONLY_TEXT;
                }
            }

            // now create effect from preset
            pEffect = std::make_shared<CustomAnimationEffect>( xNode );
            pEffect->setEffectSequence( this );
            pEffect->setTarget( rTarget );
            pEffect->setTargetSubItem( nSubItem );
            if( fDuration != -1.0 )
                pEffect->setDuration( fDuration );

            maEffects.push_back(pEffect);

            rebuild();
        }
    }

    DBG_ASSERT( pEffect, "sd::EffectSequenceHelper::append(), failed!" );
    return pEffect;
}

CustomAnimationEffectPtr EffectSequenceHelper::append( const SdrPathObj& rPathObj, const Any& rTarget, double fDuration /* = -1.0 */, const OUString& rPresetId )
{
    CustomAnimationEffectPtr pEffect;

    if( fDuration <= 0.0 )
        fDuration = 2.0;

    try
    {
        Reference< XTimeContainer > xEffectContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
        Reference< XAnimationNode > xAnimateMotion( AnimateMotion::create( ::comphelper::getProcessComponentContext() ) );

        xAnimateMotion->setDuration( Any( fDuration ) );
        xAnimateMotion->setFill( AnimationFill::HOLD );
        xEffectContainer->appendChild( xAnimateMotion );

        sal_Int16 nSubItem = ShapeAnimationSubType::AS_WHOLE;

        if( rTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
            nSubItem = ShapeAnimationSubType::ONLY_TEXT;

        pEffect = std::make_shared<CustomAnimationEffect>( xEffectContainer );
        pEffect->setEffectSequence( this );
        pEffect->setTarget( rTarget );
        pEffect->setTargetSubItem( nSubItem );
        pEffect->setNodeType( css::presentation::EffectNodeType::ON_CLICK );
        pEffect->setPresetClassAndId( css::presentation::EffectPresetClass::MOTIONPATH, rPresetId );
        pEffect->setAcceleration( 0.5 );
        pEffect->setDecelerate( 0.5 );
        pEffect->setFill( AnimationFill::HOLD );
        pEffect->setBegin( 0.0 );
        pEffect->updatePathFromSdrPathObj( rPathObj );
        if( fDuration != -1.0 )
            pEffect->setDuration( fDuration );

        maEffects.push_back(pEffect);

        rebuild();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::EffectSequenceHelper::append()" );
    }

    return pEffect;
}

void EffectSequenceHelper::replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pPreset, const OUString& rPresetSubType, double fDuration /* = -1.0 */ )
{
    if( !(pEffect && pPreset) )
        return;

    try
    {
        Reference< XAnimationNode > xNewNode( pPreset->create( rPresetSubType ) );
        if( xNewNode.is() )
        {
            pEffect->replaceNode( xNewNode );
            if( fDuration != -1.0 )
                pEffect->setDuration( fDuration );
        }

        rebuild();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::EffectSequenceHelper::replace()" );
    }
}

void EffectSequenceHelper::replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pPreset, double fDuration /* = -1.0 */ )
{
    replace( pEffect, pPreset, u""_ustr, fDuration );
}

void EffectSequenceHelper::remove( const CustomAnimationEffectPtr& pEffect )
{
    if( pEffect )
    {
        pEffect->setEffectSequence( nullptr );
        maEffects.remove( pEffect );
    }

    rebuild();
}

void EffectSequenceHelper::moveToBeforeEffect( const CustomAnimationEffectPtr& pEffect, const CustomAnimationEffectPtr& pInsertBefore)
{
    if ( pEffect )
    {
        maEffects.remove( pEffect );
        EffectSequence::iterator aInsertIter( find( pInsertBefore ) );

        // aInsertIter being end() is OK: pInsertBefore could be null, so put at end.
        maEffects.insert( aInsertIter, pEffect );

        rebuild();
    }
}

void EffectSequenceHelper::rebuild()
{
    implRebuild();
}

void EffectSequenceHelper::implRebuild()
{
    try
    {
        // first we delete all time containers on the first two levels
        Reference< XEnumerationAccess > xEnumerationAccess( mxSequenceRoot, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        while( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
            Reference< XTimeContainer > xChildContainer( xChildNode, UNO_QUERY_THROW );

            Reference< XEnumerationAccess > xChildEnumerationAccess( xChildNode, UNO_QUERY_THROW );
            Reference< XEnumeration > xChildEnumeration( xChildEnumerationAccess->createEnumeration(), UNO_SET_THROW );
            while( xChildEnumeration->hasMoreElements() )
            {
                Reference< XAnimationNode > xNode( xChildEnumeration->nextElement(), UNO_QUERY_THROW );
                xChildContainer->removeChild( xNode );
            }

            mxSequenceRoot->removeChild( xChildNode );
        }

        // second, rebuild main sequence
        EffectSequence::iterator aIter( maEffects.begin() );
        EffectSequence::iterator aEnd( maEffects.end() );
        if( aIter != aEnd )
        {
            std::vector< sd::AfterEffectNode > aAfterEffects;

            CustomAnimationEffectPtr pEffect = *aIter++;

            bool bFirst = true;
            do
            {
                // create a par container for the next click node and all following with and after effects
                Reference< XTimeContainer > xOnClickContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );

                Event aEvent;
                if( mxEventSource.is() )
                {
                    aEvent.Source <<= mxEventSource;
                    aEvent.Trigger = EventTrigger::ON_CLICK;
                }
                else
                {
                    aEvent.Trigger = EventTrigger::ON_NEXT;
                }
                aEvent.Repeat = 0;

                Any aBegin( aEvent );
                if( bFirst )
                {
                    // if the first node is not a click action, this click container
                    // must not have INDEFINITE begin but start at 0s
                    bFirst = false;
                    if( pEffect->getNodeType() != EffectNodeType::ON_CLICK )
                        aBegin <<= 0.0;
                }

                xOnClickContainer->setBegin( aBegin );

                mxSequenceRoot->appendChild( xOnClickContainer );

                double fBegin = 0.0;

                do
                {
                    // create a par container for the current click or after effect node and all following with effects
                    Reference< XTimeContainer > xWithContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
                    xWithContainer->setBegin( Any( fBegin ) );
                    xOnClickContainer->appendChild( xWithContainer );

                    double fDuration = 0.0;
                    do
                    {
                        Reference< XAnimationNode > xEffectNode( pEffect->getNode() );
                        xWithContainer->appendChild( xEffectNode );

                        if( pEffect->hasAfterEffect() )
                        {
                            Reference< XAnimationNode > xAfterEffect( pEffect->createAfterEffectNode() );
                            AfterEffectNode a( xAfterEffect, xEffectNode, pEffect->IsAfterEffectOnNext() );
                            aAfterEffects.push_back( a );
                        }

                        double fTemp = pEffect->getBegin() + pEffect->getAbsoluteDuration();
                        if( fTemp > fDuration )
                            fDuration = fTemp;

                        if( aIter != aEnd )
                            pEffect = *aIter++;
                        else
                            pEffect.reset();
                    }
                    while( pEffect && (pEffect->getNodeType() == EffectNodeType::WITH_PREVIOUS) );

                    fBegin += fDuration;
                }
                while( pEffect && (pEffect->getNodeType() != EffectNodeType::ON_CLICK) );
            }
            while( pEffect );

            // process after effect nodes
            std::for_each( aAfterEffects.begin(), aAfterEffects.end(), stl_process_after_effect_node_func );

            updateTextGroups();

            // reset duration, might have been altered (see below)
            mxSequenceRoot->setDuration( Any() );
        }
        else
        {
            // empty sequence, set duration to 0.0 explicitly
            // (otherwise, this sequence will never end)
            mxSequenceRoot->setDuration( Any(0.0) );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd""sd::EffectSequenceHelper::rebuild()" );
    }
}

stl_CustomAnimationEffect_search_node_predict::stl_CustomAnimationEffect_search_node_predict( const css::uno::Reference< css::animations::XAnimationNode >& xSearchNode )
: mxSearchNode( xSearchNode )
{
}

bool stl_CustomAnimationEffect_search_node_predict::operator()( const CustomAnimationEffectPtr& pEffect ) const
{
    return pEffect->getNode() == mxSearchNode;
}

/// @throws Exception
static bool implFindNextContainer( Reference< XTimeContainer > const & xParent, Reference< XTimeContainer > const & xCurrent, Reference< XTimeContainer >& xNext )
{
    Reference< XEnumerationAccess > xEnumerationAccess( xParent, UNO_QUERY_THROW );
    Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration() );
    if( xEnumeration.is() )
    {
        Reference< XInterface > x;
        while( xEnumeration->hasMoreElements() && !xNext.is() )
        {
            if( (xEnumeration->nextElement() >>= x) && (x == xCurrent) )
            {
                if( xEnumeration->hasMoreElements() )
                    xEnumeration->nextElement() >>= xNext;
            }
        }
    }
    return xNext.is();
}

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=96 G=94

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.