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