/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sd.hxx"
#include <tools/debug.hxx>
#include <com/sun/star/util/XCloneable.hpp>
#include <com/sun/star/animations/AnimationFill.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/presentation/EffectNodeType.hpp>
#include <com/sun/star/presentation/EffectCommands.hpp>
#include <com/sun/star/presentation/EffectPresetClass.hpp>
#include <com/sun/star/presentation/ParagraphTarget.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
#include <com/sun/star/animations/AnimationNodeType.hpp>
#include <com/sun/star/animations/XCommand.hpp>
#include <com/sun/star/animations/AnimationTransformType.hpp>
#include <com/sun/star/animations/XIterateContainer.hpp>
#include <com/sun/star/animations/XAnimateTransform.hpp>
#include <com/sun/star/animations/Event.hpp>
#include <com/sun/star/animations/EventTrigger.hpp>
#include <com/sun/star/animations/Timing.hpp>
#include <com/sun/star/drawing/XDrawPage.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/animations/XAnimate.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/util/XChangesNotifier.hpp>
#include <com/sun/star/animations/XAnimateMotion.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/sequence.hxx>
#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
#ifndef _COM_SUN_STAR_TEXT_WORDTYPE_HPP_
#include <com/sun/star/i18n/WordType.hpp>
#endif
#include <com/sun/star/presentation/TextAnimationType.hpp>

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

#include <algorithm>

#include <cppuhelper/implbase1.hxx>

#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdpage.hxx>
#include <svx/unoapi.hxx>
#include "CustomAnimationEffect.hxx"
#include <CustomAnimationPreset.hxx>
#include "animations.hxx"

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

using ::rtl::OUString;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Sequence;
using ::com::sun::star::uno::XInterface;
using ::com::sun::star::uno::UNO_QUERY;
using ::com::sun::star::uno::UNO_QUERY_THROW;
using ::com::sun::star::uno::Any;
using ::com::sun::star::uno::makeAny;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::uno::RuntimeException;
using ::com::sun::star::container::XEnumerationAccess;
using ::com::sun::star::container::XEnumeration;
using ::com::sun::star::beans::NamedValue;
using ::com::sun::star::container::XChild;
using ::com::sun::star::container::XElementAccess;
using ::com::sun::star::drawing::XShape;
using ::com::sun::star::lang::XInitialization;
using ::com::sun::star::drawing::XShapes;
using ::com::sun::star::drawing::XDrawPage;
using ::com::sun::star::text::XText;
using ::com::sun::star::text::XTextRange;
using ::com::sun::star::beans::XPropertySet;
using ::com::sun::star::lang::XMultiServiceFactory;
using ::com::sun::star::util::XCloneable;
using ::com::sun::star::lang::Locale;
using ::com::sun::star::util::XChangesNotifier;
using ::com::sun::star::util::XChangesListener;

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

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

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

private:
	MainSequence* mpMainSequence;
};

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

// --------------------------------------------------------------------

void CustomAnimationEffect::setNode( const ::com::sun::star::uno::Reference< ::com::sun::star::animations::XAnimationNode >& xNode )
{
	mxNode = xNode;
	mxAudio.clear();

	Sequence< NamedValue > aUserData( mxNode->getUserData() );
	sal_Int32 nLength = aUserData.getLength();
	const NamedValue* p = aUserData.getConstArray();

	while( nLength-- )
	{
        if( p->Name.equalsAscii( "node-type" ) )
		{
			p->Value >>= mnNodeType;
		}
		else if( p->Name.equalsAscii( "preset-id" ) )
		{
			p->Value >>= maPresetId;
		}
		else if( p->Name.equalsAscii( "preset-sub-type" ) )
		{
			p->Value >>= maPresetSubType;
		}
		else if( p->Name.equalsAscii( "preset-class" ) )
		{
			p->Value >>= mnPresetClass;
		}
		else if( p->Name.equalsAscii( "preset-property" ) )
		{
			p->Value >>= maProperty;
		}
		else if( p->Name.equalsAscii( "group-id" ) )
		{
			p->Value >>= mnGroupId;
		}

		p++;
	}

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

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

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

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

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

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

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

	mfAbsoluteDuration = mfDuration;
	checkForText();
}

// --------------------------------------------------------------------

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

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

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

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

		if( xShape.is() )
		{
			// TODO/LATER: Optimize this, don't create a break iterator each time
			Reference< lang::XMultiServiceFactory > xMSF( ::comphelper::getProcessServiceFactory() );
			Reference < i18n::XBreakIterator > xBI( xMSF->createInstance( OUString::createFromAscii( "com.sun.star.i18n.BreakIterator" ) ), UNO_QUERY );
			DBG_ASSERT( xBI.is(), "sd::CustomAnimationEffect::getNumberOfSubitems(), could not create a 'com.sun.star.i18n.BreakIterator'!" );

			if( xBI.is() )
			{
				Reference< XEnumerationAccess > xEA( xShape, UNO_QUERY_THROW );
				Reference< XEnumeration > xEnumeration( xEA->createEnumeration(), UNO_QUERY_THROW );
				Locale aLocale;
				const OUString aStrLocaleName( RTL_CONSTASCII_USTRINGPARAM("CharLocale") );
				Reference< XTextRange > xParagraph;

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

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

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

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

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

					if( nPara == nOnlyPara )
						break;

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

	return nSubItems;
}

// --------------------------------------------------------------------

CustomAnimationEffect::~CustomAnimationEffect()
{
}

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

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

	if( xNode.is() )
	{
		Sequence< NamedValue > aUserData( xNode->getUserData() );
		sal_Int32 nLength = aUserData.getLength();
		if( nLength )
		{
			const NamedValue* p = aUserData.getConstArray();
			while( nLength-- )
			{
				if( p->Name.equalsAscii( "node-type" ) )
				{
					p->Value >>= nNodeType;
					break;
				}
				p++;
			}
		}
	}

	return nNodeType;
}

// --------------------------------------------------------------------

void CustomAnimationEffect::setPresetClass( sal_Int16 nPresetClass )
{
	if( mnPresetClass != nPresetClass )
	{
		mnPresetClass = nPresetClass;
		if( mxNode.is() )
		{
			// first try to find a "preset-class" entry in the user data
			// and change it
			Sequence< NamedValue > aUserData( mxNode->getUserData() );
			sal_Int32 nLength = aUserData.getLength();
			bool bFound = false;
			if( nLength )
			{
				NamedValue* p = aUserData.getArray();
				while( nLength-- )
				{
					if( p->Name.equalsAscii( "preset-class" ) )
					{
						p->Value <<= mnPresetClass;
						bFound = true;
						break;
					}
					p++;
				}
			}

			// no "node-type" entry inside user data, so add it
			if( !bFound )
			{
				nLength = aUserData.getLength();
				aUserData.realloc( nLength + 1);
				aUserData[nLength].Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "preset-class" ) );
				aUserData[nLength].Value <<= mnPresetClass;
			}

			mxNode->setUserData( aUserData );
		}
	}
}

void CustomAnimationEffect::setNodeType( sal_Int16 nNodeType )
{
	if( mnNodeType != nNodeType )
	{
		mnNodeType = nNodeType;
		if( mxNode.is() )
		{
			// first try to find a "node-type" entry in the user data
			// and change it
			Sequence< NamedValue > aUserData( mxNode->getUserData() );
			sal_Int32 nLength = aUserData.getLength();
			bool bFound = false;
			if( nLength )
			{
				NamedValue* p = aUserData.getArray();
				while( nLength-- )
				{
					if( p->Name.equalsAscii( "node-type" ) )
					{
						p->Value <<= mnNodeType;
						bFound = true;
						break;
					}
					p++;
				}
			}

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

			mxNode->setUserData( aUserData );
		}
	}
}

// --------------------------------------------------------------------

void CustomAnimationEffect::setGroupId( sal_Int32 nGroupId )
{
	mnGroupId = nGroupId;
	if( mxNode.is() )
	{
		// first try to find a "group-id" entry in the user data
		// and change it
		Sequence< NamedValue > aUserData( mxNode->getUserData() );
		sal_Int32 nLength = aUserData.getLength();
		bool bFound = false;
		if( nLength )
		{
			NamedValue* p = aUserData.getArray();
			while( nLength-- )
			{
				if( p->Name.equalsAscii( "group-id" ) )
				{
					p->Value <<= mnGroupId;
					bFound = true;
					break;
				}
				p++;
			}
		}

		// no "node-type" entry inside user data, so add it
		if( !bFound )
		{
			nLength = aUserData.getLength();
			aUserData.realloc( nLength + 1);
			aUserData[nLength].Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "group-id" ) );
			aUserData[nLength].Value <<= mnGroupId;
		}

		mxNode->setUserData( aUserData );
	}
}

// --------------------------------------------------------------------

/** checks if the text for this effect has changed and updates internal flags.
	returns true if something changed.
*/
bool CustomAnimationEffect::checkForText()
{
	bool bChange = false;

	Reference< XText > xText;

	if( maTarget.getValueType() == ::getCppuType((const ParagraphTarget*)0) )
	{
		// calc para depth
		ParagraphTarget aParaTarget;
		maTarget >>= aParaTarget;

		xText = Reference< XText >::query( aParaTarget.Shape );

		// get paragraph
		if( xText.is() )
		{
			Reference< XEnumerationAccess > xEA( xText, UNO_QUERY );
			if( xEA.is() )
			{
				Reference< XEnumeration > xEnumeration( xEA->createEnumeration(), UNO_QUERY );
				if( xEnumeration.is() )
				{
					sal_Bool bHasText = xEnumeration->hasMoreElements();
					bChange |= bHasText != mbHasText;
					mbHasText = bHasText;

					sal_Int32 nPara = aParaTarget.Paragraph;

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

					if( xEnumeration->hasMoreElements() )
					{
						Reference< XPropertySet > xParaSet;
						xEnumeration->nextElement() >>= xParaSet;
						if( xParaSet.is() )
						{
							sal_Int32 nParaDepth = 0;
							const OUString strNumberingLevel( RTL_CONSTASCII_USTRINGPARAM("NumberingLevel") );
							xParaSet->getPropertyValue( strNumberingLevel ) >>= nParaDepth;
							bChange |= nParaDepth != mnParaDepth;
							mnParaDepth = nParaDepth;
						}
					}
				}
			}
		}
	}
	else
	{
		maTarget >>= xText;
		sal_Bool bHasText = xText.is() && xText->getString().getLength();
		bChange |= bHasText != mbHasText;
		mbHasText = bHasText;
	}

	bChange |= calculateIterateDuration();
	return bChange;
}

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

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

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

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

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

	return bChange;
}

// --------------------------------------------------------------------

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

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

// --------------------------------------------------------------------

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

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

// --------------------------------------------------------------------

void CustomAnimationEffect::setDuration( double fDuration )
{
	if( (mfDuration != -1.0) && (mfDuration != fDuration) ) try
	{
		double fScale = fDuration / mfDuration;
		mfDuration = fDuration;
		mfAbsoluteDuration = mfDuration;
	
		// calculate effect duration and get target shape
		Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY );
		if( xEnumerationAccess.is() )
		{
			Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY );
			if( xEnumeration.is() )
			{
				while( xEnumeration->hasMoreElements() )
				{
					Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY );
					if( !xChildNode.is() )
						continue;


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

					double fChildDuration = 0.0;
					xChildNode->getDuration() >>= fChildDuration;
					if( fChildDuration != 0.0 )
					{
						fChildDuration *= fScale;
						xChildNode->setDuration( makeAny( fChildDuration ) );
					}
				}
			}
		}
		calculateIterateDuration();
	}
	catch( Exception& )
	{
		DBG_ERROR( "sd::CustomAnimationEffect::setDuration(), exception caught!" );
	}
}

// --------------------------------------------------------------------

void CustomAnimationEffect::setBegin( double fBegin )
{
	if( mxNode.is() ) try
	{
		mfBegin = fBegin;
		mxNode->setBegin( makeAny( fBegin ) );
	}
	catch( Exception& )
	{
		DBG_ERROR( "sd::CustomAnimationEffect::setBegin(), exception caught!" );
	}
}

// --------------------------------------------------------------------

void CustomAnimationEffect::setAcceleration( double fAcceleration )
{
	if( mxNode.is() ) try
	{
		mfAcceleration = fAcceleration;
		mxNode->setAcceleration( fAcceleration );
	}
	catch( Exception& )
	{
		DBG_ERROR( "sd::CustomAnimationEffect::setAcceleration(), exception caught!" );
	}
}
// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

void CustomAnimationEffect::setAutoReverse( sal_Bool bAutoReverse )
{
	if( mxNode.is() ) try
	{
		mbAutoReverse = bAutoReverse;
		mxNode->setAutoReverse( bAutoReverse );
	}
	catch( Exception& )
	{
		DBG_ERROR( "sd::CustomAnimationEffect::setAutoReverse(), exception caught!" );
	}
}

// --------------------------------------------------------------------

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

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

	setNode( xNode );

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

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

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

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

// --------------------------------------------------------------------

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

	return xShape;
}

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

sal_Int16 CustomAnimationEffect::getFill() const
{
	if( mxNode.is() )
		return mxNode->getFill();
	else
		return 0;
}

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

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

	Reference< XMultiServiceFactory > xMsf( ::comphelper::getProcessServiceFactory() );

	const char* pServiceName = maDimColor.hasValue() ? 
		"com.sun.star.animations.AnimateColor" : "com.sun.star.animations.AnimateSet";
				
	Reference< XAnimate > xAnimate( xMsf->createInstance(OUString::createFromAscii(pServiceName) ), UNO_QUERY_THROW );

	Any aTo;
	OUString aAttributeName;

	if( maDimColor.hasValue() )
	{
		aTo = maDimColor;
		aAttributeName = OUString( RTL_CONSTASCII_USTRINGPARAM( "DimColor" ) );
	}
	else
	{
		aTo = makeAny( (sal_Bool)sal_False );
		aAttributeName = OUString( RTL_CONSTASCII_USTRINGPARAM( "Visibility" ) );
	}

	Any aBegin;
	if( !mbAfterEffectOnNextEffect ) // sameClick
	{
		Event aEvent;
		
		aEvent.Source <<= getNode();
		aEvent.Trigger = EventTrigger::END_EVENT;
		aEvent.Repeat = 0;

		aBegin <<= aEvent;
	}
	else
	{
		aBegin <<= (double)0.0;
	}

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

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

	return Reference< XAnimationNode >( xAnimate, UNO_QUERY_THROW );
}

// --------------------------------------------------------------------

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

			Reference< XMultiServiceFactory > xMsf( ::comphelper::getProcessServiceFactory() );
			const char * pServiceName =
				nIterateType ? "com.sun.star.animations.IterateContainer" : "com.sun.star.animations.ParallelTimeContainer";
			Reference< XTimeContainer > xNewContainer( 
				xMsf->createInstance( OUString::createFromAscii(pServiceName) ), UNO_QUERY_THROW );

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

			Reference< XAnimationNode > xNewNode( xNewContainer, UNO_QUERY_THROW );

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

			mxNode = xNewNode;

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

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

		mnIterateType = nIterateType;

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

		checkForText();
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR( "sd::CustomAnimationEffect::setIterateType(), Exception caught!" );
	}
}

// --------------------------------------------------------------------

void CustomAnimationEffect::setIterateInterval( double fIterateInterval )
{
	if( mfIterateInterval != fIterateInterval )
	{
		Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );

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

		calculateIterateDuration();
	}
}

// --------------------------------------------------------------------

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

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

	return aPath;
}

// --------------------------------------------------------------------

void CustomAnimationEffect::setPath( const ::rtl::OUString& rPath )
{
	if( mxNode.is() ) try
	{
		Reference< XEnumerationAccess > xEnumerationAccess( mxNode, UNO_QUERY_THROW );
		Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_THROW );
		while( xEnumeration->hasMoreElements() )
		{
			Reference< XAnimateMotion > xMotion( xEnumeration->nextElement(), UNO_QUERY );
			if( xMotion.is() )
			{

				MainSequenceChangeGuard aGuard( mpEffectSequence );
				xMotion->setPath( Any( rPath ) );
				break;
			}
		}
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR("sd::CustomAnimationEffect::setPath(), exception caught!" );
	}
}

// --------------------------------------------------------------------

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

					if( xAnimate->getType() == nNodeType )
					{
						if( xAnimate->getAttributeName() == rAttributeName )
						{
							switch( eValue )
							{
							case VALUE_FROM: aProperty = xAnimate->getFrom(); break;
							case VALUE_TO:   aProperty = xAnimate->getTo(); break;
							case VALUE_BY:   aProperty = xAnimate->getBy(); break;
							case VALUE_FIRST:
							case VALUE_LAST:
								{
									Sequence<Any> aValues( xAnimate->getValues() );
									if( aValues.hasElements() )
										aProperty =  aValues[ eValue == VALUE_FIRST ? 0 : aValues.getLength() - 1 ]; 
								}
								break;
							}
						}
					}
				}
			}
		}
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR("sd::CustomAnimationEffect::getProperty(), exception caught!" );
	}

	return aProperty;
}

// --------------------------------------------------------------------

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

					if( xAnimate->getType() == nNodeType )
					{
						if( xAnimate->getAttributeName() == rAttributeName )
						{
							switch( eValue )
							{
							case VALUE_FROM:
								if( xAnimate->getFrom() != rValue )
								{
									xAnimate->setFrom( rValue ); 
									bChanged = true;
								}
								break;
							case VALUE_TO:
								if( xAnimate->getTo() != rValue )
								{
									xAnimate->setTo( rValue );
									bChanged = true;
								}	
								break;
							case VALUE_BY:  
								if( xAnimate->getTo() != rValue )
								{
									xAnimate->setBy( rValue );
									bChanged = true;
								}
								break;
							case VALUE_FIRST:
							case VALUE_LAST:
								{
									Sequence<Any> aValues( xAnimate->getValues() );
									if( !aValues.hasElements() )
										aValues.realloc(1);

									sal_Int32 nIndex = eValue == VALUE_FIRST ? 0 : aValues.getLength() - 1;

									if( aValues[ nIndex ] != rValue )
									{
										aValues[ nIndex ] = rValue; 
										xAnimate->setValues( aValues );
										bChanged = true;
									}
								}
							}
						}
					}
				}
			}
		}
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR("sd::CustomAnimationEffect::setProperty(), exception caught!" );
	}

	return bChanged;
}

// --------------------------------------------------------------------

static bool implIsColorAttribute( const OUString& rAttributeName )
{
	return rAttributeName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("FillColor") ) ||
		   rAttributeName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("LineColor") ) ||
		   rAttributeName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("CharColor") );
}

// --------------------------------------------------------------------

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

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

	return aColor;
}

// --------------------------------------------------------------------

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

					switch( xAnimate->getType() )
					{
					case AnimationNodeType::SET:
					case AnimationNodeType::ANIMATE:
						if( !implIsColorAttribute( xAnimate->getAttributeName() ) )
							break;
					case AnimationNodeType::ANIMATECOLOR:
					{
						Sequence<Any> aValues( xAnimate->getValues() );
						if( aValues.hasElements() )
						{
							if( aValues.getLength() > nIndex )
							{
								aValues[nIndex] = rColor;
								xAnimate->setValues( aValues );
							}
						}
						else if( (nIndex == 0) && xAnimate->getFrom().hasValue() )
							xAnimate->setFrom(rColor);
						else if( (nIndex == 1) && xAnimate->getTo().hasValue() )
							xAnimate->setTo(rColor);
					}
					break;
				
					}
				}
			}
		}
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR("sd::CustomAnimationEffect::setColor(), exception caught!" );
	}
}

// --------------------------------------------------------------------

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

					if( xTransform->getTransformType() == nTransformType )
					{
						switch( eValue )
						{
						case VALUE_FROM: aProperty = xTransform->getFrom(); break;
						case VALUE_TO:   aProperty = xTransform->getTo(); break;
						case VALUE_BY:   aProperty = xTransform->getBy(); break;
						case VALUE_FIRST:
						case VALUE_LAST:
							{
								Sequence<Any> aValues( xTransform->getValues() );
								if( aValues.hasElements() )
									aProperty =  aValues[ eValue == VALUE_FIRST ? 0 : aValues.getLength() - 1 ]; 
							}
							break;
						}
					}
				}
			}
		}
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR("sd::CustomAnimationEffect::getTransformationProperty(), exception caught!" );
	}

	return aProperty;
}

// --------------------------------------------------------------------

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

					if( xTransform->getTransformType() == nTransformType )
					{
						switch( eValue )
						{
						case VALUE_FROM:
							if( xTransform->getFrom() != rValue )
							{
								xTransform->setFrom( rValue ); 
								bChanged = true;
							}
							break;
						case VALUE_TO:
							if( xTransform->getTo() != rValue )
							{
								xTransform->setTo( rValue );
								bChanged = true;
							}
							break;
						case VALUE_BY:
							if( xTransform->getBy() != rValue )
							{
								xTransform->setBy( rValue );
								bChanged = true;
							}
							break;
						case VALUE_FIRST:
						case VALUE_LAST:
							{
								Sequence<Any> aValues( xTransform->getValues() );
								if( !aValues.hasElements() )
									aValues.realloc(1);

								sal_Int32 nIndex = eValue == VALUE_FIRST ? 0 : aValues.getLength() - 1;
								if( aValues[nIndex] != rValue )
								{
									aValues[nIndex] = rValue;
									xTransform->setValues( aValues );
									bChanged = true;
								}
							}
						}
					}
				}
			}
		}
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR("sd::CustomAnimationEffect::setTransformationProperty(), exception caught!" );
	}

	return bChanged;
}

