bump product version to 6.4.0.3
[LibreOffice.git] / sd / source / core / CustomAnimationEffect.cxx
blob0493f885ec8dcd42c5615fb8ed7724c4e3503311
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <tools/debug.hxx>
21 #include <sal/log.hxx>
22 #include <com/sun/star/animations/AnimationNodeType.hpp>
23 #include <com/sun/star/animations/AnimateColor.hpp>
24 #include <com/sun/star/animations/AnimateMotion.hpp>
25 #include <com/sun/star/animations/AnimateSet.hpp>
26 #include <com/sun/star/animations/AnimationFill.hpp>
27 #include <com/sun/star/animations/Audio.hpp>
28 #include <com/sun/star/animations/Command.hpp>
29 #include <com/sun/star/animations/Event.hpp>
30 #include <com/sun/star/animations/EventTrigger.hpp>
31 #include <com/sun/star/animations/IterateContainer.hpp>
32 #include <com/sun/star/animations/ParallelTimeContainer.hpp>
33 #include <com/sun/star/animations/SequenceTimeContainer.hpp>
34 #include <com/sun/star/animations/XCommand.hpp>
35 #include <com/sun/star/animations/XIterateContainer.hpp>
36 #include <com/sun/star/animations/XAnimateTransform.hpp>
37 #include <com/sun/star/animations/XAnimateMotion.hpp>
38 #include <com/sun/star/animations/XAnimate.hpp>
39 #include <com/sun/star/animations/AnimationRestart.hpp>
40 #include <com/sun/star/beans/NamedValue.hpp>
41 #include <com/sun/star/beans/XPropertySet.hpp>
42 #include <com/sun/star/container/XEnumerationAccess.hpp>
43 #include <com/sun/star/lang/XInitialization.hpp>
44 #include <com/sun/star/presentation/EffectNodeType.hpp>
45 #include <com/sun/star/presentation/EffectCommands.hpp>
46 #include <com/sun/star/presentation/EffectPresetClass.hpp>
47 #include <com/sun/star/presentation/ParagraphTarget.hpp>
48 #include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
49 #include <com/sun/star/text/XText.hpp>
50 #include <com/sun/star/util/XCloneable.hpp>
51 #include <com/sun/star/util/XChangesNotifier.hpp>
52 #include <comphelper/processfactory.hxx>
53 #include <comphelper/sequence.hxx>
54 #include <com/sun/star/lang/Locale.hpp>
55 #include <com/sun/star/i18n/BreakIterator.hpp>
56 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
57 #include <com/sun/star/i18n/WordType.hpp>
58 #include <com/sun/star/presentation/TextAnimationType.hpp>
60 #include <basegfx/polygon/b2dpolypolygon.hxx>
61 #include <basegfx/polygon/b2dpolypolygontools.hxx>
62 #include <basegfx/range/b2drange.hxx>
63 #include <basegfx/matrix/b2dhommatrixtools.hxx>
65 #include <algorithm>
66 #include <deque>
67 #include <numeric>
69 #include <cppuhelper/implbase.hxx>
71 #include <drawinglayer/geometry/viewinformation2d.hxx>
72 #include <svx/sdr/contact/viewcontact.hxx>
73 #include <svx/svdopath.hxx>
74 #include <svx/svdpage.hxx>
75 #include <svx/unoapi.hxx>
76 #include <CustomAnimationEffect.hxx>
77 #include <CustomAnimationPreset.hxx>
78 #include <animations.hxx>
80 using namespace ::com::sun::star;
81 using namespace ::com::sun::star::uno;
82 using namespace ::com::sun::star::presentation;
83 using namespace ::com::sun::star::animations;
85 using ::com::sun::star::container::XEnumerationAccess;
86 using ::com::sun::star::container::XEnumeration;
87 using ::com::sun::star::beans::NamedValue;
88 using ::com::sun::star::container::XChild;
89 using ::com::sun::star::drawing::XShape;
90 using ::com::sun::star::lang::XInitialization;
91 using ::com::sun::star::text::XText;
92 using ::com::sun::star::text::XTextRange;
93 using ::com::sun::star::beans::XPropertySet;
94 using ::com::sun::star::util::XCloneable;
95 using ::com::sun::star::lang::Locale;
96 using ::com::sun::star::util::XChangesNotifier;
97 using ::com::sun::star::util::XChangesListener;
99 namespace sd
101 class MainSequenceChangeGuard
103 public:
104 explicit MainSequenceChangeGuard( EffectSequenceHelper* pSequence )
106 mpMainSequence = dynamic_cast< MainSequence* >( pSequence );
107 if( mpMainSequence == nullptr )
109 InteractiveSequence* pI = dynamic_cast< InteractiveSequence* >( pSequence );
110 if( pI )
111 mpMainSequence = pI->mpMainSequence;
113 DBG_ASSERT( mpMainSequence, "sd::MainSequenceChangeGuard::MainSequenceChangeGuard(), no main sequence to guard!" );
115 if( mpMainSequence )
116 mpMainSequence->mbIgnoreChanges++;
119 ~MainSequenceChangeGuard()
121 if( mpMainSequence )
122 mpMainSequence->mbIgnoreChanges++;
125 private:
126 MainSequence* mpMainSequence;
129 CustomAnimationEffect::CustomAnimationEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
130 : mnNodeType(-1),
131 mnPresetClass(-1),
132 mnFill(AnimationFill::HOLD),
133 mfBegin(-1.0),
134 mfDuration(-1.0),
135 mfAbsoluteDuration(-1.0),
136 mnGroupId(-1),
137 mnIterateType(0),
138 mfIterateInterval(0.0),
139 mnParaDepth( -1 ),
140 mbHasText(false),
141 mfAcceleration( 1.0 ),
142 mfDecelerate( 1.0 ),
143 mbAutoReverse(false),
144 mnTargetSubItem(0),
145 mnCommand(0),
146 mpEffectSequence( nullptr ),
147 mbHasAfterEffect(false),
148 mbAfterEffectOnNextEffect(false)
150 setNode( xNode );
153 void CustomAnimationEffect::setNode( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
155 mxNode = xNode;
156 mxAudio.clear();
158 const Sequence< NamedValue > aUserData( mxNode->getUserData() );
160 for( const NamedValue& rProp : aUserData )
162 if ( rProp.Name == "node-type" )
164 rProp.Value >>= mnNodeType;
166 else if ( rProp.Name == "preset-id" )
168 rProp.Value >>= maPresetId;
170 else if ( rProp.Name == "preset-sub-type" )
172 rProp.Value >>= maPresetSubType;
174 else if ( rProp.Name == "preset-class" )
176 rProp.Value >>= mnPresetClass;
178 else if ( rProp.Name == "preset-property" )
180 rProp.Value >>= maProperty;
182 else if ( rProp.Name == "group-id" )
184 rProp.Value >>= mnGroupId;
188 // get effect start time
189 mxNode->getBegin() >>= mfBegin;
191 mfAcceleration = mxNode->getAcceleration();
192 mfDecelerate = mxNode->getDecelerate();
193 mbAutoReverse = mxNode->getAutoReverse();
195 mnFill = mxNode->getFill();
197 // get iteration data
198 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
199 if( xIter.is() )
201 mfIterateInterval = xIter->getIterateInterval();
202 mnIterateType = xIter->getIterateType();
203 maTarget = xIter->getTarget();
204 mnTargetSubItem = xIter->getSubItem();
206 else
208 mfIterateInterval = 0.0f;
209 mnIterateType = 0;
212 // calculate effect duration and get target shape
213 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
214 if( xEnumerationAccess.is() )
216 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
217 if( xEnumeration.is() )
219 while( xEnumeration->hasMoreElements() )
221 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY );
222 if( !xChildNode.is() )
223 continue;
225 if( xChildNode->getType() == AnimationNodeType::AUDIO )
227 mxAudio.set( xChildNode, UNO_QUERY );
229 else if( xChildNode->getType() == AnimationNodeType::COMMAND )
231 Reference< XCommand > xCommand( xChildNode, UNO_QUERY );
232 if( xCommand.is() )
234 mnCommand = xCommand->getCommand();
235 if( !maTarget.hasValue() )
236 maTarget = xCommand->getTarget();
239 else
241 double fBegin = 0.0;
242 double fDuration = 0.0;
243 xChildNode->getBegin() >>= fBegin;
244 xChildNode->getDuration() >>= fDuration;
246 fDuration += fBegin;
247 if( fDuration > mfDuration )
248 mfDuration = fDuration;
250 // no target shape yet?
251 if( !maTarget.hasValue() )
253 // go get it boys!
254 Reference< XAnimate > xAnimate( xChildNode, UNO_QUERY );
255 if( xAnimate.is() )
257 maTarget = xAnimate->getTarget();
258 mnTargetSubItem = xAnimate->getSubItem();
266 mfAbsoluteDuration = mfDuration;
267 double fRepeatCount = 1.0;
268 if( (mxNode->getRepeatCount()) >>= fRepeatCount )
269 mfAbsoluteDuration *= fRepeatCount;
271 checkForText();
274 sal_Int32 CustomAnimationEffect::getNumberOfSubitems( const Any& aTarget, sal_Int16 nIterateType )
276 sal_Int32 nSubItems = 0;
280 // first get target text
281 sal_Int32 nOnlyPara = -1;
283 Reference< XText > xShape;
284 aTarget >>= xShape;
285 if( !xShape.is() )
287 ParagraphTarget aParaTarget;
288 if( aTarget >>= aParaTarget )
290 xShape.set( aParaTarget.Shape, UNO_QUERY );
291 nOnlyPara = aParaTarget.Paragraph;
295 // now use the break iterator to iterate over the given text
296 // and count the sub items
298 if( xShape.is() )
300 // TODO/LATER: Optimize this, don't create a break iterator each time
301 Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
302 Reference < i18n::XBreakIterator > xBI = i18n::BreakIterator::create(xContext);
304 Reference< XEnumerationAccess > xEA( xShape, UNO_QUERY_THROW );
305 Reference< XEnumeration > xEnumeration( xEA->createEnumeration(), UNO_SET_THROW );
306 css::lang::Locale aLocale;
307 const OUString aStrLocaleName( "CharLocale" );
308 Reference< XTextRange > xParagraph;
310 sal_Int32 nPara = 0;
311 while( xEnumeration->hasMoreElements() )
313 xEnumeration->nextElement() >>= xParagraph;
315 // skip this if it's not the only paragraph we want to count
316 if( (nOnlyPara != -1) && (nOnlyPara != nPara ) )
317 continue;
319 if( nIterateType == TextAnimationType::BY_PARAGRAPH )
321 nSubItems++;
323 else
325 const OUString aText( xParagraph->getString() );
326 Reference< XPropertySet > xSet( xParagraph, UNO_QUERY_THROW );
327 xSet->getPropertyValue( aStrLocaleName ) >>= aLocale;
329 sal_Int32 nPos;
330 const sal_Int32 nEndPos = aText.getLength();
332 if( nIterateType == TextAnimationType::BY_WORD )
334 for( nPos = 0; nPos < nEndPos; nPos++ )
336 nPos = xBI->getWordBoundary(aText, nPos, aLocale, i18n::WordType::ANY_WORD, true).endPos;
337 nSubItems++;
339 break;
341 else
343 sal_Int32 nDone;
344 for( nPos = 0; nPos < nEndPos; nPos++ )
346 nPos = xBI->nextCharacters(aText, nPos, aLocale, i18n::CharacterIteratorMode::SKIPCELL, 0, nDone);
347 nSubItems++;
352 if( nPara == nOnlyPara )
353 break;
355 nPara++;
359 catch( Exception& )
361 nSubItems = 0;
362 OSL_FAIL( "sd::CustomAnimationEffect::getNumberOfSubitems(), exception caught!" );
365 return nSubItems;
368 CustomAnimationEffect::~CustomAnimationEffect()
372 CustomAnimationEffectPtr CustomAnimationEffect::clone() const
374 Reference< XCloneable > xCloneable( mxNode, UNO_QUERY_THROW );
375 Reference< XAnimationNode > xNode( xCloneable->createClone(), UNO_QUERY_THROW );
376 CustomAnimationEffectPtr pEffect( new CustomAnimationEffect( xNode ) );
377 pEffect->setEffectSequence( getEffectSequence() );
378 return pEffect;
381 sal_Int32 CustomAnimationEffect::get_node_type( const Reference< XAnimationNode >& xNode )
383 sal_Int16 nNodeType = -1;
385 if( xNode.is() )
387 Sequence< NamedValue > aUserData( xNode->getUserData() );
388 if( aUserData.hasElements() )
390 const NamedValue* pProp = std::find_if(aUserData.begin(), aUserData.end(),
391 [](const NamedValue& rProp) { return rProp.Name == "node-type"; });
392 if (pProp != aUserData.end())
393 pProp->Value >>= nNodeType;
397 return nNodeType;
400 void CustomAnimationEffect::setPresetClass( sal_Int16 nPresetClass )
402 if( mnPresetClass == nPresetClass )
403 return;
405 mnPresetClass = nPresetClass;
406 if( !mxNode.is() )
407 return;
409 // first try to find a "preset-class" entry in the user data
410 // and change it
411 Sequence< NamedValue > aUserData( mxNode->getUserData() );
412 sal_Int32 nLength = aUserData.getLength();
413 bool bFound = false;
414 if( nLength )
416 NamedValue* pProp = std::find_if(aUserData.begin(), aUserData.end(),
417 [](const NamedValue& rProp) { return rProp.Name == "preset-class"; });
418 if (pProp != aUserData.end())
420 pProp->Value <<= mnPresetClass;
421 bFound = true;
425 // no "preset-class" entry inside user data, so add it
426 if( !bFound )
428 aUserData.realloc( nLength + 1);
429 aUserData[nLength].Name = "preset-class";
430 aUserData[nLength].Value <<= mnPresetClass;
433 mxNode->setUserData( aUserData );
436 void CustomAnimationEffect::setNodeType( sal_Int16 nNodeType )
438 if( mnNodeType == nNodeType )
439 return;
441 mnNodeType = nNodeType;
442 if( !mxNode.is() )
443 return;
445 // first try to find a "node-type" entry in the user data
446 // and change it
447 Sequence< NamedValue > aUserData( mxNode->getUserData() );
448 sal_Int32 nLength = aUserData.getLength();
449 bool bFound = false;
450 if( nLength )
452 NamedValue* pProp = std::find_if(aUserData.begin(), aUserData.end(),
453 [](const NamedValue& rProp) { return rProp.Name == "node-type"; });
454 if (pProp != aUserData.end())
456 pProp->Value <<= mnNodeType;
457 bFound = true;
461 // no "node-type" entry inside user data, so add it
462 if( !bFound )
464 aUserData.realloc( nLength + 1);
465 aUserData[nLength].Name = "node-type";
466 aUserData[nLength].Value <<= mnNodeType;
469 mxNode->setUserData( aUserData );
472 void CustomAnimationEffect::setGroupId( sal_Int32 nGroupId )
474 mnGroupId = nGroupId;
475 if( !mxNode.is() )
476 return;
478 // first try to find a "group-id" entry in the user data
479 // and change it
480 Sequence< NamedValue > aUserData( mxNode->getUserData() );
481 sal_Int32 nLength = aUserData.getLength();
482 bool bFound = false;
483 if( nLength )
485 NamedValue* pProp = std::find_if(aUserData.begin(), aUserData.end(),
486 [](const NamedValue& rProp) { return rProp.Name == "group-id"; });
487 if (pProp != aUserData.end())
489 pProp->Value <<= mnGroupId;
490 bFound = true;
494 // no "group-id" entry inside user data, so add it
495 if( !bFound )
497 aUserData.realloc( nLength + 1);
498 aUserData[nLength].Name = "group-id";
499 aUserData[nLength].Value <<= mnGroupId;
502 mxNode->setUserData( aUserData );
505 /** checks if the text for this effect has changed and updates internal flags.
506 returns true if something changed.
508 bool CustomAnimationEffect::checkForText( const std::vector<sal_Int32>* paragraphNumberingLevel )
510 bool bChange = false;
512 Reference< XText > xText;
514 if( maTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
516 // calc para depth
517 ParagraphTarget aParaTarget;
518 maTarget >>= aParaTarget;
520 xText.set( aParaTarget.Shape, UNO_QUERY );
522 // get paragraph
523 if( xText.is() )
525 sal_Int32 nPara = aParaTarget.Paragraph;
527 bool bHasText = false;
528 sal_Int32 nParaDepth = 0;
530 if ( paragraphNumberingLevel )
532 bHasText = !paragraphNumberingLevel->empty();
533 if (nPara >= 0 && static_cast<size_t>(nPara) < paragraphNumberingLevel->size())
534 nParaDepth = paragraphNumberingLevel->at(nPara);
536 else
538 Reference< XEnumerationAccess > xEA( xText, UNO_QUERY );
539 if( xEA.is() )
541 Reference< XEnumeration > xEnumeration = xEA->createEnumeration();
542 if( xEnumeration.is() )
544 bHasText = xEnumeration->hasMoreElements();
546 while( xEnumeration->hasMoreElements() && nPara-- )
547 xEnumeration->nextElement();
549 if( xEnumeration->hasMoreElements() )
551 Reference< XPropertySet > xParaSet;
552 xEnumeration->nextElement() >>= xParaSet;
553 if( xParaSet.is() )
555 const OUString strNumberingLevel( "NumberingLevel" );
556 xParaSet->getPropertyValue( strNumberingLevel ) >>= nParaDepth;
563 if( bHasText )
565 bChange |= bHasText != mbHasText;
566 mbHasText = bHasText;
568 bChange |= nParaDepth != mnParaDepth;
569 mnParaDepth = nParaDepth;
573 else
575 maTarget >>= xText;
576 bool bHasText = xText.is() && !xText->getString().isEmpty();
577 bChange |= bHasText != mbHasText;
578 mbHasText = bHasText;
581 bChange |= calculateIterateDuration();
582 return bChange;
585 bool CustomAnimationEffect::calculateIterateDuration()
587 bool bChange = false;
589 // if we have an iteration, we must also calculate the
590 // 'true' container duration, that is
591 // ( ( is form animated ) ? [contained effects duration] : 0 ) +
592 // ( [number of animated children] - 1 ) * [interval-delay] + [contained effects duration]
593 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
594 if( xIter.is() )
596 double fDuration = mfDuration;
597 const double fSubEffectDuration = mfDuration;
599 if( mnTargetSubItem != ShapeAnimationSubType::ONLY_BACKGROUND ) // does not make sense for iterate container but better check
601 const sal_Int32 nSubItems = getNumberOfSubitems( maTarget, mnIterateType );
602 if( nSubItems )
604 const double f = (nSubItems-1) * mfIterateInterval;
605 fDuration += f;
609 // if we also animate the form first, we have to add the
610 // sub effect duration to the whole effect duration
611 if( mnTargetSubItem == ShapeAnimationSubType::AS_WHOLE )
612 fDuration += fSubEffectDuration;
614 bChange |= fDuration != mfAbsoluteDuration;
615 mfAbsoluteDuration = fDuration;
618 return bChange;
621 void CustomAnimationEffect::setTarget( const css::uno::Any& rTarget )
625 maTarget = rTarget;
627 // first, check special case for random node
628 Reference< XInitialization > xInit( mxNode, UNO_QUERY );
629 if( xInit.is() )
631 const Sequence< Any > aArgs( &maTarget, 1 );
632 xInit->initialize( aArgs );
634 else
636 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
637 if( xIter.is() )
639 xIter->setTarget(maTarget);
641 else
643 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
644 if( xEnumerationAccess.is() )
646 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
647 if( xEnumeration.is() )
649 while( xEnumeration->hasMoreElements() )
651 const Any aElem( xEnumeration->nextElement() );
652 Reference< XAnimate > xAnimate( aElem, UNO_QUERY );
653 if( xAnimate.is() )
654 xAnimate->setTarget( rTarget );
655 else
657 Reference< XCommand > xCommand( aElem, UNO_QUERY );
658 if( xCommand.is() )
659 xCommand->setTarget( rTarget );
666 checkForText();
668 catch( Exception& )
670 OSL_FAIL( "sd::CustomAnimationEffect::setTarget(), exception caught!" );
674 void CustomAnimationEffect::setTargetSubItem( sal_Int16 nSubItem )
678 mnTargetSubItem = nSubItem;
680 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
681 if( xIter.is() )
683 xIter->setSubItem(mnTargetSubItem);
685 else
687 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
688 if( xEnumerationAccess.is() )
690 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
691 if( xEnumeration.is() )
693 while( xEnumeration->hasMoreElements() )
695 Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
696 if( xAnimate.is() )
697 xAnimate->setSubItem( mnTargetSubItem );
703 catch( Exception& )
705 OSL_FAIL( "sd::CustomAnimationEffect::setTargetSubItem(), exception caught!" );
709 void CustomAnimationEffect::setDuration( double fDuration )
711 if( (mfDuration == -1.0) || (mfDuration == fDuration) )
712 return;
716 double fScale = fDuration / mfDuration;
717 mfDuration = fDuration;
718 double fRepeatCount = 1.0;
719 getRepeatCount() >>= fRepeatCount;
720 mfAbsoluteDuration = mfDuration * fRepeatCount;
722 // calculate effect duration and get target shape
723 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
724 if( xEnumerationAccess.is() )
726 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
727 if( xEnumeration.is() )
729 while( xEnumeration->hasMoreElements() )
731 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY );
732 if( !xChildNode.is() )
733 continue;
735 double fChildBegin = 0.0;
736 xChildNode->getBegin() >>= fChildBegin;
737 if( fChildBegin != 0.0 )
739 fChildBegin *= fScale;
740 xChildNode->setBegin( makeAny( fChildBegin ) );
743 double fChildDuration = 0.0;
744 xChildNode->getDuration() >>= fChildDuration;
745 if( fChildDuration != 0.0 )
747 fChildDuration *= fScale;
748 xChildNode->setDuration( makeAny( fChildDuration ) );
753 calculateIterateDuration();
755 catch( Exception& )
757 OSL_FAIL( "sd::CustomAnimationEffect::setDuration(), exception caught!" );
761 void CustomAnimationEffect::setBegin( double fBegin )
763 if( mxNode.is() ) try
765 mfBegin = fBegin;
766 mxNode->setBegin( makeAny( fBegin ) );
768 catch( Exception& )
770 OSL_FAIL( "sd::CustomAnimationEffect::setBegin(), exception caught!" );
774 void CustomAnimationEffect::setAcceleration( double fAcceleration )
776 if( mxNode.is() ) try
778 mfAcceleration = fAcceleration;
779 mxNode->setAcceleration( fAcceleration );
781 catch( Exception& )
783 OSL_FAIL( "sd::CustomAnimationEffect::setAcceleration(), exception caught!" );
787 void CustomAnimationEffect::setDecelerate( double fDecelerate )
789 if( mxNode.is() ) try
791 mfDecelerate = fDecelerate;
792 mxNode->setDecelerate( fDecelerate );
794 catch( Exception& )
796 OSL_FAIL( "sd::CustomAnimationEffect::setDecelerate(), exception caught!" );
800 void CustomAnimationEffect::setAutoReverse( bool bAutoReverse )
802 if( mxNode.is() ) try
804 mbAutoReverse = bAutoReverse;
805 mxNode->setAutoReverse( bAutoReverse );
807 catch( Exception& )
809 OSL_FAIL( "sd::CustomAnimationEffect::setAutoReverse(), exception caught!" );
813 void CustomAnimationEffect::replaceNode( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
815 sal_Int16 nNodeType = mnNodeType;
816 Any aTarget = maTarget;
818 sal_Int16 nFill = mnFill;
819 double fBegin = mfBegin;
820 double fDuration = mfDuration;
821 double fAcceleration = mfAcceleration;
822 double fDecelerate = mfDecelerate ;
823 bool bAutoReverse = mbAutoReverse;
824 Reference< XAudio > xAudio( mxAudio );
825 sal_Int16 nIterateType = mnIterateType;
826 double fIterateInterval = mfIterateInterval;
827 sal_Int16 nSubItem = mnTargetSubItem;
829 setNode( xNode );
831 setAudio( xAudio );
832 setNodeType( nNodeType );
833 setTarget( aTarget );
834 setTargetSubItem( nSubItem );
835 setDuration( fDuration );
836 setBegin( fBegin );
837 setFill( nFill );
839 setAcceleration( fAcceleration );
840 setDecelerate( fDecelerate );
841 setAutoReverse( bAutoReverse );
843 if( nIterateType != mnIterateType )
844 setIterateType( nIterateType );
846 if( mnIterateType && ( fIterateInterval != mfIterateInterval ) )
847 setIterateInterval( fIterateInterval );
850 Reference< XShape > CustomAnimationEffect::getTargetShape() const
852 Reference< XShape > xShape;
853 maTarget >>= xShape;
854 if( !xShape.is() )
856 ParagraphTarget aParaTarget;
857 if( maTarget >>= aParaTarget )
858 xShape = aParaTarget.Shape;
861 return xShape;
864 Any CustomAnimationEffect::getRepeatCount() const
866 if( mxNode.is() )
868 return mxNode->getRepeatCount();
870 else
872 Any aAny;
873 return aAny;
877 Any CustomAnimationEffect::getEnd() const
879 if( mxNode.is() )
881 return mxNode->getEnd();
883 else
885 Any aAny;
886 return aAny;
890 void CustomAnimationEffect::setRepeatCount( const Any& rRepeatCount )
892 if( mxNode.is() )
894 mxNode->setRepeatCount( rRepeatCount );
895 double fRepeatCount = 1.0;
896 rRepeatCount >>= fRepeatCount;
897 mfAbsoluteDuration = mfDuration * fRepeatCount;
901 void CustomAnimationEffect::setEnd( const Any& rEnd )
903 if( mxNode.is() )
904 mxNode->setEnd( rEnd );
907 void CustomAnimationEffect::setFill( sal_Int16 nFill )
909 if (mxNode.is())
911 mnFill = nFill;
912 mxNode->setFill( nFill );
916 Reference< XAnimationNode > CustomAnimationEffect::createAfterEffectNode() const
918 DBG_ASSERT( mbHasAfterEffect, "sd::CustomAnimationEffect::createAfterEffectNode(), this node has no after effect!" );
920 Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
922 Reference< XAnimate > xAnimate;
923 if( maDimColor.hasValue() )
924 xAnimate = AnimateColor::create( xContext );
925 else
926 xAnimate = AnimateSet::create( xContext );
928 Any aTo;
929 OUString aAttributeName;
931 if( maDimColor.hasValue() )
933 aTo = maDimColor;
934 aAttributeName = "DimColor";
936 else
938 aTo <<= false;
939 aAttributeName = "Visibility";
942 Any aBegin;
943 if( !mbAfterEffectOnNextEffect ) // sameClick
945 Event aEvent;
947 aEvent.Source <<= getNode();
948 aEvent.Trigger = EventTrigger::END_EVENT;
949 aEvent.Repeat = 0;
951 aBegin <<= aEvent;
953 else
955 aBegin <<= 0.0;
958 xAnimate->setBegin( aBegin );
959 xAnimate->setTo( aTo );
960 xAnimate->setAttributeName( aAttributeName );
962 xAnimate->setDuration( makeAny( 0.001 ) );
963 xAnimate->setFill( AnimationFill::HOLD );
964 xAnimate->setTarget( maTarget );
966 return xAnimate;
969 void CustomAnimationEffect::setIterateType( sal_Int16 nIterateType )
971 if( mnIterateType == nIterateType )
972 return;
976 // do we need to exchange the container node?
977 if( (mnIterateType == 0) || (nIterateType == 0) )
979 sal_Int16 nTargetSubItem = mnTargetSubItem;
981 Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
982 Reference< XTimeContainer > xNewContainer;
983 if(nIterateType)
985 xNewContainer.set( IterateContainer::create( xContext ) );
987 else
988 xNewContainer.set( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW );
990 Reference< XTimeContainer > xOldContainer( mxNode, UNO_QUERY_THROW );
991 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
992 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
993 while( xEnumeration->hasMoreElements() )
995 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
996 xOldContainer->removeChild( xChildNode );
997 xNewContainer->appendChild( xChildNode );
1000 xNewContainer->setBegin( mxNode->getBegin() );
1001 xNewContainer->setDuration( mxNode->getDuration() );
1002 xNewContainer->setEnd( mxNode->getEnd() );
1003 xNewContainer->setEndSync( mxNode->getEndSync() );
1004 xNewContainer->setRepeatCount( mxNode->getRepeatCount() );
1005 xNewContainer->setFill( mxNode->getFill() );
1006 xNewContainer->setFillDefault( mxNode->getFillDefault() );
1007 xNewContainer->setRestart( mxNode->getRestart() );
1008 xNewContainer->setRestartDefault( mxNode->getRestartDefault() );
1009 xNewContainer->setAcceleration( mxNode->getAcceleration() );
1010 xNewContainer->setDecelerate( mxNode->getDecelerate() );
1011 xNewContainer->setAutoReverse( mxNode->getAutoReverse() );
1012 xNewContainer->setRepeatDuration( mxNode->getRepeatDuration() );
1013 xNewContainer->setEndSync( mxNode->getEndSync() );
1014 xNewContainer->setRepeatCount( mxNode->getRepeatCount() );
1015 xNewContainer->setUserData( mxNode->getUserData() );
1017 mxNode = xNewContainer;
1019 Any aTarget;
1020 if( nIterateType )
1022 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY_THROW );
1023 xIter->setTarget(maTarget);
1024 xIter->setSubItem( nTargetSubItem );
1026 else
1028 aTarget = maTarget;
1031 Reference< XEnumerationAccess > xEA( mxNode, UNO_QUERY_THROW );
1032 Reference< XEnumeration > xE( xEA->createEnumeration(), UNO_SET_THROW );
1033 while( xE->hasMoreElements() )
1035 Reference< XAnimate > xAnimate( xE->nextElement(), UNO_QUERY );
1036 if( xAnimate.is() )
1038 xAnimate->setTarget( aTarget );
1039 xAnimate->setSubItem( nTargetSubItem );
1044 mnIterateType = nIterateType;
1046 // if we have an iteration container, we must set its type
1047 if( mnIterateType )
1049 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY_THROW );
1050 xIter->setIterateType( nIterateType );
1053 checkForText();
1055 catch( Exception& )
1057 OSL_FAIL( "sd::CustomAnimationEffect::setIterateType(), Exception caught!" );
1061 void CustomAnimationEffect::setIterateInterval( double fIterateInterval )
1063 if( mfIterateInterval == fIterateInterval )
1064 return;
1066 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
1068 DBG_ASSERT( xIter.is(), "sd::CustomAnimationEffect::setIterateInterval(), not an iteration node" );
1069 if( xIter.is() )
1071 mfIterateInterval = fIterateInterval;
1072 xIter->setIterateInterval( fIterateInterval );
1075 calculateIterateDuration();
1078 OUString CustomAnimationEffect::getPath() const
1080 OUString aPath;
1082 if( mxNode.is() ) try
1084 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
1085 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
1086 while( xEnumeration->hasMoreElements() )
1088 Reference< XAnimateMotion > xMotion( xEnumeration->nextElement(), UNO_QUERY );
1089 if( xMotion.is() )
1091 xMotion->getPath() >>= aPath;
1092 break;
1096 catch( Exception& )
1098 OSL_FAIL("sd::CustomAnimationEffect::getPath(), exception caught!" );
1101 return aPath;
1104 void CustomAnimationEffect::setPath( const OUString& rPath )
1106 if( !mxNode.is() )
1107 return;
1111 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
1112 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
1113 while( xEnumeration->hasMoreElements() )
1115 Reference< XAnimateMotion > xMotion( xEnumeration->nextElement(), UNO_QUERY );
1116 if( xMotion.is() )
1119 MainSequenceChangeGuard aGuard( mpEffectSequence );
1120 xMotion->setPath( Any( rPath ) );
1121 break;
1125 catch( Exception& )
1127 OSL_FAIL("sd::CustomAnimationEffect::setPath(), exception caught!" );
1131 Any CustomAnimationEffect::getProperty( sal_Int32 nNodeType, const OUString& rAttributeName, EValue eValue )
1133 Any aProperty;
1134 if( mxNode.is() ) try
1136 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1137 if( xEnumerationAccess.is() )
1139 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1140 if( xEnumeration.is() )
1142 while( xEnumeration->hasMoreElements() && !aProperty.hasValue() )
1144 Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
1145 if( !xAnimate.is() )
1146 continue;
1148 if( xAnimate->getType() == nNodeType )
1150 if( xAnimate->getAttributeName() == rAttributeName )
1152 switch( eValue )
1154 case EValue::To: aProperty = xAnimate->getTo(); break;
1155 case EValue::By: aProperty = xAnimate->getBy(); break;
1163 catch( Exception& )
1165 OSL_FAIL("sd::CustomAnimationEffect::getProperty(), exception caught!" );
1168 return aProperty;
1171 bool CustomAnimationEffect::setProperty( sal_Int32 nNodeType, const OUString& rAttributeName, EValue eValue, const Any& rValue )
1173 bool bChanged = false;
1174 if( mxNode.is() ) try
1176 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1177 if( xEnumerationAccess.is() )
1179 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1180 if( xEnumeration.is() )
1182 while( xEnumeration->hasMoreElements() )
1184 Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
1185 if( !xAnimate.is() )
1186 continue;
1188 if( xAnimate->getType() == nNodeType )
1190 if( xAnimate->getAttributeName() == rAttributeName )
1192 switch( eValue )
1194 case EValue::To:
1195 if( xAnimate->getTo() != rValue )
1197 xAnimate->setTo( rValue );
1198 bChanged = true;
1200 break;
1201 case EValue::By:
1202 if( xAnimate->getTo() != rValue )
1204 xAnimate->setBy( rValue );
1205 bChanged = true;
1207 break;
1215 catch( Exception& )
1217 OSL_FAIL("sd::CustomAnimationEffect::setProperty(), exception caught!" );
1220 return bChanged;
1223 static bool implIsColorAttribute( const OUString& rAttributeName )
1225 return rAttributeName == "FillColor" || rAttributeName == "LineColor" || rAttributeName == "CharColor";
1228 Any CustomAnimationEffect::getColor( sal_Int32 nIndex )
1230 Any aColor;
1231 if( mxNode.is() ) try
1233 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1234 if( xEnumerationAccess.is() )
1236 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1237 if( xEnumeration.is() )
1239 while( xEnumeration->hasMoreElements() && !aColor.hasValue() )
1241 Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
1242 if( !xAnimate.is() )
1243 continue;
1245 switch( xAnimate->getType() )
1247 case AnimationNodeType::SET:
1248 case AnimationNodeType::ANIMATE:
1249 if( !implIsColorAttribute( xAnimate->getAttributeName() ) )
1250 break;
1251 [[fallthrough]];
1252 case AnimationNodeType::ANIMATECOLOR:
1253 Sequence<Any> aValues( xAnimate->getValues() );
1254 if( aValues.hasElements() )
1256 if( aValues.getLength() > nIndex )
1257 aColor = aValues[nIndex];
1259 else if( nIndex == 0 )
1260 aColor = xAnimate->getFrom();
1261 else
1262 aColor = xAnimate->getTo();
1268 catch( Exception& )
1270 OSL_FAIL("sd::CustomAnimationEffect::getColor(), exception caught!" );
1273 return aColor;
1276 void CustomAnimationEffect::setColor( sal_Int32 nIndex, const Any& rColor )
1278 if( !mxNode.is() )
1279 return;
1283 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1284 if( xEnumerationAccess.is() )
1286 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1287 if( xEnumeration.is() )
1289 while( xEnumeration->hasMoreElements() )
1291 Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
1292 if( !xAnimate.is() )
1293 continue;
1295 switch( xAnimate->getType() )
1297 case AnimationNodeType::SET:
1298 case AnimationNodeType::ANIMATE:
1299 if( !implIsColorAttribute( xAnimate->getAttributeName() ) )
1300 break;
1301 [[fallthrough]];
1302 case AnimationNodeType::ANIMATECOLOR:
1304 Sequence<Any> aValues( xAnimate->getValues() );
1305 if( aValues.hasElements() )
1307 if( aValues.getLength() > nIndex )
1309 aValues[nIndex] = rColor;
1310 xAnimate->setValues( aValues );
1313 else if( (nIndex == 0) && xAnimate->getFrom().hasValue() )
1314 xAnimate->setFrom(rColor);
1315 else if( (nIndex == 1) && xAnimate->getTo().hasValue() )
1316 xAnimate->setTo(rColor);
1318 break;
1325 catch( Exception& )
1327 OSL_FAIL("sd::CustomAnimationEffect::setColor(), exception caught!" );
1331 Any CustomAnimationEffect::getTransformationProperty( sal_Int32 nTransformType, EValue eValue )
1333 Any aProperty;
1334 if( mxNode.is() ) try
1336 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1337 if( xEnumerationAccess.is() )
1339 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1340 if( xEnumeration.is() )
1342 while( xEnumeration->hasMoreElements() && !aProperty.hasValue() )
1344 Reference< XAnimateTransform > xTransform( xEnumeration->nextElement(), UNO_QUERY );
1345 if( !xTransform.is() )
1346 continue;
1348 if( xTransform->getTransformType() == nTransformType )
1350 switch( eValue )
1352 case EValue::To: aProperty = xTransform->getTo(); break;
1353 case EValue::By: aProperty = xTransform->getBy(); break;
1360 catch( Exception& )
1362 OSL_FAIL("sd::CustomAnimationEffect::getTransformationProperty(), exception caught!" );
1365 return aProperty;
1368 bool CustomAnimationEffect::setTransformationProperty( sal_Int32 nTransformType, EValue eValue, const Any& rValue )
1370 bool bChanged = false;
1371 if( mxNode.is() ) try
1373 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1374 if( xEnumerationAccess.is() )
1376 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1377 if( xEnumeration.is() )
1379 while( xEnumeration->hasMoreElements() )
1381 Reference< XAnimateTransform > xTransform( xEnumeration->nextElement(), UNO_QUERY );
1382 if( !xTransform.is() )
1383 continue;
1385 if( xTransform->getTransformType() == nTransformType )
1387 switch( eValue )
1389 case EValue::To:
1390 if( xTransform->getTo() != rValue )
1392 xTransform->setTo( rValue );
1393 bChanged = true;
1395 break;
1396 case EValue::By:
1397 if( xTransform->getBy() != rValue )
1399 xTransform->setBy( rValue );
1400 bChanged = true;
1402 break;
1409 catch( Exception& )
1411 OSL_FAIL("sd::CustomAnimationEffect::setTransformationProperty(), exception caught!" );
1414 return bChanged;
1417 void CustomAnimationEffect::createAudio( const css::uno::Any& rSource )
1419 DBG_ASSERT( !mxAudio.is(), "sd::CustomAnimationEffect::createAudio(), node already has an audio!" );
1421 if( mxAudio.is() )
1422 return;
1426 Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
1427 Reference< XAudio > xAudio( Audio::create( xContext ) );
1428 xAudio->setSource( rSource );
1429 xAudio->setVolume( 1.0 );
1430 setAudio( xAudio );
1432 catch( Exception& )
1434 OSL_FAIL("sd::CustomAnimationEffect::createAudio(), exception caught!" );
1438 static Reference< XCommand > findCommandNode( const Reference< XAnimationNode >& xRootNode )
1440 Reference< XCommand > xCommand;
1442 if( xRootNode.is() ) try
1444 Reference< XEnumerationAccess > xEnumerationAccess( xRootNode, UNO_QUERY_THROW );
1445 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
1446 while( !xCommand.is() && xEnumeration->hasMoreElements() )
1448 Reference< XAnimationNode > xNode( xEnumeration->nextElement(), UNO_QUERY );
1449 if( xNode.is() && (xNode->getType() == AnimationNodeType::COMMAND) )
1450 xCommand.set( xNode, UNO_QUERY_THROW );
1453 catch( Exception& )
1455 OSL_FAIL("sd::findCommandNode(), exception caught!" );
1458 return xCommand;
1461 void CustomAnimationEffect::removeAudio()
1465 Reference< XAnimationNode > xChild;
1467 if( mxAudio.is() )
1469 xChild = mxAudio;
1470 mxAudio.clear();
1472 else if( mnCommand == EffectCommands::STOPAUDIO )
1474 xChild = findCommandNode( mxNode );
1475 mnCommand = 0;
1478 if( xChild.is() )
1480 Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
1481 if( xContainer.is() )
1482 xContainer->removeChild( xChild );
1485 catch( Exception& )
1487 OSL_FAIL("sd::CustomAnimationEffect::removeAudio(), exception caught!" );
1492 void CustomAnimationEffect::setAudio( const Reference< css::animations::XAudio >& xAudio )
1494 if( mxAudio == xAudio )
1495 return;
1499 removeAudio();
1500 mxAudio = xAudio;
1501 Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
1502 if( xContainer.is() && mxAudio.is() )
1503 xContainer->appendChild( mxAudio );
1505 catch( Exception& )
1507 OSL_FAIL("sd::CustomAnimationEffect::setAudio(), exception caught!" );
1511 void CustomAnimationEffect::setStopAudio()
1513 if( mnCommand == EffectCommands::STOPAUDIO )
1514 return;
1518 if( mxAudio.is() )
1519 removeAudio();
1521 Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
1522 Reference< XCommand > xCommand( Command::create( xContext ) );
1524 xCommand->setCommand( EffectCommands::STOPAUDIO );
1526 Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY_THROW );
1527 xContainer->appendChild( xCommand );
1529 mnCommand = EffectCommands::STOPAUDIO;
1531 catch( Exception& )
1533 OSL_FAIL("sd::CustomAnimationEffect::setStopAudio(), exception caught!" );
1537 bool CustomAnimationEffect::getStopAudio() const
1539 return mnCommand == EffectCommands::STOPAUDIO;
1542 SdrPathObj* CustomAnimationEffect::createSdrPathObjFromPath(SdrModel& rTargetModel)
1544 SdrPathObj * pPathObj = new SdrPathObj(rTargetModel, OBJ_PATHLINE);
1545 updateSdrPathObjFromPath( *pPathObj );
1546 return pPathObj;
1549 void CustomAnimationEffect::updateSdrPathObjFromPath( SdrPathObj& rPathObj )
1551 ::basegfx::B2DPolyPolygon aPolyPoly;
1552 if( ::basegfx::utils::importFromSvgD( aPolyPoly, getPath(), true, nullptr ) )
1554 SdrObject* pObj = GetSdrObjectFromXShape( getTargetShape() );
1555 if( pObj )
1557 SdrPage* pPage = pObj->getSdrPageFromSdrObject();
1558 if( pPage )
1560 const Size aPageSize( pPage->GetSize() );
1561 aPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(static_cast<double>(aPageSize.Width()), static_cast<double>(aPageSize.Height())));
1564 const ::tools::Rectangle aBoundRect( pObj->GetCurrentBoundRect() );
1565 const Point aCenter( aBoundRect.Center() );
1566 aPolyPoly.transform(basegfx::utils::createTranslateB2DHomMatrix(aCenter.X(), aCenter.Y()));
1570 rPathObj.SetPathPoly( aPolyPoly );
1573 void CustomAnimationEffect::updatePathFromSdrPathObj( const SdrPathObj& rPathObj )
1575 ::basegfx::B2DPolyPolygon aPolyPoly( rPathObj.GetPathPoly() );
1577 SdrObject* pObj = GetSdrObjectFromXShape( getTargetShape() );
1578 if( pObj )
1580 ::tools::Rectangle aBoundRect(0,0,0,0);
1582 const drawinglayer::primitive2d::Primitive2DContainer& xPrimitives(pObj->GetViewContact().getViewIndependentPrimitive2DContainer());
1583 const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
1584 const basegfx::B2DRange aRange(xPrimitives.getB2DRange(aViewInformation2D));
1586 if(!aRange.isEmpty())
1588 aBoundRect = ::tools::Rectangle(
1589 static_cast<sal_Int32>(floor(aRange.getMinX())), static_cast<sal_Int32>(floor(aRange.getMinY())),
1590 static_cast<sal_Int32>(ceil(aRange.getMaxX())), static_cast<sal_Int32>(ceil(aRange.getMaxY())));
1593 const Point aCenter( aBoundRect.Center() );
1595 aPolyPoly.transform(basegfx::utils::createTranslateB2DHomMatrix(-aCenter.X(), -aCenter.Y()));
1597 SdrPage* pPage = pObj->getSdrPageFromSdrObject();
1598 if( pPage )
1600 const Size aPageSize( pPage->GetSize() );
1601 aPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(
1602 1.0 / static_cast<double>(aPageSize.Width()), 1.0 / static_cast<double>(aPageSize.Height())));
1606 setPath( ::basegfx::utils::exportToSvgD( aPolyPoly, true, true, true) );
1609 EffectSequenceHelper::EffectSequenceHelper()
1610 : mnSequenceType( EffectNodeType::DEFAULT )
1614 EffectSequenceHelper::EffectSequenceHelper( const css::uno::Reference< css::animations::XTimeContainer >& xSequenceRoot )
1615 : mxSequenceRoot( xSequenceRoot ), mnSequenceType( EffectNodeType::DEFAULT )
1617 Reference< XAnimationNode > xNode( mxSequenceRoot, UNO_QUERY_THROW );
1618 create( xNode );
1621 EffectSequenceHelper::~EffectSequenceHelper()
1623 reset();
1626 void EffectSequenceHelper::reset()
1628 for( CustomAnimationEffectPtr& pEffect : maEffects )
1630 pEffect->setEffectSequence(nullptr);
1632 maEffects.clear();
1635 Reference< XAnimationNode > EffectSequenceHelper::getRootNode()
1637 return mxSequenceRoot;
1640 void EffectSequenceHelper::append( const CustomAnimationEffectPtr& pEffect )
1642 pEffect->setEffectSequence( this );
1643 maEffects.push_back(pEffect);
1644 rebuild();
1647 CustomAnimationEffectPtr EffectSequenceHelper::append( const CustomAnimationPresetPtr& pPreset, const Any& rTarget, double fDuration /* = -1.0 */ )
1649 CustomAnimationEffectPtr pEffect;
1651 if( pPreset.get() )
1653 Reference< XAnimationNode > xNode( pPreset->create( "" ) );
1654 if( xNode.is() )
1656 // first, filter all only ui relevant user data
1657 std::vector< NamedValue > aNewUserData;
1658 Sequence< NamedValue > aUserData( xNode->getUserData() );
1660 std::copy_if(aUserData.begin(), aUserData.end(), std::back_inserter(aNewUserData),
1661 [](const NamedValue& rProp) { return rProp.Name != "text-only" && rProp.Name != "preset-property"; });
1663 if( !aNewUserData.empty() )
1665 aUserData = ::comphelper::containerToSequence( aNewUserData );
1666 xNode->setUserData( aUserData );
1669 // check target, maybe we need to force it to text
1670 sal_Int16 nSubItem = ShapeAnimationSubType::AS_WHOLE;
1672 if( rTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
1674 nSubItem = ShapeAnimationSubType::ONLY_TEXT;
1676 else if( pPreset->isTextOnly() )
1678 Reference< XShape > xShape;
1679 rTarget >>= xShape;
1680 if( xShape.is() )
1682 // that's bad, we target a shape here but the effect is only for text
1683 // so change subitem
1684 nSubItem = ShapeAnimationSubType::ONLY_TEXT;
1688 // now create effect from preset
1689 pEffect.reset( new CustomAnimationEffect( xNode ) );
1690 pEffect->setEffectSequence( this );
1691 pEffect->setTarget( rTarget );
1692 pEffect->setTargetSubItem( nSubItem );
1693 if( fDuration != -1.0 )
1694 pEffect->setDuration( fDuration );
1696 maEffects.push_back(pEffect);
1698 rebuild();
1702 DBG_ASSERT( pEffect.get(), "sd::EffectSequenceHelper::append(), failed!" );
1703 return pEffect;
1706 CustomAnimationEffectPtr EffectSequenceHelper::append( const SdrPathObj& rPathObj, const Any& rTarget, double fDuration /* = -1.0 */ )
1708 CustomAnimationEffectPtr pEffect;
1710 if( fDuration <= 0.0 )
1711 fDuration = 2.0;
1715 Reference< XTimeContainer > xEffectContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
1716 Reference< XAnimationNode > xAnimateMotion( AnimateMotion::create( ::comphelper::getProcessComponentContext() ) );
1718 xAnimateMotion->setDuration( Any( fDuration ) );
1719 xAnimateMotion->setFill( AnimationFill::HOLD );
1720 xEffectContainer->appendChild( xAnimateMotion );
1722 sal_Int16 nSubItem = ShapeAnimationSubType::AS_WHOLE;
1724 if( rTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
1725 nSubItem = ShapeAnimationSubType::ONLY_TEXT;
1727 pEffect.reset( new CustomAnimationEffect( xEffectContainer ) );
1728 pEffect->setEffectSequence( this );
1729 pEffect->setTarget( rTarget );
1730 pEffect->setTargetSubItem( nSubItem );
1731 pEffect->setNodeType( css::presentation::EffectNodeType::ON_CLICK );
1732 pEffect->setPresetClass( css::presentation::EffectPresetClass::MOTIONPATH );
1733 pEffect->setAcceleration( 0.5 );
1734 pEffect->setDecelerate( 0.5 );
1735 pEffect->setFill( AnimationFill::HOLD );
1736 pEffect->setBegin( 0.0 );
1737 pEffect->updatePathFromSdrPathObj( rPathObj );
1738 if( fDuration != -1.0 )
1739 pEffect->setDuration( fDuration );
1741 maEffects.push_back(pEffect);
1743 rebuild();
1745 catch( Exception& )
1747 OSL_FAIL( "sd::EffectSequenceHelper::append(), exception caught!" );
1750 return pEffect;
1753 void EffectSequenceHelper::replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pPreset, const OUString& rPresetSubType, double fDuration /* = -1.0 */ )
1755 if( !(pEffect.get() && pPreset.get()) )
1756 return;
1760 Reference< XAnimationNode > xNewNode( pPreset->create( rPresetSubType ) );
1761 if( xNewNode.is() )
1763 pEffect->replaceNode( xNewNode );
1764 if( fDuration != -1.0 )
1765 pEffect->setDuration( fDuration );
1768 rebuild();
1770 catch( Exception& )
1772 OSL_FAIL( "sd::EffectSequenceHelper::replace(), exception caught!" );
1776 void EffectSequenceHelper::replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pPreset, double fDuration /* = -1.0 */ )
1778 replace( pEffect, pPreset, "", fDuration );
1781 void EffectSequenceHelper::remove( const CustomAnimationEffectPtr& pEffect )
1783 if( pEffect.get() )
1785 pEffect->setEffectSequence( nullptr );
1786 maEffects.remove( pEffect );
1789 rebuild();
1792 void EffectSequenceHelper::moveToBeforeEffect( const CustomAnimationEffectPtr& pEffect, const CustomAnimationEffectPtr& pInsertBefore)
1794 if ( pEffect.get() )
1796 maEffects.remove( pEffect );
1797 EffectSequence::iterator aInsertIter( find( pInsertBefore ) );
1799 // aInsertIter being end() is OK: pInsertBefore could be null, so put at end.
1800 maEffects.insert( aInsertIter, pEffect );
1802 rebuild();
1806 void EffectSequenceHelper::rebuild()
1808 implRebuild();
1811 void EffectSequenceHelper::implRebuild()
1815 // first we delete all time containers on the first two levels
1816 Reference< XEnumerationAccess > xEnumerationAccess( mxSequenceRoot, UNO_QUERY_THROW );
1817 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
1818 while( xEnumeration->hasMoreElements() )
1820 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
1821 Reference< XTimeContainer > xChildContainer( xChildNode, UNO_QUERY_THROW );
1823 Reference< XEnumerationAccess > xChildEnumerationAccess( xChildNode, UNO_QUERY_THROW );
1824 Reference< XEnumeration > xChildEnumeration( xChildEnumerationAccess->createEnumeration(), UNO_SET_THROW );
1825 while( xChildEnumeration->hasMoreElements() )
1827 Reference< XAnimationNode > xNode( xChildEnumeration->nextElement(), UNO_QUERY_THROW );
1828 xChildContainer->removeChild( xNode );
1831 mxSequenceRoot->removeChild( xChildNode );
1834 // second, rebuild main sequence
1835 EffectSequence::iterator aIter( maEffects.begin() );
1836 EffectSequence::iterator aEnd( maEffects.end() );
1837 if( aIter != aEnd )
1839 std::vector< sd::AfterEffectNode > aAfterEffects;
1841 CustomAnimationEffectPtr pEffect = *aIter++;
1843 bool bFirst = true;
1846 // create a par container for the next click node and all following with and after effects
1847 Reference< XTimeContainer > xOnClickContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
1849 Event aEvent;
1850 if( mxEventSource.is() )
1852 aEvent.Source <<= mxEventSource;
1853 aEvent.Trigger = EventTrigger::ON_CLICK;
1855 else
1857 aEvent.Trigger = EventTrigger::ON_NEXT;
1859 aEvent.Repeat = 0;
1861 Any aBegin( makeAny( aEvent ) );
1862 if( bFirst )
1864 // if the first node is not a click action, this click container
1865 // must not have INDEFINITE begin but start at 0s
1866 bFirst = false;
1867 if( pEffect->getNodeType() != EffectNodeType::ON_CLICK )
1868 aBegin <<= 0.0;
1871 xOnClickContainer->setBegin( aBegin );
1873 mxSequenceRoot->appendChild( xOnClickContainer );
1875 double fBegin = 0.0;
1879 // create a par container for the current click or after effect node and all following with effects
1880 Reference< XTimeContainer > xWithContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
1881 xWithContainer->setBegin( makeAny( fBegin ) );
1882 xOnClickContainer->appendChild( xWithContainer );
1884 double fDuration = 0.0;
1887 Reference< XAnimationNode > xEffectNode( pEffect->getNode() );
1888 xWithContainer->appendChild( xEffectNode );
1890 if( pEffect->hasAfterEffect() )
1892 Reference< XAnimationNode > xAfterEffect( pEffect->createAfterEffectNode() );
1893 AfterEffectNode a( xAfterEffect, xEffectNode, pEffect->IsAfterEffectOnNext() );
1894 aAfterEffects.push_back( a );
1897 double fTemp = pEffect->getBegin() + pEffect->getAbsoluteDuration();
1898 if( fTemp > fDuration )
1899 fDuration = fTemp;
1901 if( aIter != aEnd )
1902 pEffect = *aIter++;
1903 else
1904 pEffect.reset();
1906 while( pEffect.get() && (pEffect->getNodeType() == EffectNodeType::WITH_PREVIOUS) );
1908 fBegin += fDuration;
1910 while( pEffect.get() && (pEffect->getNodeType() != EffectNodeType::ON_CLICK) );
1912 while( pEffect.get() );
1914 // process after effect nodes
1915 std::for_each( aAfterEffects.begin(), aAfterEffects.end(), stl_process_after_effect_node_func );
1917 updateTextGroups();
1919 // reset duration, might have been altered (see below)
1920 mxSequenceRoot->setDuration( Any() );
1922 else
1924 // empty sequence, set duration to 0.0 explicitly
1925 // (otherwise, this sequence will never end)
1926 mxSequenceRoot->setDuration( makeAny(0.0) );
1929 catch( Exception& )
1931 OSL_FAIL( "sd::EffectSequenceHelper::rebuild(), exception caught!" );
1935 stl_CustomAnimationEffect_search_node_predict::stl_CustomAnimationEffect_search_node_predict( const css::uno::Reference< css::animations::XAnimationNode >& xSearchNode )
1936 : mxSearchNode( xSearchNode )
1940 bool stl_CustomAnimationEffect_search_node_predict::operator()( const CustomAnimationEffectPtr& pEffect ) const
1942 return pEffect->getNode() == mxSearchNode;
1945 /// @throws Exception
1946 static bool implFindNextContainer( Reference< XTimeContainer > const & xParent, Reference< XTimeContainer > const & xCurrent, Reference< XTimeContainer >& xNext )
1948 Reference< XEnumerationAccess > xEnumerationAccess( xParent, UNO_QUERY_THROW );
1949 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration() );
1950 if( xEnumeration.is() )
1952 Reference< XInterface > x;
1953 while( xEnumeration->hasMoreElements() && !xNext.is() )
1955 if( (xEnumeration->nextElement() >>= x) && (x == xCurrent) )
1957 if( xEnumeration->hasMoreElements() )
1958 xEnumeration->nextElement() >>= xNext;
1962 return xNext.is();
1965 void stl_process_after_effect_node_func(AfterEffectNode const & rNode)
1969 if( rNode.mxNode.is() && rNode.mxMaster.is() )
1971 // set master node
1972 Reference< XAnimationNode > xMasterNode( rNode.mxMaster, UNO_SET_THROW );
1973 Sequence< NamedValue > aUserData( rNode.mxNode->getUserData() );
1974 sal_Int32 nSize = aUserData.getLength();
1975 aUserData.realloc(nSize+1);
1976 aUserData[nSize].Name = "master-element";
1977 aUserData[nSize].Value <<= xMasterNode;
1978 rNode.mxNode->setUserData( aUserData );
1980 // insert after effect node into timeline
1981 Reference< XTimeContainer > xContainer( rNode.mxMaster->getParent(), UNO_QUERY_THROW );
1983 if( !rNode.mbOnNextEffect ) // sameClick
1985 // insert the aftereffect after its effect is animated
1986 xContainer->insertAfter( rNode.mxNode, rNode.mxMaster );
1988 else // nextClick
1990 Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
1991 // insert the aftereffect in the next group
1993 Reference< XTimeContainer > xClickContainer( xContainer->getParent(), UNO_QUERY_THROW );
1994 Reference< XTimeContainer > xSequenceContainer( xClickContainer->getParent(), UNO_QUERY_THROW );
1996 Reference< XTimeContainer > xNextContainer;
1998 // first try if we have an after effect container
1999 if( !implFindNextContainer( xClickContainer, xContainer, xNextContainer ) )
2001 Reference< XTimeContainer > xNextClickContainer;
2002 // if not, try to find the next click effect container
2003 if( implFindNextContainer( xSequenceContainer, xClickContainer, xNextClickContainer ) )
2005 Reference< XEnumerationAccess > xEnumerationAccess( xNextClickContainer, UNO_QUERY_THROW );
2006 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
2007 if( xEnumeration->hasMoreElements() )
2009 // the next container is the first child container
2010 xEnumeration->nextElement() >>= xNextContainer;
2012 else
2014 // this does not yet have a child container, create one
2015 xNextContainer.set( ParallelTimeContainer::create(xContext), UNO_QUERY_THROW );
2017 xNextContainer->setBegin( makeAny( 0.0 ) );
2018 xNextClickContainer->appendChild( xNextContainer );
2020 DBG_ASSERT( xNextContainer.is(), "ppt::stl_process_after_effect_node_func::operator(), could not find/create container!" );
2024 // if we don't have a next container, we add one to the sequence container
2025 if( !xNextContainer.is() )
2027 Reference< XTimeContainer > xNewClickContainer( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW );
2029 Event aEvent;
2030 aEvent.Trigger = EventTrigger::ON_NEXT;
2031 aEvent.Repeat = 0;
2032 xNewClickContainer->setBegin( makeAny( aEvent ) );
2034 xSequenceContainer->insertAfter( xNewClickContainer, xClickContainer );
2036 xNextContainer.set( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW );
2038 xNextContainer->setBegin( makeAny( 0.0 ) );
2039 xNewClickContainer->appendChild( xNextContainer );
2042 if( xNextContainer.is() )
2044 // find begin time of first element
2045 Reference< XEnumerationAccess > xEnumerationAccess( xNextContainer, UNO_QUERY_THROW );
2046 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
2047 if( xEnumeration->hasMoreElements() )
2049 Reference< XAnimationNode > xChild;
2050 // the next container is the first child container
2051 xEnumeration->nextElement() >>= xChild;
2052 if( xChild.is() )
2054 Any aBegin( xChild->getBegin() );
2055 double fBegin = 0.0;
2056 if( (aBegin >>= fBegin) && (fBegin >= 0.0))
2057 rNode.mxNode->setBegin( aBegin );
2061 xNextContainer->appendChild( rNode.mxNode );
2066 catch( Exception& )
2068 OSL_FAIL( "ppt::stl_process_after_effect_node_func::operator(), exception caught!" );
2072 EffectSequence::iterator EffectSequenceHelper::find( const CustomAnimationEffectPtr& pEffect )
2074 return std::find( maEffects.begin(), maEffects.end(), pEffect );
2077 CustomAnimationEffectPtr EffectSequenceHelper::findEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) const
2079 CustomAnimationEffectPtr pEffect;
2081 EffectSequence::const_iterator aIter = std::find_if(maEffects.begin(), maEffects.end(),
2082 [&xNode](const CustomAnimationEffectPtr& rxEffect) { return rxEffect->getNode() == xNode; });
2083 if (aIter != maEffects.end())
2084 pEffect = *aIter;
2086 return pEffect;
2089 sal_Int32 EffectSequenceHelper::getOffsetFromEffect( const CustomAnimationEffectPtr& xEffect ) const
2091 auto aIter = std::find(maEffects.begin(), maEffects.end(), xEffect);
2092 if (aIter != maEffects.end())
2093 return static_cast<sal_Int32>(std::distance(maEffects.begin(), aIter));
2095 return -1;
2098 CustomAnimationEffectPtr EffectSequenceHelper::getEffectFromOffset( sal_Int32 nOffset ) const
2100 EffectSequence::const_iterator aIter( maEffects.begin() );
2101 nOffset = std::min(nOffset, static_cast<sal_Int32>(maEffects.size()));
2102 std::advance(aIter, nOffset);
2104 CustomAnimationEffectPtr pEffect;
2105 if( aIter != maEffects.end() )
2106 pEffect = *aIter;
2108 return pEffect;
2111 bool EffectSequenceHelper::disposeShape( const Reference< XShape >& xShape )
2113 bool bChanges = false;
2115 EffectSequence::iterator aIter( maEffects.begin() );
2116 while( aIter != maEffects.end() )
2118 if( (*aIter)->getTargetShape() == xShape )
2120 (*aIter)->setEffectSequence( nullptr );
2121 bChanges = true;
2122 aIter = maEffects.erase( aIter );
2124 else
2126 ++aIter;
2130 return bChanges;
2133 bool EffectSequenceHelper::hasEffect( const css::uno::Reference< css::drawing::XShape >& xShape )
2135 return std::any_of(maEffects.begin(), maEffects.end(),
2136 [&xShape](const CustomAnimationEffectPtr& rxEffect) { return rxEffect->getTargetShape() == xShape; });
2139 bool EffectSequenceHelper::getParagraphNumberingLevels( const Reference< XShape >& xShape, std::vector< sal_Int32 >& rParagraphNumberingLevel )
2141 rParagraphNumberingLevel.clear();
2143 if( !hasEffect( xShape ) )
2144 return false;
2146 Reference< XText > xText( xShape, UNO_QUERY );
2147 if( xText.is() )
2149 Reference< XEnumerationAccess > xEA( xText, UNO_QUERY );
2150 if( xEA.is() )
2152 Reference< XEnumeration > xEnumeration = xEA->createEnumeration();
2154 if( xEnumeration.is() )
2156 for( sal_Int32 index = 0; xEnumeration->hasMoreElements(); index++ )
2158 Reference< XPropertySet > xParaSet;
2159 xEnumeration->nextElement() >>= xParaSet;
2161 sal_Int32 nParaDepth = 0;
2162 if( xParaSet.is() )
2164 const OUString strNumberingLevel( "NumberingLevel" );
2165 xParaSet->getPropertyValue( strNumberingLevel ) >>= nParaDepth;
2168 rParagraphNumberingLevel.push_back( nParaDepth );
2174 return true;
2177 void EffectSequenceHelper::insertTextRange( const css::uno::Any& aTarget )
2179 ParagraphTarget aParaTarget;
2180 if( !(aTarget >>= aParaTarget ) )
2181 return;
2183 // get map [paragraph index] -> [NumberingLevel]
2184 // for following reusage inside all animation effects
2185 std::vector< sal_Int32 > paragraphNumberingLevel;
2186 std::vector< sal_Int32 >* paragraphNumberingLevelParam = nullptr;
2187 if ( getParagraphNumberingLevels( aParaTarget.Shape, paragraphNumberingLevel ) )
2188 paragraphNumberingLevelParam = &paragraphNumberingLevel;
2190 // update internal flags for each animation effect
2191 const bool bChanges = std::accumulate(maEffects.begin(), maEffects.end(), false,
2192 [&aParaTarget, &paragraphNumberingLevelParam](const bool bCheck, const CustomAnimationEffectPtr& rxEffect) {
2193 bool bRes = bCheck;
2194 if (rxEffect->getTargetShape() == aParaTarget.Shape)
2195 bRes |= rxEffect->checkForText( paragraphNumberingLevelParam );
2196 return bRes;
2199 if( bChanges )
2200 rebuild();
2203 static bool isParagraphTargetTextEmpty( ParagraphTarget aParaTarget )
2205 // get paragraph
2206 Reference< XText > xText ( aParaTarget.Shape, UNO_QUERY );
2207 if( xText.is() )
2209 Reference< XEnumerationAccess > xEA( xText, UNO_QUERY );
2210 if( xEA.is() )
2212 Reference< XEnumeration > xEnumeration = xEA->createEnumeration();
2213 if( xEnumeration.is() )
2215 // advance to the Nth paragraph
2216 sal_Int32 nPara = aParaTarget.Paragraph;
2217 while( xEnumeration->hasMoreElements() && nPara-- )
2218 xEnumeration->nextElement();
2220 // get Nth paragraph's text and check if it's empty
2221 if( xEnumeration->hasMoreElements() )
2223 Reference< XTextRange > xRange( xEnumeration->nextElement(), UNO_QUERY );
2224 if( xRange.is() )
2226 OUString text = xRange->getString();
2227 return text.isEmpty();
2233 return false;
2236 void EffectSequenceHelper::disposeTextRange( const css::uno::Any& aTarget )
2238 ParagraphTarget aParaTarget;
2239 if( !(aTarget >>= aParaTarget ) )
2240 return;
2242 bool bChanges = false;
2244 // building list of effects for target shape; process effects not on target shape
2245 EffectSequence aTargetParagraphEffects;
2246 for( const auto &pEffect : maEffects )
2248 Any aIterTarget( pEffect->getTarget() );
2249 if( aIterTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2251 ParagraphTarget aIterParaTarget;
2252 if( (aIterTarget >>= aIterParaTarget) && (aIterParaTarget.Shape == aParaTarget.Shape) )
2254 aTargetParagraphEffects.push_back(pEffect);
2257 else if( pEffect->getTargetShape() == aParaTarget.Shape )
2259 bChanges |= pEffect->checkForText();
2263 // select effect to delete:
2264 // if paragraph before target is blank, then delete its animation effect (if any) instead
2265 ParagraphTarget aPreviousParagraph = aParaTarget;
2266 --aPreviousParagraph.Paragraph;
2267 bool bIsPreviousParagraphEmpty = isParagraphTargetTextEmpty( aPreviousParagraph );
2268 sal_Int16 anParaNumToDelete = bIsPreviousParagraphEmpty ? aPreviousParagraph.Paragraph : aParaTarget.Paragraph;
2270 // update effects
2271 for( const auto &pEffect : aTargetParagraphEffects )
2273 Any aIterTarget( pEffect->getTarget() );
2275 ParagraphTarget aIterParaTarget;
2276 aIterTarget >>= aIterParaTarget;
2278 // delete effect for target paragraph (may have effects in more than one text group)
2279 if( aIterParaTarget.Paragraph == anParaNumToDelete )
2281 auto aItr = find( pEffect );
2282 DBG_ASSERT( aItr != maEffects.end(), "sd::EffectSequenceHelper::disposeTextRange(), Expected effect missing.");
2283 if( aItr != maEffects.end() )
2285 (*aItr)->setEffectSequence( nullptr );
2286 maEffects.erase(aItr);
2287 bChanges = true;
2291 // shift all paragraphs after disposed paragraph
2292 if( aIterParaTarget.Paragraph > anParaNumToDelete )
2294 --aIterParaTarget.Paragraph;
2295 pEffect->setTarget( makeAny( aIterParaTarget ) );
2296 bChanges = true;
2300 if( bChanges )
2302 rebuild();
2306 CustomAnimationTextGroup::CustomAnimationTextGroup( const Reference< XShape >& rTarget, sal_Int32 nGroupId )
2307 : maTarget( rTarget ),
2308 mnGroupId( nGroupId )
2310 reset();
2313 void CustomAnimationTextGroup::reset()
2315 mnTextGrouping = -1;
2316 mbAnimateForm = false;
2317 mbTextReverse = false;
2318 mfGroupingAuto = -1.0;
2319 mnLastPara = -1; // used to check for TextReverse
2321 for (sal_Int8 & rn : mnDepthFlags)
2323 rn = 0;
2326 maEffects.clear();
2329 void CustomAnimationTextGroup::addEffect( CustomAnimationEffectPtr const & pEffect )
2331 maEffects.push_back( pEffect );
2333 Any aTarget( pEffect->getTarget() );
2334 if( aTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2336 // now look at the paragraph
2337 ParagraphTarget aParaTarget;
2338 aTarget >>= aParaTarget;
2340 if( mnLastPara != -1 )
2341 mbTextReverse = mnLastPara > aParaTarget.Paragraph;
2343 mnLastPara = aParaTarget.Paragraph;
2345 const sal_Int32 nParaDepth = pEffect->getParaDepth();
2347 // only look at the first PARA_LEVELS levels
2348 if( nParaDepth < PARA_LEVELS )
2350 // our first paragraph with this level?
2351 if( mnDepthFlags[nParaDepth] == 0 )
2353 // so set it to the first found
2354 mnDepthFlags[nParaDepth] = static_cast<sal_Int8>(pEffect->getNodeType());
2356 else if( mnDepthFlags[nParaDepth] != pEffect->getNodeType() )
2358 mnDepthFlags[nParaDepth] = -1;
2361 if( pEffect->getNodeType() == EffectNodeType::AFTER_PREVIOUS )
2362 mfGroupingAuto = pEffect->getBegin();
2364 mnTextGrouping = PARA_LEVELS;
2365 while( (mnTextGrouping > 0)
2366 && (mnDepthFlags[mnTextGrouping - 1] <= 0) )
2367 --mnTextGrouping;
2370 else
2372 // if we have an effect with the shape as a target, we animate the background
2373 mbAnimateForm = pEffect->getTargetSubItem() != ShapeAnimationSubType::ONLY_TEXT;
2377 CustomAnimationTextGroupPtr EffectSequenceHelper::findGroup( sal_Int32 nGroupId )
2379 CustomAnimationTextGroupPtr aPtr;
2381 CustomAnimationTextGroupMap::iterator aIter( maGroupMap.find( nGroupId ) );
2382 if( aIter != maGroupMap.end() )
2383 aPtr = (*aIter).second;
2385 return aPtr;
2388 void EffectSequenceHelper::updateTextGroups()
2390 maGroupMap.clear();
2392 // first create all the groups
2393 for( CustomAnimationEffectPtr& pEffect : maEffects )
2395 const sal_Int32 nGroupId = pEffect->getGroupId();
2397 if( nGroupId == -1 )
2398 continue; // trivial case, no group
2400 CustomAnimationTextGroupPtr pGroup = findGroup( nGroupId );
2401 if( !pGroup.get() )
2403 pGroup.reset( new CustomAnimationTextGroup( pEffect->getTargetShape(), nGroupId ) );
2404 maGroupMap[nGroupId] = pGroup;
2407 pGroup->addEffect( pEffect );
2410 // Now that all the text groups have been cleared up and rebuilt, we need to update its
2411 // text grouping. addEffect() already make mnTextGrouping the last possible level,
2412 // so just continue to find the last level that is not EffectNodeType::WITH_PREVIOUS.
2413 for(const auto &rGroupMapItem: maGroupMap)
2415 const CustomAnimationTextGroupPtr &pGroup = rGroupMapItem.second;
2416 while(pGroup->mnTextGrouping > 0 && pGroup->mnDepthFlags[pGroup->mnTextGrouping - 1] == EffectNodeType::WITH_PREVIOUS)
2417 --pGroup->mnTextGrouping;
2421 CustomAnimationTextGroupPtr
2422 EffectSequenceHelper::createTextGroup(const CustomAnimationEffectPtr& pEffect,
2423 sal_Int32 nTextGrouping, double fTextGroupingAuto,
2424 bool bAnimateForm, bool bTextReverse)
2426 // first find a free group-id
2427 sal_Int32 nGroupId = 0;
2429 CustomAnimationTextGroupMap::iterator aIter( maGroupMap.begin() );
2430 const CustomAnimationTextGroupMap::iterator aEnd( maGroupMap.end() );
2431 while( aIter != aEnd )
2433 if( (*aIter).first == nGroupId )
2435 nGroupId++;
2436 aIter = maGroupMap.begin();
2438 else
2440 ++aIter;
2444 Reference< XShape > xTarget( pEffect->getTargetShape() );
2446 CustomAnimationTextGroupPtr pTextGroup( new CustomAnimationTextGroup( xTarget, nGroupId ) );
2447 maGroupMap[nGroupId] = pTextGroup;
2449 bool bUsed = false;
2451 // do we need to target the shape?
2452 if( (nTextGrouping == 0) || bAnimateForm )
2454 sal_Int16 nSubItem;
2455 if( nTextGrouping == 0)
2456 nSubItem = bAnimateForm ? ShapeAnimationSubType::AS_WHOLE : ShapeAnimationSubType::ONLY_TEXT;
2457 else
2458 nSubItem = ShapeAnimationSubType::ONLY_BACKGROUND;
2460 pEffect->setTarget( makeAny( xTarget ) );
2461 pEffect->setTargetSubItem( nSubItem );
2462 pEffect->setEffectSequence( this );
2463 pEffect->setGroupId( nGroupId );
2465 pTextGroup->addEffect( pEffect );
2466 bUsed = true;
2469 pTextGroup->mnTextGrouping = nTextGrouping;
2470 pTextGroup->mfGroupingAuto = fTextGroupingAuto;
2471 pTextGroup->mbTextReverse = bTextReverse;
2473 // now add an effect for each paragraph
2474 createTextGroupParagraphEffects( pTextGroup, pEffect, bUsed );
2476 notify_listeners();
2478 return pTextGroup;
2481 void EffectSequenceHelper::createTextGroupParagraphEffects( const CustomAnimationTextGroupPtr& pTextGroup, const CustomAnimationEffectPtr& pEffect, bool bUsed )
2483 Reference< XShape > xTarget( pTextGroup->maTarget );
2485 sal_Int32 nTextGrouping = pTextGroup->mnTextGrouping;
2486 double fTextGroupingAuto = pTextGroup->mfGroupingAuto;
2487 bool bTextReverse = pTextGroup->mbTextReverse;
2489 // now add an effect for each paragraph
2490 if( nTextGrouping < 0 )
2491 return;
2495 EffectSequence::iterator aInsertIter( find( pEffect ) );
2497 Reference< XEnumerationAccess > xText( xTarget, UNO_QUERY_THROW );
2498 Reference< XEnumeration > xEnumeration( xText->createEnumeration(), UNO_SET_THROW );
2500 std::deque< sal_Int16 > aParaList;
2501 sal_Int16 nPara;
2503 // fill the list with all valid paragraphs
2504 for( nPara = 0; xEnumeration->hasMoreElements(); nPara++ )
2506 Reference< XTextRange > xRange( xEnumeration->nextElement(), UNO_QUERY );
2507 if( xRange.is() && !xRange->getString().isEmpty() )
2509 if( bTextReverse ) // sort them
2510 aParaList.push_front( nPara );
2511 else
2512 aParaList.push_back( nPara );
2516 ParagraphTarget aTarget;
2517 aTarget.Shape = xTarget;
2519 for( const auto i : aParaList )
2521 aTarget.Paragraph = i;
2523 CustomAnimationEffectPtr pNewEffect;
2524 if( bUsed )
2526 // clone a new effect from first effect
2527 pNewEffect = pEffect->clone();
2528 ++aInsertIter;
2529 aInsertIter = maEffects.insert( aInsertIter, pNewEffect );
2531 else
2533 // reuse first effect if it's not yet used
2534 pNewEffect = pEffect;
2535 bUsed = true;
2536 aInsertIter = find( pNewEffect );
2539 // set target and group-id
2540 pNewEffect->setTarget( makeAny( aTarget ) );
2541 pNewEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_TEXT );
2542 pNewEffect->setGroupId( pTextGroup->mnGroupId );
2543 pNewEffect->setEffectSequence( this );
2545 // set correct node type
2546 if( pNewEffect->getParaDepth() < nTextGrouping )
2548 if( fTextGroupingAuto == -1.0 )
2550 pNewEffect->setNodeType( EffectNodeType::ON_CLICK );
2551 pNewEffect->setBegin( 0.0 );
2553 else
2555 pNewEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
2556 pNewEffect->setBegin( fTextGroupingAuto );
2559 else
2561 pNewEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
2562 pNewEffect->setBegin( 0.0 );
2565 pTextGroup->addEffect( pNewEffect );
2567 notify_listeners();
2569 catch( Exception& )
2571 OSL_FAIL("sd::EffectSequenceHelper::createTextGroup(), exception caught!" );
2575 void EffectSequenceHelper::setTextGrouping( const CustomAnimationTextGroupPtr& pTextGroup, sal_Int32 nTextGrouping )
2577 if( pTextGroup->mnTextGrouping == nTextGrouping )
2579 // first case, trivial case, do nothing
2581 else if( (pTextGroup->mnTextGrouping == -1) && (nTextGrouping >= 0) )
2583 // second case, we need to add new effects for each paragraph
2585 CustomAnimationEffectPtr pEffect( pTextGroup->maEffects.front() );
2587 pTextGroup->mnTextGrouping = nTextGrouping;
2588 createTextGroupParagraphEffects( pTextGroup, pEffect, true );
2589 notify_listeners();
2591 else if( (pTextGroup->mnTextGrouping >= 0) && (nTextGrouping == -1 ) )
2593 // third case, we need to remove effects for each paragraph
2595 EffectSequence aEffects( pTextGroup->maEffects );
2596 pTextGroup->reset();
2598 for( const CustomAnimationEffectPtr& pEffect : aEffects )
2600 if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2601 remove( pEffect );
2602 else
2603 pTextGroup->addEffect( pEffect );
2605 notify_listeners();
2607 else
2609 // fourth case, we need to change the node types for the text nodes
2610 double fTextGroupingAuto = pTextGroup->mfGroupingAuto;
2612 EffectSequence aEffects( pTextGroup->maEffects );
2613 pTextGroup->reset();
2615 for( CustomAnimationEffectPtr& pEffect : aEffects )
2617 if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2619 // set correct node type
2620 if( pEffect->getParaDepth() < nTextGrouping )
2622 if( fTextGroupingAuto == -1.0 )
2624 pEffect->setNodeType( EffectNodeType::ON_CLICK );
2625 pEffect->setBegin( 0.0 );
2627 else
2629 pEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
2630 pEffect->setBegin( fTextGroupingAuto );
2633 else
2635 pEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
2636 pEffect->setBegin( 0.0 );
2640 pTextGroup->addEffect( pEffect );
2643 notify_listeners();
2647 void EffectSequenceHelper::setAnimateForm( const CustomAnimationTextGroupPtr& pTextGroup, bool bAnimateForm )
2649 if( pTextGroup->mbAnimateForm == bAnimateForm )
2651 // trivial case, do nothing
2653 else
2655 EffectSequence aEffects( pTextGroup->maEffects );
2656 pTextGroup->reset();
2658 SAL_WARN_IF(aEffects.empty(), "sd", "EffectSequenceHelper::setAnimateForm effects empty" );
2660 if (aEffects.empty())
2661 return;
2663 EffectSequence::iterator aIter( aEffects.begin() );
2664 const EffectSequence::iterator aEnd( aEffects.end() );
2666 // first insert if we have to
2667 if( bAnimateForm )
2669 EffectSequence::iterator aInsertIter( find( *aIter ) );
2671 CustomAnimationEffectPtr pEffect;
2672 if( (aEffects.size() == 1) && ((*aIter)->getTarget().getValueType() != ::cppu::UnoType<ParagraphTarget>::get() ) )
2674 // special case, only one effect and that targets whole text,
2675 // convert this to target whole shape
2676 pEffect = *aIter++;
2677 pEffect->setTargetSubItem( ShapeAnimationSubType::AS_WHOLE );
2679 else
2681 pEffect = (*aIter)->clone();
2682 pEffect->setTarget( makeAny( (*aIter)->getTargetShape() ) );
2683 pEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_BACKGROUND );
2684 maEffects.insert( aInsertIter, pEffect );
2687 pTextGroup->addEffect( pEffect );
2690 if( !bAnimateForm && (aEffects.size() == 1) )
2692 CustomAnimationEffectPtr pEffect( *aIter );
2693 pEffect->setTarget( makeAny( (*aIter)->getTargetShape() ) );
2694 pEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_TEXT );
2695 pTextGroup->addEffect( pEffect );
2697 else
2699 // read the rest to the group again
2700 while( aIter != aEnd )
2702 CustomAnimationEffectPtr pEffect( *aIter++ );
2704 if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2706 pTextGroup->addEffect( pEffect );
2708 else
2710 DBG_ASSERT( !bAnimateForm, "sd::EffectSequenceHelper::setAnimateForm(), something is wrong here!" );
2711 remove( pEffect );
2715 notify_listeners();
2719 void EffectSequenceHelper::setTextGroupingAuto( const CustomAnimationTextGroupPtr& pTextGroup, double fTextGroupingAuto )
2721 sal_Int32 nTextGrouping = pTextGroup->mnTextGrouping;
2723 EffectSequence aEffects( pTextGroup->maEffects );
2724 pTextGroup->reset();
2726 for( CustomAnimationEffectPtr& pEffect : aEffects )
2728 if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2730 // set correct node type
2731 if( pEffect->getParaDepth() < nTextGrouping )
2733 if( fTextGroupingAuto == -1.0 )
2735 pEffect->setNodeType( EffectNodeType::ON_CLICK );
2736 pEffect->setBegin( 0.0 );
2738 else
2740 pEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
2741 pEffect->setBegin( fTextGroupingAuto );
2744 else
2746 pEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
2747 pEffect->setBegin( 0.0 );
2751 pTextGroup->addEffect( pEffect );
2754 notify_listeners();
2757 struct ImplStlTextGroupSortHelper
2759 explicit ImplStlTextGroupSortHelper( bool bReverse ) : mbReverse( bReverse ) {};
2760 bool operator()( const CustomAnimationEffectPtr& p1, const CustomAnimationEffectPtr& p2 );
2761 bool const mbReverse;
2762 sal_Int32 getTargetParagraph( const CustomAnimationEffectPtr& p1 );
2765 sal_Int32 ImplStlTextGroupSortHelper::getTargetParagraph( const CustomAnimationEffectPtr& p1 )
2767 const Any aTarget(p1->getTarget());
2768 if( aTarget.hasValue() && aTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2770 ParagraphTarget aParaTarget;
2771 aTarget >>= aParaTarget;
2772 return aParaTarget.Paragraph;
2774 else
2776 return mbReverse ? 0x7fffffff : -1;
2780 bool ImplStlTextGroupSortHelper::operator()( const CustomAnimationEffectPtr& p1, const CustomAnimationEffectPtr& p2 )
2782 if( mbReverse )
2784 return getTargetParagraph( p2 ) < getTargetParagraph( p1 );
2786 else
2788 return getTargetParagraph( p1 ) < getTargetParagraph( p2 );
2792 void EffectSequenceHelper::setTextReverse( const CustomAnimationTextGroupPtr& pTextGroup, bool bTextReverse )
2794 if( pTextGroup->mbTextReverse == bTextReverse )
2796 // do nothing
2798 else
2800 std::vector< CustomAnimationEffectPtr > aSortedVector(pTextGroup->maEffects.size());
2801 std::copy( pTextGroup->maEffects.begin(), pTextGroup->maEffects.end(), aSortedVector.begin() );
2802 ImplStlTextGroupSortHelper aSortHelper( bTextReverse );
2803 std::sort( aSortedVector.begin(), aSortedVector.end(), aSortHelper );
2805 pTextGroup->reset();
2807 std::vector< CustomAnimationEffectPtr >::iterator aIter( aSortedVector.begin() );
2808 const std::vector< CustomAnimationEffectPtr >::iterator aEnd( aSortedVector.end() );
2810 if( aIter != aEnd )
2812 pTextGroup->addEffect( *aIter );
2813 EffectSequence::iterator aInsertIter( find( *aIter++ ) );
2814 while( aIter != aEnd )
2816 CustomAnimationEffectPtr pEffect( *aIter++ );
2817 maEffects.erase( find( pEffect ) );
2818 aInsertIter = maEffects.insert( ++aInsertIter, pEffect );
2819 pTextGroup->addEffect( pEffect );
2822 notify_listeners();
2826 void EffectSequenceHelper::addListener( ISequenceListener* pListener )
2828 if( std::find( maListeners.begin(), maListeners.end(), pListener ) == maListeners.end() )
2829 maListeners.push_back( pListener );
2832 void EffectSequenceHelper::removeListener( ISequenceListener* pListener )
2834 maListeners.remove( pListener );
2837 struct stl_notify_listeners_func
2839 stl_notify_listeners_func() {}
2840 void operator()(ISequenceListener* pListener) { pListener->notify_change(); }
2843 void EffectSequenceHelper::notify_listeners()
2845 stl_notify_listeners_func aFunc;
2846 std::for_each( maListeners.begin(), maListeners.end(), aFunc );
2849 void EffectSequenceHelper::create( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
2851 DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::create(), illegal argument" );
2853 if( !xNode.is() )
2854 return;
2858 Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
2859 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
2860 while( xEnumeration->hasMoreElements() )
2862 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
2863 createEffectsequence( xChildNode );
2866 catch( Exception& )
2868 OSL_FAIL( "sd::EffectSequenceHelper::create(), exception caught!" );
2872 void EffectSequenceHelper::createEffectsequence( const Reference< XAnimationNode >& xNode )
2874 DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffectsequence(), illegal argument" );
2876 if( !xNode.is() )
2877 return;
2881 Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
2882 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
2883 while( xEnumeration->hasMoreElements() )
2885 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
2887 createEffects( xChildNode );
2890 catch( Exception& )
2892 OSL_FAIL( "sd::EffectSequenceHelper::createEffectsequence(), exception caught!" );
2896 void EffectSequenceHelper::createEffects( const Reference< XAnimationNode >& xNode )
2898 DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffects(), illegal argument" );
2900 if( !xNode.is() )
2901 return;
2905 Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
2906 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
2907 while( xEnumeration->hasMoreElements() )
2909 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
2911 switch( xChildNode->getType() )
2913 // found an effect
2914 case AnimationNodeType::PAR:
2915 case AnimationNodeType::ITERATE:
2917 CustomAnimationEffectPtr pEffect( new CustomAnimationEffect( xChildNode ) );
2919 if( pEffect->mnNodeType != -1 )
2921 pEffect->setEffectSequence( this );
2922 maEffects.push_back(pEffect);
2925 break;
2927 // found an after effect
2928 case AnimationNodeType::SET:
2929 case AnimationNodeType::ANIMATECOLOR:
2931 processAfterEffect( xChildNode );
2933 break;
2937 catch( Exception& )
2939 OSL_FAIL( "sd::EffectSequenceHelper::createEffects(), exception caught!" );
2943 void EffectSequenceHelper::processAfterEffect( const Reference< XAnimationNode >& xNode )
2947 Reference< XAnimationNode > xMaster;
2949 Sequence< NamedValue > aUserData( xNode->getUserData() );
2950 const NamedValue* pProp = std::find_if(aUserData.begin(), aUserData.end(),
2951 [](const NamedValue& rProp) { return rProp.Name == "master-element"; });
2953 if (pProp != aUserData.end())
2954 pProp->Value >>= xMaster;
2956 // only process if this is a valid after effect
2957 if( xMaster.is() )
2959 CustomAnimationEffectPtr pMasterEffect;
2961 // find the master effect
2962 stl_CustomAnimationEffect_search_node_predict aSearchPredict( xMaster );
2963 EffectSequence::iterator aIter( std::find_if( maEffects.begin(), maEffects.end(), aSearchPredict ) );
2964 if( aIter != maEffects.end() )
2965 pMasterEffect = *aIter;
2967 if( pMasterEffect.get() )
2969 pMasterEffect->setHasAfterEffect( true );
2971 // find out what kind of after effect this is
2972 if( xNode->getType() == AnimationNodeType::ANIMATECOLOR )
2974 // it's a dim
2975 Reference< XAnimate > xAnimate( xNode, UNO_QUERY_THROW );
2976 pMasterEffect->setDimColor( xAnimate->getTo() );
2977 pMasterEffect->setAfterEffectOnNext( true );
2979 else
2981 // it's a hide
2982 pMasterEffect->setAfterEffectOnNext( xNode->getParent() != xMaster->getParent() );
2987 catch( Exception& )
2989 OSL_FAIL( "sd::EffectSequenceHelper::processAfterEffect(), exception caught!" );
2993 class AnimationChangeListener : public cppu::WeakImplHelper< XChangesListener >
2995 public:
2996 explicit AnimationChangeListener( MainSequence* pMainSequence ) : mpMainSequence( pMainSequence ) {}
2998 virtual void SAL_CALL changesOccurred( const css::util::ChangesEvent& Event ) override;
2999 virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
3000 private:
3001 MainSequence* mpMainSequence;
3004 void SAL_CALL AnimationChangeListener::changesOccurred( const css::util::ChangesEvent& )
3006 if( mpMainSequence )
3007 mpMainSequence->startRecreateTimer();
3010 void SAL_CALL AnimationChangeListener::disposing( const css::lang::EventObject& )
3014 MainSequence::MainSequence()
3015 : mxTimingRootNode(SequenceTimeContainer::create(::comphelper::getProcessComponentContext()))
3016 , mbTimerMode(false)
3017 , mbRebuilding( false )
3018 , mnRebuildLockGuard( 0 )
3019 , mbPendingRebuildRequest( false )
3020 , mbIgnoreChanges( 0 )
3022 if( mxTimingRootNode.is() )
3024 Sequence< css::beans::NamedValue > aUserData
3025 { { "node-type", css::uno::makeAny(css::presentation::EffectNodeType::MAIN_SEQUENCE) } };
3026 mxTimingRootNode->setUserData( aUserData );
3028 init();
3031 MainSequence::MainSequence( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
3032 : mxTimingRootNode( xNode, UNO_QUERY )
3033 , mbTimerMode( false )
3034 , mbRebuilding( false )
3035 , mnRebuildLockGuard( 0 )
3036 , mbPendingRebuildRequest( false )
3037 , mbIgnoreChanges( 0 )
3039 init();
3042 MainSequence::~MainSequence()
3044 reset();
3047 void MainSequence::init()
3049 mnSequenceType = EffectNodeType::MAIN_SEQUENCE;
3051 maTimer.SetInvokeHandler( LINK(this, MainSequence, onTimerHdl) );
3052 maTimer.SetTimeout(50);
3054 mxChangesListener.set( new AnimationChangeListener( this ) );
3056 createMainSequence();
3059 void MainSequence::reset( const css::uno::Reference< css::animations::XAnimationNode >& xTimingRootNode )
3061 reset();
3063 mxTimingRootNode.set( xTimingRootNode, UNO_QUERY );
3065 createMainSequence();
3068 Reference< css::animations::XAnimationNode > MainSequence::getRootNode()
3070 DBG_ASSERT( mnRebuildLockGuard == 0, "MainSequence::getRootNode(), rebuild is locked, is this really what you want?" );
3072 if( maTimer.IsActive() && mbTimerMode )
3074 // force a rebuild NOW if one is pending
3075 maTimer.Stop();
3076 implRebuild();
3079 return EffectSequenceHelper::getRootNode();
3082 void MainSequence::createMainSequence()
3084 if( mxTimingRootNode.is() ) try
3086 Reference< XEnumerationAccess > xEnumerationAccess( mxTimingRootNode, UNO_QUERY_THROW );
3087 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
3088 while( xEnumeration->hasMoreElements() )
3090 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
3091 sal_Int32 nNodeType = CustomAnimationEffect::get_node_type( xChildNode );
3092 if( nNodeType == EffectNodeType::MAIN_SEQUENCE )
3094 mxSequenceRoot.set( xChildNode, UNO_QUERY );
3095 EffectSequenceHelper::create( xChildNode );
3097 else if( nNodeType == EffectNodeType::INTERACTIVE_SEQUENCE )
3099 Reference< XTimeContainer > xInteractiveRoot( xChildNode, UNO_QUERY_THROW );
3100 InteractiveSequencePtr pIS( new InteractiveSequence( xInteractiveRoot, this ) );
3101 pIS->addListener( this );
3102 maInteractiveSequenceVector.push_back( pIS );
3106 // see if we have a mainsequence at all. if not, create one...
3107 if( !mxSequenceRoot.is() )
3109 mxSequenceRoot = SequenceTimeContainer::create( ::comphelper::getProcessComponentContext() );
3111 uno::Sequence< css::beans::NamedValue > aUserData
3112 { { "node-type", css::uno::makeAny(css::presentation::EffectNodeType::MAIN_SEQUENCE) } };
3113 mxSequenceRoot->setUserData( aUserData );
3115 // empty sequence until now, set duration to 0.0
3116 // explicitly (otherwise, this sequence will never
3117 // end)
3118 mxSequenceRoot->setDuration( makeAny(0.0) );
3120 Reference< XAnimationNode > xMainSequenceNode( mxSequenceRoot, UNO_QUERY_THROW );
3121 mxTimingRootNode->appendChild( xMainSequenceNode );
3124 updateTextGroups();
3126 notify_listeners();
3128 Reference< XChangesNotifier > xNotifier( mxTimingRootNode, UNO_QUERY );
3129 if( xNotifier.is() )
3130 xNotifier->addChangesListener( mxChangesListener );
3132 catch( Exception& )
3134 OSL_FAIL( "sd::MainSequence::create(), exception caught!" );
3135 return;
3138 DBG_ASSERT( mxSequenceRoot.is(), "sd::MainSequence::create(), found no main sequence!" );
3141 void MainSequence::reset()
3143 EffectSequenceHelper::reset();
3145 for (auto const& interactiveSequence : maInteractiveSequenceVector)
3146 interactiveSequence->reset();
3147 maInteractiveSequenceVector.clear();
3151 Reference< XChangesNotifier > xNotifier( mxTimingRootNode, UNO_QUERY );
3152 if( xNotifier.is() )
3153 xNotifier->removeChangesListener( mxChangesListener );
3155 catch( Exception& )
3161 InteractiveSequencePtr MainSequence::createInteractiveSequence( const css::uno::Reference< css::drawing::XShape >& xShape )
3163 InteractiveSequencePtr pIS;
3165 // create a new interactive sequence container
3166 Reference< XTimeContainer > xISRoot = SequenceTimeContainer::create( ::comphelper::getProcessComponentContext() );
3168 uno::Sequence< css::beans::NamedValue > aUserData
3169 { { "node-type", css::uno::makeAny(css::presentation::EffectNodeType::INTERACTIVE_SEQUENCE) } };
3170 xISRoot->setUserData( aUserData );
3171 xISRoot->setRestart( css::animations::AnimationRestart::WHEN_NOT_ACTIVE );
3173 Reference< XChild > xChild( mxSequenceRoot, UNO_QUERY_THROW );
3174 Reference< XTimeContainer > xParent( xChild->getParent(), UNO_QUERY_THROW );
3175 xParent->appendChild( xISRoot );
3177 pIS.reset( new InteractiveSequence( xISRoot, this) );
3178 pIS->setTriggerShape( xShape );
3179 pIS->addListener( this );
3180 maInteractiveSequenceVector.push_back( pIS );
3181 return pIS;
3184 CustomAnimationEffectPtr MainSequence::findEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) const
3186 CustomAnimationEffectPtr pEffect = EffectSequenceHelper::findEffect( xNode );
3188 if( pEffect.get() == nullptr )
3190 for (auto const& interactiveSequence : maInteractiveSequenceVector)
3192 pEffect = interactiveSequence->findEffect( xNode );
3193 if (pEffect.get())
3194 break;
3197 return pEffect;
3200 sal_Int32 MainSequence::getOffsetFromEffect( const CustomAnimationEffectPtr& pEffect ) const
3202 sal_Int32 nOffset = EffectSequenceHelper::getOffsetFromEffect( pEffect );
3204 if( nOffset != -1 )
3205 return nOffset;
3207 nOffset = EffectSequenceHelper::getCount();
3209 for (auto const& interactiveSequence : maInteractiveSequenceVector)
3211 sal_Int32 nTemp = interactiveSequence->getOffsetFromEffect( pEffect );
3212 if( nTemp != -1 )
3213 return nOffset + nTemp;
3215 nOffset += interactiveSequence->getCount();
3218 return -1;
3221 CustomAnimationEffectPtr MainSequence::getEffectFromOffset( sal_Int32 nOffset ) const
3223 if( nOffset >= 0 )
3225 if( nOffset < getCount() )
3226 return EffectSequenceHelper::getEffectFromOffset( nOffset );
3228 nOffset -= getCount();
3230 auto aIter( maInteractiveSequenceVector.begin() );
3232 while( (aIter != maInteractiveSequenceVector.end()) && (nOffset > (*aIter)->getCount()) )
3233 nOffset -= (*aIter++)->getCount();
3235 if( (aIter != maInteractiveSequenceVector.end()) && (nOffset >= 0) )
3236 return (*aIter)->getEffectFromOffset( nOffset );
3239 CustomAnimationEffectPtr pEffect;
3240 return pEffect;
3243 bool MainSequence::disposeShape( const Reference< XShape >& xShape )
3245 bool bChanges = EffectSequenceHelper::disposeShape( xShape );
3247 for (auto const& iterativeSequence : maInteractiveSequenceVector)
3249 bChanges |= iterativeSequence->disposeShape( xShape );
3252 if( bChanges )
3253 startRebuildTimer();
3255 return bChanges;
3258 bool MainSequence::hasEffect( const css::uno::Reference< css::drawing::XShape >& xShape )
3260 if( EffectSequenceHelper::hasEffect( xShape ) )
3261 return true;
3263 for (auto const& iterativeSequence : maInteractiveSequenceVector)
3265 if( iterativeSequence->getTriggerShape() == xShape )
3266 return true;
3268 if( iterativeSequence->hasEffect( xShape ) )
3269 return true;
3272 return false;
3275 void MainSequence::insertTextRange( const css::uno::Any& aTarget )
3277 EffectSequenceHelper::insertTextRange( aTarget );
3279 for (auto const& iterativeSequence : maInteractiveSequenceVector)
3281 iterativeSequence->insertTextRange( aTarget );
3285 void MainSequence::disposeTextRange( const css::uno::Any& aTarget )
3287 EffectSequenceHelper::disposeTextRange( aTarget );
3289 for (auto const& iterativeSequence : maInteractiveSequenceVector)
3291 iterativeSequence->disposeTextRange( aTarget );
3295 /** callback from the sd::View when an object just left text edit mode */
3296 void MainSequence::onTextChanged( const Reference< XShape >& xShape )
3298 EffectSequenceHelper::onTextChanged( xShape );
3300 for (auto const& iterativeSequence : maInteractiveSequenceVector)
3302 iterativeSequence->onTextChanged( xShape );
3306 void EffectSequenceHelper::onTextChanged( const Reference< XShape >& xShape )
3308 // get map [paragraph index] -> [NumberingLevel]
3309 // for following reusage inside all animation effects
3310 std::vector< sal_Int32 > paragraphNumberingLevel;
3311 std::vector< sal_Int32 >* paragraphNumberingLevelParam = nullptr;
3312 if ( getParagraphNumberingLevels( xShape, paragraphNumberingLevel ) )
3313 paragraphNumberingLevelParam = &paragraphNumberingLevel;
3315 // update internal flags for each animation effect
3316 const bool bChanges = std::accumulate(maEffects.begin(), maEffects.end(), false,
3317 [&xShape, &paragraphNumberingLevelParam](const bool bCheck, const CustomAnimationEffectPtr& rxEffect) {
3318 bool bRes = bCheck;
3319 if (rxEffect->getTargetShape() == xShape)
3320 bRes |= rxEffect->checkForText( paragraphNumberingLevelParam );
3321 return bRes;
3324 if( bChanges )
3325 rebuild();
3328 void MainSequence::rebuild()
3330 startRebuildTimer();
3333 void MainSequence::lockRebuilds()
3335 mnRebuildLockGuard++;
3338 void MainSequence::unlockRebuilds()
3340 DBG_ASSERT( mnRebuildLockGuard, "sd::MainSequence::unlockRebuilds(), no corresponding lockRebuilds() call!" );
3341 if( mnRebuildLockGuard )
3342 mnRebuildLockGuard--;
3344 if( (mnRebuildLockGuard == 0) && mbPendingRebuildRequest )
3346 mbPendingRebuildRequest = false;
3347 startRebuildTimer();
3351 void MainSequence::implRebuild()
3353 if( mnRebuildLockGuard )
3355 mbPendingRebuildRequest = true;
3356 return;
3359 mbRebuilding = true;
3361 EffectSequenceHelper::implRebuild();
3363 auto aIter( maInteractiveSequenceVector.begin() );
3364 while( aIter != maInteractiveSequenceVector.end() )
3366 InteractiveSequencePtr pIS( *aIter );
3367 if( pIS->maEffects.empty() )
3369 // remove empty interactive sequences
3370 aIter = maInteractiveSequenceVector.erase( aIter );
3372 Reference< XChild > xChild( mxSequenceRoot, UNO_QUERY_THROW );
3373 Reference< XTimeContainer > xParent( xChild->getParent(), UNO_QUERY_THROW );
3374 Reference< XAnimationNode > xISNode( pIS->mxSequenceRoot, UNO_QUERY_THROW );
3375 xParent->removeChild( xISNode );
3377 else
3379 pIS->implRebuild();
3380 ++aIter;
3384 notify_listeners();
3385 mbRebuilding = false;
3388 void MainSequence::notify_change()
3390 notify_listeners();
3393 bool MainSequence::setTrigger( const CustomAnimationEffectPtr& pEffect, const css::uno::Reference< css::drawing::XShape >& xTriggerShape )
3395 EffectSequenceHelper* pOldSequence = pEffect->getEffectSequence();
3397 EffectSequenceHelper* pNewSequence = nullptr;
3398 if( xTriggerShape.is() )
3400 for (InteractiveSequencePtr const& pIS : maInteractiveSequenceVector)
3402 if( pIS->getTriggerShape() == xTriggerShape )
3404 pNewSequence = pIS.get();
3405 break;
3409 if( !pNewSequence )
3410 pNewSequence = createInteractiveSequence( xTriggerShape ).get();
3412 else
3414 pNewSequence = this;
3417 if( pOldSequence != pNewSequence )
3419 if( pOldSequence )
3420 pOldSequence->maEffects.remove( pEffect );
3421 if( pNewSequence )
3422 pNewSequence->maEffects.push_back( pEffect );
3423 pEffect->setEffectSequence( pNewSequence );
3424 return true;
3426 else
3428 return false;
3433 IMPL_LINK_NOARG(MainSequence, onTimerHdl, Timer *, void)
3435 if( mbTimerMode )
3437 implRebuild();
3439 else
3441 reset();
3442 createMainSequence();
3446 /** starts a timer that recreates the internal structure from the API core */
3447 void MainSequence::startRecreateTimer()
3449 if( !mbRebuilding && (mbIgnoreChanges == 0) )
3451 mbTimerMode = false;
3452 maTimer.Start();
3457 * starts a timer that rebuilds the API core from the internal structure
3458 * This is used to reduce the number of screen redraws due to animation changes.
3460 void MainSequence::startRebuildTimer()
3462 mbTimerMode = true;
3463 maTimer.Start();
3466 InteractiveSequence::InteractiveSequence( const Reference< XTimeContainer >& xSequenceRoot, MainSequence* pMainSequence )
3467 : EffectSequenceHelper( xSequenceRoot ), mpMainSequence( pMainSequence )
3469 mnSequenceType = EffectNodeType::INTERACTIVE_SEQUENCE;
3473 if( mxSequenceRoot.is() )
3475 Reference< XEnumerationAccess > xEnumerationAccess( mxSequenceRoot, UNO_QUERY_THROW );
3476 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
3477 while( !mxEventSource.is() && xEnumeration->hasMoreElements() )
3479 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
3481 Event aEvent;
3482 if( (xChildNode->getBegin() >>= aEvent) && (aEvent.Trigger == EventTrigger::ON_CLICK) )
3483 aEvent.Source >>= mxEventSource;
3487 catch( Exception& )
3489 OSL_FAIL( "sd::InteractiveSequence::InteractiveSequence(), exception caught!" );
3490 return;
3494 void InteractiveSequence::rebuild()
3496 mpMainSequence->rebuild();
3499 void InteractiveSequence::implRebuild()
3501 EffectSequenceHelper::implRebuild();
3504 MainSequenceRebuildGuard::MainSequenceRebuildGuard( const MainSequencePtr& pMainSequence )
3505 : mpMainSequence( pMainSequence )
3507 if( mpMainSequence.get() )
3508 mpMainSequence->lockRebuilds();
3511 MainSequenceRebuildGuard::~MainSequenceRebuildGuard()
3513 if( mpMainSequence.get() )
3514 mpMainSequence->unlockRebuilds();
3519 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */