| /************************************************************** |
| * |
| * 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_slideshow.hxx" |
| |
| // must be first |
| #include <canvas/debug.hxx> |
| #include <canvas/verbosetrace.hxx> |
| |
| #include <com/sun/star/drawing/XShape.hpp> |
| #include <com/sun/star/animations/XAnimate.hpp> |
| #include <com/sun/star/animations/AnimationNodeType.hpp> |
| #include <com/sun/star/presentation/EffectNodeType.hpp> |
| #include <com/sun/star/presentation/TextAnimationType.hpp> |
| #include <com/sun/star/animations/XAnimateSet.hpp> |
| #include <com/sun/star/animations/XIterateContainer.hpp> |
| #include <com/sun/star/presentation/ShapeAnimationSubType.hpp> |
| #include <com/sun/star/animations/XAnimateMotion.hpp> |
| #include <com/sun/star/animations/XAnimateColor.hpp> |
| #include <com/sun/star/animations/XAnimateTransform.hpp> |
| #include <com/sun/star/animations/AnimationTransformType.hpp> |
| #include <com/sun/star/animations/XTransitionFilter.hpp> |
| #include <com/sun/star/animations/XAudio.hpp> |
| #include <com/sun/star/presentation/ParagraphTarget.hpp> |
| #include <com/sun/star/beans/XPropertySet.hpp> |
| #include <animations/animationnodehelper.hxx> |
| #include <basegfx/numeric/ftools.hxx> |
| |
| #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 <boost/bind.hpp> |
| |
| 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; i<nTreeNodes; ++i ) |
| { |
| // create subset with the corresponding tree nodes |
| if( bParagraphTarget ) |
| { |
| // create subsets relative to paragraph subset |
| aContext.mpMasterShapeSubset.reset( |
| new ShapeSubset( |
| pTargetSubset, |
| rTreeNodeSupplier.getSubsetTreeNode( |
| pTargetSubset->getSubset(), |
| 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<BaseContainerNode>( |
| pRootNode).get() ); |
| } |
| #endif |
| |
| } // namespace internal |
| } // namespace slideshow |
| |