// --------------------------------------------------------------------

void CustomAnimationEffect::createAudio( const ::com::sun::star::uno::Any& rSource, double fVolume /* = 1.0 */ )
{
	DBG_ASSERT( !mxAudio.is(), "sd::CustomAnimationEffect::createAudio(), node already has an audio!" );

	if( !mxAudio.is() ) try
	{
		Reference< XMultiServiceFactory > xMsf( ::comphelper::getProcessServiceFactory() );
		Reference< XAudio > xAudio( xMsf->createInstance( OUString( RTL_CONSTASCII_USTRINGPARAM("com.sun.star.animations.Audio") ) ), UNO_QUERY_THROW );
		xAudio->setSource( rSource );
		xAudio->setVolume( fVolume );
		setAudio( xAudio );
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR("sd::CustomAnimationEffect::createAudio(), exception caught!" );
	}
}

// --------------------------------------------------------------------

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

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

	return xCommand;
}

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

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

		if( xChild.is() )
		{
			Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
			if( xContainer.is() )
				xContainer->removeChild( xChild );
		}
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR("sd::CustomAnimationEffect::removeAudio(), exception caught!" );
	}

}

// --------------------------------------------------------------------

void CustomAnimationEffect::setAudio( const Reference< ::com::sun::star::animations::XAudio >& xAudio )
{
	if( mxAudio != xAudio ) try
	{
		removeAudio();
		mxAudio = xAudio;
		Reference< XTimeContainer > xContainer( mxNode, UNO_QUERY );
		Reference< XAnimationNode > xChild( mxAudio, UNO_QUERY );
		if( xContainer.is() && xChild.is() )
			xContainer->appendChild( xChild );
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR("sd::CustomAnimationEffect::setAudio(), exception caught!" );
	}
}

// --------------------------------------------------------------------

void CustomAnimationEffect::setStopAudio()
{
	if( mnCommand != EffectCommands::STOPAUDIO ) try
	{
		if( mxAudio.is() )
			removeAudio();

		Reference< XMultiServiceFactory > xMsf( ::comphelper::getProcessServiceFactory() );
		Reference< XCommand > xCommand( xMsf->createInstance( OUString( RTL_CONSTASCII_USTRINGPARAM("com.sun.star.animations.Command") ) ), UNO_QUERY_THROW );

		xCommand->setCommand( EffectCommands::STOPAUDIO );

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

		mnCommand = EffectCommands::STOPAUDIO;
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR("sd::CustomAnimationEffect::setStopAudio(), exception caught!" );
	}
}

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

SdrPathObj* CustomAnimationEffect::createSdrPathObjFromPath()
{
	SdrPathObj * pPathObj = new SdrPathObj( OBJ_PATHLINE );
	updateSdrPathObjFromPath( *pPathObj );
	return pPathObj;
}

// --------------------------------------------------------------------

void CustomAnimationEffect::updateSdrPathObjFromPath( SdrPathObj& rPathObj )
{
	::basegfx::B2DPolyPolygon xPolyPoly;
	if( ::basegfx::tools::importFromSvgD( xPolyPoly, getPath(), true, 0 ) )
	{
		SdrObject* pObj = GetSdrObjectFromXShape( getTargetShape() );
		if( pObj )
		{
			SdrPage* pPage = pObj->GetPage();
			if( pPage )
			{
				const Size aPageSize( pPage->GetSize() );
                xPolyPoly.transform(basegfx::tools::createScaleB2DHomMatrix((double)aPageSize.Width(), (double)aPageSize.Height()));
			}

			const Rectangle aBoundRect( pObj->GetCurrentBoundRect() );
			const Point aCenter( aBoundRect.Center() );
            xPolyPoly.transform(basegfx::tools::createTranslateB2DHomMatrix(aCenter.X(), aCenter.Y()));
		}
	}

	rPathObj.SetPathPoly( xPolyPoly );
}

// --------------------------------------------------------------------

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

	SdrObject* pObj = GetSdrObjectFromXShape( getTargetShape() );
	if( pObj )
	{
		Rectangle aBoundRect(0,0,0,0);

    	const drawinglayer::primitive2d::Primitive2DSequence xPrimitives(pObj->GetViewContact().getViewIndependentPrimitive2DSequence());
	    const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
   		const basegfx::B2DRange aRange(drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(xPrimitives, aViewInformation2D));

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

		const Point aCenter( aBoundRect.Center() );

        xPolyPoly.transform(basegfx::tools::createTranslateB2DHomMatrix(-aCenter.X(), -aCenter.Y()));

		SdrPage* pPage = pObj->GetPage();
		if( pPage )
		{
			const Size aPageSize( pPage->GetSize() );
            xPolyPoly.transform(basegfx::tools::createScaleB2DHomMatrix(
                1.0 / (double)aPageSize.Width(), 1.0 / (double)aPageSize.Height()));
		}
	}

	setPath( ::basegfx::tools::exportToSvgD( xPolyPoly, true, true, true) );
}

// ====================================================================

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

// --------------------------------------------------------------------

EffectSequenceHelper::EffectSequenceHelper( const ::com::sun::star::uno::Reference< ::com::sun::star::animations::XTimeContainer >& xSequenceRoot )
: mxSequenceRoot( xSequenceRoot ), mnSequenceType( EffectNodeType::DEFAULT )
{
	Reference< XAnimationNode > xNode( mxSequenceRoot, UNO_QUERY_THROW );
	create( xNode );
}

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

void EffectSequenceHelper::reset()
{
	EffectSequence::iterator aIter( maEffects.begin() );
	EffectSequence::iterator aEnd( maEffects.end() );
	if( aIter != aEnd )
	{
		CustomAnimationEffectPtr pEffect = (*aIter++);
		pEffect->setEffectSequence(0);
	}
	maEffects.clear();
}

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

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

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

	if( pPreset.get() )
	{
		OUString strEmpty;
		Reference< XAnimationNode > xNode( pPreset->create( strEmpty ) );
		if( xNode.is() )
		{
			// first, filter all only ui relevant user data
			std::vector< NamedValue > aNewUserData;
			Sequence< NamedValue > aUserData( xNode->getUserData() );
			sal_Int32 nLength = aUserData.getLength();
			const NamedValue* p = aUserData.getConstArray();
			bool bFilter = false;

			while( nLength-- )
			{
				if( !p->Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "text-only" ) ) &&
					!p->Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "preset-property" ) ) )
				{
					aNewUserData.push_back( *p );
					bFilter = true;
				}
				p++;
			}
			
			if( bFilter )
			{
				aUserData = ::comphelper::containerToSequence< NamedValue, std::vector< NamedValue > >( aNewUserData );
				xNode->setUserData( aUserData );
			}

			// check target, maybe we need to force it to text
			Any aTarget( rTarget );
			sal_Int16 nSubItem = ShapeAnimationSubType::AS_WHOLE;

			if(	aTarget.getValueType() == ::getCppuType((const ParagraphTarget*)0) )
			{
				nSubItem = ShapeAnimationSubType::ONLY_TEXT; 
			}
			else if( pPreset->isTextOnly() )
			{
				Reference< XShape > xShape;
				aTarget >>= xShape;
				if( xShape.is() )
				{
					// thats bad, we target a shape here but the effect is only for text
					// so change subitem
					nSubItem = ShapeAnimationSubType::ONLY_TEXT; 
				}
			}

			// now create effect from preset
			pEffect.reset( new CustomAnimationEffect( xNode ) );
			pEffect->setEffectSequence( this );
			pEffect->setTarget( aTarget );
			pEffect->setTargetSubItem( nSubItem );
			if( fDuration != -1.0 )
				pEffect->setDuration( fDuration );
									
			maEffects.push_back(pEffect);

			rebuild();
		}
	}

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

// --------------------------------------------------------------------

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

	if( fDuration <= 0.0 )
		fDuration = 2.0;

	try
	{
		Reference< XTimeContainer > xEffectContainer( createParallelTimeContainer() );
		const OUString aServiceName( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.animations.AnimateMotion" ) );
		Reference< XAnimationNode > xAnimateMotion( ::comphelper::getProcessServiceFactory()->createInstance(aServiceName), UNO_QUERY_THROW );

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

		sal_Int16 nSubItem = ShapeAnimationSubType::AS_WHOLE;

		if(	rTarget.getValueType() == ::getCppuType((const ParagraphTarget*)0) )
			nSubItem = ShapeAnimationSubType::ONLY_TEXT; 

		Reference< XAnimationNode > xEffectNode( xEffectContainer, UNO_QUERY_THROW );
		pEffect.reset( new CustomAnimationEffect( xEffectNode ) );
		pEffect->setEffectSequence( this );
		pEffect->setTarget( rTarget );
		pEffect->setTargetSubItem( nSubItem );
		pEffect->setNodeType( ::com::sun::star::presentation::EffectNodeType::ON_CLICK );
		pEffect->setPresetClass( ::com::sun::star::presentation::EffectPresetClass::MOTIONPATH );
		pEffect->setAcceleration( 0.5 );
		pEffect->setDecelerate( 0.5 );
		pEffect->setFill( AnimationFill::HOLD );
		pEffect->setBegin( 0.0 );
		pEffect->updatePathFromSdrPathObj( rPathObj );
		if( fDuration != -1.0 )
			pEffect->setDuration( fDuration );
								
		maEffects.push_back(pEffect);

		rebuild();
	}
	catch( Exception& )
	{
		DBG_ERROR( "sd::EffectSequenceHelper::append(), exception caught!" );
	}

	return pEffect;
}

// --------------------------------------------------------------------

void EffectSequenceHelper::replace( const CustomAnimationEffectPtr& pEffect, const CustomAnimationPresetPtr& pPreset, const OUString& rPresetSubType, double fDuration /* = -1.0 */ )
{
	if( pEffect.get() && pPreset.get() ) try
	{
		Reference< XAnimationNode > xNewNode( pPreset->create( rPresetSubType ) );
		if( xNewNode.is() )
		{
			pEffect->replaceNode( xNewNode );
			if( fDuration != -1.0 )
				pEffect->setDuration( fDuration );
		}

		rebuild();
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR( "sd::EffectSequenceHelper::replace(), exception caught!" );
	}
}

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

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

	rebuild();
}

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

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

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

			mxSequenceRoot->removeChild( xChildNode );
		}

		// second, rebuild main sequence
		EffectSequence::iterator aIter( maEffects.begin() );
		EffectSequence::iterator aEnd( maEffects.end() );
		if( aIter != aEnd )
		{
			AfterEffectNodeList aAfterEffects;

			CustomAnimationEffectPtr pEffect = (*aIter++);

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

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

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

				xOnClickContainer->setBegin( aBegin );

				Reference< XAnimationNode > xOnClickContainerNode( xOnClickContainer, UNO_QUERY_THROW );
				mxSequenceRoot->appendChild( xOnClickContainerNode );

				double fBegin = 0.0;

				do
				{
					// create a par container for the current click or after effect node and all following with effects
					Reference< XTimeContainer > xWithContainer( createParallelTimeContainer() );
					Reference< XAnimationNode > xWithContainerNode( xWithContainer, UNO_QUERY_THROW );
					xWithContainer->setBegin( makeAny( fBegin ) );
					xOnClickContainer->appendChild( xWithContainerNode );

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

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

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

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

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

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

			updateTextGroups();

            // reset duration, might have been altered (see below)
            mxSequenceRoot->setDuration( Any() );
		}
        else
        {
            // empty sequence, set duration to 0.0 explicitely
            // (otherwise, this sequence will never end)
            mxSequenceRoot->setDuration( makeAny((double)0.0) );
        }
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR( "sd::EffectSequenceHelper::rebuild(), exception caught!" );
	}
}

