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