1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_slideshow.hxx"
26 
27 // must be first
28 #include <canvas/debug.hxx>
29 #include <canvas/verbosetrace.hxx>
30 
31 #include <com/sun/star/drawing/XShape.hpp>
32 #include <com/sun/star/animations/XAnimate.hpp>
33 #include <com/sun/star/animations/AnimationNodeType.hpp>
34 #include <com/sun/star/presentation/EffectNodeType.hpp>
35 #include <com/sun/star/presentation/TextAnimationType.hpp>
36 #include <com/sun/star/animations/XAnimateSet.hpp>
37 #include <com/sun/star/animations/XIterateContainer.hpp>
38 #include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
39 #include <com/sun/star/animations/XAnimateMotion.hpp>
40 #include <com/sun/star/animations/XAnimateColor.hpp>
41 #include <com/sun/star/animations/XAnimateTransform.hpp>
42 #include <com/sun/star/animations/AnimationTransformType.hpp>
43 #include <com/sun/star/animations/XTransitionFilter.hpp>
44 #include <com/sun/star/animations/XAudio.hpp>
45 #include <com/sun/star/presentation/ParagraphTarget.hpp>
46 #include <com/sun/star/beans/XPropertySet.hpp>
47 #include <animations/animationnodehelper.hxx>
48 #include <basegfx/numeric/ftools.hxx>
49 
50 #include "animationnodefactory.hxx"
51 #include "paralleltimecontainer.hxx"
52 #include "sequentialtimecontainer.hxx"
53 #include "propertyanimationnode.hxx"
54 #include "animationsetnode.hxx"
55 #include "animationpathmotionnode.hxx"
56 #include "animationcolornode.hxx"
57 #include "animationtransformnode.hxx"
58 #include "animationtransitionfilternode.hxx"
59 #include "animationaudionode.hxx"
60 #include "animationcommandnode.hxx"
61 #include "nodetools.hxx"
62 #include "tools.hxx"
63 
64 #include <boost/bind.hpp>
65 
66 using namespace ::com::sun::star;
67 
68 namespace slideshow {
69 namespace internal {
70 
71 namespace {
72 
73 // forward declaration needed by NodeCreator
74 BaseNodeSharedPtr implCreateAnimationNode(
75     const uno::Reference< animations::XAnimationNode >&  xNode,
76     const BaseContainerNodeSharedPtr&                    rParent,
77     const NodeContext&                                   rContext );
78 
79 class NodeCreator
80 {
81 public:
NodeCreator(BaseContainerNodeSharedPtr & rParent,const NodeContext & rContext)82     NodeCreator( BaseContainerNodeSharedPtr&    rParent,
83                  const NodeContext&             rContext )
84         : mrParent( rParent ), mrContext( rContext ) {}
85 
operator ()(const uno::Reference<animations::XAnimationNode> & xChildNode) const86     void operator()(
87         const uno::Reference< animations::XAnimationNode >& xChildNode ) const
88     {
89         createChild( xChildNode, mrContext );
90     }
91 
92 protected:
createChild(const uno::Reference<animations::XAnimationNode> & xChildNode,const NodeContext & rContext) const93     void createChild(
94         const uno::Reference< animations::XAnimationNode >&   xChildNode,
95         const NodeContext&                                    rContext ) const
96     {
97         BaseNodeSharedPtr pChild( implCreateAnimationNode( xChildNode,
98                                                            mrParent,
99                                                            rContext ) );
100 
101         OSL_ENSURE( pChild,
102                     "NodeCreator::operator(): child creation failed" );
103 
104         // TODO(Q1): This yields circular references, which, it seems, is
105         // unavoidable here
106         if( pChild )
107             mrParent->appendChildNode( pChild );
108     }
109 
110     BaseContainerNodeSharedPtr&     mrParent;
111     const NodeContext&              mrContext;
112 };
113 
114 /** Same as NodeCreator, only that NodeContext's
115     SubsetShape is cloned for every child node.
116 
117     This is used for iterated animation node generation
118 */
119 class CloningNodeCreator : private NodeCreator
120 {
121 public:
CloningNodeCreator(BaseContainerNodeSharedPtr & rParent,const NodeContext & rContext)122     CloningNodeCreator( BaseContainerNodeSharedPtr& rParent,
123                         const NodeContext&          rContext )
124         : NodeCreator( rParent, rContext ) {}
125 
operator ()(const uno::Reference<animations::XAnimationNode> & xChildNode) const126     void operator()(
127         const uno::Reference< animations::XAnimationNode >& xChildNode ) const
128     {
129         NodeContext aContext( mrContext );
130 
131         // TODO(Q1): There's a catch here. If you clone a
132         // subset whose actual subsetting has already been
133         // realized (i.e. if enableSubsetShape() has been
134         // called already), and the original of your clone
135         // goes out of scope, then your subset will be
136         // gone (SubsettableShapeManager::revokeSubset() be
137         // called). As of now, this behaviour is not
138         // triggered here (we either clone, XOR we enable
139         // subset initially), but one might consider
140         // reworking DrawShape/ShapeSubset to avoid this.
141 
142         // clone ShapeSubset, since each node needs their
143         // own version of the ShapeSubset (otherwise,
144         // e.g. activity counting does not work - subset
145         // would be removed after first animation node
146         // disables it).
147         //
148         // NOTE: this is only a problem for animation
149         // nodes that explicitely call
150         // disableSubsetShape(). Independent shape subsets
151         // (like those created for ParagraphTargets)
152         // solely rely on the ShapeSubset destructor to
153         // normalize things, which does the right thing
154         // here: the subset is only removed after _the
155         // last_ animation node releases the shared ptr.
156         aContext.mpMasterShapeSubset.reset(
157             new ShapeSubset( *aContext.mpMasterShapeSubset ) );
158 
159         createChild( xChildNode, aContext );
160     }
161 };
162 
163 /** Create animation nodes for text iterations
164 
165     This method clones the animation nodes below xIterNode
166     for every iterated shape entity.
167 */
implCreateIteratedNodes(const uno::Reference<animations::XIterateContainer> & xIterNode,BaseContainerNodeSharedPtr & rParent,const NodeContext & rContext)168 bool implCreateIteratedNodes(
169     const uno::Reference< animations::XIterateContainer >&    xIterNode,
170     BaseContainerNodeSharedPtr&                               rParent,
171     const NodeContext&                                        rContext )
172 {
173     ENSURE_OR_THROW( xIterNode.is(),
174                       "implCreateIteratedNodes(): Invalid node" );
175 
176     const double nIntervalTimeout( xIterNode->getIterateInterval() );
177 
178     // valid iterate interval? We're ruling out monstrous
179     // values here, to avoid pseudo 'hangs' in the
180     // presentation
181     if( nIntervalTimeout < 0.0 ||
182         nIntervalTimeout > 1000.0 )
183     {
184         return false; // not an active iteration
185     }
186 
187     if( ::basegfx::fTools::equalZero( nIntervalTimeout ) )
188         OSL_TRACE( "implCreateIteratedNodes(): "
189                    "iterate interval close to zero, there's "
190                    "no point in defining such an effect "
191                    "(visually equivalent to whole-shape effect)" );
192 
193     // Determine target shape (or subset)
194     // ==================================
195 
196     // TODO(E1): I'm not too sure what to expect here...
197     ENSURE_OR_RETURN_FALSE(
198         xIterNode->getTarget().hasValue(),
199         "implCreateIteratedNodes(): no target on ITERATE node" );
200 
201     uno::Reference< drawing::XShape > xTargetShape( xIterNode->getTarget(),
202                                                     uno::UNO_QUERY );
203 
204     presentation::ParagraphTarget aTarget;
205     sal_Int16                     nSubItem( xIterNode->getSubItem() );
206     bool                          bParagraphTarget( false );
207 
208     if( !xTargetShape.is() )
209     {
210         // no shape provided. Maybe a ParagraphTarget?
211         if( !(xIterNode->getTarget() >>= aTarget) )
212             ENSURE_OR_RETURN_FALSE(
213                 false,
214                 "implCreateIteratedNodes(): could not extract any "
215                 "target information" );
216 
217         xTargetShape = aTarget.Shape;
218 
219         ENSURE_OR_RETURN_FALSE(
220             xTargetShape.is(),
221             "implCreateIteratedNodes(): invalid shape in ParagraphTarget" );
222 
223         // we've a paragraph target to iterate over, thus,
224         // the whole animation container refers only to
225         // the text
226         nSubItem = presentation::ShapeAnimationSubType::ONLY_TEXT;
227 
228         bParagraphTarget = true;
229     }
230 
231     // Lookup shape, and fill NodeContext
232     // ==================================
233 
234     AttributableShapeSharedPtr  pTargetShape(
235         lookupAttributableShape( rContext.maContext.mpSubsettableShapeManager,
236                                  xTargetShape ) );
237 
238     const DocTreeNodeSupplier& rTreeNodeSupplier(
239         pTargetShape->getTreeNodeSupplier() );
240 
241     ShapeSubsetSharedPtr pTargetSubset;
242 
243     NodeContext aContext( rContext );
244 
245     // paragraph targets already need a subset as the
246     // master shape (they're representing only a single
247     // paragraph)
248     if( bParagraphTarget )
249     {
250         ENSURE_OR_RETURN_FALSE(
251             aTarget.Paragraph >= 0 &&
252             rTreeNodeSupplier.getNumberOfTreeNodes(
253                 DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ) > aTarget.Paragraph,
254             "implCreateIteratedNodes(): paragraph index out of range" );
255 
256         pTargetSubset.reset(
257             new ShapeSubset(
258                 pTargetShape,
259                 // retrieve index aTarget.Paragraph of
260                 // type PARAGRAPH from this shape
261                 rTreeNodeSupplier.getTreeNode(
262                     aTarget.Paragraph,
263                     DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ),
264                 rContext.maContext.mpSubsettableShapeManager ) );
265 
266         // iterate target is not the whole shape, but only
267         // the selected paragraph - subset _must_ be
268         // independent, to be able to affect visibility
269         // independent of master shape
270         aContext.mbIsIndependentSubset = true;
271 
272         // already enable parent subset right here, to
273         // make potentially generated subsets subtract
274         // their content from the parent subset (and not
275         // the original shape). Otherwise, already
276         // subsetted parents (e.g. paragraphs) would not
277         // have their characters removed, when the child
278         // iterations start.
279         // Furthermore, the setup of initial shape
280         // attributes of course needs the subset shape
281         // generated, to apply e.g. visibility changes.
282         pTargetSubset->enableSubsetShape();
283     }
284     else
285     {
286         pTargetSubset.reset(
287             new ShapeSubset( pTargetShape,
288                              rContext.maContext.mpSubsettableShapeManager ));
289     }
290 
291     aContext.mpMasterShapeSubset = pTargetSubset;
292     uno::Reference< animations::XAnimationNode > xNode( xIterNode,
293                                                         uno::UNO_QUERY_THROW );
294 
295     // Generate subsets
296     // ================
297 
298     if( bParagraphTarget ||
299         nSubItem != presentation::ShapeAnimationSubType::ONLY_TEXT )
300     {
301         // prepend with animations for
302         // full Shape (will be subtracted
303         // from the subset parts within
304         // the Shape::createSubset()
305         // method). For ONLY_TEXT effects,
306         // we skip this part, to animate
307         // only the text.
308         //
309         // OR
310         //
311         // prepend with subset animation for full
312         // _paragraph_, from which the individual
313         // paragraph subsets are subtracted. Note that the
314         // subitem is superfluous here, we always assume
315         // ONLY_TEXT, if a paragraph is referenced as the
316         // master of an iteration effect.
317         NodeCreator aCreator( rParent, aContext );
318         if( !::anim::for_each_childNode( xNode,
319                                          aCreator ) )
320         {
321             ENSURE_OR_RETURN_FALSE(
322                 false,
323                 "implCreateIteratedNodes(): iterated child node creation failed" );
324         }
325     }
326 
327     // TODO(F2): This does not do the correct
328     // thing. Having nSubItem be set to ONLY_BACKGROUND
329     // should result in the text staying unanimated in the
330     // foreground, while the shape moves in the background
331     // (this behaviour is perfectly possible with the
332     // slideshow engine, only that the text won't be
333     // currently visible, because animations are always in
334     // the foreground)
335     if( nSubItem != presentation::ShapeAnimationSubType::ONLY_BACKGROUND )
336     {
337         // determine type of subitem iteration (logical
338         // text unit to animate)
339         DocTreeNode::NodeType eIterateNodeType(
340             DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL );
341 
342         switch( xIterNode->getIterateType() )
343         {
344         case presentation::TextAnimationType::BY_PARAGRAPH:
345             eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH;
346             break;
347 
348         case presentation::TextAnimationType::BY_WORD:
349             eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_WORD;
350             break;
351 
352         case presentation::TextAnimationType::BY_LETTER:
353             eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL;
354             break;
355 
356         default:
357             ENSURE_OR_THROW(
358                 false, "implCreateIteratedNodes(): "
359                 "Unexpected IterateType on XIterateContainer");
360             break;
361         }
362 
363         if( bParagraphTarget &&
364             eIterateNodeType != DocTreeNode::NODETYPE_LOGICAL_WORD &&
365             eIterateNodeType != DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL )
366         {
367             // will not animate the whole paragraph, when
368             // only the paragraph is animated at all.
369             OSL_ENSURE( false,
370                         "implCreateIteratedNodes(): Ignoring paragraph iteration for paragraph master" );
371         }
372         else
373         {
374             // setup iteration parameters
375             // --------------------------
376 
377             // iterate target is the whole shape (or the
378             // whole parent subshape), thus, can save
379             // loads of subset shapes by generating them
380             // only when the effects become active -
381             // before and after the effect active
382             // duration, all attributes are shared by
383             // master shape and subset (since the iterated
384             // effects are all the same).
385             aContext.mbIsIndependentSubset = false;
386 
387             // determine number of nodes for given subitem
388             // type
389             sal_Int32 nTreeNodes( 0 );
390             if( bParagraphTarget )
391             {
392                 // create the iterated subset _relative_ to
393                 // the given paragraph index (i.e. animate the
394                 // given subset type, but only when it's part
395                 // of the given paragraph)
396                 nTreeNodes = rTreeNodeSupplier.getNumberOfSubsetTreeNodes(
397                     pTargetSubset->getSubset(),
398                     eIterateNodeType );
399             }
400             else
401             {
402                 // generate normal subset
403                 nTreeNodes = rTreeNodeSupplier.getNumberOfTreeNodes(
404                     eIterateNodeType );
405             }
406 
407 
408             // iterate node, generate copies of the children for each subset
409             // -------------------------------------------------------------
410 
411             // NodeContext::mnStartDelay contains additional node delay.
412             // This will make the duplicated nodes for each iteration start
413             // increasingly later.
414             aContext.mnStartDelay = nIntervalTimeout;
415 
416             for( sal_Int32 i=0; i<nTreeNodes; ++i )
417             {
418                 // create subset with the corresponding tree nodes
419                 if( bParagraphTarget )
420                 {
421                     // create subsets relative to paragraph subset
422                     aContext.mpMasterShapeSubset.reset(
423                         new ShapeSubset(
424                             pTargetSubset,
425                             rTreeNodeSupplier.getSubsetTreeNode(
426                                 pTargetSubset->getSubset(),
427                                 i,
428                                 eIterateNodeType ) ) );
429                 }
430                 else
431                 {
432                     // create subsets from main shape
433                     aContext.mpMasterShapeSubset.reset(
434                         new ShapeSubset( pTargetSubset,
435                                          rTreeNodeSupplier.getTreeNode(
436                                              i,
437                                              eIterateNodeType ) ) );
438                 }
439 
440                 CloningNodeCreator aCreator( rParent, aContext );
441                 if( !::anim::for_each_childNode( xNode,
442                                                  aCreator ) )
443                 {
444                     ENSURE_OR_RETURN_FALSE(
445                         false, "implCreateIteratedNodes(): "
446                         "iterated child node creation failed" );
447                 }
448 
449                 aContext.mnStartDelay += nIntervalTimeout;
450             }
451         }
452     }
453 
454     // done with iterate child generation
455     return true;
456 }
457 
implCreateAnimationNode(const uno::Reference<animations::XAnimationNode> & xNode,const BaseContainerNodeSharedPtr & rParent,const NodeContext & rContext)458 BaseNodeSharedPtr implCreateAnimationNode(
459     const uno::Reference< animations::XAnimationNode >&  xNode,
460     const BaseContainerNodeSharedPtr&                    rParent,
461     const NodeContext&                                   rContext )
462 {
463     ENSURE_OR_THROW( xNode.is(),
464                       "implCreateAnimationNode(): invalid XAnimationNode" );
465 
466     BaseNodeSharedPtr           pCreatedNode;
467     BaseContainerNodeSharedPtr  pCreatedContainer;
468 
469     // create the internal node, corresponding to xNode
470     switch( xNode->getType() )
471     {
472     case animations::AnimationNodeType::CUSTOM:
473         OSL_ENSURE( false, "implCreateAnimationNode(): "
474                     "CUSTOM not yet implemented" );
475         return pCreatedNode;
476 
477     case animations::AnimationNodeType::PAR:
478         pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr(
479             new ParallelTimeContainer( xNode, rParent, rContext ) );
480         break;
481 
482     case animations::AnimationNodeType::ITERATE:
483         // map iterate container to ParallelTimeContainer.
484         // the iterating functionality is to be found
485         // below, (see method implCreateIteratedNodes)
486         pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr(
487             new ParallelTimeContainer( xNode, rParent, rContext ) );
488         break;
489 
490     case animations::AnimationNodeType::SEQ:
491         pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr(
492             new SequentialTimeContainer( xNode, rParent, rContext ) );
493         break;
494 
495     case animations::AnimationNodeType::ANIMATE:
496         pCreatedNode.reset( new PropertyAnimationNode(
497                                 xNode, rParent, rContext ) );
498         break;
499 
500     case animations::AnimationNodeType::SET:
501         pCreatedNode.reset( new AnimationSetNode(
502                                 xNode, rParent, rContext ) );
503         break;
504 
505     case animations::AnimationNodeType::ANIMATEMOTION:
506         pCreatedNode.reset( new AnimationPathMotionNode(
507                                 xNode, rParent, rContext ) );
508         break;
509 
510     case animations::AnimationNodeType::ANIMATECOLOR:
511         pCreatedNode.reset( new AnimationColorNode(
512                                 xNode, rParent, rContext ) );
513         break;
514 
515     case animations::AnimationNodeType::ANIMATETRANSFORM:
516         pCreatedNode.reset( new AnimationTransformNode(
517                                 xNode, rParent, rContext ) );
518         break;
519 
520     case animations::AnimationNodeType::TRANSITIONFILTER:
521         pCreatedNode.reset( new AnimationTransitionFilterNode(
522                                 xNode, rParent, rContext ) );
523         break;
524 
525     case animations::AnimationNodeType::AUDIO:
526         pCreatedNode.reset( new AnimationAudioNode(
527                                 xNode, rParent, rContext ) );
528         break;
529 
530     case animations::AnimationNodeType::COMMAND:
531         pCreatedNode.reset( new AnimationCommandNode(
532                                 xNode, rParent, rContext ) );
533         break;
534 
535     default:
536         OSL_ENSURE( false, "implCreateAnimationNode(): "
537                     "invalid AnimationNodeType" );
538         return pCreatedNode;
539     }
540 
541     // TODO(Q1): This yields circular references, which, it seems, is
542     // unavoidable here
543 
544     // HACK: node objects need shared_ptr to themselves,
545     // which we pass them here.
546     pCreatedNode->setSelf( pCreatedNode );
547 
548     // if we've got a container node object, recursively add
549     // its children
550     if( pCreatedContainer )
551     {
552         uno::Reference< animations::XIterateContainer > xIterNode(
553             xNode, uno::UNO_QUERY );
554 
555         // when this node is an XIterateContainer with
556         // active iterations, this method will generate
557         // the appropriate children
558         if( xIterNode.is() )
559         {
560             // note that implCreateIteratedNodes() might
561             // choose not to generate any child nodes
562             // (e.g. when the iterate timeout is outside
563             // sensible limits). Then, no child nodes are
564             // generated at all, since typically, child
565             // node attribute are incomplete for iteration
566             // children.
567             implCreateIteratedNodes( xIterNode,
568                                      pCreatedContainer,
569                                      rContext );
570         }
571         else
572         {
573             // no iterate subset node, just plain child generation now
574             NodeCreator aCreator( pCreatedContainer, rContext );
575             if( !::anim::for_each_childNode( xNode, aCreator ) )
576             {
577                 OSL_ENSURE( false, "implCreateAnimationNode(): "
578                             "child node creation failed" );
579                 return BaseNodeSharedPtr();
580             }
581         }
582     }
583 
584     return pCreatedNode;
585 }
586 
587 } // anon namespace
588 
createAnimationNode(const uno::Reference<animations::XAnimationNode> & xNode,const::basegfx::B2DVector & rSlideSize,const SlideShowContext & rContext)589 AnimationNodeSharedPtr AnimationNodeFactory::createAnimationNode(
590     const uno::Reference< animations::XAnimationNode >&   xNode,
591     const ::basegfx::B2DVector&                           rSlideSize,
592     const SlideShowContext&                               rContext )
593 {
594     ENSURE_OR_THROW(
595         xNode.is(),
596         "AnimationNodeFactory::createAnimationNode(): invalid XAnimationNode" );
597 
598     return BaseNodeSharedPtr( implCreateAnimationNode(
599                                   xNode,
600                                   BaseContainerNodeSharedPtr(), // no parent
601                                   NodeContext( rContext,
602                                                rSlideSize )));
603 }
604 
605 #if defined(VERBOSE) && defined(DBG_UTIL)
showTree(AnimationNodeSharedPtr & pRootNode)606 void AnimationNodeFactory::showTree( AnimationNodeSharedPtr& pRootNode )
607 {
608     if( pRootNode )
609         DEBUG_NODES_SHOWTREE( boost::dynamic_pointer_cast<BaseContainerNode>(
610                                   pRootNode).get() );
611 }
612 #endif
613 
614 } // namespace internal
615 } // namespace slideshow
616 
617