// --------------------------------------------------------------------

Reference< XTimeContainer > EffectSequenceHelper::createParallelTimeContainer() const
{
	const OUString aServiceName( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.animations.ParallelTimeContainer" ) );
	return Reference< XTimeContainer >( ::comphelper::getProcessServiceFactory()->createInstance(aServiceName), UNO_QUERY );
}

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

void stl_process_after_effect_node_func(AfterEffectNode& rNode)
{
	try
	{
		if( rNode.mxNode.is() && rNode.mxMaster.is() )
		{
			// set master node
			Reference< XAnimationNode > xMasterNode( rNode.mxMaster, UNO_QUERY_THROW );
			Sequence< NamedValue > aUserData( rNode.mxNode->getUserData() );
			sal_Int32 nSize = aUserData.getLength();
			aUserData.realloc(nSize+1);
			aUserData[nSize].Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "master-element" ) );
			aUserData[nSize].Value <<= xMasterNode;
			rNode.mxNode->setUserData( aUserData );

			// insert after effect node into timeline
			Reference< XTimeContainer > xContainer( rNode.mxMaster->getParent(), UNO_QUERY_THROW );

			if( !rNode.mbOnNextEffect ) // sameClick
			{
				// insert the aftereffect after its effect is animated
				xContainer->insertAfter( rNode.mxNode, rNode.mxMaster );
			}
			else // nextClick
			{
				Reference< XMultiServiceFactory > xMsf( ::comphelper::getProcessServiceFactory() );
				// insert the aftereffect in the next group

				Reference< XTimeContainer > xClickContainer( xContainer->getParent(), UNO_QUERY_THROW );
				Reference< XTimeContainer > xSequenceContainer( xClickContainer->getParent(), UNO_QUERY_THROW );

				Reference< XTimeContainer > xNextContainer;		
				
				// first try if we have an after effect container
				if( !implFindNextContainer( xClickContainer, xContainer, xNextContainer ) )
				{
					Reference< XTimeContainer > xNextClickContainer;
					// if not, try to find the next click effect container
					if( implFindNextContainer( xSequenceContainer, xClickContainer, xNextClickContainer ) )
					{
						Reference< XEnumerationAccess > xEnumerationAccess( xNextClickContainer, UNO_QUERY_THROW );
						Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_THROW );
						if( xEnumeration->hasMoreElements() )
						{
							// the next container is the first child container
							xEnumeration->nextElement() >>= xNextContainer;
						}
						else
						{
							// this does not yet have a child container, create one
							const OUString aServiceName( RTL_CONSTASCII_USTRINGPARAM("com.sun.star.animations.ParallelTimeContainer") );
							xNextContainer = Reference< XTimeContainer >::query( xMsf->createInstance(aServiceName) );

							if( xNextContainer.is() )
							{
								Reference< XAnimationNode > xNode( xNextContainer, UNO_QUERY_THROW );
								xNode->setBegin( makeAny( (double)0.0 ) );
//								xNode->setFill( AnimationFill::HOLD );
								xNextClickContainer->appendChild( xNode );
							}
						}
						DBG_ASSERT( xNextContainer.is(), "ppt::stl_process_after_effect_node_func::operator(), could not find/create container!" );
					}
				}

				// if we don't have a next container, we add one to the sequence container
				if( !xNextContainer.is() )
				{
					const OUString aServiceName( RTL_CONSTASCII_USTRINGPARAM("com.sun.star.animations.ParallelTimeContainer") );
					Reference< XTimeContainer > xNewClickContainer( xMsf->createInstance(aServiceName), UNO_QUERY_THROW );

					Reference< XAnimationNode > xNewClickNode( xNewClickContainer, UNO_QUERY_THROW );

                    Event aEvent;
                    aEvent.Trigger = EventTrigger::ON_NEXT;
                    aEvent.Repeat = 0;
					xNewClickNode->setBegin( makeAny( aEvent ) );

					Reference< XAnimationNode > xRefNode( xClickContainer, UNO_QUERY_THROW );
					xSequenceContainer->insertAfter( xNewClickNode, xRefNode );

					xNextContainer = Reference< XTimeContainer >::query( xMsf->createInstance(aServiceName) );

					DBG_ASSERT( xNextContainer.is(), "ppt::stl_process_after_effect_node_func::operator(), could not create container!" );
					if( xNextContainer.is() )
					{
						Reference< XAnimationNode > xNode( xNextContainer, UNO_QUERY_THROW );
						xNode->setBegin( makeAny( (double)0.0 ) );
//						xNode->setFill( AnimationFill::HOLD );
						xNewClickContainer->appendChild( xNode );
					}
				}

				if( xNextContainer.is() )
				{
					// find begin time of first element
					Reference< XEnumerationAccess > xEnumerationAccess( xNextContainer, UNO_QUERY_THROW );
					Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_THROW );
					if( xEnumeration->hasMoreElements() )
					{
						Reference< XAnimationNode > xChild;
						// the next container is the first child container
						xEnumeration->nextElement() >>= xChild;
						if( xChild.is() )
						{
							Any aBegin( xChild->getBegin() );
							double fBegin = 0.0;
							if( (aBegin >>= fBegin) && (fBegin >= 0.0))
								rNode.mxNode->setBegin( aBegin );
						}
					}

					xNextContainer->appendChild( rNode.mxNode );
				}
			}
		}
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR( "ppt::stl_process_after_effect_node_func::operator(), exception caught!" );
	}
}

// --------------------------------------------------------------------

EffectSequence::iterator EffectSequenceHelper::find( const CustomAnimationEffectPtr& pEffect )
{
	return std::find( maEffects.begin(), maEffects.end(), pEffect );
}

// --------------------------------------------------------------------

CustomAnimationEffectPtr EffectSequenceHelper::findEffect( const ::com::sun::star::uno::Reference< ::com::sun::star::animations::XAnimationNode >& xNode ) const
{
	CustomAnimationEffectPtr pEffect;

	EffectSequence::const_iterator aIter( maEffects.begin() );
	for( ; aIter != maEffects.end(); aIter++ )
	{
		if( (*aIter)->getNode() == xNode )
		{
			pEffect = (*aIter);
			break;
		}
	}

	return pEffect;
}

// --------------------------------------------------------------------

sal_Int32 EffectSequenceHelper::getOffsetFromEffect( const CustomAnimationEffectPtr& xEffect ) const
{
	sal_Int32 nOffset = 0;

	EffectSequence::const_iterator aIter( maEffects.begin() );
	for( ; aIter != maEffects.end(); aIter++, nOffset++ )
	{
		if( (*aIter) == xEffect )
			return nOffset;
	}

	return -1;
}

// --------------------------------------------------------------------

CustomAnimationEffectPtr EffectSequenceHelper::getEffectFromOffset( sal_Int32 nOffset ) const
{
	EffectSequence::const_iterator aIter( maEffects.begin() );
	while( nOffset-- && aIter != maEffects.end() )
		aIter++;

	CustomAnimationEffectPtr pEffect;
	if( aIter != maEffects.end() )
		pEffect = (*aIter);

	return pEffect;
}

// --------------------------------------------------------------------

bool EffectSequenceHelper::disposeShape( const Reference< XShape >& xShape )
{
	bool bChanges = false;

	EffectSequence::iterator aIter( maEffects.begin() );
	while( aIter != maEffects.end() )
	{
		if( (*aIter)->getTargetShape() == xShape )
		{
			(*aIter)->setEffectSequence( 0 );
			bChanges = true;
			aIter = maEffects.erase( aIter );
		}
		else
		{
			aIter++;
		}
	}

	return bChanges;
}

// --------------------------------------------------------------------

bool EffectSequenceHelper::hasEffect( const com::sun::star::uno::Reference< com::sun::star::drawing::XShape >& xShape )
{
	EffectSequence::iterator aIter( maEffects.begin() );
	while( aIter != maEffects.end() )
	{
		if( (*aIter)->getTargetShape() == xShape )
			return true;
		aIter++;
	}

	return false;
}

// --------------------------------------------------------------------

void EffectSequenceHelper::insertTextRange( const com::sun::star::uno::Any& aTarget )
{
	bool bChanges = false;

	ParagraphTarget aParaTarget;
	if( !(aTarget >>= aParaTarget ) )
		return;

	EffectSequence::iterator aIter( maEffects.begin() );
	while( aIter != maEffects.end() )
	{
		if( (*aIter)->getTargetShape() == aParaTarget.Shape )
			bChanges |= (*aIter)->checkForText();
		aIter++;
	}

	if( bChanges )
		rebuild();
}

// --------------------------------------------------------------------

void EffectSequenceHelper::disposeTextRange( const com::sun::star::uno::Any& aTarget )
{
	ParagraphTarget aParaTarget;
	if( !(aTarget >>= aParaTarget ) )
		return;

	bool bChanges = false;
	bool bErased = false;

	EffectSequence::iterator aIter( maEffects.begin() );
	while( aIter != maEffects.end() )
	{
		Any aIterTarget( (*aIter)->getTarget() );
		if( aIterTarget.getValueType() == ::getCppuType((const ParagraphTarget*)0) )
		{
			ParagraphTarget aIterParaTarget;
			if( (aIterTarget >>= aIterParaTarget) && (aIterParaTarget.Shape == aParaTarget.Shape) )
			{
				if( aIterParaTarget.Paragraph == aParaTarget.Paragraph )
				{
					// delete this effect if it targets the disposed paragraph directly
					(*aIter)->setEffectSequence( 0 );
					aIter = maEffects.erase( aIter );
					bChanges = true;
					bErased = true;
				}
				else
				{
					if( aIterParaTarget.Paragraph > aParaTarget.Paragraph )
					{
						// shift all paragraphs after disposed paragraph
						aIterParaTarget.Paragraph--;
						(*aIter)->setTarget( makeAny( aIterParaTarget ) );
					}
				}
			}
		}
		else if( (*aIter)->getTargetShape() == aParaTarget.Shape )
		{
			bChanges |= (*aIter)->checkForText();
		}

		if( bErased )
			bErased = false;
		else
			aIter++;
	}

	if( bChanges )
		rebuild();
}

// --------------------------------------------------------------------

CustomAnimationTextGroup::CustomAnimationTextGroup( const Reference< XShape >& rTarget, sal_Int32 nGroupId )
:	maTarget( rTarget ), 
	mnGroupId( nGroupId )
{
	reset();
}

// --------------------------------------------------------------------

void CustomAnimationTextGroup::reset()
{
	mnTextGrouping = -1;
	mbAnimateForm = false;
	mbTextReverse = false;
	mfGroupingAuto = -1.0;
	mnLastPara = -1; // used to check for TextReverse

	int i = 5;
	while( i-- ) mnDepthFlags[i] = 0;

	maEffects.clear();
}

// --------------------------------------------------------------------

void CustomAnimationTextGroup::addEffect( CustomAnimationEffectPtr& pEffect )
{
	maEffects.push_back( pEffect );

	Any aTarget( pEffect->getTarget() );
	if( aTarget.getValueType() == ::getCppuType((const ParagraphTarget*)0) )
	{
		// now look at the paragraph
		ParagraphTarget aParaTarget;
		aTarget >>= aParaTarget;

		if( mnLastPara != -1 )
			mbTextReverse = mnLastPara > aParaTarget.Paragraph;
		
		mnLastPara = aParaTarget.Paragraph;

		const sal_Int32 nParaDepth = pEffect->getParaDepth();

		// only look at the first 5 levels
		if( nParaDepth < 5 )
		{
			// our first paragraph with this level?
			if( mnDepthFlags[nParaDepth] == 0 )
			{
				// so set it to the first found
				mnDepthFlags[nParaDepth] = (sal_Int8)pEffect->getNodeType();
			}
			else if( mnDepthFlags[nParaDepth] != pEffect->getNodeType() )
			{
				mnDepthFlags[nParaDepth] = -1;
			}

			if( pEffect->getNodeType() == EffectNodeType::AFTER_PREVIOUS )
				mfGroupingAuto = pEffect->getBegin();

			mnTextGrouping = 0;
			while( (mnTextGrouping < 5) && (mnDepthFlags[mnTextGrouping] > 0) )
				mnTextGrouping++;
		}
	}
	else
	{
		// if we have an effect with the shape as a target, we animate the background
		mbAnimateForm = pEffect->getTargetSubItem() != ShapeAnimationSubType::ONLY_TEXT;
	}
}

// --------------------------------------------------------------------

class TextGroupMapImpl : public std::map< sal_Int32, CustomAnimationTextGroup* >
{
public:
	CustomAnimationTextGroup* findGroup( sal_Int32 nGroupId );
};

// --------------------------------------------------------------------

CustomAnimationTextGroupPtr EffectSequenceHelper::findGroup( sal_Int32 nGroupId )
{
	CustomAnimationTextGroupPtr aPtr;

	CustomAnimationTextGroupMap::iterator aIter( maGroupMap.find( nGroupId ) );
	if( aIter != maGroupMap.end() )
		aPtr = (*aIter).second;

	return aPtr;
}

// --------------------------------------------------------------------

void EffectSequenceHelper::updateTextGroups()
{
	maGroupMap.clear();

	// first create all the groups
	EffectSequence::iterator aIter( maEffects.begin() );
	const EffectSequence::iterator aEnd( maEffects.end() );
	while( aIter != aEnd )
	{
		CustomAnimationEffectPtr pEffect( (*aIter++) );

		const sal_Int32 nGroupId = pEffect->getGroupId();

		if( nGroupId == -1 )
			continue; // trivial case, no group

		CustomAnimationTextGroupPtr pGroup = findGroup( nGroupId );
		if( !pGroup.get() )
		{
			pGroup.reset( new CustomAnimationTextGroup( pEffect->getTargetShape(), nGroupId ) );
			maGroupMap[nGroupId] = pGroup;
		}

		pGroup->addEffect( pEffect );
	}
}

// --------------------------------------------------------------------

CustomAnimationTextGroupPtr	EffectSequenceHelper::createTextGroup( CustomAnimationEffectPtr pEffect, sal_Int32 nTextGrouping, double fTextGroupingAuto, sal_Bool bAnimateForm, sal_Bool bTextReverse )
{
	// first finde a free group-id
	sal_Int32 nGroupId = 0;

	CustomAnimationTextGroupMap::iterator aIter( maGroupMap.begin() );
	const CustomAnimationTextGroupMap::iterator aEnd( maGroupMap.end() );
	while( aIter != aEnd )
	{
		if( (*aIter).first == nGroupId )
		{
			nGroupId++;
			aIter = maGroupMap.begin();
		}
		else
		{
			aIter++;
		}
	}

	Reference< XShape > xTarget( pEffect->getTargetShape() );

	CustomAnimationTextGroupPtr	pTextGroup( new CustomAnimationTextGroup( xTarget, nGroupId ) );
	maGroupMap[nGroupId] = pTextGroup;

	bool bUsed = false;

	// do we need to target the shape?
	if( (nTextGrouping == 0) || bAnimateForm )
	{
		sal_Int16 nSubItem;
		if( nTextGrouping == 0) 
			nSubItem = bAnimateForm ? ShapeAnimationSubType::AS_WHOLE : ShapeAnimationSubType::ONLY_TEXT;
		else
			nSubItem = ShapeAnimationSubType::ONLY_BACKGROUND;

		pEffect->setTarget( makeAny( xTarget ) );
		pEffect->setTargetSubItem( nSubItem );
		pEffect->setEffectSequence( this );
		pEffect->setGroupId( nGroupId );

		pTextGroup->addEffect( pEffect );
		bUsed = true;
	}

	pTextGroup->mnTextGrouping = nTextGrouping;
	pTextGroup->mfGroupingAuto = fTextGroupingAuto;
	pTextGroup->mbTextReverse = bTextReverse;

	// now add an effect for each paragraph
	createTextGroupParagraphEffects( pTextGroup, pEffect, bUsed );

	notify_listeners();

	return pTextGroup;
}

// --------------------------------------------------------------------

void EffectSequenceHelper::createTextGroupParagraphEffects( CustomAnimationTextGroupPtr pTextGroup, CustomAnimationEffectPtr pEffect, bool bUsed )
{
	Reference< XShape > xTarget( pTextGroup->maTarget );

	sal_Int32 nTextGrouping = pTextGroup->mnTextGrouping;
	double fTextGroupingAuto = pTextGroup->mfGroupingAuto;
	sal_Bool bTextReverse = pTextGroup->mbTextReverse;

	// now add an effect for each paragraph
	if( nTextGrouping >= 0 ) try
	{
		EffectSequence::iterator aInsertIter( find( pEffect ) );

		const OUString strNumberingLevel( RTL_CONSTASCII_USTRINGPARAM("NumberingLevel") );
		Reference< XEnumerationAccess > xText( xTarget, UNO_QUERY_THROW );
		Reference< XEnumeration > xEnumeration( xText->createEnumeration(), UNO_QUERY_THROW );

		std::list< sal_Int16 > aParaList;
		sal_Int16 nPara;

		// fill the list with all valid paragraphs
		for( nPara = 0; xEnumeration->hasMoreElements(); nPara++ )
		{
			Reference< XTextRange > xRange( xEnumeration->nextElement(), UNO_QUERY );
			if( xRange.is() && xRange->getString().getLength() )
			{
				if( bTextReverse ) // sort them
					aParaList.push_front( nPara );
				else 
					aParaList.push_back( nPara );
			}
		}

		ParagraphTarget aTarget;
		aTarget.Shape = xTarget;

		std::list< sal_Int16 >::iterator aIter( aParaList.begin() );
		std::list< sal_Int16 >::iterator aEnd( aParaList.end() );
		while( aIter != aEnd )
		{
			aTarget.Paragraph = (*aIter++);

			CustomAnimationEffectPtr pNewEffect;
			if( bUsed )
			{
				// clone a new effect from first effect
				pNewEffect = pEffect->clone();
				++aInsertIter;
				aInsertIter = maEffects.insert( aInsertIter, pNewEffect );
			}
			else
			{
				// reuse first effect if its not yet used
				pNewEffect = pEffect;
				bUsed = true;
				aInsertIter = find( pNewEffect );
			}

			// set target and group-id
			pNewEffect->setTarget( makeAny( aTarget ) );
			pNewEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_TEXT );
			pNewEffect->setGroupId( pTextGroup->mnGroupId );
			pNewEffect->setEffectSequence( this );

			// set correct node type
			if( pNewEffect->getParaDepth() < nTextGrouping )
			{
				if( fTextGroupingAuto == -1.0 )
				{
					pNewEffect->setNodeType( EffectNodeType::ON_CLICK );
					pNewEffect->setBegin( 0.0 );
				}
				else
				{
					pNewEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
					pNewEffect->setBegin( fTextGroupingAuto );
				}
			}
			else
			{
				pNewEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
				pNewEffect->setBegin( 0.0 );
			}

			pTextGroup->addEffect( pNewEffect );
		}
		notify_listeners();
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR("sd::EffectSequenceHelper::createTextGroup(), exception caught!" );
	}
}

// --------------------------------------------------------------------

void EffectSequenceHelper::setTextGrouping( CustomAnimationTextGroupPtr pTextGroup, sal_Int32 nTextGrouping )
{
	if( pTextGroup->mnTextGrouping == nTextGrouping )
	{
		// first case, trivial case, do nothing
	}
	else if( (pTextGroup->mnTextGrouping == -1) && (nTextGrouping >= 0) )
	{
		// second case, we need to add new effects for each paragraph

		CustomAnimationEffectPtr pEffect( pTextGroup->maEffects.front() );

		pTextGroup->mnTextGrouping = nTextGrouping;
		createTextGroupParagraphEffects( pTextGroup, pEffect, true );
		notify_listeners();
	}
	else if( (pTextGroup->mnTextGrouping >= 0) && (nTextGrouping == -1 ) )
	{
		// third case, we need to remove effects for each paragraph

		EffectSequence aEffects( pTextGroup->maEffects );
		pTextGroup->reset();

		EffectSequence::iterator aIter( aEffects.begin() );
		const EffectSequence::iterator aEnd( aEffects.end() );
		while( aIter != aEnd )
		{
			CustomAnimationEffectPtr pEffect( (*aIter++) );

			if( pEffect->getTarget().getValueType() == ::getCppuType((const ParagraphTarget*)0) )
				remove( pEffect );
			else
				pTextGroup->addEffect( pEffect );
		}
		notify_listeners();
	}
	else
	{
		// fourth case, we need to change the node types for the text nodes
		double fTextGroupingAuto = pTextGroup->mfGroupingAuto;

		EffectSequence aEffects( pTextGroup->maEffects );
		pTextGroup->reset();

		EffectSequence::iterator aIter( aEffects.begin() );
		const EffectSequence::iterator aEnd( aEffects.end() );
		while( aIter != aEnd )
		{
			CustomAnimationEffectPtr pEffect( (*aIter++) );

			if( pEffect->getTarget().getValueType() == ::getCppuType((const ParagraphTarget*)0) )
			{
				// set correct node type
				if( pEffect->getParaDepth() < nTextGrouping )
				{
					if( fTextGroupingAuto == -1.0 )
					{
						pEffect->setNodeType( EffectNodeType::ON_CLICK );
						pEffect->setBegin( 0.0 );
					}
					else
					{
						pEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
						pEffect->setBegin( fTextGroupingAuto );
					}
				}
				else
				{
					pEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
					pEffect->setBegin( 0.0 );
				}
			}

			pTextGroup->addEffect( pEffect );

		}
		notify_listeners();
	}
}

