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 #include <cppuhelper/exc_hlp.hxx>
35 #include <comphelper/anytostring.hxx>
36 #include <com/sun/star/presentation/ParagraphTarget.hpp>
37 #include <com/sun/star/animations/Timing.hpp>
38 #include <com/sun/star/animations/AnimationAdditiveMode.hpp>
39 #include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
40 
41 #include "nodetools.hxx"
42 #include "doctreenode.hxx"
43 #include "animationbasenode.hxx"
44 #include "delayevent.hxx"
45 #include "framerate.hxx"
46 
47 #include <boost/bind.hpp>
48 #include <boost/optional.hpp>
49 #include <algorithm>
50 
51 using namespace com::sun::star;
52 
53 namespace slideshow {
54 namespace internal {
55 
56 AnimationBaseNode::AnimationBaseNode(
57     const uno::Reference< animations::XAnimationNode >&   xNode,
58     const BaseContainerNodeSharedPtr&                     rParent,
59     const NodeContext&                                    rContext )
60     : BaseNode( xNode, rParent, rContext ),
61       mxAnimateNode( xNode, uno::UNO_QUERY_THROW ),
62       maAttributeLayerHolder(),
63       maSlideSize( rContext.maSlideSize ),
64       mpActivity(),
65       mpShape(),
66       mpShapeSubset(),
67       mpSubsetManager(rContext.maContext.mpSubsettableShapeManager),
68       mbIsIndependentSubset( rContext.mbIsIndependentSubset )
69 {
70     // extract native node targets
71     // ===========================
72 
73     // plain shape target
74     uno::Reference< drawing::XShape > xShape( mxAnimateNode->getTarget(),
75                                               uno::UNO_QUERY );
76 
77     // distinguish 5 cases:
78     //
79     //  - plain shape target
80     //  (NodeContext.mpMasterShapeSubset full set)
81     //
82     //  - parent-generated subset (generate an
83     //  independent subset)
84     //
85     //  - parent-generated subset from iteration
86     //  (generate a dependent subset)
87     //
88     //  - XShape target at the XAnimatioNode (generate
89     //  a plain shape target)
90     //
91     //  - ParagraphTarget target at the XAnimationNode
92     //  (generate an independent shape subset)
93     if( rContext.mpMasterShapeSubset )
94     {
95         if( rContext.mpMasterShapeSubset->isFullSet() )
96         {
97             // case 1: plain shape target from parent
98             mpShape = rContext.mpMasterShapeSubset->getSubsetShape();
99         }
100         else
101         {
102             // cases 2 & 3: subset shape
103             mpShapeSubset = rContext.mpMasterShapeSubset;
104         }
105     }
106     else
107     {
108         // no parent-provided shape, try to extract
109         // from XAnimationNode - cases 4 and 5
110 
111         if( xShape.is() )
112         {
113             mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager,
114                                                xShape );
115         }
116         else
117         {
118             // no shape provided. Maybe a ParagraphTarget?
119             presentation::ParagraphTarget aTarget;
120 
121             if( !(mxAnimateNode->getTarget() >>= aTarget) )
122                 ENSURE_OR_THROW(
123                     false, "could not extract any target information" );
124 
125             xShape = aTarget.Shape;
126 
127             ENSURE_OR_THROW( xShape.is(), "invalid shape in ParagraphTarget" );
128 
129             mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager,
130                                                xShape );
131 
132             // NOTE: For shapes with ParagraphTarget, we ignore
133             // the SubItem property. We implicitely assume that it
134             // is set to ONLY_TEXT.
135             OSL_ENSURE(
136                 mxAnimateNode->getSubItem() ==
137                 presentation::ShapeAnimationSubType::ONLY_TEXT ||
138                 mxAnimateNode->getSubItem() ==
139                 presentation::ShapeAnimationSubType::AS_WHOLE,
140                 "ParagraphTarget given, but subitem not AS_TEXT or AS_WHOLE? "
141                 "Make up your mind, I'll ignore the subitem." );
142 
143             // okay, found a ParagraphTarget with a valid XShape. Does the shape
144             // provide the given paragraph?
145             const DocTreeNode& rTreeNode(
146                 mpShape->getTreeNodeSupplier().getTreeNode(
147                     aTarget.Paragraph,
148                     DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ) );
149 
150             // CAUTION: the creation of the subset shape
151             // _must_ stay in the node constructor, since
152             // Slide::prefetchShow() initializes shape
153             // attributes right after animation import (or
154             // the Slide class must be changed).
155             mpShapeSubset.reset(
156                 new ShapeSubset( mpShape,
157                                  rTreeNode,
158                                  mpSubsetManager ));
159 
160             // Override NodeContext, and flag this node as
161             // a special independent subset one. This is
162             // important when applying initial attributes:
163             // independent shape subsets must be setup
164             // when the slide starts, since they, as their
165             // name suggest, can have state independent to
166             // the master shape. The following example
167             // might illustrate that: a master shape has
168             // no effect, one of the text paragraphs
169             // within it has an appear effect. Now, the
170             // respective paragraph must be invisible when
171             // the slide is initially shown, and become
172             // visible only when the effect starts.
173             mbIsIndependentSubset = true;
174 
175             // already enable subset right here, the
176             // setup of initial shape attributes of
177             // course needs the subset shape
178             // generated, to apply e.g. visibility
179             // changes.
180             mpShapeSubset->enableSubsetShape();
181         }
182     }
183 }
184 
185 void AnimationBaseNode::dispose()
186 {
187     if (mpActivity) {
188         mpActivity->dispose();
189         mpActivity.reset();
190     }
191 
192     maAttributeLayerHolder.reset();
193     mxAnimateNode.clear();
194     mpShape.reset();
195     mpShapeSubset.reset();
196 
197     BaseNode::dispose();
198 }
199 
200 bool AnimationBaseNode::init_st()
201 {
202     // if we've still got an old activity lying around, dispose it:
203     if (mpActivity) {
204         mpActivity->dispose();
205         mpActivity.reset();
206     }
207 
208     // note: actually disposing the activity too early might cause problems,
209     // because on dequeued() it calls endAnimation(pAnim->end()), thus ending
210     // animation _after_ last screen update.
211     // review that end() is properly called (which calls endAnimation(), too).
212 
213     try {
214         // TODO(F2): For restart functionality, we must regenerate activities,
215         // since they are not able to reset their state (or implement _that_)
216         mpActivity = createActivity();
217     }
218     catch (uno::Exception const&) {
219         OSL_ENSURE( false, rtl::OUStringToOString(
220                         comphelper::anyToString(cppu::getCaughtException()),
221                         RTL_TEXTENCODING_UTF8 ) );
222         // catch and ignore. We later handle empty activities, but for
223         // other nodes to function properly, the core functionality of
224         // this node must remain up and running.
225     }
226     return true;
227 }
228 
229 bool AnimationBaseNode::resolve_st()
230 {
231     // enable shape subset for automatically generated
232     // subsets. Independent subsets are already setup
233     // during construction time. Doing it only here
234     // saves us a lot of sprites and shapes lying
235     // around. This is especially important for
236     // character-wise iterations, since the shape
237     // content (e.g. thousands of characters) would
238     // otherwise be painted character-by-character.
239     if (isDependentSubsettedShape() && mpShapeSubset) {
240         mpShapeSubset->enableSubsetShape();
241     }
242     return true;
243 }
244 
245 void AnimationBaseNode::activate_st()
246 {
247     // create new attribute layer
248     maAttributeLayerHolder.createAttributeLayer( getShape() );
249 
250     ENSURE_OR_THROW( maAttributeLayerHolder.get(),
251                       "Could not generate shape attribute layer" );
252 
253     // TODO(Q2): This affects the way mpActivity
254     // works, but is performed here because of
255     // locality (we're fiddling with the additive mode
256     // here, anyway, and it's the only place where we
257     // do). OTOH, maybe the complete additive mode
258     // setup should be moved to the activities.
259 
260     // for simple by-animations, the SMIL spec
261     // requires us to emulate "0,by-value" value list
262     // behaviour, with additive mode forced to "sum",
263     // no matter what the input is
264     // (http://www.w3.org/TR/smil20/animation.html#adef-by).
265     if( mxAnimateNode->getBy().hasValue() &&
266         !mxAnimateNode->getTo().hasValue() &&
267         !mxAnimateNode->getFrom().hasValue() )
268     {
269         // force attribute mode to REPLACE (note the
270         // subtle discrepancy to the paragraph above,
271         // where SMIL requires SUM. This is internally
272         // handled by the FromToByActivity, and is
273         // because otherwise DOM values would not be
274         // handled correctly: the activity cannot
275         // determine whether an
276         // Activity::getUnderlyingValue() yields the
277         // DOM value, or already a summed-up conglomerate)
278         //
279         // Note that this poses problems with our
280         // hybrid activity duration (time or min number of frames),
281         // since if activities
282         // exceed their duration, wrong 'by' start
283         // values might arise ('Laser effect')
284         maAttributeLayerHolder.get()->setAdditiveMode(
285             animations::AnimationAdditiveMode::REPLACE );
286     }
287     else
288     {
289         // apply additive mode to newly created Attribute layer
290         maAttributeLayerHolder.get()->setAdditiveMode(
291             mxAnimateNode->getAdditive() );
292     }
293 
294     // fake normal animation behaviour, even if we
295     // show nothing.  This is the appropriate way to
296     // handle errors on Activity generation, because
297     // maybe all other effects on the slide are
298     // correctly initialized (but won't run, if we
299     // signal an error here)
300     if (mpActivity) {
301         // supply Activity (and the underlying Animation) with
302         // it's AttributeLayer, to perform the animation on
303         mpActivity->setTargets( getShape(), maAttributeLayerHolder.get() );
304 
305         // add to activities queue
306         getContext().mrActivitiesQueue.addActivity( mpActivity );
307     }
308     else {
309         // Actually, DO generate the event for empty activity,
310         // to keep the chain of animations running
311         BaseNode::scheduleDeactivationEvent();
312     }
313 }
314 
315 void AnimationBaseNode::deactivate_st( NodeState eDestState )
316 {
317     if (eDestState == FROZEN) {
318         if (mpActivity)
319             mpActivity->end();
320     }
321 
322     if (isDependentSubsettedShape()) {
323         // for dependent subsets, remove subset shape
324         // from layer, re-integrate subsetted part
325         // back into original shape. For independent
326         // subsets, we cannot make any assumptions
327         // about subset attribute state relative to
328         // master shape, thus, have to keep it. This
329         // will effectively re-integrate the subsetted
330         // part into the original shape (whose
331         // animation will hopefully have ended, too)
332 
333         // this statement will save a whole lot of
334         // sprites for iterated text effects, since
335         // those sprites will only exist during the
336         // actual lifetime of the effects
337         if (mpShapeSubset) {
338             mpShapeSubset->disableSubsetShape();
339         }
340     }
341 
342     if (eDestState == ENDED) {
343 
344         // no shape anymore, no layer needed:
345         maAttributeLayerHolder.reset();
346 
347         if (! isDependentSubsettedShape()) {
348 
349             // for all other shapes, removing the
350             // attribute layer quite possibly changes
351             // shape display. Thus, force update
352             AttributableShapeSharedPtr const pShape( getShape() );
353 
354             // don't anybody dare to check against
355             // pShape->isVisible() here, removing the
356             // attribute layer might actually make the
357             // shape invisible!
358             getContext().mpSubsettableShapeManager->notifyShapeUpdate( pShape );
359         }
360 
361         if (mpActivity) {
362             // kill activity, if still running
363             mpActivity->dispose();
364             mpActivity.reset();
365         }
366     }
367 }
368 
369 bool AnimationBaseNode::hasPendingAnimation() const
370 {
371     // TODO(F1): This might not always be true. Are there 'inactive'
372     // animation nodes?
373     return true;
374 }
375 
376 #if defined(VERBOSE) && defined(DBG_UTIL)
377 void AnimationBaseNode::showState() const
378 {
379     BaseNode::showState();
380 
381     VERBOSE_TRACE( "AnimationBaseNode info: independent subset=%s",
382                    mbIsIndependentSubset ? "y" : "n" );
383 }
384 #endif
385 
386 ActivitiesFactory::CommonParameters
387 AnimationBaseNode::fillCommonParameters() const
388 {
389     double nDuration = 0.0;
390 
391     // TODO(F3): Duration/End handling is barely there
392     if( !(mxAnimateNode->getDuration() >>= nDuration) ) {
393         mxAnimateNode->getEnd() >>= nDuration; // Wah.
394     }
395 
396     // minimal duration we fallback to (avoid 0 here!)
397     nDuration = ::std::max( 0.001, nDuration );
398 
399     const bool bAutoReverse( mxAnimateNode->getAutoReverse() );
400 
401     boost::optional<double> aRepeats;
402     double nRepeats = 0;
403     if( (mxAnimateNode->getRepeatCount() >>= nRepeats) ) {
404         aRepeats.reset( nRepeats );
405     }
406     else {
407         if( (mxAnimateNode->getRepeatDuration() >>= nRepeats) ) {
408             // when repeatDuration is given,
409             // autoreverse does _not_ modify the
410             // active duration. Thus, calc repeat
411             // count with already adapted simple
412             // duration (twice the specified duration)
413 
414             // convert duration back to repeat counts
415             if( bAutoReverse )
416                 aRepeats.reset( nRepeats / (2.0 * nDuration) );
417             else
418                 aRepeats.reset( nRepeats / nDuration );
419         }
420         else {
421             // no double value for both values - Timing::INDEFINITE?
422             animations::Timing eTiming;
423 
424             if( !(mxAnimateNode->getRepeatDuration() >>= eTiming) ||
425                 eTiming != animations::Timing_INDEFINITE )
426             {
427                 if( !(mxAnimateNode->getRepeatCount() >>= eTiming) ||
428                     eTiming != animations::Timing_INDEFINITE )
429                 {
430                     // no indefinite timing, no other values given -
431                     // use simple run, i.e. repeat of 1.0
432                     aRepeats.reset( 1.0 );
433                 }
434             }
435         }
436     }
437 
438     // calc accel/decel:
439     double nAcceleration = 0.0;
440     double nDeceleration = 0.0;
441     BaseNodeSharedPtr const pSelf( getSelf() );
442     for ( boost::shared_ptr<BaseNode> pNode( pSelf );
443           pNode; pNode = pNode->getParentNode() )
444     {
445         uno::Reference<animations::XAnimationNode> const xAnimationNode(
446             pNode->getXAnimationNode() );
447         nAcceleration = std::max( nAcceleration,
448                                   xAnimationNode->getAcceleration() );
449         nDeceleration = std::max( nDeceleration,
450                                   xAnimationNode->getDecelerate() );
451     }
452 
453     EventSharedPtr pEndEvent;
454     if (pSelf) {
455         pEndEvent = makeEvent(
456             boost::bind( &AnimationNode::deactivate, pSelf ),
457             "AnimationBaseNode::deactivate");
458     }
459 
460     // Calculate the minimum frame count that depends on the duration and
461     // the minimum frame count.
462     const sal_Int32 nMinFrameCount (basegfx::clamp<sal_Int32>(
463         basegfx::fround(nDuration * FrameRate::MinimumFramesPerSecond), 1, 10));
464 
465     return ActivitiesFactory::CommonParameters(
466         pEndEvent,
467         getContext().mrEventQueue,
468         getContext().mrActivitiesQueue,
469         nDuration,
470         nMinFrameCount,
471         bAutoReverse,
472         aRepeats,
473         nAcceleration,
474         nDeceleration,
475         getShape(),
476         getSlideSize());
477 }
478 
479 AttributableShapeSharedPtr AnimationBaseNode::getShape() const
480 {
481     // any subsetting at all?
482     if (mpShapeSubset)
483         return mpShapeSubset->getSubsetShape();
484     else
485         return mpShape; // nope, plain shape always
486 }
487 
488 } // namespace internal
489 } // namespace slideshow
490 
491