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
AnimationBaseNode(const uno::Reference<animations::XAnimationNode> & xNode,const BaseContainerNodeSharedPtr & rParent,const NodeContext & rContext)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 implicitly 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
dispose()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
init_st()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
resolve_st()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
activate_st()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
deactivate_st(NodeState eDestState)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
hasPendingAnimation() const365 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)
showState() const373 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
fillCommonParameters() const383 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 bool bRepeatIndefinite = false;
400 animations::Timing eTiming;
401
402 // Search parent nodes for an explicitly stated repeat count.
403 BaseNodeSharedPtr const pSelf( getSelf() );
404 for ( boost::shared_ptr<BaseNode> pNode( pSelf );
405 pNode;
406 pNode = pNode->getParentNode() )
407 {
408 uno::Reference<animations::XAnimationNode> const xAnimationNode(
409 pNode->getXAnimationNode() );
410 if( (xAnimationNode->getRepeatCount() >>= nRepeats) )
411 {
412 // Found an explicit repeat count.
413 break;
414 }
415 if( (xAnimationNode->getRepeatCount() >>= eTiming) &&
416 (eTiming == animations::Timing_INDEFINITE ))
417 {
418 // Found an explicit repeat count of Timing::INDEFINITE.
419 bRepeatIndefinite = true;
420 break;
421 }
422 }
423
424 if( nRepeats || bRepeatIndefinite ) {
425 if (nRepeats)
426 {
427 aRepeats.reset( nRepeats );
428 }
429 }
430 else {
431 if( (mxAnimateNode->getRepeatDuration() >>= nRepeats) ) {
432 // when repeatDuration is given,
433 // autoreverse does _not_ modify the
434 // active duration. Thus, calc repeat
435 // count with already adapted simple
436 // duration (twice the specified duration)
437
438 // convert duration back to repeat counts
439 if( bAutoReverse )
440 aRepeats.reset( nRepeats / (2.0 * nDuration) );
441 else
442 aRepeats.reset( nRepeats / nDuration );
443 }
444 else {
445 // no double value for both values - Timing::INDEFINITE?
446 animations::Timing eTiming;
447
448 if( !(mxAnimateNode->getRepeatDuration() >>= eTiming) ||
449 eTiming != animations::Timing_INDEFINITE )
450 {
451 if( !(mxAnimateNode->getRepeatCount() >>= eTiming) ||
452 eTiming != animations::Timing_INDEFINITE )
453 {
454 // no indefinite timing, no other values given -
455 // use simple run, i.e. repeat of 1.0
456 aRepeats.reset( 1.0 );
457 }
458 }
459 }
460 }
461
462 // calc accel/decel:
463 double nAcceleration = 0.0;
464 double nDeceleration = 0.0;
465 for ( boost::shared_ptr<BaseNode> pNode( pSelf );
466 pNode; pNode = pNode->getParentNode() )
467 {
468 uno::Reference<animations::XAnimationNode> const xAnimationNode(
469 pNode->getXAnimationNode() );
470 nAcceleration = std::max( nAcceleration,
471 xAnimationNode->getAcceleration() );
472 nDeceleration = std::max( nDeceleration,
473 xAnimationNode->getDecelerate() );
474 }
475
476 EventSharedPtr pEndEvent;
477 if (pSelf) {
478 pEndEvent = makeEvent(
479 boost::bind( &AnimationNode::deactivate, pSelf ),
480 "AnimationBaseNode::deactivate");
481 }
482
483 // Calculate the minimum frame count that depends on the duration and
484 // the minimum frame count.
485 const sal_Int32 nMinFrameCount (basegfx::clamp<sal_Int32>(
486 basegfx::fround(nDuration * FrameRate::MinimumFramesPerSecond), 1, 10));
487
488 return ActivitiesFactory::CommonParameters(
489 pEndEvent,
490 getContext().mrEventQueue,
491 getContext().mrActivitiesQueue,
492 nDuration,
493 nMinFrameCount,
494 bAutoReverse,
495 aRepeats,
496 nAcceleration,
497 nDeceleration,
498 getShape(),
499 getSlideSize());
500 }
501
getShape() const502 AttributableShapeSharedPtr AnimationBaseNode::getShape() const
503 {
504 // any subsetting at all?
505 if (mpShapeSubset)
506 return mpShapeSubset->getSubsetShape();
507 else
508 return mpShape; // nope, plain shape always
509 }
510
511 } // namespace internal
512 } // namespace slideshow
513
514