/************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_slideshow.hxx" // must be first #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "animationnodefactory.hxx" #include "paralleltimecontainer.hxx" #include "sequentialtimecontainer.hxx" #include "propertyanimationnode.hxx" #include "animationsetnode.hxx" #include "animationpathmotionnode.hxx" #include "animationcolornode.hxx" #include "animationtransformnode.hxx" #include "animationtransitionfilternode.hxx" #include "animationaudionode.hxx" #include "animationcommandnode.hxx" #include "nodetools.hxx" #include "tools.hxx" #include using namespace ::com::sun::star; namespace slideshow { namespace internal { namespace { // forward declaration needed by NodeCreator BaseNodeSharedPtr implCreateAnimationNode( const uno::Reference< animations::XAnimationNode >& xNode, const BaseContainerNodeSharedPtr& rParent, const NodeContext& rContext ); class NodeCreator { public: NodeCreator( BaseContainerNodeSharedPtr& rParent, const NodeContext& rContext ) : mrParent( rParent ), mrContext( rContext ) {} void operator()( const uno::Reference< animations::XAnimationNode >& xChildNode ) const { createChild( xChildNode, mrContext ); } protected: void createChild( const uno::Reference< animations::XAnimationNode >& xChildNode, const NodeContext& rContext ) const { BaseNodeSharedPtr pChild( implCreateAnimationNode( xChildNode, mrParent, rContext ) ); OSL_ENSURE( pChild, "NodeCreator::operator(): child creation failed" ); // TODO(Q1): This yields circular references, which, it seems, is // unavoidable here if( pChild ) mrParent->appendChildNode( pChild ); } BaseContainerNodeSharedPtr& mrParent; const NodeContext& mrContext; }; /** Same as NodeCreator, only that NodeContext's SubsetShape is cloned for every child node. This is used for iterated animation node generation */ class CloningNodeCreator : private NodeCreator { public: CloningNodeCreator( BaseContainerNodeSharedPtr& rParent, const NodeContext& rContext ) : NodeCreator( rParent, rContext ) {} void operator()( const uno::Reference< animations::XAnimationNode >& xChildNode ) const { NodeContext aContext( mrContext ); // TODO(Q1): There's a catch here. If you clone a // subset whose actual subsetting has already been // realized (i.e. if enableSubsetShape() has been // called already), and the original of your clone // goes out of scope, then your subset will be // gone (SubsettableShapeManager::revokeSubset() be // called). As of now, this behaviour is not // triggered here (we either clone, XOR we enable // subset initially), but one might consider // reworking DrawShape/ShapeSubset to avoid this. // clone ShapeSubset, since each node needs their // own version of the ShapeSubset (otherwise, // e.g. activity counting does not work - subset // would be removed after first animation node // disables it). // // NOTE: this is only a problem for animation // nodes that explicitely call // disableSubsetShape(). Independent shape subsets // (like those created for ParagraphTargets) // solely rely on the ShapeSubset destructor to // normalize things, which does the right thing // here: the subset is only removed after _the // last_ animation node releases the shared ptr. aContext.mpMasterShapeSubset.reset( new ShapeSubset( *aContext.mpMasterShapeSubset ) ); createChild( xChildNode, aContext ); } }; /** Create animation nodes for text iterations This method clones the animation nodes below xIterNode for every iterated shape entity. */ bool implCreateIteratedNodes( const uno::Reference< animations::XIterateContainer >& xIterNode, BaseContainerNodeSharedPtr& rParent, const NodeContext& rContext ) { ENSURE_OR_THROW( xIterNode.is(), "implCreateIteratedNodes(): Invalid node" ); const double nIntervalTimeout( xIterNode->getIterateInterval() ); // valid iterate interval? We're ruling out monstrous // values here, to avoid pseudo 'hangs' in the // presentation if( nIntervalTimeout < 0.0 || nIntervalTimeout > 1000.0 ) { return false; // not an active iteration } if( ::basegfx::fTools::equalZero( nIntervalTimeout ) ) OSL_TRACE( "implCreateIteratedNodes(): " "iterate interval close to zero, there's " "no point in defining such an effect " "(visually equivalent to whole-shape effect)" ); // Determine target shape (or subset) // ================================== // TODO(E1): I'm not too sure what to expect here... ENSURE_OR_RETURN_FALSE( xIterNode->getTarget().hasValue(), "implCreateIteratedNodes(): no target on ITERATE node" ); uno::Reference< drawing::XShape > xTargetShape( xIterNode->getTarget(), uno::UNO_QUERY ); presentation::ParagraphTarget aTarget; sal_Int16 nSubItem( xIterNode->getSubItem() ); bool bParagraphTarget( false ); if( !xTargetShape.is() ) { // no shape provided. Maybe a ParagraphTarget? if( !(xIterNode->getTarget() >>= aTarget) ) ENSURE_OR_RETURN_FALSE( false, "implCreateIteratedNodes(): could not extract any " "target information" ); xTargetShape = aTarget.Shape; ENSURE_OR_RETURN_FALSE( xTargetShape.is(), "implCreateIteratedNodes(): invalid shape in ParagraphTarget" ); // we've a paragraph target to iterate over, thus, // the whole animation container refers only to // the text nSubItem = presentation::ShapeAnimationSubType::ONLY_TEXT; bParagraphTarget = true; } // Lookup shape, and fill NodeContext // ================================== AttributableShapeSharedPtr pTargetShape( lookupAttributableShape( rContext.maContext.mpSubsettableShapeManager, xTargetShape ) ); const DocTreeNodeSupplier& rTreeNodeSupplier( pTargetShape->getTreeNodeSupplier() ); ShapeSubsetSharedPtr pTargetSubset; NodeContext aContext( rContext ); // paragraph targets already need a subset as the // master shape (they're representing only a single // paragraph) if( bParagraphTarget ) { ENSURE_OR_RETURN_FALSE( aTarget.Paragraph >= 0 && rTreeNodeSupplier.getNumberOfTreeNodes( DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ) > aTarget.Paragraph, "implCreateIteratedNodes(): paragraph index out of range" ); pTargetSubset.reset( new ShapeSubset( pTargetShape, // retrieve index aTarget.Paragraph of // type PARAGRAPH from this shape rTreeNodeSupplier.getTreeNode( aTarget.Paragraph, DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ), rContext.maContext.mpSubsettableShapeManager ) ); // iterate target is not the whole shape, but only // the selected paragraph - subset _must_ be // independent, to be able to affect visibility // independent of master shape aContext.mbIsIndependentSubset = true; // already enable parent subset right here, to // make potentially generated subsets subtract // their content from the parent subset (and not // the original shape). Otherwise, already // subsetted parents (e.g. paragraphs) would not // have their characters removed, when the child // iterations start. // Furthermore, the setup of initial shape // attributes of course needs the subset shape // generated, to apply e.g. visibility changes. pTargetSubset->enableSubsetShape(); } else { pTargetSubset.reset( new ShapeSubset( pTargetShape, rContext.maContext.mpSubsettableShapeManager )); } aContext.mpMasterShapeSubset = pTargetSubset; uno::Reference< animations::XAnimationNode > xNode( xIterNode, uno::UNO_QUERY_THROW ); // Generate subsets // ================ if( bParagraphTarget || nSubItem != presentation::ShapeAnimationSubType::ONLY_TEXT ) { // prepend with animations for // full Shape (will be subtracted // from the subset parts within // the Shape::createSubset() // method). For ONLY_TEXT effects, // we skip this part, to animate // only the text. // // OR // // prepend with subset animation for full // _paragraph_, from which the individual // paragraph subsets are subtracted. Note that the // subitem is superfluous here, we always assume // ONLY_TEXT, if a paragraph is referenced as the // master of an iteration effect. NodeCreator aCreator( rParent, aContext ); if( !::anim::for_each_childNode( xNode, aCreator ) ) { ENSURE_OR_RETURN_FALSE( false, "implCreateIteratedNodes(): iterated child node creation failed" ); } } // TODO(F2): This does not do the correct // thing. Having nSubItem be set to ONLY_BACKGROUND // should result in the text staying unanimated in the // foreground, while the shape moves in the background // (this behaviour is perfectly possible with the // slideshow engine, only that the text won't be // currently visible, because animations are always in // the foreground) if( nSubItem != presentation::ShapeAnimationSubType::ONLY_BACKGROUND ) { // determine type of subitem iteration (logical // text unit to animate) DocTreeNode::NodeType eIterateNodeType( DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL ); switch( xIterNode->getIterateType() ) { case presentation::TextAnimationType::BY_PARAGRAPH: eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH; break; case presentation::TextAnimationType::BY_WORD: eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_WORD; break; case presentation::TextAnimationType::BY_LETTER: eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL; break; default: ENSURE_OR_THROW( false, "implCreateIteratedNodes(): " "Unexpected IterateType on XIterateContainer"); break; } if( bParagraphTarget && eIterateNodeType != DocTreeNode::NODETYPE_LOGICAL_WORD && eIterateNodeType != DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL ) { // will not animate the whole paragraph, when // only the paragraph is animated at all. OSL_ENSURE( false, "implCreateIteratedNodes(): Ignoring paragraph iteration for paragraph master" ); } else { // setup iteration parameters // -------------------------- // iterate target is the whole shape (or the // whole parent subshape), thus, can save // loads of subset shapes by generating them // only when the effects become active - // before and after the effect active // duration, all attributes are shared by // master shape and subset (since the iterated // effects are all the same). aContext.mbIsIndependentSubset = false; // determine number of nodes for given subitem // type sal_Int32 nTreeNodes( 0 ); if( bParagraphTarget ) { // create the iterated subset _relative_ to // the given paragraph index (i.e. animate the // given subset type, but only when it's part // of the given paragraph) nTreeNodes = rTreeNodeSupplier.getNumberOfSubsetTreeNodes( pTargetSubset->getSubset(), eIterateNodeType ); } else { // generate normal subset nTreeNodes = rTreeNodeSupplier.getNumberOfTreeNodes( eIterateNodeType ); } // iterate node, generate copies of the children for each subset // ------------------------------------------------------------- // NodeContext::mnStartDelay contains additional node delay. // This will make the duplicated nodes for each iteration start // increasingly later. aContext.mnStartDelay = nIntervalTimeout; for( sal_Int32 i=0; igetSubset(), i, eIterateNodeType ) ) ); } else { // create subsets from main shape aContext.mpMasterShapeSubset.reset( new ShapeSubset( pTargetSubset, rTreeNodeSupplier.getTreeNode( i, eIterateNodeType ) ) ); } CloningNodeCreator aCreator( rParent, aContext ); if( !::anim::for_each_childNode( xNode, aCreator ) ) { ENSURE_OR_RETURN_FALSE( false, "implCreateIteratedNodes(): " "iterated child node creation failed" ); } aContext.mnStartDelay += nIntervalTimeout; } } } // done with iterate child generation return true; } BaseNodeSharedPtr implCreateAnimationNode( const uno::Reference< animations::XAnimationNode >& xNode, const BaseContainerNodeSharedPtr& rParent, const NodeContext& rContext ) { ENSURE_OR_THROW( xNode.is(), "implCreateAnimationNode(): invalid XAnimationNode" ); BaseNodeSharedPtr pCreatedNode; BaseContainerNodeSharedPtr pCreatedContainer; // create the internal node, corresponding to xNode switch( xNode->getType() ) { case animations::AnimationNodeType::CUSTOM: OSL_ENSURE( false, "implCreateAnimationNode(): " "CUSTOM not yet implemented" ); return pCreatedNode; case animations::AnimationNodeType::PAR: pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr( new ParallelTimeContainer( xNode, rParent, rContext ) ); break; case animations::AnimationNodeType::ITERATE: // map iterate container to ParallelTimeContainer. // the iterating functionality is to be found // below, (see method implCreateIteratedNodes) pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr( new ParallelTimeContainer( xNode, rParent, rContext ) ); break; case animations::AnimationNodeType::SEQ: pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr( new SequentialTimeContainer( xNode, rParent, rContext ) ); break; case animations::AnimationNodeType::ANIMATE: pCreatedNode.reset( new PropertyAnimationNode( xNode, rParent, rContext ) ); break; case animations::AnimationNodeType::SET: pCreatedNode.reset( new AnimationSetNode( xNode, rParent, rContext ) ); break; case animations::AnimationNodeType::ANIMATEMOTION: pCreatedNode.reset( new AnimationPathMotionNode( xNode, rParent, rContext ) ); break; case animations::AnimationNodeType::ANIMATECOLOR: pCreatedNode.reset( new AnimationColorNode( xNode, rParent, rContext ) ); break; case animations::AnimationNodeType::ANIMATETRANSFORM: pCreatedNode.reset( new AnimationTransformNode( xNode, rParent, rContext ) ); break; case animations::AnimationNodeType::TRANSITIONFILTER: pCreatedNode.reset( new AnimationTransitionFilterNode( xNode, rParent, rContext ) ); break; case animations::AnimationNodeType::AUDIO: pCreatedNode.reset( new AnimationAudioNode( xNode, rParent, rContext ) ); break; case animations::AnimationNodeType::COMMAND: pCreatedNode.reset( new AnimationCommandNode( xNode, rParent, rContext ) ); break; default: OSL_ENSURE( false, "implCreateAnimationNode(): " "invalid AnimationNodeType" ); return pCreatedNode; } // TODO(Q1): This yields circular references, which, it seems, is // unavoidable here // HACK: node objects need shared_ptr to themselves, // which we pass them here. pCreatedNode->setSelf( pCreatedNode ); // if we've got a container node object, recursively add // its children if( pCreatedContainer ) { uno::Reference< animations::XIterateContainer > xIterNode( xNode, uno::UNO_QUERY ); // when this node is an XIterateContainer with // active iterations, this method will generate // the appropriate children if( xIterNode.is() ) { // note that implCreateIteratedNodes() might // choose not to generate any child nodes // (e.g. when the iterate timeout is outside // sensible limits). Then, no child nodes are // generated at all, since typically, child // node attribute are incomplete for iteration // children. implCreateIteratedNodes( xIterNode, pCreatedContainer, rContext ); } else { // no iterate subset node, just plain child generation now NodeCreator aCreator( pCreatedContainer, rContext ); if( !::anim::for_each_childNode( xNode, aCreator ) ) { OSL_ENSURE( false, "implCreateAnimationNode(): " "child node creation failed" ); return BaseNodeSharedPtr(); } } } return pCreatedNode; } } // anon namespace AnimationNodeSharedPtr AnimationNodeFactory::createAnimationNode( const uno::Reference< animations::XAnimationNode >& xNode, const ::basegfx::B2DVector& rSlideSize, const SlideShowContext& rContext ) { ENSURE_OR_THROW( xNode.is(), "AnimationNodeFactory::createAnimationNode(): invalid XAnimationNode" ); return BaseNodeSharedPtr( implCreateAnimationNode( xNode, BaseContainerNodeSharedPtr(), // no parent NodeContext( rContext, rSlideSize ))); } #if defined(VERBOSE) && defined(DBG_UTIL) void AnimationNodeFactory::showTree( AnimationNodeSharedPtr& pRootNode ) { if( pRootNode ) DEBUG_NODES_SHOWTREE( boost::dynamic_pointer_cast( pRootNode).get() ); } #endif } // namespace internal } // namespace slideshow