// --------------------------------------------------------------------

void EffectSequenceHelper::setAnimateForm( CustomAnimationTextGroupPtr pTextGroup, sal_Bool bAnimateForm )
{
	if( pTextGroup->mbAnimateForm == bAnimateForm )
	{
		// trivial case, do nothing
	}
	else
	{
		EffectSequence aEffects( pTextGroup->maEffects );
		pTextGroup->reset();

		EffectSequence::iterator aIter( aEffects.begin() );
		const EffectSequence::iterator aEnd( aEffects.end() );

		// first insert if we have to
		if( bAnimateForm )
		{
			EffectSequence::iterator aInsertIter( find( (*aIter) ) );
			
			CustomAnimationEffectPtr pEffect;
			if( (aEffects.size() == 1) && ((*aIter)->getTarget().getValueType() != ::getCppuType((const ParagraphTarget*)0) ) )
			{
				// special case, only one effect and that targets whole text,
				// convert this to target whole shape
				pEffect = (*aIter++);
				pEffect->setTargetSubItem( ShapeAnimationSubType::AS_WHOLE );
			}
			else
			{
				pEffect = (*aIter)->clone();
				pEffect->setTarget( makeAny( (*aIter)->getTargetShape() ) );
				pEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_BACKGROUND );
				maEffects.insert( aInsertIter, pEffect );
			}

			pTextGroup->addEffect( pEffect );
		}
		
		if( !bAnimateForm && (aEffects.size() == 1) )
		{
			CustomAnimationEffectPtr pEffect( (*aIter) );
			pEffect->setTarget( makeAny( (*aIter)->getTargetShape() ) );
			pEffect->setTargetSubItem( ShapeAnimationSubType::ONLY_TEXT );
			pTextGroup->addEffect( pEffect );
		}
		else
		{
			// readd the rest to the group again
			while( aIter != aEnd )
			{
				CustomAnimationEffectPtr pEffect( (*aIter++) );

				if( pEffect->getTarget().getValueType() == ::getCppuType((const ParagraphTarget*)0) )
				{
					pTextGroup->addEffect( pEffect );
				}
				else
				{
					DBG_ASSERT( !bAnimateForm, "sd::EffectSequenceHelper::setAnimateForm(), something is wrong here!" );
					remove( pEffect );
				}
			}
		}
		notify_listeners();
	}
}

// --------------------------------------------------------------------

void EffectSequenceHelper::setTextGroupingAuto( CustomAnimationTextGroupPtr pTextGroup, double fTextGroupingAuto )
{
	sal_Int32 nTextGrouping = pTextGroup->mnTextGrouping;

	EffectSequence aEffects( pTextGroup->maEffects );
	pTextGroup->reset();

	EffectSequence::iterator aIter( aEffects.begin() );
	const EffectSequence::iterator aEnd( aEffects.end() );
	while( aIter != aEnd )
	{
		CustomAnimationEffectPtr pEffect( (*aIter++) );

		if( pEffect->getTarget().getValueType() == ::getCppuType((const ParagraphTarget*)0) )
		{
			// set correct node type
			if( pEffect->getParaDepth() < nTextGrouping )
			{
				if( fTextGroupingAuto == -1.0 )
				{
					pEffect->setNodeType( EffectNodeType::ON_CLICK );
					pEffect->setBegin( 0.0 );
				}
				else
				{
					pEffect->setNodeType( EffectNodeType::AFTER_PREVIOUS );
					pEffect->setBegin( fTextGroupingAuto );
				}
			}
			else
			{
				pEffect->setNodeType( EffectNodeType::WITH_PREVIOUS );
				pEffect->setBegin( 0.0 );
			}
		}

		pTextGroup->addEffect( pEffect );

	}
	notify_listeners();
}

// --------------------------------------------------------------------

struct ImplStlTextGroupSortHelper
{
	ImplStlTextGroupSortHelper( bool bReverse ) : mbReverse( bReverse ) {};
	bool operator()( const CustomAnimationEffectPtr& p1, const CustomAnimationEffectPtr& p2 );
	bool mbReverse;
	sal_Int32 getTargetParagraph( const CustomAnimationEffectPtr& p1 );
};

// --------------------------------------------------------------------

sal_Int32 ImplStlTextGroupSortHelper::getTargetParagraph( const CustomAnimationEffectPtr& p1 )
{
	const Any aTarget(p1->getTarget());
	if( aTarget.hasValue() && aTarget.getValueType() == ::getCppuType((const ParagraphTarget*)0) )
	{
		ParagraphTarget aParaTarget;
		aTarget >>= aParaTarget;
		return aParaTarget.Paragraph;
	}
	else
	{
		return mbReverse ? 0x7fffffff : -1;
	}
}

// --------------------------------------------------------------------

bool ImplStlTextGroupSortHelper::operator()( const CustomAnimationEffectPtr& p1, const CustomAnimationEffectPtr& p2 )
{
	if( mbReverse )
	{
		return getTargetParagraph( p2 ) < getTargetParagraph( p1 );
	}
	else
	{
		return getTargetParagraph( p1 ) < getTargetParagraph( p2 );
	}
}

// --------------------------------------------------------------------

void EffectSequenceHelper::setTextReverse( CustomAnimationTextGroupPtr pTextGroup, sal_Bool bTextReverse )
{
	if( pTextGroup->mbTextReverse == bTextReverse )
	{
		// do nothing
	}
	else
	{
		std::vector< CustomAnimationEffectPtr > aSortedVector(pTextGroup->maEffects.size());
		std::copy( pTextGroup->maEffects.begin(), pTextGroup->maEffects.end(), aSortedVector.begin() );
		ImplStlTextGroupSortHelper aSortHelper( bTextReverse );
		std::sort( aSortedVector.begin(), aSortedVector.end(), aSortHelper );

		pTextGroup->reset();

		std::vector< CustomAnimationEffectPtr >::iterator aIter( aSortedVector.begin() );
		const std::vector< CustomAnimationEffectPtr >::iterator aEnd( aSortedVector.end() );

		if( aIter != aEnd )
		{
			pTextGroup->addEffect( (*aIter ) );
			EffectSequence::iterator aInsertIter( find( (*aIter++) ) );
			while( aIter != aEnd )
			{
				CustomAnimationEffectPtr pEffect( (*aIter++) );
				maEffects.erase( find( pEffect ) );
				aInsertIter = maEffects.insert( ++aInsertIter, pEffect );
				pTextGroup->addEffect( pEffect );
			}
		}
		notify_listeners();
	}
}

// --------------------------------------------------------------------

void EffectSequenceHelper::addListener( ISequenceListener* pListener )
{
	if( std::find( maListeners.begin(), maListeners.end(), pListener ) == maListeners.end() )
		maListeners.push_back( pListener );
}

// --------------------------------------------------------------------

void EffectSequenceHelper::removeListener( ISequenceListener* pListener )
{
	maListeners.remove( pListener );
}

// --------------------------------------------------------------------

struct stl_notify_listeners_func : public std::unary_function<ISequenceListener*, void>
{
	stl_notify_listeners_func() {}
	void operator()(ISequenceListener* pListener) { pListener->notify_change(); }
};

// --------------------------------------------------------------------

void EffectSequenceHelper::notify_listeners()
{
	stl_notify_listeners_func aFunc;
	std::for_each( maListeners.begin(), maListeners.end(), aFunc );
}

// --------------------------------------------------------------------

void EffectSequenceHelper::create( const ::com::sun::star::uno::Reference< ::com::sun::star::animations::XAnimationNode >& xNode )
{
	DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::create(), illegal argument" );

	if( xNode.is() ) try
	{
		Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
		Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_THROW );
		while( xEnumeration->hasMoreElements() )
		{
			Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
			createEffectsequence( xChildNode );
		}
	}
	catch( Exception& )
	{
		DBG_ERROR( "sd::EffectSequenceHelper::create(), exception caught!" );
	}
}

// --------------------------------------------------------------------

void EffectSequenceHelper::createEffectsequence( const Reference< XAnimationNode >& xNode )
{
	DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffectsequence(), illegal argument" );

	if( xNode.is() ) try
	{
		Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
		Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_THROW );
		while( xEnumeration->hasMoreElements() )
		{
			Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );

			createEffects( xChildNode );
		}
	}
	catch( Exception& )
	{
		DBG_ERROR( "sd::EffectSequenceHelper::createEffectsequence(), exception caught!" );
	}
}

// --------------------------------------------------------------------

void EffectSequenceHelper::createEffects( const Reference< XAnimationNode >& xNode )
{
	DBG_ASSERT( xNode.is(), "sd::EffectSequenceHelper::createEffects(), illegal argument" );

	if( xNode.is() ) try
	{
		Reference< XEnumerationAccess > xEnumerationAccess( xNode, UNO_QUERY_THROW );
		Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_THROW );
		while( xEnumeration->hasMoreElements() )
		{
			Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );

			switch( xChildNode->getType() )
			{
			// found an effect
			case AnimationNodeType::PAR:
			case AnimationNodeType::ITERATE:
				{
					CustomAnimationEffectPtr pEffect( new CustomAnimationEffect( xChildNode ) );

					if( pEffect->mnNodeType != -1 )
					{
						pEffect->setEffectSequence( this );
						maEffects.push_back(pEffect);
					}
				}
				break;

			// found an after effect
			case AnimationNodeType::SET:
			case AnimationNodeType::ANIMATECOLOR:
				{
					processAfterEffect( xChildNode );
				}
				break;
			}
		}
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR( "sd::EffectSequenceHelper::createEffects(), exception caught!" );
	}
}

// --------------------------------------------------------------------

void EffectSequenceHelper::processAfterEffect( const Reference< XAnimationNode >& xNode )
{
	try
	{
		Reference< XAnimationNode > xMaster;

		Sequence< NamedValue > aUserData( xNode->getUserData() );
		sal_Int32 nLength = aUserData.getLength();
		const NamedValue* p = aUserData.getConstArray();

		while( nLength-- )
		{
			if( p->Name.equalsAscii( "master-element" ) )
			{
				p->Value >>= xMaster;
				break;
			}
			p++;
		}

		// only process if this is a valid after effect
		if( xMaster.is() )
		{
			CustomAnimationEffectPtr pMasterEffect;

			// find the master effect
			stl_CustomAnimationEffect_search_node_predict aSearchPredict( xMaster );
			EffectSequence::iterator aIter( std::find_if( maEffects.begin(), maEffects.end(), aSearchPredict ) );
			if( aIter != maEffects.end() )
				pMasterEffect = (*aIter );

			if( pMasterEffect.get() )
			{
				pMasterEffect->setHasAfterEffect( true );

				// find out what kind of after effect this is
				if( xNode->getType() == AnimationNodeType::ANIMATECOLOR )
				{
					// its a dim
					Reference< XAnimate > xAnimate( xNode, UNO_QUERY_THROW );
					pMasterEffect->setDimColor( xAnimate->getTo() );
					pMasterEffect->setAfterEffectOnNext( true );
				}
				else
				{
					// its a hide
					Reference< XChild > xNodeChild( xNode, UNO_QUERY_THROW );
					Reference< XChild > xMasterChild( xMaster, UNO_QUERY_THROW );
					pMasterEffect->setAfterEffectOnNext( xNodeChild->getParent() != xMasterChild->getParent() );
				}
			}
		}
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR( "sd::EffectSequenceHelper::processAfterEffect(), exception caught!" );
	}	
}

/*
double EffectSequenceHelper::calculateIterateNodeDuration(
{
    Reference< i18n::XBreakIterator > xBI( ImplGetBreakIterator() );

    sal_Int32 nDone;    
    sal_Int32 nNextCellBreak( xBI->nextCharacters(rTxt, nIdx, rLocale, i18n::CharacterIteratorMode::SKIPCELL, 0, nDone) );
    i18n::Boundary nNextWordBoundary( xBI->getWordBoundary(rTxt, nIdx, rLocale, i18n::WordType::ANY_WORD, sal_True) );
    sal_Int32 nNextSentenceBreak( xBI->endOfSentence(rTxt, nIdx, rLocale) );

    const sal_Int32 nEndPos( nIdx + nLen );
    sal_Int32 i, currOffset(0);
    for( i=nIdx; i<nEndPos; ++i )
    {
        // TODO: Check whether position update is valid for CTL/BiDi
        rOutDev.DrawText( rPos + Point(currOffset,0), rTxt, i, 1 );
        currOffset = *pDXArray++;

        // issue the comments at the respective break positions
        if( i == nNextCellBreak )
        {
            rMtf.AddAction( new MetaCommentAction( "XTEXT_EOC" ) );
            nNextCellBreak = xBI->nextCharacters(rTxt, i, rLocale, i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
        }
        if( i == nNextWordBoundary.endPos )
        {
            rMtf.AddAction( new MetaCommentAction( "XTEXT_EOW" ) );
            nNextWordBoundary = xBI->getWordBoundary(rTxt, i+1, rLocale, i18n::WordType::ANY_WORD, sal_True);
        }
        if( i == nNextSentenceBreak )
        {
            rMtf.AddAction( new MetaCommentAction( "XTEXT_EOS" ) );
            nNextSentenceBreak = xBI->endOfSentence(rTxt, i+1, rLocale);
        }
    }
}

*/
// ====================================================================

class AnimationChangeListener : public cppu::WeakImplHelper1< XChangesListener >
{
public:
	AnimationChangeListener( MainSequence* pMainSequence ) : mpMainSequence( pMainSequence ) {}

    virtual void SAL_CALL changesOccurred( const ::com::sun::star::util::ChangesEvent& Event ) throw (RuntimeException);
    virtual void SAL_CALL disposing( const ::com::sun::star::lang::EventObject& Source ) throw (RuntimeException);
private:
	MainSequence* mpMainSequence;
};

void SAL_CALL AnimationChangeListener::changesOccurred( const ::com::sun::star::util::ChangesEvent& ) throw (RuntimeException)
{
	if( mpMainSequence )
		mpMainSequence->startRecreateTimer();
}

void SAL_CALL AnimationChangeListener::disposing( const ::com::sun::star::lang::EventObject& ) throw (RuntimeException)
{
}

// ====================================================================

MainSequence::MainSequence()
: mxTimingRootNode( ::comphelper::getProcessServiceFactory()->createInstance(OUString(RTL_CONSTASCII_USTRINGPARAM("com.sun.star.animations.SequenceTimeContainer"))), UNO_QUERY )
, mbRebuilding( false )
, mnRebuildLockGuard( 0 )
, mbPendingRebuildRequest( false )
{
	if( mxTimingRootNode.is() )
	{
		Sequence< ::com::sun::star::beans::NamedValue > aUserData( 1 );
		aUserData[0].Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "node-type" ) );
		aUserData[0].Value <<= ::com::sun::star::presentation::EffectNodeType::MAIN_SEQUENCE;
		mxTimingRootNode->setUserData( aUserData );
	}
	init();
}

// --------------------------------------------------------------------

MainSequence::MainSequence( const ::com::sun::star::uno::Reference< ::com::sun::star::animations::XAnimationNode >& xNode )
: mxTimingRootNode( xNode, UNO_QUERY )
, mbRebuilding( false )
, mnRebuildLockGuard( 0 )
, mbPendingRebuildRequest( false )
, mbIgnoreChanges( 0 )
{
	init();
}

// --------------------------------------------------------------------

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

// --------------------------------------------------------------------

void MainSequence::init()
{
	mnSequenceType = EffectNodeType::MAIN_SEQUENCE;

	maTimer.SetTimeoutHdl( LINK(this, MainSequence, onTimerHdl) );
	maTimer.SetTimeout(500);

	mxChangesListener.set( new AnimationChangeListener( this ) );

	createMainSequence();
}

// --------------------------------------------------------------------

void MainSequence::reset( const ::com::sun::star::uno::Reference< ::com::sun::star::animations::XAnimationNode >& xTimingRootNode )
{
	reset();

	mxTimingRootNode.set( xTimingRootNode, UNO_QUERY );

	createMainSequence();
}

// --------------------------------------------------------------------

Reference< ::com::sun::star::animations::XAnimationNode > MainSequence::getRootNode()
{
	DBG_ASSERT( mnRebuildLockGuard == 0, "MainSequence::getRootNode(), rebuild is locked, ist this really what you want?" );

	if( maTimer.IsActive() && mbTimerMode )
	{
		// force a rebuild NOW if one is pending
		maTimer.Stop();
		implRebuild();
	}

	return EffectSequenceHelper::getRootNode();
}

// --------------------------------------------------------------------

void MainSequence::createMainSequence()
{
	if( mxTimingRootNode.is() ) try
	{
		Reference< XEnumerationAccess > xEnumerationAccess( mxTimingRootNode, UNO_QUERY_THROW );
		Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_THROW );
		while( xEnumeration->hasMoreElements() )
		{
			Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
			sal_Int32 nNodeType = CustomAnimationEffect::get_node_type( xChildNode );
			if( nNodeType == EffectNodeType::MAIN_SEQUENCE )
			{
				mxSequenceRoot.set( xChildNode, UNO_QUERY );
				EffectSequenceHelper::create( xChildNode );
			}
			else if( nNodeType == EffectNodeType::INTERACTIVE_SEQUENCE )
			{
				Reference< XTimeContainer > xInteractiveRoot( xChildNode, UNO_QUERY_THROW );
				InteractiveSequencePtr pIS( new InteractiveSequence( xInteractiveRoot, this ) );
				pIS->addListener( this );
				maInteractiveSequenceList.push_back( pIS );
			}
		}

		// see if we have a mainsequence at all. if not, create one...
		if( !mxSequenceRoot.is() )
		{
			mxSequenceRoot = Reference< XTimeContainer >::query(::comphelper::getProcessServiceFactory()->createInstance(OUString(RTL_CONSTASCII_USTRINGPARAM("com.sun.star.animations.SequenceTimeContainer"))));
			if( mxSequenceRoot.is() )
			{
				uno::Sequence< ::com::sun::star::beans::NamedValue > aUserData( 1 );
				aUserData[0].Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "node-type" ) );
				aUserData[0].Value <<= ::com::sun::star::presentation::EffectNodeType::MAIN_SEQUENCE;
				mxSequenceRoot->setUserData( aUserData );

                // empty sequence until now, set duration to 0.0
                // explicitely (otherwise, this sequence will never
                // end)
                mxSequenceRoot->setDuration( makeAny((double)0.0) );

				Reference< XAnimationNode > xMainSequenceNode( mxSequenceRoot, UNO_QUERY_THROW );
				mxTimingRootNode->appendChild( xMainSequenceNode );
			}
		}

		updateTextGroups();

		notify_listeners();

		Reference< XChangesNotifier > xNotifier( mxTimingRootNode, UNO_QUERY );
		if( xNotifier.is() )
			xNotifier->addChangesListener( mxChangesListener );
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR( "sd::MainSequence::create(), exception caught!" );
		return;
	}

	DBG_ASSERT( mxSequenceRoot.is(), "sd::MainSequence::create(), found no main sequence!" );
}

// --------------------------------------------------------------------

void MainSequence::reset()
{
	EffectSequenceHelper::reset();

	InteractiveSequenceList::iterator aIter;
	for( aIter = maInteractiveSequenceList.begin(); aIter != maInteractiveSequenceList.end(); aIter++ )
		(*aIter)->reset();
	maInteractiveSequenceList.clear();

	try
	{
		Reference< XChangesNotifier > xNotifier( mxTimingRootNode, UNO_QUERY );
		if( xNotifier.is() )
			xNotifier->removeChangesListener( mxChangesListener );
	}
	catch( Exception& )
	{
		// ...
	}
}

// --------------------------------------------------------------------

InteractiveSequencePtr MainSequence::createInteractiveSequence( const ::com::sun::star::uno::Reference< ::com::sun::star::drawing::XShape >& xShape )
{
	InteractiveSequencePtr pIS;

	// create a new interactive sequence container
	Reference< XTimeContainer > xISRoot( ::comphelper::getProcessServiceFactory()->createInstance(OUString(RTL_CONSTASCII_USTRINGPARAM("com.sun.star.animations.SequenceTimeContainer"))), UNO_QUERY );
	DBG_ASSERT( xISRoot.is(), "sd::MainSequence::createInteractiveSequence(), could not create \"com.sun.star.animations.SequenceTimeContainer\"!");
	if( xISRoot.is() )
	{
		uno::Sequence< ::com::sun::star::beans::NamedValue > aUserData( 1 );
		aUserData[0].Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "node-type" ) );
		aUserData[0].Value <<= ::com::sun::star::presentation::EffectNodeType::INTERACTIVE_SEQUENCE ;
		xISRoot->setUserData( aUserData );

		Reference< XChild > xChild( mxSequenceRoot, UNO_QUERY_THROW );
		Reference< XAnimationNode > xISNode( xISRoot, UNO_QUERY_THROW );
		Reference< XTimeContainer > xParent( xChild->getParent(), UNO_QUERY_THROW );
		xParent->appendChild( xISNode );
	}
	pIS.reset( new InteractiveSequence( xISRoot, this) );
    pIS->setTriggerShape( xShape );
	pIS->addListener( this );
	maInteractiveSequenceList.push_back( pIS );
	return pIS;
}

// --------------------------------------------------------------------

CustomAnimationEffectPtr MainSequence::findEffect( const ::com::sun::star::uno::Reference< ::com::sun::star::animations::XAnimationNode >& xNode ) const
{
	CustomAnimationEffectPtr pEffect = EffectSequenceHelper::findEffect( xNode );

	if( pEffect.get() == 0 )
	{
		InteractiveSequenceList::const_iterator aIter;
		for( aIter = maInteractiveSequenceList.begin(); (aIter != maInteractiveSequenceList.end()) && (pEffect.get() == 0); aIter++ )
		{
			pEffect = (*aIter)->findEffect( xNode );
		}
	}
	return pEffect;
}

// --------------------------------------------------------------------

sal_Int32 MainSequence::getOffsetFromEffect( const CustomAnimationEffectPtr& pEffect ) const
{
	sal_Int32 nOffset = EffectSequenceHelper::getOffsetFromEffect( pEffect );

	if( nOffset != -1 )
		return nOffset;

	nOffset = EffectSequenceHelper::getCount();

	InteractiveSequenceList::const_iterator aIter;
	for( aIter = maInteractiveSequenceList.begin(); aIter != maInteractiveSequenceList.end(); aIter++ )
	{
		sal_Int32 nTemp = (*aIter)->getOffsetFromEffect( pEffect );
		if( nTemp != -1 )
			return nOffset + nTemp;

		nOffset += (*aIter)->getCount();
	}

	return -1;
}

// --------------------------------------------------------------------

CustomAnimationEffectPtr MainSequence::getEffectFromOffset( sal_Int32 nOffset ) const
{
	if( nOffset >= 0 )
	{
		if( nOffset < getCount() )
			return EffectSequenceHelper::getEffectFromOffset( nOffset );
		
		nOffset -= getCount();

		InteractiveSequenceList::const_iterator aIter( maInteractiveSequenceList.begin() );

		while( (aIter != maInteractiveSequenceList.end()) && (nOffset > (*aIter)->getCount()) )
			nOffset -= (*aIter++)->getCount();

		if( (aIter != maInteractiveSequenceList.end()) && (nOffset >= 0) )
			return (*aIter)->getEffectFromOffset( nOffset );
	}

	CustomAnimationEffectPtr pEffect;
	return pEffect;
}

// --------------------------------------------------------------------

bool MainSequence::disposeShape( const Reference< XShape >& xShape )
{
	bool bChanges = EffectSequenceHelper::disposeShape( xShape );

	InteractiveSequenceList::iterator aIter;
	for( aIter = maInteractiveSequenceList.begin(); aIter != maInteractiveSequenceList.end();  )
	{
			bChanges |= (*aIter++)->disposeShape( xShape );
	}

	if( bChanges )
		startRebuildTimer();

	return bChanges;
}

// --------------------------------------------------------------------

bool MainSequence::hasEffect( const com::sun::star::uno::Reference< com::sun::star::drawing::XShape >& xShape )
{
	if( EffectSequenceHelper::hasEffect( xShape ) )
		return true;

	InteractiveSequenceList::iterator aIter;
	for( aIter = maInteractiveSequenceList.begin(); aIter != maInteractiveSequenceList.end();  )
	{
		if( (*aIter)->getTriggerShape() == xShape )
			return true;

		if( (*aIter++)->hasEffect( xShape ) )
			return true;
	}

	return false;
}

// --------------------------------------------------------------------

void MainSequence::insertTextRange( const com::sun::star::uno::Any& aTarget )
{
	EffectSequenceHelper::insertTextRange( aTarget );

	InteractiveSequenceList::iterator aIter;
	for( aIter = maInteractiveSequenceList.begin(); aIter != maInteractiveSequenceList.end(); aIter++ )
	{
		(*aIter)->insertTextRange( aTarget );
	}
}
// --------------------------------------------------------------------

void MainSequence::disposeTextRange( const com::sun::star::uno::Any& aTarget )
{
	EffectSequenceHelper::disposeTextRange( aTarget );

	InteractiveSequenceList::iterator aIter;
	for( aIter = maInteractiveSequenceList.begin(); aIter != maInteractiveSequenceList.end(); aIter++ )
	{
		(*aIter)->disposeTextRange( aTarget );
	}
}

// --------------------------------------------------------------------

/** callback from the sd::View when an object just left text edit mode */
void MainSequence::onTextChanged( const Reference< XShape >& xShape )
{
	EffectSequenceHelper::onTextChanged( xShape );

	InteractiveSequenceList::iterator aIter;
	for( aIter = maInteractiveSequenceList.begin(); aIter != maInteractiveSequenceList.end(); aIter++ )
	{
		(*aIter)->onTextChanged( xShape );
	}
}

// --------------------------------------------------------------------

void EffectSequenceHelper::onTextChanged( const Reference< XShape >& xShape )
{
	bool bChanges = false;

	EffectSequence::iterator aIter;
	for( aIter = maEffects.begin(); aIter != maEffects.end(); aIter++ )
	{
		if( (*aIter)->getTargetShape() == xShape )
			bChanges |= (*aIter)->checkForText();
	}

	if( bChanges )
		EffectSequenceHelper::implRebuild();
}

// --------------------------------------------------------------------

void MainSequence::rebuild()
{
	startRebuildTimer();
}

// --------------------------------------------------------------------

void MainSequence::lockRebuilds()
{
	mnRebuildLockGuard++;
}

// --------------------------------------------------------------------

void MainSequence::unlockRebuilds()
{
	DBG_ASSERT( mnRebuildLockGuard, "sd::MainSequence::unlockRebuilds(), no corresponding lockRebuilds() call!" );
	if( mnRebuildLockGuard )
		mnRebuildLockGuard--;

	if( (mnRebuildLockGuard == 0) && mbPendingRebuildRequest )
	{
		mbPendingRebuildRequest = false;
		startRebuildTimer();
	}
}

// --------------------------------------------------------------------

void MainSequence::implRebuild()
{
	if( mnRebuildLockGuard )
	{
		mbPendingRebuildRequest = true;
		return;
	}

	mbRebuilding = true;

	EffectSequenceHelper::implRebuild();

	InteractiveSequenceList::iterator aIter( maInteractiveSequenceList.begin() );
	const InteractiveSequenceList::iterator aEnd( maInteractiveSequenceList.end() );
	while( aIter != aEnd )
	{
		InteractiveSequencePtr pIS( (*aIter) );
		if( pIS->maEffects.empty() )
		{
			// remove empty interactive sequences
			aIter = maInteractiveSequenceList.erase( aIter );

			Reference< XChild > xChild( mxSequenceRoot, UNO_QUERY_THROW );
			Reference< XTimeContainer > xParent( xChild->getParent(), UNO_QUERY_THROW );
			Reference< XAnimationNode > xISNode( pIS->mxSequenceRoot, UNO_QUERY_THROW );
			xParent->removeChild( xISNode );
		}
		else
		{
			pIS->implRebuild();
			aIter++;
		}
	}

	notify_listeners();
	mbRebuilding = false;
}

// --------------------------------------------------------------------

void MainSequence::notify_change()
{
	notify_listeners();
}

// --------------------------------------------------------------------

bool MainSequence::setTrigger( const CustomAnimationEffectPtr& pEffect, const ::com::sun::star::uno::Reference< ::com::sun::star::drawing::XShape >& xTriggerShape )
{
	EffectSequenceHelper* pOldSequence = pEffect->getEffectSequence();

	EffectSequenceHelper* pNewSequence = 0;
	if( xTriggerShape.is() )
	{
		InteractiveSequenceList::iterator aIter( maInteractiveSequenceList.begin() );
		const InteractiveSequenceList::iterator aEnd( maInteractiveSequenceList.end() );
		while( aIter != aEnd )
		{
			InteractiveSequencePtr pIS( (*aIter++) );
			if( pIS->getTriggerShape() == xTriggerShape )
			{
				pNewSequence = pIS.get();
				break;
			}
		}

		if( !pNewSequence )
			pNewSequence = createInteractiveSequence( xTriggerShape ).get();
	}
	else
	{
		pNewSequence = this;
	}

	if( pOldSequence != pNewSequence )
	{
		if( pOldSequence )
			pOldSequence->maEffects.remove( pEffect );
		if( pNewSequence )
			pNewSequence->maEffects.push_back( pEffect );
		pEffect->setEffectSequence( pNewSequence );
		return true;
	}
	else
	{
		return false;
	}

}

// --------------------------------------------------------------------

IMPL_LINK( MainSequence, onTimerHdl, Timer *, EMPTYARG )
{
	if( mbTimerMode )
	{
		implRebuild();
	}
	else
	{
		reset();
		createMainSequence();
	}

	return 0;
}

// --------------------------------------------------------------------

/** starts a timer that recreates the internal structure from the API core after 1 second */
void MainSequence::startRecreateTimer()
{
	if( !mbRebuilding && (mbIgnoreChanges == 0) )
	{
		mbTimerMode = false;
		maTimer.Start();
	}
}

// --------------------------------------------------------------------

/** starts a timer that rebuilds the API core from the internal structure after 1 second */
void MainSequence::startRebuildTimer()
{
	mbTimerMode = true;
	maTimer.Start();
}

// ====================================================================

InteractiveSequence::InteractiveSequence( const Reference< XTimeContainer >& xSequenceRoot, MainSequence* pMainSequence )
: EffectSequenceHelper( xSequenceRoot ), mpMainSequence( pMainSequence )
{
	mnSequenceType = EffectNodeType::INTERACTIVE_SEQUENCE;

	try
	{
		if( mxSequenceRoot.is() )
		{
			Reference< XEnumerationAccess > xEnumerationAccess( mxSequenceRoot, UNO_QUERY_THROW );
			Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_THROW );
			while( !mxEventSource.is() && xEnumeration->hasMoreElements() )
			{
				Reference< XAnimationNode > xChildNode( xEnumeration->nextElement(), UNO_QUERY_THROW );

				Event aEvent;
				if( (xChildNode->getBegin() >>= aEvent) && (aEvent.Trigger == EventTrigger::ON_CLICK) )
					aEvent.Source >>= mxEventSource;
			}
		}
	}
	catch( Exception& e )
	{
		(void)e;
		DBG_ERROR( "sd::InteractiveSequence::InteractiveSequence(), exception caught!" );
		return;
	}
}

// --------------------------------------------------------------------

void InteractiveSequence::rebuild()
{
	mpMainSequence->rebuild();
}

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

// --------------------------------------------------------------------

MainSequenceRebuildGuard::MainSequenceRebuildGuard( const MainSequencePtr& pMainSequence )
: mpMainSequence( pMainSequence )
{
	if( mpMainSequence.get() )
		mpMainSequence->lockRebuilds();
}

MainSequenceRebuildGuard::~MainSequenceRebuildGuard()
{
	if( mpMainSequence.get() )
		mpMainSequence->unlockRebuilds();
}


}