loplugin:sequenceloop in sd
[LibreOffice.git] / sd / source / core / CustomAnimationEffect.cxx
bloba6f75ee9cdc05c364f2551b98553e25384e4f60a
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()
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 Reference< XEnumerationAccess > xEA( xText, UNO_QUERY );
526 if( xEA.is() )
528 Reference< XEnumeration > xEnumeration = xEA->createEnumeration();
529 if( xEnumeration.is() )
531 bool bHasText = xEnumeration->hasMoreElements();
532 bChange |= bHasText != mbHasText;
533 mbHasText = bHasText;
535 sal_Int32 nPara = aParaTarget.Paragraph;
537 while( xEnumeration->hasMoreElements() && nPara-- )
538 xEnumeration->nextElement();
540 if( xEnumeration->hasMoreElements() )
542 Reference< XPropertySet > xParaSet;
543 xEnumeration->nextElement() >>= xParaSet;
544 if( xParaSet.is() )
546 sal_Int32 nParaDepth = 0;
547 const OUString strNumberingLevel( "NumberingLevel" );
548 xParaSet->getPropertyValue( strNumberingLevel ) >>= nParaDepth;
549 bChange |= nParaDepth != mnParaDepth;
550 mnParaDepth = nParaDepth;
557 else
559 maTarget >>= xText;
560 bool bHasText = xText.is() && !xText->getString().isEmpty();
561 bChange |= bHasText != mbHasText;
562 mbHasText = bHasText;
565 bChange |= calculateIterateDuration();
566 return bChange;
569 bool CustomAnimationEffect::calculateIterateDuration()
571 bool bChange = false;
573 // if we have an iteration, we must also calculate the
574 // 'true' container duration, that is
575 // ( ( is form animated ) ? [contained effects duration] : 0 ) +
576 // ( [number of animated children] - 1 ) * [interval-delay] + [contained effects duration]
577 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
578 if( xIter.is() )
580 double fDuration = mfDuration;
581 const double fSubEffectDuration = mfDuration;
583 if( mnTargetSubItem != ShapeAnimationSubType::ONLY_BACKGROUND ) // does not make sense for iterate container but better check
585 const sal_Int32 nSubItems = getNumberOfSubitems( maTarget, mnIterateType );
586 if( nSubItems )
588 const double f = (nSubItems-1) * mfIterateInterval;
589 fDuration += f;
593 // if we also animate the form first, we have to add the
594 // sub effect duration to the whole effect duration
595 if( mnTargetSubItem == ShapeAnimationSubType::AS_WHOLE )
596 fDuration += fSubEffectDuration;
598 bChange |= fDuration != mfAbsoluteDuration;
599 mfAbsoluteDuration = fDuration;
602 return bChange;
605 void CustomAnimationEffect::setTarget( const css::uno::Any& rTarget )
609 maTarget = rTarget;
611 // first, check special case for random node
612 Reference< XInitialization > xInit( mxNode, UNO_QUERY );
613 if( xInit.is() )
615 const Sequence< Any > aArgs( &maTarget, 1 );
616 xInit->initialize( aArgs );
618 else
620 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
621 if( xIter.is() )
623 xIter->setTarget(maTarget);
625 else
627 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
628 if( xEnumerationAccess.is() )
630 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
631 if( xEnumeration.is() )
633 while( xEnumeration->hasMoreElements() )
635 const Any aElem( xEnumeration->nextElement() );
636 Reference< XAnimate > xAnimate( aElem, UNO_QUERY );
637 if( xAnimate.is() )
638 xAnimate->setTarget( rTarget );
639 else
641 Reference< XCommand > xCommand( aElem, UNO_QUERY );
642 if( xCommand.is() )
643 xCommand->setTarget( rTarget );
650 checkForText();
652 catch( Exception& )
654 OSL_FAIL( "sd::CustomAnimationEffect::setTarget(), exception caught!" );
658 void CustomAnimationEffect::setTargetSubItem( sal_Int16 nSubItem )
662 mnTargetSubItem = nSubItem;
664 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
665 if( xIter.is() )
667 xIter->setSubItem(mnTargetSubItem);
669 else
671 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
672 if( xEnumerationAccess.is() )
674 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
675 if( xEnumeration.is() )
677 while( xEnumeration->hasMoreElements() )
679 Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
680 if( xAnimate.is() )
681 xAnimate->setSubItem( mnTargetSubItem );
687 catch( Exception& )
689 OSL_FAIL( "sd::CustomAnimationEffect::setTargetSubItem(), exception caught!" );
693 void CustomAnimationEffect::setDuration( double fDuration )
695 if( (mfDuration == -1.0) || (mfDuration == fDuration) )
696 return;
700 double fScale = fDuration / mfDuration;
701 mfDuration = fDuration;
702 double fRepeatCount = 1.0;
703 getRepeatCount() >>= fRepeatCount;
704 mfAbsoluteDuration = mfDuration * fRepeatCount;
706 // calculate effect duration and get target shape
707 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
708 if( xEnumerationAccess.is() )
710 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
711 if( xEnumeration.is() )
713 while( xEnumeration->hasMoreElements() )
715 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY );
716 if( !xChildNode.is() )
717 continue;
719 double fChildBegin = 0.0;
720 xChildNode->getBegin() >>= fChildBegin;
721 if( fChildBegin != 0.0 )
723 fChildBegin *= fScale;
724 xChildNode->setBegin( makeAny( fChildBegin ) );
727 double fChildDuration = 0.0;
728 xChildNode->getDuration() >>= fChildDuration;
729 if( fChildDuration != 0.0 )
731 fChildDuration *= fScale;
732 xChildNode->setDuration( makeAny( fChildDuration ) );
737 calculateIterateDuration();
739 catch( Exception& )
741 OSL_FAIL( "sd::CustomAnimationEffect::setDuration(), exception caught!" );
745 void CustomAnimationEffect::setBegin( double fBegin )
747 if( mxNode.is() ) try
749 mfBegin = fBegin;
750 mxNode->setBegin( makeAny( fBegin ) );
752 catch( Exception& )
754 OSL_FAIL( "sd::CustomAnimationEffect::setBegin(), exception caught!" );
758 void CustomAnimationEffect::setAcceleration( double fAcceleration )
760 if( mxNode.is() ) try
762 mfAcceleration = fAcceleration;
763 mxNode->setAcceleration( fAcceleration );
765 catch( Exception& )
767 OSL_FAIL( "sd::CustomAnimationEffect::setAcceleration(), exception caught!" );
771 void CustomAnimationEffect::setDecelerate( double fDecelerate )
773 if( mxNode.is() ) try
775 mfDecelerate = fDecelerate;
776 mxNode->setDecelerate( fDecelerate );
778 catch( Exception& )
780 OSL_FAIL( "sd::CustomAnimationEffect::setDecelerate(), exception caught!" );
784 void CustomAnimationEffect::setAutoReverse( bool bAutoReverse )
786 if( mxNode.is() ) try
788 mbAutoReverse = bAutoReverse;
789 mxNode->setAutoReverse( bAutoReverse );
791 catch( Exception& )
793 OSL_FAIL( "sd::CustomAnimationEffect::setAutoReverse(), exception caught!" );
797 void CustomAnimationEffect::replaceNode( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
799 sal_Int16 nNodeType = mnNodeType;
800 Any aTarget = maTarget;
802 sal_Int16 nFill = mnFill;
803 double fBegin = mfBegin;
804 double fDuration = mfDuration;
805 double fAcceleration = mfAcceleration;
806 double fDecelerate = mfDecelerate ;
807 bool bAutoReverse = mbAutoReverse;
808 Reference< XAudio > xAudio( mxAudio );
809 sal_Int16 nIterateType = mnIterateType;
810 double fIterateInterval = mfIterateInterval;
811 sal_Int16 nSubItem = mnTargetSubItem;
813 setNode( xNode );
815 setAudio( xAudio );
816 setNodeType( nNodeType );
817 setTarget( aTarget );
818 setTargetSubItem( nSubItem );
819 setDuration( fDuration );
820 setBegin( fBegin );
821 setFill( nFill );
823 setAcceleration( fAcceleration );
824 setDecelerate( fDecelerate );
825 setAutoReverse( bAutoReverse );
827 if( nIterateType != mnIterateType )
828 setIterateType( nIterateType );
830 if( mnIterateType && ( fIterateInterval != mfIterateInterval ) )
831 setIterateInterval( fIterateInterval );
834 Reference< XShape > CustomAnimationEffect::getTargetShape() const
836 Reference< XShape > xShape;
837 maTarget >>= xShape;
838 if( !xShape.is() )
840 ParagraphTarget aParaTarget;
841 if( maTarget >>= aParaTarget )
842 xShape = aParaTarget.Shape;
845 return xShape;
848 Any CustomAnimationEffect::getRepeatCount() const
850 if( mxNode.is() )
852 return mxNode->getRepeatCount();
854 else
856 Any aAny;
857 return aAny;
861 Any CustomAnimationEffect::getEnd() const
863 if( mxNode.is() )
865 return mxNode->getEnd();
867 else
869 Any aAny;
870 return aAny;
874 void CustomAnimationEffect::setRepeatCount( const Any& rRepeatCount )
876 if( mxNode.is() )
878 mxNode->setRepeatCount( rRepeatCount );
879 double fRepeatCount = 1.0;
880 rRepeatCount >>= fRepeatCount;
881 mfAbsoluteDuration = mfDuration * fRepeatCount;
885 void CustomAnimationEffect::setEnd( const Any& rEnd )
887 if( mxNode.is() )
888 mxNode->setEnd( rEnd );
891 void CustomAnimationEffect::setFill( sal_Int16 nFill )
893 if (mxNode.is())
895 mnFill = nFill;
896 mxNode->setFill( nFill );
900 Reference< XAnimationNode > CustomAnimationEffect::createAfterEffectNode() const
902 DBG_ASSERT( mbHasAfterEffect, "sd::CustomAnimationEffect::createAfterEffectNode(), this node has no after effect!" );
904 Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
906 Reference< XAnimate > xAnimate;
907 if( maDimColor.hasValue() )
908 xAnimate = AnimateColor::create( xContext );
909 else
910 xAnimate = AnimateSet::create( xContext );
912 Any aTo;
913 OUString aAttributeName;
915 if( maDimColor.hasValue() )
917 aTo = maDimColor;
918 aAttributeName = "DimColor";
920 else
922 aTo <<= false;
923 aAttributeName = "Visibility";
926 Any aBegin;
927 if( !mbAfterEffectOnNextEffect ) // sameClick
929 Event aEvent;
931 aEvent.Source <<= getNode();
932 aEvent.Trigger = EventTrigger::END_EVENT;
933 aEvent.Repeat = 0;
935 aBegin <<= aEvent;
937 else
939 aBegin <<= 0.0;
942 xAnimate->setBegin( aBegin );
943 xAnimate->setTo( aTo );
944 xAnimate->setAttributeName( aAttributeName );
946 xAnimate->setDuration( makeAny( 0.001 ) );
947 xAnimate->setFill( AnimationFill::HOLD );
948 xAnimate->setTarget( maTarget );
950 return xAnimate;
953 void CustomAnimationEffect::setIterateType( sal_Int16 nIterateType )
955 if( mnIterateType == nIterateType )
956 return;
960 // do we need to exchange the container node?
961 if( (mnIterateType == 0) || (nIterateType == 0) )
963 sal_Int16 nTargetSubItem = mnTargetSubItem;
965 Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
966 Reference< XTimeContainer > xNewContainer;
967 if(nIterateType)
969 xNewContainer.set( IterateContainer::create( xContext ) );
971 else
972 xNewContainer.set( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW );
974 Reference< XTimeContainer > xOldContainer( mxNode, UNO_QUERY_THROW );
975 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
976 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
977 while( xEnumeration->hasMoreElements() )
979 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
980 xOldContainer->removeChild( xChildNode );
981 xNewContainer->appendChild( xChildNode );
984 xNewContainer->setBegin( mxNode->getBegin() );
985 xNewContainer->setDuration( mxNode->getDuration() );
986 xNewContainer->setEnd( mxNode->getEnd() );
987 xNewContainer->setEndSync( mxNode->getEndSync() );
988 xNewContainer->setRepeatCount( mxNode->getRepeatCount() );
989 xNewContainer->setFill( mxNode->getFill() );
990 xNewContainer->setFillDefault( mxNode->getFillDefault() );
991 xNewContainer->setRestart( mxNode->getRestart() );
992 xNewContainer->setRestartDefault( mxNode->getRestartDefault() );
993 xNewContainer->setAcceleration( mxNode->getAcceleration() );
994 xNewContainer->setDecelerate( mxNode->getDecelerate() );
995 xNewContainer->setAutoReverse( mxNode->getAutoReverse() );
996 xNewContainer->setRepeatDuration( mxNode->getRepeatDuration() );
997 xNewContainer->setEndSync( mxNode->getEndSync() );
998 xNewContainer->setRepeatCount( mxNode->getRepeatCount() );
999 xNewContainer->setUserData( mxNode->getUserData() );
1001 mxNode = xNewContainer;
1003 Any aTarget;
1004 if( nIterateType )
1006 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY_THROW );
1007 xIter->setTarget(maTarget);
1008 xIter->setSubItem( nTargetSubItem );
1010 else
1012 aTarget = maTarget;
1015 Reference< XEnumerationAccess > xEA( mxNode, UNO_QUERY_THROW );
1016 Reference< XEnumeration > xE( xEA->createEnumeration(), UNO_SET_THROW );
1017 while( xE->hasMoreElements() )
1019 Reference< XAnimate > xAnimate( xE->nextElement(), UNO_QUERY );
1020 if( xAnimate.is() )
1022 xAnimate->setTarget( aTarget );
1023 xAnimate->setSubItem( nTargetSubItem );
1028 mnIterateType = nIterateType;
1030 // if we have an iteration container, we must set its type
1031 if( mnIterateType )
1033 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY_THROW );
1034 xIter->setIterateType( nIterateType );
1037 checkForText();
1039 catch( Exception& )
1041 OSL_FAIL( "sd::CustomAnimationEffect::setIterateType(), Exception caught!" );
1045 void CustomAnimationEffect::setIterateInterval( double fIterateInterval )
1047 if( mfIterateInterval == fIterateInterval )
1048 return;
1050 Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );
1052 DBG_ASSERT( xIter.is(), "sd::CustomAnimationEffect::setIterateInterval(), not an iteration node" );
1053 if( xIter.is() )
1055 mfIterateInterval = fIterateInterval;
1056 xIter->setIterateInterval( fIterateInterval );
1059 calculateIterateDuration();
1062 OUString CustomAnimationEffect::getPath() const
1064 OUString aPath;
1066 if( mxNode.is() ) try
1068 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
1069 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
1070 while( xEnumeration->hasMoreElements() )
1072 Reference< XAnimateMotion > xMotion( xEnumeration->nextElement(), UNO_QUERY );
1073 if( xMotion.is() )
1075 xMotion->getPath() >>= aPath;
1076 break;
1080 catch( Exception& )
1082 OSL_FAIL("sd::CustomAnimationEffect::getPath(), exception caught!" );
1085 return aPath;
1088 void CustomAnimationEffect::setPath( const OUString& rPath )
1090 if( !mxNode.is() )
1091 return;
1095 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
1096 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
1097 while( xEnumeration->hasMoreElements() )
1099 Reference< XAnimateMotion > xMotion( xEnumeration->nextElement(), UNO_QUERY );
1100 if( xMotion.is() )
1103 MainSequenceChangeGuard aGuard( mpEffectSequence );
1104 xMotion->setPath( Any( rPath ) );
1105 break;
1109 catch( Exception& )
1111 OSL_FAIL("sd::CustomAnimationEffect::setPath(), exception caught!" );
1115 Any CustomAnimationEffect::getProperty( sal_Int32 nNodeType, const OUString& rAttributeName, EValue eValue )
1117 Any aProperty;
1118 if( mxNode.is() ) try
1120 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1121 if( xEnumerationAccess.is() )
1123 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1124 if( xEnumeration.is() )
1126 while( xEnumeration->hasMoreElements() && !aProperty.hasValue() )
1128 Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
1129 if( !xAnimate.is() )
1130 continue;
1132 if( xAnimate->getType() == nNodeType )
1134 if( xAnimate->getAttributeName() == rAttributeName )
1136 switch( eValue )
1138 case EValue::To: aProperty = xAnimate->getTo(); break;
1139 case EValue::By: aProperty = xAnimate->getBy(); break;
1147 catch( Exception& )
1149 OSL_FAIL("sd::CustomAnimationEffect::getProperty(), exception caught!" );
1152 return aProperty;
1155 bool CustomAnimationEffect::setProperty( sal_Int32 nNodeType, const OUString& rAttributeName, EValue eValue, const Any& rValue )
1157 bool bChanged = false;
1158 if( mxNode.is() ) try
1160 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1161 if( xEnumerationAccess.is() )
1163 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1164 if( xEnumeration.is() )
1166 while( xEnumeration->hasMoreElements() )
1168 Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
1169 if( !xAnimate.is() )
1170 continue;
1172 if( xAnimate->getType() == nNodeType )
1174 if( xAnimate->getAttributeName() == rAttributeName )
1176 switch( eValue )
1178 case EValue::To:
1179 if( xAnimate->getTo() != rValue )
1181 xAnimate->setTo( rValue );
1182 bChanged = true;
1184 break;
1185 case EValue::By:
1186 if( xAnimate->getTo() != rValue )
1188 xAnimate->setBy( rValue );
1189 bChanged = true;
1191 break;
1199 catch( Exception& )
1201 OSL_FAIL("sd::CustomAnimationEffect::setProperty(), exception caught!" );
1204 return bChanged;
1207 static bool implIsColorAttribute( const OUString& rAttributeName )
1209 return rAttributeName == "FillColor" || rAttributeName == "LineColor" || rAttributeName == "CharColor";
1212 Any CustomAnimationEffect::getColor( sal_Int32 nIndex )
1214 Any aColor;
1215 if( mxNode.is() ) try
1217 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1218 if( xEnumerationAccess.is() )
1220 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1221 if( xEnumeration.is() )
1223 while( xEnumeration->hasMoreElements() && !aColor.hasValue() )
1225 Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
1226 if( !xAnimate.is() )
1227 continue;
1229 switch( xAnimate->getType() )
1231 case AnimationNodeType::SET:
1232 case AnimationNodeType::ANIMATE:
1233 if( !implIsColorAttribute( xAnimate->getAttributeName() ) )
1234 break;
1235 [[fallthrough]];
1236 case AnimationNodeType::ANIMATECOLOR:
1237 Sequence<Any> aValues( xAnimate->getValues() );
1238 if( aValues.hasElements() )
1240 if( aValues.getLength() > nIndex )
1241 aColor = aValues[nIndex];
1243 else if( nIndex == 0 )
1244 aColor = xAnimate->getFrom();
1245 else
1246 aColor = xAnimate->getTo();
1252 catch( Exception& )
1254 OSL_FAIL("sd::CustomAnimationEffect::getColor(), exception caught!" );
1257 return aColor;
1260 void CustomAnimationEffect::setColor( sal_Int32 nIndex, const Any& rColor )
1262 if( !mxNode.is() )
1263 return;
1267 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1268 if( xEnumerationAccess.is() )
1270 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1271 if( xEnumeration.is() )
1273 while( xEnumeration->hasMoreElements() )
1275 Reference< XAnimate > xAnimate( xEnumeration->nextElement(), UNO_QUERY );
1276 if( !xAnimate.is() )
1277 continue;
1279 switch( xAnimate->getType() )
1281 case AnimationNodeType::SET:
1282 case AnimationNodeType::ANIMATE:
1283 if( !implIsColorAttribute( xAnimate->getAttributeName() ) )
1284 break;
1285 [[fallthrough]];
1286 case AnimationNodeType::ANIMATECOLOR:
1288 Sequence<Any> aValues( xAnimate->getValues() );
1289 if( aValues.hasElements() )
1291 if( aValues.getLength() > nIndex )
1293 aValues[nIndex] = rColor;
1294 xAnimate->setValues( aValues );
1297 else if( (nIndex == 0) && xAnimate->getFrom().hasValue() )
1298 xAnimate->setFrom(rColor);
1299 else if( (nIndex == 1) && xAnimate->getTo().hasValue() )
1300 xAnimate->setTo(rColor);
1302 break;
1309 catch( Exception& )
1311 OSL_FAIL("sd::CustomAnimationEffect::setColor(), exception caught!" );
1315 Any CustomAnimationEffect::getTransformationProperty( sal_Int32 nTransformType, EValue eValue )
1317 Any aProperty;
1318 if( mxNode.is() ) try
1320 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1321 if( xEnumerationAccess.is() )
1323 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1324 if( xEnumeration.is() )
1326 while( xEnumeration->hasMoreElements() && !aProperty.hasValue() )
1328 Reference< XAnimateTransform > xTransform( xEnumeration->nextElement(), UNO_QUERY );
1329 if( !xTransform.is() )
1330 continue;
1332 if( xTransform->getTransformType() == nTransformType )
1334 switch( eValue )
1336 case EValue::To: aProperty = xTransform->getTo(); break;
1337 case EValue::By: aProperty = xTransform->getBy(); break;
1344 catch( Exception& )
1346 OSL_FAIL("sd::CustomAnimationEffect::getTransformationProperty(), exception caught!" );
1349 return aProperty;
1352 bool CustomAnimationEffect::setTransformationProperty( sal_Int32 nTransformType, EValue eValue, const Any& rValue )
1354 bool bChanged = false;
1355 if( mxNode.is() ) try
1357 Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
1358 if( xEnumerationAccess.is() )
1360 Reference< XEnumeration > xEnumeration = xEnumerationAccess->createEnumeration();
1361 if( xEnumeration.is() )
1363 while( xEnumeration->hasMoreElements() )
1365 Reference< XAnimateTransform > xTransform( xEnumeration->nextElement(), UNO_QUERY );
1366 if( !xTransform.is() )
1367 continue;
1369 if( xTransform->getTransformType() == nTransformType )
1371 switch( eValue )
1373 case EValue::To:
1374 if( xTransform->getTo() != rValue )
1376 xTransform->setTo( rValue );
1377 bChanged = true;
1379 break;
1380 case EValue::By:
1381 if( xTransform->getBy() != rValue )
1383 xTransform->setBy( rValue );
1384 bChanged = true;
1386 break;
1393 catch( Exception& )
1395 OSL_FAIL("sd::CustomAnimationEffect::setTransformationProperty(), exception caught!" );
1398 return bChanged;
1401 void CustomAnimationEffect::createAudio( const css::uno::Any& rSource )
1403 DBG_ASSERT( !mxAudio.is(), "sd::CustomAnimationEffect::createAudio(), node already has an audio!" );
1405 if( mxAudio.is() )
1406 return;
1410 Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
1411 Reference< XAudio > xAudio( Audio::create( xContext ) );
1412 xAudio->setSource( rSource );
1413 xAudio->setVolume( 1.0 );
1414 setAudio( xAudio );
1416 catch( Exception& )
1418 OSL_FAIL("sd::CustomAnimationEffect::createAudio(), exception caught!" );
1422 static Reference< XCommand > findCommandNode( const Reference< XAnimationNode >& xRootNode )
1424 Reference< XCommand > xCommand;
1426 if( xRootNode.is() ) try
1428 Reference< XEnumerationAccess > xEnumerationAccess( xRootNode, UNO_QUERY_THROW );
1429 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
1430 while( !xCommand.is() && xEnumeration->hasMoreElements() )
1432 Reference< XAnimationNode > xNode( xEnumeration->nextElement(), UNO_QUERY );
1433 if( xNode.is() && (xNode->getType() == AnimationNodeType::COMMAND) )
1434 xCommand.set( xNode, UNO_QUERY_THROW );
1437 catch( Exception& )
1439 OSL_FAIL("sd::findCommandNode(), exception caught!" );
1442 return xCommand;
1445 void CustomAnimationEffect::removeAudio()
1449 Reference< XAnimationNode > xChild;
1451 if( mxAudio.is() )
1453 xChild = mxAudio;
1454 mxAudio.clear();
1456 else if( mnCommand == EffectCommands::STOPAUDIO )
1458 xChild = findCommandNode( mxNode );
1459 mnCommand = 0;
1462 if( xChild.is() )
1464 Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
1465 if( xContainer.is() )
1466 xContainer->removeChild( xChild );
1469 catch( Exception& )
1471 OSL_FAIL("sd::CustomAnimationEffect::removeAudio(), exception caught!" );
1476 void CustomAnimationEffect::setAudio( const Reference< css::animations::XAudio >& xAudio )
1478 if( mxAudio == xAudio )
1479 return;
1483 removeAudio();
1484 mxAudio = xAudio;
1485 Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
1486 if( xContainer.is() && mxAudio.is() )
1487 xContainer->appendChild( mxAudio );
1489 catch( Exception& )
1491 OSL_FAIL("sd::CustomAnimationEffect::setAudio(), exception caught!" );
1495 void CustomAnimationEffect::setStopAudio()
1497 if( mnCommand == EffectCommands::STOPAUDIO )
1498 return;
1502 if( mxAudio.is() )
1503 removeAudio();
1505 Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
1506 Reference< XCommand > xCommand( Command::create( xContext ) );
1508 xCommand->setCommand( EffectCommands::STOPAUDIO );
1510 Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY_THROW );
1511 xContainer->appendChild( xCommand );
1513 mnCommand = EffectCommands::STOPAUDIO;
1515 catch( Exception& )
1517 OSL_FAIL("sd::CustomAnimationEffect::setStopAudio(), exception caught!" );
1521 bool CustomAnimationEffect::getStopAudio() const
1523 return mnCommand == EffectCommands::STOPAUDIO;
1526 SdrPathObj* CustomAnimationEffect::createSdrPathObjFromPath(SdrModel& rTargetModel)
1528 SdrPathObj * pPathObj = new SdrPathObj(rTargetModel, OBJ_PATHLINE);
1529 updateSdrPathObjFromPath( *pPathObj );
1530 return pPathObj;
1533 void CustomAnimationEffect::updateSdrPathObjFromPath( SdrPathObj& rPathObj )
1535 ::basegfx::B2DPolyPolygon aPolyPoly;
1536 if( ::basegfx::utils::importFromSvgD( aPolyPoly, getPath(), true, nullptr ) )
1538 SdrObject* pObj = GetSdrObjectFromXShape( getTargetShape() );
1539 if( pObj )
1541 SdrPage* pPage = pObj->getSdrPageFromSdrObject();
1542 if( pPage )
1544 const Size aPageSize( pPage->GetSize() );
1545 aPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(static_cast<double>(aPageSize.Width()), static_cast<double>(aPageSize.Height())));
1548 const ::tools::Rectangle aBoundRect( pObj->GetCurrentBoundRect() );
1549 const Point aCenter( aBoundRect.Center() );
1550 aPolyPoly.transform(basegfx::utils::createTranslateB2DHomMatrix(aCenter.X(), aCenter.Y()));
1554 rPathObj.SetPathPoly( aPolyPoly );
1557 void CustomAnimationEffect::updatePathFromSdrPathObj( const SdrPathObj& rPathObj )
1559 ::basegfx::B2DPolyPolygon aPolyPoly( rPathObj.GetPathPoly() );
1561 SdrObject* pObj = GetSdrObjectFromXShape( getTargetShape() );
1562 if( pObj )
1564 ::tools::Rectangle aBoundRect(0,0,0,0);
1566 const drawinglayer::primitive2d::Primitive2DContainer xPrimitives(pObj->GetViewContact().getViewIndependentPrimitive2DContainer());
1567 const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
1568 const basegfx::B2DRange aRange(xPrimitives.getB2DRange(aViewInformation2D));
1570 if(!aRange.isEmpty())
1572 aBoundRect = ::tools::Rectangle(
1573 static_cast<sal_Int32>(floor(aRange.getMinX())), static_cast<sal_Int32>(floor(aRange.getMinY())),
1574 static_cast<sal_Int32>(ceil(aRange.getMaxX())), static_cast<sal_Int32>(ceil(aRange.getMaxY())));
1577 const Point aCenter( aBoundRect.Center() );
1579 aPolyPoly.transform(basegfx::utils::createTranslateB2DHomMatrix(-aCenter.X(), -aCenter.Y()));
1581 SdrPage* pPage = pObj->getSdrPageFromSdrObject();
1582 if( pPage )
1584 const Size aPageSize( pPage->GetSize() );
1585 aPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(
1586 1.0 / static_cast<double>(aPageSize.Width()), 1.0 / static_cast<double>(aPageSize.Height())));
1590 setPath( ::basegfx::utils::exportToSvgD( aPolyPoly, true, true, true) );
1593 EffectSequenceHelper::EffectSequenceHelper()
1594 : mnSequenceType( EffectNodeType::DEFAULT )
1598 EffectSequenceHelper::EffectSequenceHelper( const css::uno::Reference< css::animations::XTimeContainer >& xSequenceRoot )
1599 : mxSequenceRoot( xSequenceRoot ), mnSequenceType( EffectNodeType::DEFAULT )
1601 Reference< XAnimationNode > xNode( mxSequenceRoot, UNO_QUERY_THROW );
1602 create( xNode );
1605 EffectSequenceHelper::~EffectSequenceHelper()
1607 reset();
1610 void EffectSequenceHelper::reset()
1612 for( CustomAnimationEffectPtr& pEffect : maEffects )
1614 pEffect->setEffectSequence(nullptr);
1616 maEffects.clear();
1619 Reference< XAnimationNode > EffectSequenceHelper::getRootNode()
1621 return mxSequenceRoot;
1624 void EffectSequenceHelper::append( const CustomAnimationEffectPtr& pEffect )
1626 pEffect->setEffectSequence( this );
1627 maEffects.push_back(pEffect);
1628 rebuild();
1631 CustomAnimationEffectPtr EffectSequenceHelper::append( const CustomAnimationPresetPtr& pPreset, const Any& rTarget, double fDuration /* = -1.0 */ )
1633 CustomAnimationEffectPtr pEffect;
1635 if( pPreset.get() )
1637 Reference< XAnimationNode > xNode( pPreset->create( "" ) );
1638 if( xNode.is() )
1640 // first, filter all only ui relevant user data
1641 std::vector< NamedValue > aNewUserData;
1642 Sequence< NamedValue > aUserData( xNode->getUserData() );
1644 std::copy_if(aUserData.begin(), aUserData.end(), std::back_inserter(aNewUserData),
1645 [](const NamedValue& rProp) { return rProp.Name != "text-only" && rProp.Name != "preset-property"; });
1647 if( !aNewUserData.empty() )
1649 aUserData = ::comphelper::containerToSequence( aNewUserData );
1650 xNode->setUserData( aUserData );
1653 // check target, maybe we need to force it to text
1654 sal_Int16 nSubItem = ShapeAnimationSubType::AS_WHOLE;
1656 if( rTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
1658 nSubItem = ShapeAnimationSubType::ONLY_TEXT;
1660 else if( pPreset->isTextOnly() )
1662 Reference< XShape > xShape;
1663 rTarget >>= xShape;
1664 if( xShape.is() )
1666 // that's bad, we target a shape here but the effect is only for text
1667 // so change subitem
1668 nSubItem = ShapeAnimationSubType::ONLY_TEXT;
1672 // now create effect from preset
1673 pEffect.reset( new CustomAnimationEffect( xNode ) );
1674 pEffect->setEffectSequence( this );
1675 pEffect->setTarget( rTarget );
1676 pEffect->setTargetSubItem( nSubItem );
1677 if( fDuration != -1.0 )
1678 pEffect->setDuration( fDuration );
1680 maEffects.push_back(pEffect);
1682 rebuild();
1686 DBG_ASSERT( pEffect.get(), "sd::EffectSequenceHelper::append(), failed!" );
1687 return pEffect;
1690 CustomAnimationEffectPtr EffectSequenceHelper::append( const SdrPathObj& rPathObj, const Any& rTarget, double fDuration /* = -1.0 */ )
1692 CustomAnimationEffectPtr pEffect;
1694 if( fDuration <= 0.0 )
1695 fDuration = 2.0;
1699 Reference< XTimeContainer > xEffectContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
1700 Reference< XAnimationNode > xAnimateMotion( AnimateMotion::create( ::comphelper::getProcessComponentContext() ) );
1702 xAnimateMotion->setDuration( Any( fDuration ) );
1703 xAnimateMotion->setFill( AnimationFill::HOLD );
1704 xEffectContainer->appendChild( xAnimateMotion );
1706 sal_Int16 nSubItem = ShapeAnimationSubType::AS_WHOLE;
1708 if( rTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
1709 nSubItem = ShapeAnimationSubType::ONLY_TEXT;
1711 pEffect.reset( new CustomAnimationEffect( xEffectContainer ) );
1712 pEffect->setEffectSequence( this );
1713 pEffect->setTarget( rTarget );
1714 pEffect->setTargetSubItem( nSubItem );
1715 pEffect->setNodeType( css::presentation::EffectNodeType::ON_CLICK );
1716 pEffect->setPresetClass( css::presentation::EffectPresetClass::MOTIONPATH );
1717 pEffect->setAcceleration( 0.5 );
1718 pEffect->setDecelerate( 0.5 );
1719 pEffect->setFill( AnimationFill::HOLD );
1720 pEffect->setBegin( 0.0 );
1721 pEffect->updatePathFromSdrPathObj( rPathObj );
1722 if( fDuration != -1.0 )
1723 pEffect->setDuration( fDuration );
1725 maEffects.push_back(pEffect);
1727 rebuild();
1729 catch( Exception& )
1731 OSL_FAIL( "sd::EffectSequenceHelper::append(), exception caught!" );
1734 return pEffect;
1737 void EffectSequenceHelper::replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pPreset, const OUString& rPresetSubType, double fDuration /* = -1.0 */ )
1739 if( !(pEffect.get() && pPreset.get()) )
1740 return;
1744 Reference< XAnimationNode > xNewNode( pPreset->create( rPresetSubType ) );
1745 if( xNewNode.is() )
1747 pEffect->replaceNode( xNewNode );
1748 if( fDuration != -1.0 )
1749 pEffect->setDuration( fDuration );
1752 rebuild();
1754 catch( Exception& )
1756 OSL_FAIL( "sd::EffectSequenceHelper::replace(), exception caught!" );
1760 void EffectSequenceHelper::replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pPreset, double fDuration /* = -1.0 */ )
1762 replace( pEffect, pPreset, "", fDuration );
1765 void EffectSequenceHelper::remove( const CustomAnimationEffectPtr& pEffect )
1767 if( pEffect.get() )
1769 pEffect->setEffectSequence( nullptr );
1770 maEffects.remove( pEffect );
1773 rebuild();
1776 void EffectSequenceHelper::moveToBeforeEffect( const CustomAnimationEffectPtr& pEffect, const CustomAnimationEffectPtr& pInsertBefore)
1778 if ( pEffect.get() )
1780 maEffects.remove( pEffect );
1781 EffectSequence::iterator aInsertIter( find( pInsertBefore ) );
1783 // aInsertIter being end() is OK: pInsertBefore could be null, so put at end.
1784 maEffects.insert( aInsertIter, pEffect );
1786 rebuild();
1790 void EffectSequenceHelper::rebuild()
1792 implRebuild();
1795 void EffectSequenceHelper::implRebuild()
1799 // first we delete all time containers on the first two levels
1800 Reference< XEnumerationAccess > xEnumerationAccess( mxSequenceRoot, UNO_QUERY_THROW );
1801 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
1802 while( xEnumeration->hasMoreElements() )
1804 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
1805 Reference< XTimeContainer > xChildContainer( xChildNode, UNO_QUERY_THROW );
1807 Reference< XEnumerationAccess > xChildEnumerationAccess( xChildNode, UNO_QUERY_THROW );
1808 Reference< XEnumeration > xChildEnumeration( xChildEnumerationAccess->createEnumeration(), UNO_SET_THROW );
1809 while( xChildEnumeration->hasMoreElements() )
1811 Reference< XAnimationNode > xNode( xChildEnumeration->nextElement(), UNO_QUERY_THROW );
1812 xChildContainer->removeChild( xNode );
1815 mxSequenceRoot->removeChild( xChildNode );
1818 // second, rebuild main sequence
1819 EffectSequence::iterator aIter( maEffects.begin() );
1820 EffectSequence::iterator aEnd( maEffects.end() );
1821 if( aIter != aEnd )
1823 std::vector< sd::AfterEffectNode > aAfterEffects;
1825 CustomAnimationEffectPtr pEffect = *aIter++;
1827 bool bFirst = true;
1830 // create a par container for the next click node and all following with and after effects
1831 Reference< XTimeContainer > xOnClickContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
1833 Event aEvent;
1834 if( mxEventSource.is() )
1836 aEvent.Source <<= mxEventSource;
1837 aEvent.Trigger = EventTrigger::ON_CLICK;
1839 else
1841 aEvent.Trigger = EventTrigger::ON_NEXT;
1843 aEvent.Repeat = 0;
1845 Any aBegin( makeAny( aEvent ) );
1846 if( bFirst )
1848 // if the first node is not a click action, this click container
1849 // must not have INDEFINITE begin but start at 0s
1850 bFirst = false;
1851 if( pEffect->getNodeType() != EffectNodeType::ON_CLICK )
1852 aBegin <<= 0.0;
1855 xOnClickContainer->setBegin( aBegin );
1857 mxSequenceRoot->appendChild( xOnClickContainer );
1859 double fBegin = 0.0;
1863 // create a par container for the current click or after effect node and all following with effects
1864 Reference< XTimeContainer > xWithContainer( ParallelTimeContainer::create( ::comphelper::getProcessComponentContext() ), UNO_QUERY_THROW );
1865 xWithContainer->setBegin( makeAny( fBegin ) );
1866 xOnClickContainer->appendChild( xWithContainer );
1868 double fDuration = 0.0;
1871 Reference< XAnimationNode > xEffectNode( pEffect->getNode() );
1872 xWithContainer->appendChild( xEffectNode );
1874 if( pEffect->hasAfterEffect() )
1876 Reference< XAnimationNode > xAfterEffect( pEffect->createAfterEffectNode() );
1877 AfterEffectNode a( xAfterEffect, xEffectNode, pEffect->IsAfterEffectOnNext() );
1878 aAfterEffects.push_back( a );
1881 double fTemp = pEffect->getBegin() + pEffect->getAbsoluteDuration();
1882 if( fTemp > fDuration )
1883 fDuration = fTemp;
1885 if( aIter != aEnd )
1886 pEffect = *aIter++;
1887 else
1888 pEffect.reset();
1890 while( pEffect.get() && (pEffect->getNodeType() == EffectNodeType::WITH_PREVIOUS) );
1892 fBegin += fDuration;
1894 while( pEffect.get() && (pEffect->getNodeType() != EffectNodeType::ON_CLICK) );
1896 while( pEffect.get() );
1898 // process after effect nodes
1899 std::for_each( aAfterEffects.begin(), aAfterEffects.end(), stl_process_after_effect_node_func );
1901 updateTextGroups();
1903 // reset duration, might have been altered (see below)
1904 mxSequenceRoot->setDuration( Any() );
1906 else
1908 // empty sequence, set duration to 0.0 explicitly
1909 // (otherwise, this sequence will never end)
1910 mxSequenceRoot->setDuration( makeAny(0.0) );
1913 catch( Exception& )
1915 OSL_FAIL( "sd::EffectSequenceHelper::rebuild(), exception caught!" );
1919 stl_CustomAnimationEffect_search_node_predict::stl_CustomAnimationEffect_search_node_predict( const css::uno::Reference< css::animations::XAnimationNode >& xSearchNode )
1920 : mxSearchNode( xSearchNode )
1924 bool stl_CustomAnimationEffect_search_node_predict::operator()( const CustomAnimationEffectPtr& pEffect ) const
1926 return pEffect->getNode() == mxSearchNode;
1929 /// @throws Exception
1930 static bool implFindNextContainer( Reference< XTimeContainer > const & xParent, Reference< XTimeContainer > const & xCurrent, Reference< XTimeContainer >& xNext )
1932 Reference< XEnumerationAccess > xEnumerationAccess( xParent, UNO_QUERY_THROW );
1933 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration() );
1934 if( xEnumeration.is() )
1936 Reference< XInterface > x;
1937 while( xEnumeration->hasMoreElements() && !xNext.is() )
1939 if( (xEnumeration->nextElement() >>= x) && (x == xCurrent) )
1941 if( xEnumeration->hasMoreElements() )
1942 xEnumeration->nextElement() >>= xNext;
1946 return xNext.is();
1949 void stl_process_after_effect_node_func(AfterEffectNode const & rNode)
1953 if( rNode.mxNode.is() && rNode.mxMaster.is() )
1955 // set master node
1956 Reference< XAnimationNode > xMasterNode( rNode.mxMaster, UNO_SET_THROW );
1957 Sequence< NamedValue > aUserData( rNode.mxNode->getUserData() );
1958 sal_Int32 nSize = aUserData.getLength();
1959 aUserData.realloc(nSize+1);
1960 aUserData[nSize].Name = "master-element";
1961 aUserData[nSize].Value <<= xMasterNode;
1962 rNode.mxNode->setUserData( aUserData );
1964 // insert after effect node into timeline
1965 Reference< XTimeContainer > xContainer( rNode.mxMaster->getParent(), UNO_QUERY_THROW );
1967 if( !rNode.mbOnNextEffect ) // sameClick
1969 // insert the aftereffect after its effect is animated
1970 xContainer->insertAfter( rNode.mxNode, rNode.mxMaster );
1972 else // nextClick
1974 Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
1975 // insert the aftereffect in the next group
1977 Reference< XTimeContainer > xClickContainer( xContainer->getParent(), UNO_QUERY_THROW );
1978 Reference< XTimeContainer > xSequenceContainer( xClickContainer->getParent(), UNO_QUERY_THROW );
1980 Reference< XTimeContainer > xNextContainer;
1982 // first try if we have an after effect container
1983 if( !implFindNextContainer( xClickContainer, xContainer, xNextContainer ) )
1985 Reference< XTimeContainer > xNextClickContainer;
1986 // if not, try to find the next click effect container
1987 if( implFindNextContainer( xSequenceContainer, xClickContainer, xNextClickContainer ) )
1989 Reference< XEnumerationAccess > xEnumerationAccess( xNextClickContainer, UNO_QUERY_THROW );
1990 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
1991 if( xEnumeration->hasMoreElements() )
1993 // the next container is the first child container
1994 xEnumeration->nextElement() >>= xNextContainer;
1996 else
1998 // this does not yet have a child container, create one
1999 xNextContainer.set( ParallelTimeContainer::create(xContext), UNO_QUERY_THROW );
2001 xNextContainer->setBegin( makeAny( 0.0 ) );
2002 xNextClickContainer->appendChild( xNextContainer );
2004 DBG_ASSERT( xNextContainer.is(), "ppt::stl_process_after_effect_node_func::operator(), could not find/create container!" );
2008 // if we don't have a next container, we add one to the sequence container
2009 if( !xNextContainer.is() )
2011 Reference< XTimeContainer > xNewClickContainer( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW );
2013 Event aEvent;
2014 aEvent.Trigger = EventTrigger::ON_NEXT;
2015 aEvent.Repeat = 0;
2016 xNewClickContainer->setBegin( makeAny( aEvent ) );
2018 xSequenceContainer->insertAfter( xNewClickContainer, xClickContainer );
2020 xNextContainer.set( ParallelTimeContainer::create( xContext ), UNO_QUERY_THROW );
2022 xNextContainer->setBegin( makeAny( 0.0 ) );
2023 xNewClickContainer->appendChild( xNextContainer );
2026 if( xNextContainer.is() )
2028 // find begin time of first element
2029 Reference< XEnumerationAccess > xEnumerationAccess( xNextContainer, UNO_QUERY_THROW );
2030 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
2031 if( xEnumeration->hasMoreElements() )
2033 Reference< XAnimationNode > xChild;
2034 // the next container is the first child container
2035 xEnumeration->nextElement() >>= xChild;
2036 if( xChild.is() )
2038 Any aBegin( xChild->getBegin() );
2039 double fBegin = 0.0;
2040 if( (aBegin >>= fBegin) && (fBegin >= 0.0))
2041 rNode.mxNode->setBegin( aBegin );
2045 xNextContainer->appendChild( rNode.mxNode );
2050 catch( Exception& )
2052 OSL_FAIL( "ppt::stl_process_after_effect_node_func::operator(), exception caught!" );
2056 EffectSequence::iterator EffectSequenceHelper::find( const CustomAnimationEffectPtr& pEffect )
2058 return std::find( maEffects.begin(), maEffects.end(), pEffect );
2061 CustomAnimationEffectPtr EffectSequenceHelper::findEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) const
2063 CustomAnimationEffectPtr pEffect;
2065 EffectSequence::const_iterator aIter = std::find_if(maEffects.begin(), maEffects.end(),
2066 [&xNode](const CustomAnimationEffectPtr& rxEffect) { return rxEffect->getNode() == xNode; });
2067 if (aIter != maEffects.end())
2068 pEffect = *aIter;
2070 return pEffect;
2073 sal_Int32 EffectSequenceHelper::getOffsetFromEffect( const CustomAnimationEffectPtr& xEffect ) const
2075 auto aIter = std::find(maEffects.begin(), maEffects.end(), xEffect);
2076 if (aIter != maEffects.end())
2077 return static_cast<sal_Int32>(std::distance(maEffects.begin(), aIter));
2079 return -1;
2082 CustomAnimationEffectPtr EffectSequenceHelper::getEffectFromOffset( sal_Int32 nOffset ) const
2084 EffectSequence::const_iterator aIter( maEffects.begin() );
2085 nOffset = std::min(nOffset, static_cast<sal_Int32>(maEffects.size()));
2086 std::advance(aIter, nOffset);
2088 CustomAnimationEffectPtr pEffect;
2089 if( aIter != maEffects.end() )
2090 pEffect = *aIter;
2092 return pEffect;
2095 bool EffectSequenceHelper::disposeShape( const Reference< XShape >& xShape )
2097 bool bChanges = false;
2099 EffectSequence::iterator aIter( maEffects.begin() );
2100 while( aIter != maEffects.end() )
2102 if( (*aIter)->getTargetShape() == xShape )
2104 (*aIter)->setEffectSequence( nullptr );
2105 bChanges = true;
2106 aIter = maEffects.erase( aIter );
2108 else
2110 ++aIter;
2114 return bChanges;
2117 bool EffectSequenceHelper::hasEffect( const css::uno::Reference< css::drawing::XShape >& xShape )
2119 return std::any_of(maEffects.begin(), maEffects.end(),
2120 [&xShape](const CustomAnimationEffectPtr& rxEffect) { return rxEffect->getTargetShape() == xShape; });
2123 void EffectSequenceHelper::insertTextRange( const css::uno::Any& aTarget )
2125 ParagraphTarget aParaTarget;
2126 if( !(aTarget >>= aParaTarget ) )
2127 return;
2129 bool bChanges = std::accumulate(maEffects.begin(), maEffects.end(), false,
2130 [&aParaTarget](const bool bCheck, const CustomAnimationEffectPtr& rxEffect) {
2131 bool bRes = bCheck;
2132 if (rxEffect->getTargetShape() == aParaTarget.Shape)
2133 bRes |= rxEffect->checkForText();
2134 return bRes;
2137 if( bChanges )
2138 rebuild();
2141 static bool isParagraphTargetTextEmpty( ParagraphTarget aParaTarget )
2143 // get paragraph
2144 Reference< XText > xText ( aParaTarget.Shape, UNO_QUERY );
2145 if( xText.is() )
2147 Reference< XEnumerationAccess > xEA( xText, UNO_QUERY );
2148 if( xEA.is() )
2150 Reference< XEnumeration > xEnumeration = xEA->createEnumeration();
2151 if( xEnumeration.is() )
2153 // advance to the Nth paragraph
2154 sal_Int32 nPara = aParaTarget.Paragraph;
2155 while( xEnumeration->hasMoreElements() && nPara-- )
2156 xEnumeration->nextElement();
2158 // get Nth paragraph's text and check if it's empty
2159 if( xEnumeration->hasMoreElements() )
2161 Reference< XTextRange > xRange( xEnumeration->nextElement(), UNO_QUERY );
2162 if( xRange.is() )
2164 OUString text = xRange->getString();
2165 return text.isEmpty();
2171 return false;
2174 void EffectSequenceHelper::disposeTextRange( const css::uno::Any& aTarget )
2176 ParagraphTarget aParaTarget;
2177 if( !(aTarget >>= aParaTarget ) )
2178 return;
2180 bool bChanges = false;
2182 // building list of effects for target shape; process effects not on target shape
2183 EffectSequence aTargetParagraphEffects;
2184 for( const auto &pEffect : maEffects )
2186 Any aIterTarget( pEffect->getTarget() );
2187 if( aIterTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2189 ParagraphTarget aIterParaTarget;
2190 if( (aIterTarget >>= aIterParaTarget) && (aIterParaTarget.Shape == aParaTarget.Shape) )
2192 aTargetParagraphEffects.push_back(pEffect);
2195 else if( pEffect->getTargetShape() == aParaTarget.Shape )
2197 bChanges |= pEffect->checkForText();
2201 // select effect to delete:
2202 // if paragraph before target is blank, then delete its animation effect (if any) instead
2203 ParagraphTarget aPreviousParagraph = aParaTarget;
2204 --aPreviousParagraph.Paragraph;
2205 bool bIsPreviousParagraphEmpty = isParagraphTargetTextEmpty( aPreviousParagraph );
2206 sal_Int16 anParaNumToDelete = bIsPreviousParagraphEmpty ? aPreviousParagraph.Paragraph : aParaTarget.Paragraph;
2208 // update effects
2209 for( const auto &pEffect : aTargetParagraphEffects )
2211 Any aIterTarget( pEffect->getTarget() );
2213 ParagraphTarget aIterParaTarget;
2214 aIterTarget >>= aIterParaTarget;
2216 // delete effect for target paragraph (may have effects in more than one text group)
2217 if( aIterParaTarget.Paragraph == anParaNumToDelete )
2219 auto aItr = find( pEffect );
2220 DBG_ASSERT( aItr != maEffects.end(), "sd::EffectSequenceHelper::disposeTextRange(), Expected effect missing.");
2221 if( aItr != maEffects.end() )
2223 (*aItr)->setEffectSequence( nullptr );
2224 maEffects.erase(aItr);
2225 bChanges = true;
2229 // shift all paragraphs after disposed paragraph
2230 if( aIterParaTarget.Paragraph > anParaNumToDelete )
2232 --aIterParaTarget.Paragraph;
2233 pEffect->setTarget( makeAny( aIterParaTarget ) );
2234 bChanges = true;
2238 if( bChanges )
2240 rebuild();
2244 CustomAnimationTextGroup::CustomAnimationTextGroup( const Reference< XShape >& rTarget, sal_Int32 nGroupId )
2245 : maTarget( rTarget ),
2246 mnGroupId( nGroupId )
2248 reset();
2251 void CustomAnimationTextGroup::reset()
2253 mnTextGrouping = -1;
2254 mbAnimateForm = false;
2255 mbTextReverse = false;
2256 mfGroupingAuto = -1.0;
2257 mnLastPara = -1; // used to check for TextReverse
2259 for (sal_Int8 & rn : mnDepthFlags)
2261 rn = 0;
2264 maEffects.clear();
2267 void CustomAnimationTextGroup::addEffect( CustomAnimationEffectPtr const & pEffect )
2269 maEffects.push_back( pEffect );
2271 Any aTarget( pEffect->getTarget() );
2272 if( aTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2274 // now look at the paragraph
2275 ParagraphTarget aParaTarget;
2276 aTarget >>= aParaTarget;
2278 if( mnLastPara != -1 )
2279 mbTextReverse = mnLastPara > aParaTarget.Paragraph;
2281 mnLastPara = aParaTarget.Paragraph;
2283 const sal_Int32 nParaDepth = pEffect->getParaDepth();
2285 // only look at the first PARA_LEVELS levels
2286 if( nParaDepth < PARA_LEVELS )
2288 // our first paragraph with this level?
2289 if( mnDepthFlags[nParaDepth] == 0 )
2291 // so set it to the first found
2292 mnDepthFlags[nParaDepth] = static_cast<sal_Int8>(pEffect->getNodeType());
2294 else if( mnDepthFlags[nParaDepth] != pEffect->getNodeType() )
2296 mnDepthFlags[nParaDepth] = -1;
2299 if( pEffect->getNodeType() == EffectNodeType::AFTER_PREVIOUS )
2300 mfGroupingAuto = pEffect->getBegin();
2302 mnTextGrouping = PARA_LEVELS;
2303 while( (mnTextGrouping > 0)
2304 && (mnDepthFlags[mnTextGrouping - 1] <= 0) )
2305 --mnTextGrouping;
2308 else
2310 // if we have an effect with the shape as a target, we animate the background
2311 mbAnimateForm = pEffect->getTargetSubItem() != ShapeAnimationSubType::ONLY_TEXT;
2315 CustomAnimationTextGroupPtr EffectSequenceHelper::findGroup( sal_Int32 nGroupId )
2317 CustomAnimationTextGroupPtr aPtr;
2319 CustomAnimationTextGroupMap::iterator aIter( maGroupMap.find( nGroupId ) );
2320 if( aIter != maGroupMap.end() )
2321 aPtr = (*aIter).second;
2323 return aPtr;
2326 void EffectSequenceHelper::updateTextGroups()
2328 maGroupMap.clear();
2330 // first create all the groups
2331 for( CustomAnimationEffectPtr& pEffect : maEffects )
2333 const sal_Int32 nGroupId = pEffect->getGroupId();
2335 if( nGroupId == -1 )
2336 continue; // trivial case, no group
2338 CustomAnimationTextGroupPtr pGroup = findGroup( nGroupId );
2339 if( !pGroup.get() )
2341 pGroup.reset( new CustomAnimationTextGroup( pEffect->getTargetShape(), nGroupId ) );
2342 maGroupMap[nGroupId] = pGroup;
2345 pGroup->addEffect( pEffect );
2348 // Now that all the text groups have been cleared up and rebuilt, we need to update its
2349 // text grouping. addEffect() already make mnTextGrouping the last possible level,
2350 // so just continue to find the last level that is not EffectNodeType::WITH_PREVIOUS.
2351 for(const auto &rGroupMapItem: maGroupMap)
2353 const CustomAnimationTextGroupPtr &pGroup = rGroupMapItem.second;
2354 while(pGroup->mnTextGrouping > 0 && pGroup->mnDepthFlags[pGroup->mnTextGrouping - 1] == EffectNodeType::WITH_PREVIOUS)
2355 --pGroup->mnTextGrouping;
2359 CustomAnimationTextGroupPtr
2360 EffectSequenceHelper::createTextGroup(const CustomAnimationEffectPtr& pEffect,
2361 sal_Int32 nTextGrouping, double fTextGroupingAuto,
2362 bool bAnimateForm, bool bTextReverse)
2364 // first find a free group-id
2365 sal_Int32 nGroupId = 0;
2367 CustomAnimationTextGroupMap::iterator aIter( maGroupMap.begin() );
2368 const CustomAnimationTextGroupMap::iterator aEnd( maGroupMap.end() );
2369 while( aIter != aEnd )
2371 if( (*aIter).first == nGroupId )
2373 nGroupId++;
2374 aIter = maGroupMap.begin();
2376 else
2378 ++aIter;
2382 Reference< XShape > xTarget( pEffect->getTargetShape() );
2384 CustomAnimationTextGroupPtr pTextGroup( new CustomAnimationTextGroup( xTarget, nGroupId ) );
2385 maGroupMap[nGroupId] = pTextGroup;
2387 bool bUsed = false;
2389 // do we need to target the shape?
2390 if( (nTextGrouping == 0) || bAnimateForm )
2392 sal_Int16 nSubItem;
2393 if( nTextGrouping == 0)
2394 nSubItem = bAnimateForm ? ShapeAnimationSubType::AS_WHOLE : ShapeAnimationSubType::ONLY_TEXT;
2395 else
2396 nSubItem = ShapeAnimationSubType::ONLY_BACKGROUND;
2398 pEffect->setTarget( makeAny( xTarget ) );
2399 pEffect->setTargetSubItem( nSubItem );
2400 pEffect->setEffectSequence( this );
2401 pEffect->setGroupId( nGroupId );
2403 pTextGroup->addEffect( pEffect );
2404 bUsed = true;
2407 pTextGroup->mnTextGrouping = nTextGrouping;
2408 pTextGroup->mfGroupingAuto = fTextGroupingAuto;
2409 pTextGroup->mbTextReverse = bTextReverse;
2411 // now add an effect for each paragraph
2412 createTextGroupParagraphEffects( pTextGroup, pEffect, bUsed );
2414 notify_listeners();
2416 return pTextGroup;
2419 void EffectSequenceHelper::createTextGroupParagraphEffects( const CustomAnimationTextGroupPtr& pTextGroup, const CustomAnimationEffectPtr& pEffect, bool bUsed )
2421 Reference< XShape > xTarget( pTextGroup->maTarget );
2423 sal_Int32 nTextGrouping = pTextGroup->mnTextGrouping;
2424 double fTextGroupingAuto = pTextGroup->mfGroupingAuto;
2425 bool bTextReverse = pTextGroup->mbTextReverse;
2427 // now add an effect for each paragraph
2428 if( nTextGrouping < 0 )
2429 return;
2433 EffectSequence::iterator aInsertIter( find( pEffect ) );
2435 Reference< XEnumerationAccess > xText( xTarget, UNO_QUERY_THROW );
2436 Reference< XEnumeration > xEnumeration( xText->createEnumeration(), UNO_SET_THROW );
2438 std::deque< sal_Int16 > aParaList;
2439 sal_Int16 nPara;
2441 // fill the list with all valid paragraphs
2442 for( nPara = 0; xEnumeration->hasMoreElements(); nPara++ )
2444 Reference< XTextRange > xRange( xEnumeration->nextElement(), UNO_QUERY );
2445 if( xRange.is() && !xRange->getString().isEmpty() )
2447 if( bTextReverse ) // sort them
2448 aParaList.push_front( nPara );
2449 else
2450 aParaList.push_back( nPara );
2454 ParagraphTarget aTarget;
2455 aTarget.Shape = xTarget;
2457 for( const auto i : aParaList )
2459 aTarget.Paragraph = i;
2461 CustomAnimationEffectPtr pNewEffect;
2462 if( bUsed )
2464 // clone a new effect from first effect
2465 pNewEffect = pEffect->clone();
2466 ++aInsertIter;
2467 aInsertIter = maEffects.insert( aInsertIter, pNewEffect );
2469 else
2471 // reuse first effect if it's not yet used
2472 pNewEffect = pEffect;
2473 bUsed = true;
2474 aInsertIter = find( pNewEffect );
2477 // set target and group-id
2478 pNewEffect->setTarget( makeAny( aTarget ) );
2479 pNewEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_TEXT );
2480 pNewEffect->setGroupId( pTextGroup->mnGroupId );
2481 pNewEffect->setEffectSequence( this );
2483 // set correct node type
2484 if( pNewEffect->getParaDepth() < nTextGrouping )
2486 if( fTextGroupingAuto == -1.0 )
2488 pNewEffect->setNodeType( EffectNodeType::ON_CLICK );
2489 pNewEffect->setBegin( 0.0 );
2491 else
2493 pNewEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
2494 pNewEffect->setBegin( fTextGroupingAuto );
2497 else
2499 pNewEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
2500 pNewEffect->setBegin( 0.0 );
2503 pTextGroup->addEffect( pNewEffect );
2505 notify_listeners();
2507 catch( Exception& )
2509 OSL_FAIL("sd::EffectSequenceHelper::createTextGroup(), exception caught!" );
2513 void EffectSequenceHelper::setTextGrouping( const CustomAnimationTextGroupPtr& pTextGroup, sal_Int32 nTextGrouping )
2515 if( pTextGroup->mnTextGrouping == nTextGrouping )
2517 // first case, trivial case, do nothing
2519 else if( (pTextGroup->mnTextGrouping == -1) && (nTextGrouping >= 0) )
2521 // second case, we need to add new effects for each paragraph
2523 CustomAnimationEffectPtr pEffect( pTextGroup->maEffects.front() );
2525 pTextGroup->mnTextGrouping = nTextGrouping;
2526 createTextGroupParagraphEffects( pTextGroup, pEffect, true );
2527 notify_listeners();
2529 else if( (pTextGroup->mnTextGrouping >= 0) && (nTextGrouping == -1 ) )
2531 // third case, we need to remove effects for each paragraph
2533 EffectSequence aEffects( pTextGroup->maEffects );
2534 pTextGroup->reset();
2536 for( CustomAnimationEffectPtr& pEffect : aEffects )
2538 if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2539 remove( pEffect );
2540 else
2541 pTextGroup->addEffect( pEffect );
2543 notify_listeners();
2545 else
2547 // fourth case, we need to change the node types for the text nodes
2548 double fTextGroupingAuto = pTextGroup->mfGroupingAuto;
2550 EffectSequence aEffects( pTextGroup->maEffects );
2551 pTextGroup->reset();
2553 for( CustomAnimationEffectPtr& pEffect : aEffects )
2555 if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2557 // set correct node type
2558 if( pEffect->getParaDepth() < nTextGrouping )
2560 if( fTextGroupingAuto == -1.0 )
2562 pEffect->setNodeType( EffectNodeType::ON_CLICK );
2563 pEffect->setBegin( 0.0 );
2565 else
2567 pEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
2568 pEffect->setBegin( fTextGroupingAuto );
2571 else
2573 pEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
2574 pEffect->setBegin( 0.0 );
2578 pTextGroup->addEffect( pEffect );
2581 notify_listeners();
2585 void EffectSequenceHelper::setAnimateForm( const CustomAnimationTextGroupPtr& pTextGroup, bool bAnimateForm )
2587 if( pTextGroup->mbAnimateForm == bAnimateForm )
2589 // trivial case, do nothing
2591 else
2593 EffectSequence aEffects( pTextGroup->maEffects );
2594 pTextGroup->reset();
2596 SAL_WARN_IF(aEffects.empty(), "sd", "EffectSequenceHelper::setAnimateForm effects empty" );
2598 if (aEffects.empty())
2599 return;
2601 EffectSequence::iterator aIter( aEffects.begin() );
2602 const EffectSequence::iterator aEnd( aEffects.end() );
2604 // first insert if we have to
2605 if( bAnimateForm )
2607 EffectSequence::iterator aInsertIter( find( *aIter ) );
2609 CustomAnimationEffectPtr pEffect;
2610 if( (aEffects.size() == 1) && ((*aIter)->getTarget().getValueType() != ::cppu::UnoType<ParagraphTarget>::get() ) )
2612 // special case, only one effect and that targets whole text,
2613 // convert this to target whole shape
2614 pEffect = *aIter++;
2615 pEffect->setTargetSubItem( ShapeAnimationSubType::AS_WHOLE );
2617 else
2619 pEffect = (*aIter)->clone();
2620 pEffect->setTarget( makeAny( (*aIter)->getTargetShape() ) );
2621 pEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_BACKGROUND );
2622 maEffects.insert( aInsertIter, pEffect );
2625 pTextGroup->addEffect( pEffect );
2628 if( !bAnimateForm && (aEffects.size() == 1) )
2630 CustomAnimationEffectPtr pEffect( *aIter );
2631 pEffect->setTarget( makeAny( (*aIter)->getTargetShape() ) );
2632 pEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_TEXT );
2633 pTextGroup->addEffect( pEffect );
2635 else
2637 // read the rest to the group again
2638 while( aIter != aEnd )
2640 CustomAnimationEffectPtr pEffect( *aIter++ );
2642 if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2644 pTextGroup->addEffect( pEffect );
2646 else
2648 DBG_ASSERT( !bAnimateForm, "sd::EffectSequenceHelper::setAnimateForm(), something is wrong here!" );
2649 remove( pEffect );
2653 notify_listeners();
2657 void EffectSequenceHelper::setTextGroupingAuto( const CustomAnimationTextGroupPtr& pTextGroup, double fTextGroupingAuto )
2659 sal_Int32 nTextGrouping = pTextGroup->mnTextGrouping;
2661 EffectSequence aEffects( pTextGroup->maEffects );
2662 pTextGroup->reset();
2664 for( CustomAnimationEffectPtr& pEffect : aEffects )
2666 if( pEffect->getTarget().getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2668 // set correct node type
2669 if( pEffect->getParaDepth() < nTextGrouping )
2671 if( fTextGroupingAuto == -1.0 )
2673 pEffect->setNodeType( EffectNodeType::ON_CLICK );
2674 pEffect->setBegin( 0.0 );
2676 else
2678 pEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
2679 pEffect->setBegin( fTextGroupingAuto );
2682 else
2684 pEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
2685 pEffect->setBegin( 0.0 );
2689 pTextGroup->addEffect( pEffect );
2692 notify_listeners();
2695 struct ImplStlTextGroupSortHelper
2697 explicit ImplStlTextGroupSortHelper( bool bReverse ) : mbReverse( bReverse ) {};
2698 bool operator()( const CustomAnimationEffectPtr& p1, const CustomAnimationEffectPtr& p2 );
2699 bool const mbReverse;
2700 sal_Int32 getTargetParagraph( const CustomAnimationEffectPtr& p1 );
2703 sal_Int32 ImplStlTextGroupSortHelper::getTargetParagraph( const CustomAnimationEffectPtr& p1 )
2705 const Any aTarget(p1->getTarget());
2706 if( aTarget.hasValue() && aTarget.getValueType() == ::cppu::UnoType<ParagraphTarget>::get() )
2708 ParagraphTarget aParaTarget;
2709 aTarget >>= aParaTarget;
2710 return aParaTarget.Paragraph;
2712 else
2714 return mbReverse ? 0x7fffffff : -1;
2718 bool ImplStlTextGroupSortHelper::operator()( const CustomAnimationEffectPtr& p1, const CustomAnimationEffectPtr& p2 )
2720 if( mbReverse )
2722 return getTargetParagraph( p2 ) < getTargetParagraph( p1 );
2724 else
2726 return getTargetParagraph( p1 ) < getTargetParagraph( p2 );
2730 void EffectSequenceHelper::setTextReverse( const CustomAnimationTextGroupPtr& pTextGroup, bool bTextReverse )
2732 if( pTextGroup->mbTextReverse == bTextReverse )
2734 // do nothing
2736 else
2738 std::vector< CustomAnimationEffectPtr > aSortedVector(pTextGroup->maEffects.size());
2739 std::copy( pTextGroup->maEffects.begin(), pTextGroup->maEffects.end(), aSortedVector.begin() );
2740 ImplStlTextGroupSortHelper aSortHelper( bTextReverse );
2741 std::sort( aSortedVector.begin(), aSortedVector.end(), aSortHelper );
2743 pTextGroup->reset();
2745 std::vector< CustomAnimationEffectPtr >::iterator aIter( aSortedVector.begin() );
2746 const std::vector< CustomAnimationEffectPtr >::iterator aEnd( aSortedVector.end() );
2748 if( aIter != aEnd )
2750 pTextGroup->addEffect( *aIter );
2751 EffectSequence::iterator aInsertIter( find( *aIter++ ) );
2752 while( aIter != aEnd )
2754 CustomAnimationEffectPtr pEffect( *aIter++ );
2755 maEffects.erase( find( pEffect ) );
2756 aInsertIter = maEffects.insert( ++aInsertIter, pEffect );
2757 pTextGroup->addEffect( pEffect );
2760 notify_listeners();
2764 void EffectSequenceHelper::addListener( ISequenceListener* pListener )
2766 if( std::find( maListeners.begin(), maListeners.end(), pListener ) == maListeners.end() )
2767 maListeners.push_back( pListener );
2770 void EffectSequenceHelper::removeListener( ISequenceListener* pListener )
2772 maListeners.remove( pListener );
2775 struct stl_notify_listeners_func
2777 stl_notify_listeners_func() {}
2778 void operator()(ISequenceListener* pListener) { pListener->notify_change(); }
2781 void EffectSequenceHelper::notify_listeners()
2783 stl_notify_listeners_func aFunc;
2784 std::for_each( maListeners.begin(), maListeners.end(), aFunc );
2787 void EffectSequenceHelper::create( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
2789 DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::create(), illegal argument" );
2791 if( !xNode.is() )
2792 return;
2796 Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
2797 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
2798 while( xEnumeration->hasMoreElements() )
2800 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
2801 createEffectsequence( xChildNode );
2804 catch( Exception& )
2806 OSL_FAIL( "sd::EffectSequenceHelper::create(), exception caught!" );
2810 void EffectSequenceHelper::createEffectsequence( const Reference< XAnimationNode >& xNode )
2812 DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffectsequence(), illegal argument" );
2814 if( !xNode.is() )
2815 return;
2819 Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
2820 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
2821 while( xEnumeration->hasMoreElements() )
2823 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
2825 createEffects( xChildNode );
2828 catch( Exception& )
2830 OSL_FAIL( "sd::EffectSequenceHelper::createEffectsequence(), exception caught!" );
2834 void EffectSequenceHelper::createEffects( const Reference< XAnimationNode >& xNode )
2836 DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffects(), illegal argument" );
2838 if( !xNode.is() )
2839 return;
2843 Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
2844 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
2845 while( xEnumeration->hasMoreElements() )
2847 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
2849 switch( xChildNode->getType() )
2851 // found an effect
2852 case AnimationNodeType::PAR:
2853 case AnimationNodeType::ITERATE:
2855 CustomAnimationEffectPtr pEffect( new CustomAnimationEffect( xChildNode ) );
2857 if( pEffect->mnNodeType != -1 )
2859 pEffect->setEffectSequence( this );
2860 maEffects.push_back(pEffect);
2863 break;
2865 // found an after effect
2866 case AnimationNodeType::SET:
2867 case AnimationNodeType::ANIMATECOLOR:
2869 processAfterEffect( xChildNode );
2871 break;
2875 catch( Exception& )
2877 OSL_FAIL( "sd::EffectSequenceHelper::createEffects(), exception caught!" );
2881 void EffectSequenceHelper::processAfterEffect( const Reference< XAnimationNode >& xNode )
2885 Reference< XAnimationNode > xMaster;
2887 Sequence< NamedValue > aUserData( xNode->getUserData() );
2888 const NamedValue* pProp = std::find_if(aUserData.begin(), aUserData.end(),
2889 [](const NamedValue& rProp) { return rProp.Name == "master-element"; });
2891 if (pProp != aUserData.end())
2892 pProp->Value >>= xMaster;
2894 // only process if this is a valid after effect
2895 if( xMaster.is() )
2897 CustomAnimationEffectPtr pMasterEffect;
2899 // find the master effect
2900 stl_CustomAnimationEffect_search_node_predict aSearchPredict( xMaster );
2901 EffectSequence::iterator aIter( std::find_if( maEffects.begin(), maEffects.end(), aSearchPredict ) );
2902 if( aIter != maEffects.end() )
2903 pMasterEffect = *aIter;
2905 if( pMasterEffect.get() )
2907 pMasterEffect->setHasAfterEffect( true );
2909 // find out what kind of after effect this is
2910 if( xNode->getType() == AnimationNodeType::ANIMATECOLOR )
2912 // it's a dim
2913 Reference< XAnimate > xAnimate( xNode, UNO_QUERY_THROW );
2914 pMasterEffect->setDimColor( xAnimate->getTo() );
2915 pMasterEffect->setAfterEffectOnNext( true );
2917 else
2919 // it's a hide
2920 pMasterEffect->setAfterEffectOnNext( xNode->getParent() != xMaster->getParent() );
2925 catch( Exception& )
2927 OSL_FAIL( "sd::EffectSequenceHelper::processAfterEffect(), exception caught!" );
2931 class AnimationChangeListener : public cppu::WeakImplHelper< XChangesListener >
2933 public:
2934 explicit AnimationChangeListener( MainSequence* pMainSequence ) : mpMainSequence( pMainSequence ) {}
2936 virtual void SAL_CALL changesOccurred( const css::util::ChangesEvent& Event ) override;
2937 virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
2938 private:
2939 MainSequence* mpMainSequence;
2942 void SAL_CALL AnimationChangeListener::changesOccurred( const css::util::ChangesEvent& )
2944 if( mpMainSequence )
2945 mpMainSequence->startRecreateTimer();
2948 void SAL_CALL AnimationChangeListener::disposing( const css::lang::EventObject& )
2952 MainSequence::MainSequence()
2953 : mxTimingRootNode(SequenceTimeContainer::create(::comphelper::getProcessComponentContext()))
2954 , mbTimerMode(false)
2955 , mbRebuilding( false )
2956 , mnRebuildLockGuard( 0 )
2957 , mbPendingRebuildRequest( false )
2958 , mbIgnoreChanges( 0 )
2960 if( mxTimingRootNode.is() )
2962 Sequence< css::beans::NamedValue > aUserData
2963 { { "node-type", css::uno::makeAny(css::presentation::EffectNodeType::MAIN_SEQUENCE) } };
2964 mxTimingRootNode->setUserData( aUserData );
2966 init();
2969 MainSequence::MainSequence( const css::uno::Reference< css::animations::XAnimationNode >& xNode )
2970 : mxTimingRootNode( xNode, UNO_QUERY )
2971 , mbTimerMode( false )
2972 , mbRebuilding( false )
2973 , mnRebuildLockGuard( 0 )
2974 , mbPendingRebuildRequest( false )
2975 , mbIgnoreChanges( 0 )
2977 init();
2980 MainSequence::~MainSequence()
2982 reset();
2985 void MainSequence::init()
2987 mnSequenceType = EffectNodeType::MAIN_SEQUENCE;
2989 maTimer.SetInvokeHandler( LINK(this, MainSequence, onTimerHdl) );
2990 maTimer.SetTimeout(50);
2992 mxChangesListener.set( new AnimationChangeListener( this ) );
2994 createMainSequence();
2997 void MainSequence::reset( const css::uno::Reference< css::animations::XAnimationNode >& xTimingRootNode )
2999 reset();
3001 mxTimingRootNode.set( xTimingRootNode, UNO_QUERY );
3003 createMainSequence();
3006 Reference< css::animations::XAnimationNode > MainSequence::getRootNode()
3008 DBG_ASSERT( mnRebuildLockGuard == 0, "MainSequence::getRootNode(), rebuild is locked, is this really what you want?" );
3010 if( maTimer.IsActive() && mbTimerMode )
3012 // force a rebuild NOW if one is pending
3013 maTimer.Stop();
3014 implRebuild();
3017 return EffectSequenceHelper::getRootNode();
3020 void MainSequence::createMainSequence()
3022 if( mxTimingRootNode.is() ) try
3024 Reference< XEnumerationAccess > xEnumerationAccess( mxTimingRootNode, UNO_QUERY_THROW );
3025 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
3026 while( xEnumeration->hasMoreElements() )
3028 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
3029 sal_Int32 nNodeType = CustomAnimationEffect::get_node_type( xChildNode );
3030 if( nNodeType == EffectNodeType::MAIN_SEQUENCE )
3032 mxSequenceRoot.set( xChildNode, UNO_QUERY );
3033 EffectSequenceHelper::create( xChildNode );
3035 else if( nNodeType == EffectNodeType::INTERACTIVE_SEQUENCE )
3037 Reference< XTimeContainer > xInteractiveRoot( xChildNode, UNO_QUERY_THROW );
3038 InteractiveSequencePtr pIS( new InteractiveSequence( xInteractiveRoot, this ) );
3039 pIS->addListener( this );
3040 maInteractiveSequenceVector.push_back( pIS );
3044 // see if we have a mainsequence at all. if not, create one...
3045 if( !mxSequenceRoot.is() )
3047 mxSequenceRoot = SequenceTimeContainer::create( ::comphelper::getProcessComponentContext() );
3049 uno::Sequence< css::beans::NamedValue > aUserData
3050 { { "node-type", css::uno::makeAny(css::presentation::EffectNodeType::MAIN_SEQUENCE) } };
3051 mxSequenceRoot->setUserData( aUserData );
3053 // empty sequence until now, set duration to 0.0
3054 // explicitly (otherwise, this sequence will never
3055 // end)
3056 mxSequenceRoot->setDuration( makeAny(0.0) );
3058 Reference< XAnimationNode > xMainSequenceNode( mxSequenceRoot, UNO_QUERY_THROW );
3059 mxTimingRootNode->appendChild( xMainSequenceNode );
3062 updateTextGroups();
3064 notify_listeners();
3066 Reference< XChangesNotifier > xNotifier( mxTimingRootNode, UNO_QUERY );
3067 if( xNotifier.is() )
3068 xNotifier->addChangesListener( mxChangesListener );
3070 catch( Exception& )
3072 OSL_FAIL( "sd::MainSequence::create(), exception caught!" );
3073 return;
3076 DBG_ASSERT( mxSequenceRoot.is(), "sd::MainSequence::create(), found no main sequence!" );
3079 void MainSequence::reset()
3081 EffectSequenceHelper::reset();
3083 for (auto const& interactiveSequence : maInteractiveSequenceVector)
3084 interactiveSequence->reset();
3085 maInteractiveSequenceVector.clear();
3089 Reference< XChangesNotifier > xNotifier( mxTimingRootNode, UNO_QUERY );
3090 if( xNotifier.is() )
3091 xNotifier->removeChangesListener( mxChangesListener );
3093 catch( Exception& )
3099 InteractiveSequencePtr MainSequence::createInteractiveSequence( const css::uno::Reference< css::drawing::XShape >& xShape )
3101 InteractiveSequencePtr pIS;
3103 // create a new interactive sequence container
3104 Reference< XTimeContainer > xISRoot = SequenceTimeContainer::create( ::comphelper::getProcessComponentContext() );
3106 uno::Sequence< css::beans::NamedValue > aUserData
3107 { { "node-type", css::uno::makeAny(css::presentation::EffectNodeType::INTERACTIVE_SEQUENCE) } };
3108 xISRoot->setUserData( aUserData );
3109 xISRoot->setRestart( css::animations::AnimationRestart::WHEN_NOT_ACTIVE );
3111 Reference< XChild > xChild( mxSequenceRoot, UNO_QUERY_THROW );
3112 Reference< XTimeContainer > xParent( xChild->getParent(), UNO_QUERY_THROW );
3113 xParent->appendChild( xISRoot );
3115 pIS.reset( new InteractiveSequence( xISRoot, this) );
3116 pIS->setTriggerShape( xShape );
3117 pIS->addListener( this );
3118 maInteractiveSequenceVector.push_back( pIS );
3119 return pIS;
3122 CustomAnimationEffectPtr MainSequence::findEffect( const css::uno::Reference< css::animations::XAnimationNode >& xNode ) const
3124 CustomAnimationEffectPtr pEffect = EffectSequenceHelper::findEffect( xNode );
3126 if( pEffect.get() == nullptr )
3128 for (auto const& interactiveSequence : maInteractiveSequenceVector)
3130 pEffect = interactiveSequence->findEffect( xNode );
3131 if (pEffect.get())
3132 break;
3135 return pEffect;
3138 sal_Int32 MainSequence::getOffsetFromEffect( const CustomAnimationEffectPtr& pEffect ) const
3140 sal_Int32 nOffset = EffectSequenceHelper::getOffsetFromEffect( pEffect );
3142 if( nOffset != -1 )
3143 return nOffset;
3145 nOffset = EffectSequenceHelper::getCount();
3147 for (auto const& interactiveSequence : maInteractiveSequenceVector)
3149 sal_Int32 nTemp = interactiveSequence->getOffsetFromEffect( pEffect );
3150 if( nTemp != -1 )
3151 return nOffset + nTemp;
3153 nOffset += interactiveSequence->getCount();
3156 return -1;
3159 CustomAnimationEffectPtr MainSequence::getEffectFromOffset( sal_Int32 nOffset ) const
3161 if( nOffset >= 0 )
3163 if( nOffset < getCount() )
3164 return EffectSequenceHelper::getEffectFromOffset( nOffset );
3166 nOffset -= getCount();
3168 auto aIter( maInteractiveSequenceVector.begin() );
3170 while( (aIter != maInteractiveSequenceVector.end()) && (nOffset > (*aIter)->getCount()) )
3171 nOffset -= (*aIter++)->getCount();
3173 if( (aIter != maInteractiveSequenceVector.end()) && (nOffset >= 0) )
3174 return (*aIter)->getEffectFromOffset( nOffset );
3177 CustomAnimationEffectPtr pEffect;
3178 return pEffect;
3181 bool MainSequence::disposeShape( const Reference< XShape >& xShape )
3183 bool bChanges = EffectSequenceHelper::disposeShape( xShape );
3185 for (auto const& iterativeSequence : maInteractiveSequenceVector)
3187 bChanges |= iterativeSequence->disposeShape( xShape );
3190 if( bChanges )
3191 startRebuildTimer();
3193 return bChanges;
3196 bool MainSequence::hasEffect( const css::uno::Reference< css::drawing::XShape >& xShape )
3198 if( EffectSequenceHelper::hasEffect( xShape ) )
3199 return true;
3201 for (auto const& iterativeSequence : maInteractiveSequenceVector)
3203 if( iterativeSequence->getTriggerShape() == xShape )
3204 return true;
3206 if( iterativeSequence->hasEffect( xShape ) )
3207 return true;
3210 return false;
3213 void MainSequence::insertTextRange( const css::uno::Any& aTarget )
3215 EffectSequenceHelper::insertTextRange( aTarget );
3217 for (auto const& iterativeSequence : maInteractiveSequenceVector)
3219 iterativeSequence->insertTextRange( aTarget );
3223 void MainSequence::disposeTextRange( const css::uno::Any& aTarget )
3225 EffectSequenceHelper::disposeTextRange( aTarget );
3227 for (auto const& iterativeSequence : maInteractiveSequenceVector)
3229 iterativeSequence->disposeTextRange( aTarget );
3233 /** callback from the sd::View when an object just left text edit mode */
3234 void MainSequence::onTextChanged( const Reference< XShape >& xShape )
3236 EffectSequenceHelper::onTextChanged( xShape );
3238 for (auto const& iterativeSequence : maInteractiveSequenceVector)
3240 iterativeSequence->onTextChanged( xShape );
3244 void EffectSequenceHelper::onTextChanged( const Reference< XShape >& xShape )
3246 bool bChanges = std::accumulate(maEffects.begin(), maEffects.end(), false,
3247 [&xShape](const bool bCheck, const CustomAnimationEffectPtr& rxEffect) {
3248 bool bRes = bCheck;
3249 if (rxEffect->getTargetShape() == xShape)
3250 bRes |= rxEffect->checkForText();
3251 return bRes;
3254 if( bChanges )
3255 rebuild();
3258 void MainSequence::rebuild()
3260 startRebuildTimer();
3263 void MainSequence::lockRebuilds()
3265 mnRebuildLockGuard++;
3268 void MainSequence::unlockRebuilds()
3270 DBG_ASSERT( mnRebuildLockGuard, "sd::MainSequence::unlockRebuilds(), no corresponding lockRebuilds() call!" );
3271 if( mnRebuildLockGuard )
3272 mnRebuildLockGuard--;
3274 if( (mnRebuildLockGuard == 0) && mbPendingRebuildRequest )
3276 mbPendingRebuildRequest = false;
3277 startRebuildTimer();
3281 void MainSequence::implRebuild()
3283 if( mnRebuildLockGuard )
3285 mbPendingRebuildRequest = true;
3286 return;
3289 mbRebuilding = true;
3291 EffectSequenceHelper::implRebuild();
3293 auto aIter( maInteractiveSequenceVector.begin() );
3294 while( aIter != maInteractiveSequenceVector.end() )
3296 InteractiveSequencePtr pIS( *aIter );
3297 if( pIS->maEffects.empty() )
3299 // remove empty interactive sequences
3300 aIter = maInteractiveSequenceVector.erase( aIter );
3302 Reference< XChild > xChild( mxSequenceRoot, UNO_QUERY_THROW );
3303 Reference< XTimeContainer > xParent( xChild->getParent(), UNO_QUERY_THROW );
3304 Reference< XAnimationNode > xISNode( pIS->mxSequenceRoot, UNO_QUERY_THROW );
3305 xParent->removeChild( xISNode );
3307 else
3309 pIS->implRebuild();
3310 ++aIter;
3314 notify_listeners();
3315 mbRebuilding = false;
3318 void MainSequence::notify_change()
3320 notify_listeners();
3323 bool MainSequence::setTrigger( const CustomAnimationEffectPtr& pEffect, const css::uno::Reference< css::drawing::XShape >& xTriggerShape )
3325 EffectSequenceHelper* pOldSequence = pEffect->getEffectSequence();
3327 EffectSequenceHelper* pNewSequence = nullptr;
3328 if( xTriggerShape.is() )
3330 for (InteractiveSequencePtr const& pIS : maInteractiveSequenceVector)
3332 if( pIS->getTriggerShape() == xTriggerShape )
3334 pNewSequence = pIS.get();
3335 break;
3339 if( !pNewSequence )
3340 pNewSequence = createInteractiveSequence( xTriggerShape ).get();
3342 else
3344 pNewSequence = this;
3347 if( pOldSequence != pNewSequence )
3349 if( pOldSequence )
3350 pOldSequence->maEffects.remove( pEffect );
3351 if( pNewSequence )
3352 pNewSequence->maEffects.push_back( pEffect );
3353 pEffect->setEffectSequence( pNewSequence );
3354 return true;
3356 else
3358 return false;
3363 IMPL_LINK_NOARG(MainSequence, onTimerHdl, Timer *, void)
3365 if( mbTimerMode )
3367 implRebuild();
3369 else
3371 reset();
3372 createMainSequence();
3376 /** starts a timer that recreates the internal structure from the API core */
3377 void MainSequence::startRecreateTimer()
3379 if( !mbRebuilding && (mbIgnoreChanges == 0) )
3381 mbTimerMode = false;
3382 maTimer.Start();
3387 * starts a timer that rebuilds the API core from the internal structure
3388 * This is used to reduce the number of screen redraws due to animation changes.
3390 void MainSequence::startRebuildTimer()
3392 mbTimerMode = true;
3393 maTimer.Start();
3396 InteractiveSequence::InteractiveSequence( const Reference< XTimeContainer >& xSequenceRoot, MainSequence* pMainSequence )
3397 : EffectSequenceHelper( xSequenceRoot ), mpMainSequence( pMainSequence )
3399 mnSequenceType = EffectNodeType::INTERACTIVE_SEQUENCE;
3403 if( mxSequenceRoot.is() )
3405 Reference< XEnumerationAccess > xEnumerationAccess( mxSequenceRoot, UNO_QUERY_THROW );
3406 Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
3407 while( !mxEventSource.is() && xEnumeration->hasMoreElements() )
3409 Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
3411 Event aEvent;
3412 if( (xChildNode->getBegin() >>= aEvent) && (aEvent.Trigger == EventTrigger::ON_CLICK) )
3413 aEvent.Source >>= mxEventSource;
3417 catch( Exception& )
3419 OSL_FAIL( "sd::InteractiveSequence::InteractiveSequence(), exception caught!" );
3420 return;
3424 void InteractiveSequence::rebuild()
3426 mpMainSequence->rebuild();
3429 void InteractiveSequence::implRebuild()
3431 EffectSequenceHelper::implRebuild();
3434 MainSequenceRebuildGuard::MainSequenceRebuildGuard( const MainSequencePtr& pMainSequence )
3435 : mpMainSequence( pMainSequence )
3437 if( mpMainSequence.get() )
3438 mpMainSequence->lockRebuilds();
3441 MainSequenceRebuildGuard::~MainSequenceRebuildGuard()
3443 if( mpMainSequence.get() )
3444 mpMainSequence->unlockRebuilds();
3449 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */