/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::presentation; using namespace ::com::sun::star::animations; using ::com::sun::star::container::XEnumerationAccess; using ::com::sun::star::container::XEnumeration; using ::com::sun::star::beans::NamedValue; using ::com::sun::star::container::XChild; using ::com::sun::star::drawing::XShape; using ::com::sun::star::lang::XInitialization; using ::com::sun::star::text::XText; using ::com::sun::star::text::XTextRange; using ::com::sun::star::beans::XPropertySet; using ::com::sun::star::util::XCloneable; using ::com::sun::star::lang::Locale; using ::com::sun::star::util::XChangesNotifier; using ::com::sun::star::util::XChangesListener; namespace sd { class MainSequenceChangeGuard { public: explicit MainSequenceChangeGuard( EffectSequenceHelper* pSequence ) { mpMainSequence = dynamic_cast< MainSequence* >( pSequence ); if( mpMainSequence == nullptr ) { InteractiveSequence* pI = dynamic_cast< InteractiveSequence* >( pSequence ); if( pI ) mpMainSequence = pI->mpMainSequence; } DBG_ASSERT( mpMainSequence, "sd::MainSequenceChangeGuard::MainSequenceChangeGuard(), no main sequence to guard!" ); if( mpMainSequence ) mpMainSequence->mbIgnoreChanges++; } ~MainSequenceChangeGuard() { if( mpMainSequence ) mpMainSequence->mbIgnoreChanges++; } private: MainSequence* mpMainSequence; }; CustomAnimationEffect::CustomAnimationEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) : mnNodeType(-1), mnPresetClass(-1), mnFill(AnimationFill::HOLD), mfBegin(-1.0), mfDuration(-1.0), mfAbsoluteDuration(-1.0), mnGroupId(-1), mnIterateType(0), mfIterateInterval(0.0), mnParaDepth( -1 ), mbHasText(false), mfAcceleration( 1.0 ), mfDecelerate( 1.0 ), mbAutoReverse(false), mnTargetSubItem(0), mnCommand(0), mpEffectSequence( nullptr ), mbHasAfterEffect(false), mbAfterEffectOnNextEffect(false) { setNode( xNode ); } void CustomAnimationEffect::setNode( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) { mxNode = xNode; mxAudio.clear(); mnCommand = 0; const Sequence< NamedValue > aUserData( mxNode->getUserData() ); for( const NamedValue& rProp : aUserData ) { if ( rProp.Name == "node-type" ) { rProp.Value >>= mnNodeType; } else if ( rProp.Name == "preset-id" ) { rProp.Value >>= maPresetId; } else if ( rProp.Name == "preset-sub-type" ) { rProp.Value >>= maPresetSubType; } else if ( rProp.Name == "preset-class" ) { rProp.Value >>= mnPresetClass; } else if ( rProp.Name == "preset-property" ) { rProp.Value >>= maProperty; } else if ( rProp.Name == "group-id" ) { rProp.Value >>= mnGroupId; } } // get effect start time mxNode->getBegin() >>= mfBegin; mfAcceleration = mxNode->getAcceleration(); mfDecelerate = mxNode->getDecelerate(); mbAutoReverse = mxNode->getAutoReverse(); mnFill = mxNode->getFill(); // get iteration data Reference< XIterateContainer > xIter( mxNode, UNO_QUERY ); if( xIter.is() ) { mfIterateInterval = xIter->getIterateInterval(); mnIterateType = xIter->getIterateType(); maTarget = xIter->getTarget(); mnTargetSubItem = xIter->getSubItem(); } else { mfIterateInterval = 0.0f; mnIterateType = 0; } // calculate effect duration and get target shape Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY ); if( xEnumerationAccess.is() ) { Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration(); if( xEnumeration.is() ) { while( xEnumeration->hasMoreElements() ) { Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY ); if( !xChildNode.is() ) continue; if( xChildNode->getType() == AnimationNodeType::AUDIO ) { mxAudio.set( xChildNode, UNO_QUERY ); } else if( xChildNode->getType() == AnimationNodeType::COMMAND ) { Reference< XCommand > xCommand( xChildNode, UNO_QUERY ); if( xCommand.is() ) { mnCommand = xCommand->getCommand(); if( !maTarget.hasValue() ) maTarget = xCommand->getTarget(); } } else { double fBegin = 0.0; double fDuration = 0.0; xChildNode->getBegin() >>= fBegin; xChildNode->getDuration() >>= fDuration; fDuration += fBegin; if( fDuration > mfDuration ) mfDuration = fDuration; // no target shape yet? if( !maTarget.hasValue() ) { // go get it boys! Reference< XAnimate > xAnimate( xChildNode, UNO_QUERY ); if( xAnimate.is() ) { maTarget = xAnimate->getTarget(); mnTargetSubItem = xAnimate->getSubItem(); } } } } } } mfAbsoluteDuration = mfDuration; double fRepeatCount = 1.0; if( (mxNode->getRepeatCount()) >>= fRepeatCount ) mfAbsoluteDuration *= fRepeatCount; checkForText(); } sal_Int32 CustomAnimationEffect::getNumberOfSubitems( const Any& aTarget, sal_Int16 nIterateType ) { sal_Int32 nSubItems = 0; try { // first get target text sal_Int32 nOnlyPara = -1; Reference< XText > xShape; aTarget >>= xShape; if( !xShape.is() ) { ParagraphTarget aParaTarget; if( aTarget >>= aParaTarget ) { xShape.set( aParaTarget.Shape, UNO_QUERY ); nOnlyPara = aParaTarget.Paragraph; } } // now use the break iterator to iterate over the given text // and count the sub items if( xShape.is() ) { // TODO/LATER: Optimize this, don't create a break iterator each time 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 const 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( 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* paragraphNumberingLevel ) { bool bChange = false; Reference< XText > xText; if( maTarget.getValueType() == ::cppu::UnoType::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( "NumberingLevel" ) >>= 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!" ); 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; 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 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 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 { 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(); 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; } SdrPathObj* CustomAnimationEffect::createSdrPathObjFromPath(SdrModel& rTargetModel) { 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(aPageSize.Width()), static_cast(aPageSize.Height()))); } const ::tools::Rectangle aBoundRect( pObj->GetCurrentBoundRect() ); const Point aCenter( aBoundRect.Center() ); aPolyPoly.transform(basegfx::utils::createTranslateB2DHomMatrix(aCenter.X(), aCenter.Y())); } } rPathObj.SetPathPoly( aPolyPoly ); } void CustomAnimationEffect::updatePathFromSdrPathObj( const SdrPathObj& rPathObj ) { ::basegfx::B2DPolyPolygon aPolyPoly( rPathObj.GetPathPoly() ); SdrObject* pObj = SdrObject::getSdrObjectFromXShape(getTargetShape()); if( pObj ) { ::tools::Rectangle aBoundRect(0,0,0,0); drawinglayer::primitive2d::Primitive2DContainer xPrimitives; pObj->GetViewContact().getViewIndependentPrimitive2DContainer(xPrimitives); const drawinglayer::geometry::ViewInformation2D aViewInformation2D; const basegfx::B2DRange aRange(xPrimitives.getB2DRange(aViewInformation2D)); if(!aRange.isEmpty()) { aBoundRect = ::tools::Rectangle( static_cast(floor(aRange.getMinX())), static_cast(floor(aRange.getMinY())), static_cast(ceil(aRange.getMaxX())), static_cast(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(aPageSize.Width()), 1.0 / static_cast(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() { 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( "" ) ); 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::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( 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::get() ) nSubItem = ShapeAnimationSubType::ONLY_TEXT; pEffect = std::make_shared( 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, "", fDuration ); } void EffectSequenceHelper::remove( const CustomAnimationEffectPtr& pEffect ) { if( pEffect ) { pEffect->setEffectSequence( nullptr ); maEffects.remove( pEffect ); } rebuild(); } void EffectSequenceHelper::moveToBeforeEffect( const CustomAnimationEffectPtr& pEffect, const CustomAnimationEffectPtr& pInsertBefore) { if ( pEffect ) { maEffects.remove( pEffect ); EffectSequence::iterator aInsertIter( find( pInsertBefore ) ); // aInsertIter being end() is OK: pInsertBefore could be null, so put at end. maEffects.insert( aInsertIter, pEffect ); rebuild(); } } void EffectSequenceHelper::rebuild() { implRebuild(); } void EffectSequenceHelper::implRebuild() { try { // first we delete all time containers on the first two levels Reference< XEnumerationAccess > xEnumerationAccess( mxSequenceRoot, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW ); while( xEnumeration->hasMoreElements() ) { Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW ); Reference< XTimeContainer > xChildContainer( xChildNode, UNO_QUERY_THROW ); Reference< XEnumerationAccess > xChildEnumerationAccess( xChildNode, UNO_QUERY_THROW ); Reference< XEnumeration > xChildEnumeration( xChildEnumerationAccess->createEnumeration(), UNO_SET_THROW ); while( xChildEnumeration->hasMoreElements() ) { Reference< XAnimationNode > xNode( xChildEnumeration->nextElement(), UNO_QUERY_THROW ); xChildContainer->removeChild( xNode ); } mxSequenceRoot->removeChild( xChildNode ); } // second, rebuild main sequence EffectSequence::iterator aIter( maEffects.begin() ); EffectSequence::iterator aEnd( maEffects.end() ); if( aIter != aEnd ) { std::vector< sd::AfterEffectNode > aAfterEffects; CustomAnimationEffectPtr pEffect = *aIter++; bool bFirst = true; do { // create a par container for the next click node and all following with and after effects Reference< XTimeContainer > xOnClickContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW ); Event aEvent; if( mxEventSource.is() ) { aEvent.Source <<= mxEventSource; aEvent.Trigger = EventTrigger::ON_CLICK; } else { aEvent.Trigger = EventTrigger::ON_NEXT; } aEvent.Repeat = 0; Any aBegin( aEvent ); if( bFirst ) { // if the first node is not a click action, this click container // must not have INDEFINITE begin but start at 0s bFirst = false; if( pEffect->getNodeType() != EffectNodeType::ON_CLICK ) aBegin <<= 0.0; } xOnClickContainer->setBegin( aBegin ); mxSequenceRoot->appendChild( xOnClickContainer ); double fBegin = 0.0; do { // create a par container for the current click or after effect node and all following with effects Reference< XTimeContainer > xWithContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW ); xWithContainer->setBegin( Any( fBegin ) ); xOnClickContainer->appendChild( xWithContainer ); double fDuration = 0.0; do { Reference< XAnimationNode > xEffectNode( pEffect->getNode() ); xWithContainer->appendChild( xEffectNode ); if( pEffect->hasAfterEffect() ) { Reference< XAnimationNode > xAfterEffect( pEffect->createAfterEffectNode() ); AfterEffectNode a( xAfterEffect, xEffectNode, pEffect->IsAfterEffectOnNext() ); aAfterEffects.push_back( a ); } double fTemp = pEffect->getBegin() + pEffect->getAbsoluteDuration(); if( fTemp > fDuration ) fDuration = fTemp; if( aIter != aEnd ) pEffect = *aIter++; else pEffect.reset(); } while( pEffect && (pEffect->getNodeType() == EffectNodeType::WITH_PREVIOUS) ); fBegin += fDuration; } while( pEffect && (pEffect->getNodeType() != EffectNodeType::ON_CLICK) ); } while( pEffect ); // process after effect nodes std::for_each( aAfterEffects.begin(), aAfterEffects.end(), stl_process_after_effect_node_func ); updateTextGroups(); // reset duration, might have been altered (see below) mxSequenceRoot->setDuration( Any() ); } else { // empty sequence, set duration to 0.0 explicitly // (otherwise, this sequence will never end) mxSequenceRoot->setDuration( Any(0.0) ); } } catch( Exception& ) { TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::rebuild()" ); } } stl_CustomAnimationEffect_search_node_predict::stl_CustomAnimationEffect_search_node_predict( const css::uno::Reference< css::animations::XAnimationNode >& xSearchNode ) : mxSearchNode( xSearchNode ) { } bool stl_CustomAnimationEffect_search_node_predict::operator()( const CustomAnimationEffectPtr& pEffect ) const { return pEffect->getNode() == mxSearchNode; } /// @throws Exception static bool implFindNextContainer( Reference< XTimeContainer > const & xParent, Reference< XTimeContainer > const & xCurrent, Reference< XTimeContainer >& xNext ) { Reference< XEnumerationAccess > xEnumerationAccess( xParent, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration() ); if( xEnumeration.is() ) { Reference< XInterface > x; while( xEnumeration->hasMoreElements() && !xNext.is() ) { if( (xEnumeration->nextElement() >>= x) && (x == xCurrent) ) { if( xEnumeration->hasMoreElements() ) xEnumeration->nextElement() >>= xNext; } } } return xNext.is(); } void stl_process_after_effect_node_func(AfterEffectNode const & rNode) { try { if( rNode.mxNode.is() && rNode.mxMaster.is() ) { // set master node Reference< XAnimationNode > xMasterNode( rNode.mxMaster, UNO_SET_THROW ); Sequence< NamedValue > aUserData( rNode.mxNode->getUserData() ); sal_Int32 nSize = aUserData.getLength(); aUserData.realloc(nSize+1); auto pUserData = aUserData.getArray(); pUserData[nSize].Name = "master-element"; pUserData[nSize].Value <<= xMasterNode; rNode.mxNode->setUserData( aUserData ); // insert after effect node into timeline Reference< XTimeContainer > xContainer( rNode.mxMaster->getParent(), UNO_QUERY_THROW ); if( !rNode.mbOnNextEffect ) // sameClick { // insert the aftereffect after its effect is animated xContainer->insertAfter( rNode.mxNode, rNode.mxMaster ); } else // nextClick { Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); // insert the aftereffect in the next group Reference< XTimeContainer > xClickContainer( xContainer->getParent(), UNO_QUERY_THROW ); Reference< XTimeContainer > xSequenceContainer( xClickContainer->getParent(), UNO_QUERY_THROW ); Reference< XTimeContainer > xNextContainer; // first try if we have an after effect container if( !implFindNextContainer( xClickContainer, xContainer, xNextContainer ) ) { Reference< XTimeContainer > xNextClickContainer; // if not, try to find the next click effect container if( implFindNextContainer( xSequenceContainer, xClickContainer, xNextClickContainer ) ) { Reference< XEnumerationAccess > xEnumerationAccess( xNextClickContainer, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW ); if( xEnumeration->hasMoreElements() ) { // the next container is the first child container xEnumeration->nextElement() >>= xNextContainer; } else { // this does not yet have a child container, create one xNextContainer.set( ParallelTimeContainer::create(xContext), UNO_QUERY_THROW ); xNextContainer->setBegin( Any( 0.0 ) ); xNextClickContainer->appendChild( xNextContainer ); } DBG_ASSERT( xNextContainer.is(), "ppt::stl_process_after_effect_node_func::operator(), could not find/create container!" ); } } // if we don't have a next container, we add one to the sequence container if( !xNextContainer.is() ) { Reference< XTimeContainer > xNewClickContainer( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW ); Event aEvent; aEvent.Trigger = EventTrigger::ON_NEXT; aEvent.Repeat = 0; xNewClickContainer->setBegin( Any( aEvent ) ); xSequenceContainer->insertAfter( xNewClickContainer, xClickContainer ); xNextContainer.set( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW ); xNextContainer->setBegin( Any( 0.0 ) ); xNewClickContainer->appendChild( xNextContainer ); } if( xNextContainer.is() ) { // find begin time of first element Reference< XEnumerationAccess > xEnumerationAccess( xNextContainer, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW ); if( xEnumeration->hasMoreElements() ) { Reference< XAnimationNode > xChild; // the next container is the first child container xEnumeration->nextElement() >>= xChild; if( xChild.is() ) { Any aBegin( xChild->getBegin() ); double fBegin = 0.0; if( (aBegin >>= fBegin) && (fBegin >= 0.0)) rNode.mxNode->setBegin( aBegin ); } } xNextContainer->appendChild( rNode.mxNode ); } } } } catch( Exception& ) { TOOLS_WARN_EXCEPTION( "sd", "ppt::stl_process_after_effect_node_func::operator()" ); } } EffectSequence::iterator EffectSequenceHelper::find( const CustomAnimationEffectPtr& pEffect ) { return std::find( maEffects.begin(), maEffects.end(), pEffect ); } CustomAnimationEffectPtr EffectSequenceHelper::findEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) const { CustomAnimationEffectPtr pEffect; EffectSequence::const_iterator aIter = std::find_if(maEffects.begin(), maEffects.end(), [&xNode](const CustomAnimationEffectPtr& rxEffect) { return rxEffect->getNode() == xNode; }); if (aIter != maEffects.end()) pEffect = *aIter; return pEffect; } sal_Int32 EffectSequenceHelper::getOffsetFromEffect( const CustomAnimationEffectPtr& xEffect ) const { auto aIter = std::find(maEffects.begin(), maEffects.end(), xEffect); if (aIter != maEffects.end()) return static_cast(std::distance(maEffects.begin(), aIter)); return -1; } CustomAnimationEffectPtr EffectSequenceHelper::getEffectFromOffset( sal_Int32 nOffset ) const { EffectSequence::const_iterator aIter( maEffects.begin() ); nOffset = std::min(nOffset, static_cast(maEffects.size())); std::advance(aIter, nOffset); CustomAnimationEffectPtr pEffect; if( aIter != maEffects.end() ) pEffect = *aIter; return pEffect; } bool EffectSequenceHelper::disposeShape( const Reference< XShape >& xShape ) { bool bChanges = false; EffectSequence::iterator aIter( maEffects.begin() ); while( aIter != maEffects.end() ) { if( (*aIter)->getTargetShape() == xShape ) { (*aIter)->setEffectSequence( nullptr ); bChanges = true; aIter = maEffects.erase( aIter ); } else { ++aIter; } } return bChanges; } bool EffectSequenceHelper::hasEffect( const css::uno::Reference< css::drawing::XShape >& xShape ) { return std::any_of(maEffects.begin(), maEffects.end(), [&xShape](const CustomAnimationEffectPtr& rxEffect) { return rxEffect->getTargetShape() == xShape; }); } bool EffectSequenceHelper::getParagraphNumberingLevels( const Reference< XShape >& xShape, std::vector< sal_Int32 >& rParagraphNumberingLevel ) { rParagraphNumberingLevel.clear(); if( !hasEffect( xShape ) ) return false; Reference< XText > xText( xShape, UNO_QUERY ); if( xText.is() ) { Reference< XEnumerationAccess > xEA( xText, UNO_QUERY ); if( xEA.is() ) { Reference< XEnumeration > xEnumeration = xEA->createEnumeration(); if( xEnumeration.is() ) { while( xEnumeration->hasMoreElements() ) { Reference< XPropertySet > xParaSet; xEnumeration->nextElement() >>= xParaSet; sal_Int32 nParaDepth = 0; if( xParaSet.is() ) { xParaSet->getPropertyValue( "NumberingLevel" ) >>= nParaDepth; } rParagraphNumberingLevel.push_back( nParaDepth ); } } } } return true; } void EffectSequenceHelper::insertTextRange( const css::uno::Any& aTarget ) { ParagraphTarget aParaTarget; if( !(aTarget >>= aParaTarget ) ) return; // get map [paragraph index] -> [NumberingLevel] // for following reusage inside all animation effects std::vector< sal_Int32 > paragraphNumberingLevel; std::vector< sal_Int32 >* paragraphNumberingLevelParam = nullptr; if ( getParagraphNumberingLevels( aParaTarget.Shape, paragraphNumberingLevel ) ) paragraphNumberingLevelParam = ¶graphNumberingLevel; // update internal flags for each animation effect const bool bChanges = std::accumulate(maEffects.begin(), maEffects.end(), false, [&aParaTarget, ¶graphNumberingLevelParam](const bool bCheck, const CustomAnimationEffectPtr& rxEffect) { bool bRes = bCheck; if (rxEffect->getTargetShape() == aParaTarget.Shape) bRes |= rxEffect->checkForText( paragraphNumberingLevelParam ); return bRes; }); if( bChanges ) rebuild(); } static bool isParagraphTargetTextEmpty( ParagraphTarget aParaTarget ) { // get paragraph Reference< XText > xText ( aParaTarget.Shape, UNO_QUERY ); if( xText.is() ) { Reference< XEnumerationAccess > xEA( xText, UNO_QUERY ); if( xEA.is() ) { Reference< XEnumeration > xEnumeration = xEA->createEnumeration(); if( xEnumeration.is() ) { // advance to the Nth paragraph sal_Int32 nPara = aParaTarget.Paragraph; while( xEnumeration->hasMoreElements() && nPara-- ) xEnumeration->nextElement(); // get Nth paragraph's text and check if it's empty if( xEnumeration->hasMoreElements() ) { Reference< XTextRange > xRange( xEnumeration->nextElement(), UNO_QUERY ); if( xRange.is() ) { OUString text = xRange->getString(); return text.isEmpty(); } } } } } return false; } void EffectSequenceHelper::disposeTextRange( const css::uno::Any& aTarget ) { ParagraphTarget aParaTarget; if( !(aTarget >>= aParaTarget ) ) return; bool bChanges = false; // building list of effects for target shape; process effects not on target shape EffectSequence aTargetParagraphEffects; for( const auto &pEffect : maEffects ) { Any aIterTarget( pEffect->getTarget() ); if( aIterTarget.getValueType() == ::cppu::UnoType::get() ) { ParagraphTarget aIterParaTarget; if( (aIterTarget >>= aIterParaTarget) && (aIterParaTarget.Shape == aParaTarget.Shape) ) { aTargetParagraphEffects.push_back(pEffect); } } else if( pEffect->getTargetShape() == aParaTarget.Shape ) { bChanges |= pEffect->checkForText(); } } // select effect to delete: // if paragraph before target is blank, then delete its animation effect (if any) instead ParagraphTarget aPreviousParagraph = aParaTarget; --aPreviousParagraph.Paragraph; bool bIsPreviousParagraphEmpty = isParagraphTargetTextEmpty( aPreviousParagraph ); sal_Int16 anParaNumToDelete = bIsPreviousParagraphEmpty ? aPreviousParagraph.Paragraph : aParaTarget.Paragraph; // update effects for( const auto &pEffect : aTargetParagraphEffects ) { Any aIterTarget( pEffect->getTarget() ); ParagraphTarget aIterParaTarget; aIterTarget >>= aIterParaTarget; // delete effect for target paragraph (may have effects in more than one text group) if( aIterParaTarget.Paragraph == anParaNumToDelete ) { auto aItr = find( pEffect ); DBG_ASSERT( aItr != maEffects.end(), "sd::EffectSequenceHelper::disposeTextRange(), Expected effect missing."); if( aItr != maEffects.end() ) { (*aItr)->setEffectSequence( nullptr ); maEffects.erase(aItr); bChanges = true; } } // shift all paragraphs after disposed paragraph if( aIterParaTarget.Paragraph > anParaNumToDelete ) { --aIterParaTarget.Paragraph; pEffect->setTarget( Any( aIterParaTarget ) ); bChanges = true; } } if( bChanges ) { rebuild(); } } CustomAnimationTextGroup::CustomAnimationTextGroup( const Reference< XShape >& rTarget, sal_Int32 nGroupId ) : maTarget( rTarget ), mnGroupId( nGroupId ) { reset(); } void CustomAnimationTextGroup::reset() { mnTextGrouping = -1; mbAnimateForm = false; mbTextReverse = false; mfGroupingAuto = -1.0; mnLastPara = -1; // used to check for TextReverse for (sal_Int8 & rn : mnDepthFlags) { rn = 0; } maEffects.clear(); } void CustomAnimationTextGroup::addEffect( CustomAnimationEffectPtr const & pEffect ) { maEffects.push_back( pEffect ); Any aTarget( pEffect->getTarget() ); if( aTarget.getValueType() == ::cppu::UnoType::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(pEffect->getNodeType()); } else if( mnDepthFlags[nParaDepth] != pEffect->getNodeType() ) { mnDepthFlags[nParaDepth] = -1; } if( pEffect->getNodeType() == EffectNodeType::AFTER_PREVIOUS ) mfGroupingAuto = pEffect->getBegin(); mnTextGrouping = PARA_LEVELS; while( (mnTextGrouping > 0) && (mnDepthFlags[mnTextGrouping - 1] <= 0) ) --mnTextGrouping; } } else { // if we have an effect with the shape as a target, we animate the background mbAnimateForm = pEffect->getTargetSubItem() != ShapeAnimationSubType::ONLY_TEXT; } } CustomAnimationTextGroupPtr EffectSequenceHelper::findGroup( sal_Int32 nGroupId ) { CustomAnimationTextGroupPtr aPtr; CustomAnimationTextGroupMap::iterator aIter( maGroupMap.find( nGroupId ) ); if( aIter != maGroupMap.end() ) aPtr = (*aIter).second; return aPtr; } void EffectSequenceHelper::updateTextGroups() { maGroupMap.clear(); // first create all the groups for( const CustomAnimationEffectPtr& pEffect : maEffects ) { const sal_Int32 nGroupId = pEffect->getGroupId(); if( nGroupId == -1 ) continue; // trivial case, no group CustomAnimationTextGroupPtr pGroup = findGroup( nGroupId ); if( !pGroup ) { pGroup = std::make_shared( pEffect->getTargetShape(), nGroupId ); maGroupMap[nGroupId] = pGroup; } pGroup->addEffect( pEffect ); } // Now that all the text groups have been cleared up and rebuilt, we need to update its // text grouping. addEffect() already make mnTextGrouping the last possible level, // so just continue to find the last level that is not EffectNodeType::WITH_PREVIOUS. for(const auto &rGroupMapItem: maGroupMap) { const CustomAnimationTextGroupPtr &pGroup = rGroupMapItem.second; while(pGroup->mnTextGrouping > 0 && pGroup->mnDepthFlags[pGroup->mnTextGrouping - 1] == EffectNodeType::WITH_PREVIOUS) --pGroup->mnTextGrouping; } } CustomAnimationTextGroupPtr EffectSequenceHelper::createTextGroup(const CustomAnimationEffectPtr& pEffect, sal_Int32 nTextGrouping, double fTextGroupingAuto, bool bAnimateForm, bool bTextReverse) { // first find a free group-id sal_Int32 nGroupId = 0; CustomAnimationTextGroupMap::iterator aIter( maGroupMap.begin() ); const CustomAnimationTextGroupMap::iterator aEnd( maGroupMap.end() ); while( aIter != aEnd ) { if( (*aIter).first == nGroupId ) { nGroupId++; aIter = maGroupMap.begin(); } else { ++aIter; } } Reference< XShape > xTarget( pEffect->getTargetShape() ); CustomAnimationTextGroupPtr pTextGroup = std::make_shared( xTarget, nGroupId ); maGroupMap[nGroupId] = pTextGroup; bool bUsed = false; // do we need to target the shape? if( (nTextGrouping == 0) || bAnimateForm ) { sal_Int16 nSubItem; if( nTextGrouping == 0) nSubItem = bAnimateForm ? ShapeAnimationSubType::AS_WHOLE : ShapeAnimationSubType::ONLY_TEXT; else nSubItem = ShapeAnimationSubType::ONLY_BACKGROUND; pEffect->setTarget( Any( xTarget ) ); pEffect->setTargetSubItem( nSubItem ); pEffect->setEffectSequence( this ); pEffect->setGroupId( nGroupId ); pTextGroup->addEffect( pEffect ); bUsed = true; } pTextGroup->mnTextGrouping = nTextGrouping; pTextGroup->mfGroupingAuto = fTextGroupingAuto; pTextGroup->mbTextReverse = bTextReverse; // now add an effect for each paragraph createTextGroupParagraphEffects( pTextGroup, pEffect, bUsed ); notify_listeners(); return pTextGroup; } void EffectSequenceHelper::createTextGroupParagraphEffects( const CustomAnimationTextGroupPtr& pTextGroup, const CustomAnimationEffectPtr& pEffect, bool bUsed ) { Reference< XShape > xTarget( pTextGroup->maTarget ); sal_Int32 nTextGrouping = pTextGroup->mnTextGrouping; double fTextGroupingAuto = pTextGroup->mfGroupingAuto; bool bTextReverse = pTextGroup->mbTextReverse; // now add an effect for each paragraph if( nTextGrouping < 0 ) return; try { EffectSequence::iterator aInsertIter( find( pEffect ) ); Reference< XEnumerationAccess > xText( xTarget, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xText->createEnumeration(), UNO_SET_THROW ); std::deque< sal_Int16 > aParaList; sal_Int16 nPara; // fill the list with all valid paragraphs for( nPara = 0; xEnumeration->hasMoreElements(); nPara++ ) { Reference< XTextRange > xRange( xEnumeration->nextElement(), UNO_QUERY ); if( xRange.is() && !xRange->getString().isEmpty() ) { if( bTextReverse ) // sort them aParaList.push_front( nPara ); else aParaList.push_back( nPara ); } } ParagraphTarget aTarget; aTarget.Shape = xTarget; for( const auto i : aParaList ) { aTarget.Paragraph = i; CustomAnimationEffectPtr pNewEffect; if( bUsed ) { // clone a new effect from first effect pNewEffect = pEffect->clone(); ++aInsertIter; aInsertIter = maEffects.insert( aInsertIter, pNewEffect ); } else { // reuse first effect if it's not yet used pNewEffect = pEffect; bUsed = true; aInsertIter = find( pNewEffect ); } // set target and group-id pNewEffect->setTarget( Any( aTarget ) ); pNewEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_TEXT ); pNewEffect->setGroupId( pTextGroup->mnGroupId ); pNewEffect->setEffectSequence( this ); // set correct node type if( pNewEffect->getParaDepth() < nTextGrouping ) { if( fTextGroupingAuto == -1.0 ) { pNewEffect->setNodeType( EffectNodeType::ON_CLICK ); pNewEffect->setBegin( 0.0 ); } else { pNewEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS ); pNewEffect->setBegin( fTextGroupingAuto ); } } else { pNewEffect->setNodeType( EffectNodeType::WITH_PREVIOUS ); pNewEffect->setBegin( 0.0 ); } pTextGroup->addEffect( pNewEffect ); } notify_listeners(); } catch( Exception& ) { TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::createTextGroup()" ); } } void EffectSequenceHelper::setTextGrouping( const CustomAnimationTextGroupPtr& pTextGroup, sal_Int32 nTextGrouping ) { if( pTextGroup->mnTextGrouping == nTextGrouping ) { // first case, trivial case, do nothing } else if( (pTextGroup->mnTextGrouping == -1) && (nTextGrouping >= 0) ) { // second case, we need to add new effects for each paragraph CustomAnimationEffectPtr pEffect( pTextGroup->maEffects.front() ); pTextGroup->mnTextGrouping = nTextGrouping; createTextGroupParagraphEffects( pTextGroup, pEffect, true ); notify_listeners(); } else if( (pTextGroup->mnTextGrouping >= 0) && (nTextGrouping == -1 ) ) { // third case, we need to remove effects for each paragraph EffectSequence aEffects( pTextGroup->maEffects ); pTextGroup->reset(); for( const CustomAnimationEffectPtr& pEffect : aEffects ) { if( pEffect->getTarget().getValueType() == ::cppu::UnoType::get() ) remove( pEffect ); else pTextGroup->addEffect( pEffect ); } notify_listeners(); } else { // fourth case, we need to change the node types for the text nodes double fTextGroupingAuto = pTextGroup->mfGroupingAuto; EffectSequence aEffects( pTextGroup->maEffects ); pTextGroup->reset(); for( CustomAnimationEffectPtr& pEffect : aEffects ) { if( pEffect->getTarget().getValueType() == ::cppu::UnoType::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::get() ) ) { // special case, only one effect and that targets whole text, // convert this to target whole shape pEffect = *aIter++; pEffect->setTargetSubItem( ShapeAnimationSubType::AS_WHOLE ); } else { pEffect = (*aIter)->clone(); pEffect->setTarget( Any( (*aIter)->getTargetShape() ) ); pEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_BACKGROUND ); maEffects.insert( aInsertIter, pEffect ); } pTextGroup->addEffect( pEffect ); } if( !bAnimateForm && (aEffects.size() == 1) ) { CustomAnimationEffectPtr pEffect( *aIter ); pEffect->setTarget( Any( (*aIter)->getTargetShape() ) ); pEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_TEXT ); pTextGroup->addEffect( pEffect ); } else { // read the rest to the group again while( aIter != aEnd ) { CustomAnimationEffectPtr pEffect( *aIter++ ); if( pEffect->getTarget().getValueType() == ::cppu::UnoType::get() ) { pTextGroup->addEffect( pEffect ); } else { DBG_ASSERT( !bAnimateForm, "sd::EffectSequenceHelper::setAnimateForm(), something is wrong here!" ); remove( pEffect ); } } } notify_listeners(); } } void EffectSequenceHelper::setTextGroupingAuto( const CustomAnimationTextGroupPtr& pTextGroup, double fTextGroupingAuto ) { sal_Int32 nTextGrouping = pTextGroup->mnTextGrouping; EffectSequence aEffects( pTextGroup->maEffects ); pTextGroup->reset(); for( CustomAnimationEffectPtr& pEffect : aEffects ) { if( pEffect->getTarget().getValueType() == ::cppu::UnoType::get() ) { // set correct node type if( pEffect->getParaDepth() < nTextGrouping ) { if( fTextGroupingAuto == -1.0 ) { pEffect->setNodeType( EffectNodeType::ON_CLICK ); pEffect->setBegin( 0.0 ); } else { pEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS ); pEffect->setBegin( fTextGroupingAuto ); } } else { pEffect->setNodeType( EffectNodeType::WITH_PREVIOUS ); pEffect->setBegin( 0.0 ); } } pTextGroup->addEffect( pEffect ); } notify_listeners(); } namespace { struct ImplStlTextGroupSortHelper { explicit ImplStlTextGroupSortHelper( bool bReverse ) : mbReverse( bReverse ) {}; bool operator()( const CustomAnimationEffectPtr& p1, const CustomAnimationEffectPtr& p2 ); bool mbReverse; sal_Int32 getTargetParagraph( const CustomAnimationEffectPtr& p1 ); }; } sal_Int32 ImplStlTextGroupSortHelper::getTargetParagraph( const CustomAnimationEffectPtr& p1 ) { const Any aTarget(p1->getTarget()); if( aTarget.hasValue() && aTarget.getValueType() == ::cppu::UnoType::get() ) { ParagraphTarget aParaTarget; aTarget >>= aParaTarget; return aParaTarget.Paragraph; } else { return mbReverse ? 0x7fffffff : -1; } } bool ImplStlTextGroupSortHelper::operator()( const CustomAnimationEffectPtr& p1, const CustomAnimationEffectPtr& p2 ) { if( mbReverse ) { return getTargetParagraph( p2 ) < getTargetParagraph( p1 ); } else { return getTargetParagraph( p1 ) < getTargetParagraph( p2 ); } } void EffectSequenceHelper::setTextReverse( const CustomAnimationTextGroupPtr& pTextGroup, bool bTextReverse ) { if( pTextGroup->mbTextReverse == bTextReverse ) { // do nothing } else { std::vector< CustomAnimationEffectPtr > aSortedVector( pTextGroup->maEffects.begin(), pTextGroup->maEffects.end() ); ImplStlTextGroupSortHelper aSortHelper( bTextReverse ); std::sort( aSortedVector.begin(), aSortedVector.end(), aSortHelper ); pTextGroup->reset(); std::vector< CustomAnimationEffectPtr >::iterator aIter( aSortedVector.begin() ); const std::vector< CustomAnimationEffectPtr >::iterator aEnd( aSortedVector.end() ); if( aIter != aEnd ) { pTextGroup->addEffect( *aIter ); EffectSequence::iterator aInsertIter( find( *aIter++ ) ); while( aIter != aEnd ) { CustomAnimationEffectPtr pEffect( *aIter++ ); maEffects.erase( find( pEffect ) ); aInsertIter = maEffects.insert( ++aInsertIter, pEffect ); pTextGroup->addEffect( pEffect ); } } notify_listeners(); } } void EffectSequenceHelper::addListener( ISequenceListener* pListener ) { if( std::find( maListeners.begin(), maListeners.end(), pListener ) == maListeners.end() ) maListeners.push_back( pListener ); } void EffectSequenceHelper::removeListener( ISequenceListener* pListener ) { maListeners.remove( pListener ); } namespace { struct stl_notify_listeners_func { stl_notify_listeners_func() {} void operator()(ISequenceListener* pListener) { pListener->notify_change(); } }; } void EffectSequenceHelper::notify_listeners() { stl_notify_listeners_func aFunc; std::for_each( maListeners.begin(), maListeners.end(), aFunc ); } void EffectSequenceHelper::create( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) { DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::create(), illegal argument" ); if( !xNode.is() ) return; try { Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW ); while( xEnumeration->hasMoreElements() ) { Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW ); createEffectsequence( xChildNode ); } } catch( Exception& ) { TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::create()" ); } } void EffectSequenceHelper::createEffectsequence( const Reference< XAnimationNode >& xNode ) { DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffectsequence(), illegal argument" ); if( !xNode.is() ) return; try { Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW ); while( xEnumeration->hasMoreElements() ) { Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW ); createEffects( xChildNode ); } } catch( Exception& ) { TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::createEffectsequence()" ); } } void EffectSequenceHelper::createEffects( const Reference< XAnimationNode >& xNode ) { DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffects(), illegal argument" ); if( !xNode.is() ) return; try { Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW ); while( xEnumeration->hasMoreElements() ) { Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW ); switch( xChildNode->getType() ) { // found an effect case AnimationNodeType::PAR: case AnimationNodeType::ITERATE: { CustomAnimationEffectPtr pEffect = std::make_shared( xChildNode ); if( pEffect->mnNodeType != -1 ) { pEffect->setEffectSequence( this ); maEffects.push_back(pEffect); } } break; // found an after effect case AnimationNodeType::SET: case AnimationNodeType::ANIMATECOLOR: { processAfterEffect( xChildNode ); } break; } } } catch( Exception& ) { TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::createEffects()" ); } } void EffectSequenceHelper::processAfterEffect( const Reference< XAnimationNode >& xNode ) { try { Reference< XAnimationNode > xMaster; const Sequence< NamedValue > aUserData( xNode->getUserData() ); const NamedValue* pProp = std::find_if(aUserData.begin(), aUserData.end(), [](const NamedValue& rProp) { return rProp.Name == "master-element"; }); if (pProp != aUserData.end()) pProp->Value >>= xMaster; // only process if this is a valid after effect if( xMaster.is() ) { CustomAnimationEffectPtr pMasterEffect; // find the master effect stl_CustomAnimationEffect_search_node_predict aSearchPredict( xMaster ); EffectSequence::iterator aIter( std::find_if( maEffects.begin(), maEffects.end(), aSearchPredict ) ); if( aIter != maEffects.end() ) pMasterEffect = *aIter; if( pMasterEffect ) { pMasterEffect->setHasAfterEffect( true ); // find out what kind of after effect this is if( xNode->getType() == AnimationNodeType::ANIMATECOLOR ) { // it's a dim Reference< XAnimate > xAnimate( xNode, UNO_QUERY_THROW ); pMasterEffect->setDimColor( xAnimate->getTo() ); pMasterEffect->setAfterEffectOnNext( true ); } else { // it's a hide pMasterEffect->setAfterEffectOnNext( xNode->getParent() != xMaster->getParent() ); } } } } catch( Exception& ) { TOOLS_WARN_EXCEPTION( "sd", "sd::EffectSequenceHelper::processAfterEffect()" ); } } namespace { class AnimationChangeListener : public cppu::WeakImplHelper< XChangesListener > { public: explicit AnimationChangeListener( MainSequence* pMainSequence ) : mpMainSequence( pMainSequence ) {} virtual void SAL_CALL changesOccurred( const css::util::ChangesEvent& Event ) override; virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; private: MainSequence* mpMainSequence; }; } void SAL_CALL AnimationChangeListener::changesOccurred( const css::util::ChangesEvent& ) { if( mpMainSequence ) mpMainSequence->startRecreateTimer(); } void SAL_CALL AnimationChangeListener::disposing( const css::lang::EventObject& ) { } MainSequence::MainSequence() : mxTimingRootNode(SequenceTimeContainer::create(::comphelper::getProcessComponentContext())) , maTimer("sd MainSequence maTimer") , mbTimerMode(false) , mbRebuilding( false ) , mnRebuildLockGuard( 0 ) , mbPendingRebuildRequest( false ) , mbIgnoreChanges( 0 ) { if( mxTimingRootNode.is() ) { Sequence< css::beans::NamedValue > aUserData { { "node-type", css::uno::Any(css::presentation::EffectNodeType::MAIN_SEQUENCE) } }; mxTimingRootNode->setUserData( aUserData ); } init(); } MainSequence::MainSequence( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) : mxTimingRootNode( xNode, UNO_QUERY ) , maTimer("sd MainSequence maTimer") , mbTimerMode( false ) , mbRebuilding( false ) , mnRebuildLockGuard( 0 ) , mbPendingRebuildRequest( false ) , mbIgnoreChanges( 0 ) { init(); } MainSequence::~MainSequence() { reset(); } void MainSequence::init() { mnSequenceType = EffectNodeType::MAIN_SEQUENCE; maTimer.SetInvokeHandler( LINK(this, MainSequence, onTimerHdl) ); maTimer.SetTimeout(50); mxChangesListener.set( new AnimationChangeListener( this ) ); createMainSequence(); } void MainSequence::reset( const css::uno::Reference< css::animations::XAnimationNode >& xTimingRootNode ) { reset(); mxTimingRootNode.set( xTimingRootNode, UNO_QUERY ); createMainSequence(); } Reference< css::animations::XAnimationNode > MainSequence::getRootNode() { DBG_ASSERT( mnRebuildLockGuard == 0, "MainSequence::getRootNode(), rebuild is locked, is this really what you want?" ); if( maTimer.IsActive() && mbTimerMode ) { // force a rebuild NOW if one is pending maTimer.Stop(); implRebuild(); } return EffectSequenceHelper::getRootNode(); } void MainSequence::createMainSequence() { if( mxTimingRootNode.is() ) try { Reference< XEnumerationAccess > xEnumerationAccess( mxTimingRootNode, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW ); while( xEnumeration->hasMoreElements() ) { Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW ); sal_Int32 nNodeType = CustomAnimationEffect::get_node_type( xChildNode ); if( nNodeType == EffectNodeType::MAIN_SEQUENCE ) { mxSequenceRoot.set( xChildNode, UNO_QUERY ); EffectSequenceHelper::create( xChildNode ); } else if( nNodeType == EffectNodeType::INTERACTIVE_SEQUENCE ) { Reference< XTimeContainer > xInteractiveRoot( xChildNode, UNO_QUERY_THROW ); InteractiveSequencePtr pIS = std::make_shared( 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::Any(css::presentation::EffectNodeType::MAIN_SEQUENCE) } }; mxSequenceRoot->setUserData( aUserData ); // empty sequence until now, set duration to 0.0 // explicitly (otherwise, this sequence will never // end) mxSequenceRoot->setDuration( Any(0.0) ); Reference< XAnimationNode > xMainSequenceNode( mxSequenceRoot, UNO_QUERY_THROW ); mxTimingRootNode->appendChild( xMainSequenceNode ); } updateTextGroups(); notify_listeners(); Reference< XChangesNotifier > xNotifier( mxTimingRootNode, UNO_QUERY ); if( xNotifier.is() ) xNotifier->addChangesListener( mxChangesListener ); } catch( Exception& ) { TOOLS_WARN_EXCEPTION( "sd", "sd::MainSequence::create()" ); return; } DBG_ASSERT( mxSequenceRoot.is(), "sd::MainSequence::create(), found no main sequence!" ); } void MainSequence::reset() { EffectSequenceHelper::reset(); for (auto const& interactiveSequence : maInteractiveSequenceVector) interactiveSequence->reset(); maInteractiveSequenceVector.clear(); try { Reference< XChangesNotifier > xNotifier( mxTimingRootNode, UNO_QUERY ); if( xNotifier.is() ) xNotifier->removeChangesListener( mxChangesListener ); } catch( Exception& ) { } } InteractiveSequencePtr MainSequence::createInteractiveSequence( const css::uno::Reference< css::drawing::XShape >& xShape ) { InteractiveSequencePtr pIS; // create a new interactive sequence container Reference< XTimeContainer > xISRoot = SequenceTimeContainer::create( ::comphelper::getProcessComponentContext() ); uno::Sequence< css::beans::NamedValue > aUserData { { "node-type", css::uno::Any(css::presentation::EffectNodeType::INTERACTIVE_SEQUENCE) } }; xISRoot->setUserData( aUserData ); xISRoot->setRestart( css::animations::AnimationRestart::WHEN_NOT_ACTIVE ); Reference< XChild > xChild( mxSequenceRoot, UNO_QUERY_THROW ); Reference< XTimeContainer > xParent( xChild->getParent(), UNO_QUERY_THROW ); xParent->appendChild( xISRoot ); pIS = std::make_shared( xISRoot, this); pIS->setTriggerShape( xShape ); pIS->addListener( this ); maInteractiveSequenceVector.push_back( pIS ); return pIS; } CustomAnimationEffectPtr MainSequence::findEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) const { CustomAnimationEffectPtr pEffect = EffectSequenceHelper::findEffect( xNode ); if( !pEffect ) { for (auto const& interactiveSequence : maInteractiveSequenceVector) { pEffect = interactiveSequence->findEffect( xNode ); if (pEffect) break; } } return pEffect; } sal_Int32 MainSequence::getOffsetFromEffect( const CustomAnimationEffectPtr& pEffect ) const { sal_Int32 nOffset = EffectSequenceHelper::getOffsetFromEffect( pEffect ); if( nOffset != -1 ) return nOffset; nOffset = EffectSequenceHelper::getCount(); for (auto const& interactiveSequence : maInteractiveSequenceVector) { sal_Int32 nTemp = interactiveSequence->getOffsetFromEffect( pEffect ); if( nTemp != -1 ) return nOffset + nTemp; nOffset += interactiveSequence->getCount(); } return -1; } CustomAnimationEffectPtr MainSequence::getEffectFromOffset( sal_Int32 nOffset ) const { if( nOffset >= 0 ) { if( nOffset < getCount() ) return EffectSequenceHelper::getEffectFromOffset( nOffset ); nOffset -= getCount(); auto aIter( maInteractiveSequenceVector.begin() ); while( (aIter != maInteractiveSequenceVector.end()) && (nOffset > (*aIter)->getCount()) ) nOffset -= (*aIter++)->getCount(); if( (aIter != maInteractiveSequenceVector.end()) && (nOffset >= 0) ) return (*aIter)->getEffectFromOffset( nOffset ); } CustomAnimationEffectPtr pEffect; return pEffect; } bool MainSequence::disposeShape( const Reference< XShape >& xShape ) { bool bChanges = EffectSequenceHelper::disposeShape( xShape ); for (auto const& iterativeSequence : maInteractiveSequenceVector) { bChanges |= iterativeSequence->disposeShape( xShape ); } if( bChanges ) startRebuildTimer(); return bChanges; } bool MainSequence::hasEffect( const css::uno::Reference< css::drawing::XShape >& xShape ) { if( EffectSequenceHelper::hasEffect( xShape ) ) return true; for (auto const& iterativeSequence : maInteractiveSequenceVector) { if( iterativeSequence->getTriggerShape() == xShape ) return true; if( iterativeSequence->hasEffect( xShape ) ) return true; } return false; } void MainSequence::insertTextRange( const css::uno::Any& aTarget ) { EffectSequenceHelper::insertTextRange( aTarget ); for (auto const& iterativeSequence : maInteractiveSequenceVector) { iterativeSequence->insertTextRange( aTarget ); } } void MainSequence::disposeTextRange( const css::uno::Any& aTarget ) { EffectSequenceHelper::disposeTextRange( aTarget ); for (auto const& iterativeSequence : maInteractiveSequenceVector) { iterativeSequence->disposeTextRange( aTarget ); } } /** callback from the sd::View when an object just left text edit mode */ void MainSequence::onTextChanged( const Reference< XShape >& xShape ) { EffectSequenceHelper::onTextChanged( xShape ); for (auto const& iterativeSequence : maInteractiveSequenceVector) { iterativeSequence->onTextChanged( xShape ); } } void EffectSequenceHelper::onTextChanged( const Reference< XShape >& xShape ) { // get map [paragraph index] -> [NumberingLevel] // for following reusage inside all animation effects std::vector< sal_Int32 > paragraphNumberingLevel; std::vector< sal_Int32 >* paragraphNumberingLevelParam = nullptr; if ( getParagraphNumberingLevels( xShape, paragraphNumberingLevel ) ) paragraphNumberingLevelParam = ¶graphNumberingLevel; // update internal flags for each animation effect const bool bChanges = std::accumulate(maEffects.begin(), maEffects.end(), false, [&xShape, ¶graphNumberingLevelParam](const bool bCheck, const CustomAnimationEffectPtr& rxEffect) { bool bRes = bCheck; if (rxEffect->getTargetShape() == xShape) bRes |= rxEffect->checkForText( paragraphNumberingLevelParam ); return bRes; }); if( bChanges ) rebuild(); } void MainSequence::rebuild() { startRebuildTimer(); } void MainSequence::lockRebuilds() { mnRebuildLockGuard++; } void MainSequence::unlockRebuilds() { DBG_ASSERT( mnRebuildLockGuard, "sd::MainSequence::unlockRebuilds(), no corresponding lockRebuilds() call!" ); if( mnRebuildLockGuard ) mnRebuildLockGuard--; if( (mnRebuildLockGuard == 0) && mbPendingRebuildRequest ) { mbPendingRebuildRequest = false; startRebuildTimer(); } } void MainSequence::implRebuild() { if( mnRebuildLockGuard ) { mbPendingRebuildRequest = true; return; } mbRebuilding = true; EffectSequenceHelper::implRebuild(); auto aIter( maInteractiveSequenceVector.begin() ); while( aIter != maInteractiveSequenceVector.end() ) { InteractiveSequencePtr pIS( *aIter ); if( pIS->maEffects.empty() ) { // remove empty interactive sequences aIter = maInteractiveSequenceVector.erase( aIter ); Reference< XChild > xChild( mxSequenceRoot, UNO_QUERY_THROW ); Reference< XTimeContainer > xParent( xChild->getParent(), UNO_QUERY_THROW ); Reference< XAnimationNode > xISNode( pIS->mxSequenceRoot, UNO_QUERY_THROW ); xParent->removeChild( xISNode ); } else { pIS->implRebuild(); ++aIter; } } notify_listeners(); mbRebuilding = false; } void MainSequence::notify_change() { notify_listeners(); } bool MainSequence::setTrigger( const CustomAnimationEffectPtr& pEffect, const css::uno::Reference< css::drawing::XShape >& xTriggerShape ) { EffectSequenceHelper* pOldSequence = pEffect->getEffectSequence(); EffectSequenceHelper* pNewSequence = nullptr; if( xTriggerShape.is() ) { for (InteractiveSequencePtr const& pIS : maInteractiveSequenceVector) { if( pIS->getTriggerShape() == xTriggerShape ) { pNewSequence = pIS.get(); break; } } if( !pNewSequence ) pNewSequence = createInteractiveSequence( xTriggerShape ).get(); } else { pNewSequence = this; } if( pOldSequence != pNewSequence ) { if( pOldSequence ) pOldSequence->maEffects.remove( pEffect ); if( pNewSequence ) pNewSequence->maEffects.push_back( pEffect ); pEffect->setEffectSequence( pNewSequence ); return true; } else { return false; } } IMPL_LINK_NOARG(MainSequence, onTimerHdl, Timer *, void) { if( mbTimerMode ) { implRebuild(); } else { reset(); createMainSequence(); } } /** starts a timer that recreates the internal structure from the API core */ void MainSequence::startRecreateTimer() { if( !mbRebuilding && (mbIgnoreChanges == 0) ) { mbTimerMode = false; maTimer.Start(); } } /** * starts a timer that rebuilds the API core from the internal structure * This is used to reduce the number of screen redraws due to animation changes. */ void MainSequence::startRebuildTimer() { mbTimerMode = true; maTimer.Start(); } InteractiveSequence::InteractiveSequence( const Reference< XTimeContainer >& xSequenceRoot, MainSequence* pMainSequence ) : EffectSequenceHelper( xSequenceRoot ), mpMainSequence( pMainSequence ) { mnSequenceType = EffectNodeType::INTERACTIVE_SEQUENCE; try { if( mxSequenceRoot.is() ) { Reference< XEnumerationAccess > xEnumerationAccess( mxSequenceRoot, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW ); while( !mxEventSource.is() && xEnumeration->hasMoreElements() ) { Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW ); Event aEvent; if( (xChildNode->getBegin() >>= aEvent) && (aEvent.Trigger == EventTrigger::ON_CLICK) ) aEvent.Source >>= mxEventSource; } } } catch( Exception& ) { TOOLS_WARN_EXCEPTION( "sd", "sd::InteractiveSequence::InteractiveSequence()" ); return; } } void InteractiveSequence::rebuild() { mpMainSequence->rebuild(); } void InteractiveSequence::implRebuild() { EffectSequenceHelper::implRebuild(); } MainSequenceRebuildGuard::MainSequenceRebuildGuard( const MainSequencePtr& pMainSequence ) : mpMainSequence( pMainSequence ) { if( mpMainSequence ) mpMainSequence->lockRebuilds(); } MainSequenceRebuildGuard::~MainSequenceRebuildGuard() { if( mpMainSequence ) mpMainSequence->unlockRebuilds(); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */