1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
22 #include <canvas/debug.hxx>
23 #include <canvas/verbosetrace.hxx>
24 #include <cppuhelper/exc_hlp.hxx>
25 #include <comphelper/anytostring.hxx>
26 #include <com/sun/star/presentation/ParagraphTarget.hpp>
27 #include <com/sun/star/animations/AnimationNodeType.hpp>
28 #include <com/sun/star/animations/Timing.hpp>
29 #include <com/sun/star/animations/AnimationAdditiveMode.hpp>
30 #include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
32 #include "nodetools.hxx"
33 #include "doctreenode.hxx"
34 #include "animationbasenode.hxx"
35 #include "delayevent.hxx"
36 #include "framerate.hxx"
38 #include <boost/bind.hpp>
39 #include <boost/optional.hpp>
42 using namespace com::sun::star
;
47 AnimationBaseNode::AnimationBaseNode(
48 const uno::Reference
< animations::XAnimationNode
>& xNode
,
49 const BaseContainerNodeSharedPtr
& rParent
,
50 const NodeContext
& rContext
)
51 : BaseNode( xNode
, rParent
, rContext
),
52 mxAnimateNode( xNode
, uno::UNO_QUERY_THROW
),
53 maAttributeLayerHolder(),
54 maSlideSize( rContext
.maSlideSize
),
58 mpSubsetManager(rContext
.maContext
.mpSubsettableShapeManager
),
59 mbIsIndependentSubset( rContext
.mbIsIndependentSubset
)
61 // extract native node targets
62 // ===========================
65 uno::Reference
< drawing::XShape
> xShape( mxAnimateNode
->getTarget(),
68 // distinguish 5 cases:
70 // - plain shape target
71 // (NodeContext.mpMasterShapeSubset full set)
73 // - parent-generated subset (generate an
74 // independent subset)
76 // - parent-generated subset from iteration
77 // (generate a dependent subset)
79 // - XShape target at the XAnimatioNode (generate
80 // a plain shape target)
82 // - ParagraphTarget target at the XAnimationNode
83 // (generate an independent shape subset)
84 if( rContext
.mpMasterShapeSubset
)
86 if( rContext
.mpMasterShapeSubset
->isFullSet() )
88 // case 1: plain shape target from parent
89 mpShape
= rContext
.mpMasterShapeSubset
->getSubsetShape();
93 // cases 2 & 3: subset shape
94 mpShapeSubset
= rContext
.mpMasterShapeSubset
;
99 // no parent-provided shape, try to extract
100 // from XAnimationNode - cases 4 and 5
104 mpShape
= lookupAttributableShape( getContext().mpSubsettableShapeManager
,
109 // no shape provided. Maybe a ParagraphTarget?
110 presentation::ParagraphTarget aTarget
;
112 if( !(mxAnimateNode
->getTarget() >>= aTarget
) )
114 false, "could not extract any target information" );
116 xShape
= aTarget
.Shape
;
118 ENSURE_OR_THROW( xShape
.is(), "invalid shape in ParagraphTarget" );
120 mpShape
= lookupAttributableShape( getContext().mpSubsettableShapeManager
,
123 // NOTE: For shapes with ParagraphTarget, we ignore
124 // the SubItem property. We implicitly assume that it
125 // is set to ONLY_TEXT.
127 mxAnimateNode
->getSubItem() ==
128 presentation::ShapeAnimationSubType::ONLY_TEXT
||
129 mxAnimateNode
->getSubItem() ==
130 presentation::ShapeAnimationSubType::AS_WHOLE
,
131 "ParagraphTarget given, but subitem not AS_TEXT or AS_WHOLE? "
132 "Make up your mind, I'll ignore the subitem." );
134 // okay, found a ParagraphTarget with a valid XShape. Does the shape
135 // provide the given paragraph?
136 if( aTarget
.Paragraph
>= 0 &&
137 mpShape
->getTreeNodeSupplier().getNumberOfTreeNodes(
138 DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH
) > aTarget
.Paragraph
)
140 const DocTreeNode
& rTreeNode(
141 mpShape
->getTreeNodeSupplier().getTreeNode(
143 DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH
) );
145 // CAUTION: the creation of the subset shape
146 // _must_ stay in the node constructor, since
147 // Slide::prefetchShow() initializes shape
148 // attributes right after animation import (or
149 // the Slide class must be changed).
151 new ShapeSubset( mpShape
,
155 // Override NodeContext, and flag this node as
156 // a special independent subset one. This is
157 // important when applying initial attributes:
158 // independent shape subsets must be setup
159 // when the slide starts, since they, as their
160 // name suggest, can have state independent to
161 // the master shape. The following example
162 // might illustrate that: a master shape has
163 // no effect, one of the text paragraphs
164 // within it has an appear effect. Now, the
165 // respective paragraph must be invisible when
166 // the slide is initially shown, and become
167 // visible only when the effect starts.
168 mbIsIndependentSubset
= true;
170 // already enable subset right here, the
171 // setup of initial shape attributes of
172 // course needs the subset shape
173 // generated, to apply e.g. visibility
175 mpShapeSubset
->enableSubsetShape();
181 void AnimationBaseNode::dispose()
184 mpActivity
->dispose();
188 maAttributeLayerHolder
.reset();
189 mxAnimateNode
.clear();
191 mpShapeSubset
.reset();
196 bool AnimationBaseNode::init_st()
198 // if we've still got an old activity lying around, dispose it:
200 mpActivity
->dispose();
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).
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();
214 catch (uno::Exception
const&) {
215 OSL_FAIL( OUStringToOString(
216 comphelper::anyToString(cppu::getCaughtException()),
217 RTL_TEXTENCODING_UTF8
).getStr() );
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.
225 bool AnimationBaseNode::resolve_st()
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();
241 void AnimationBaseNode::activate_st()
243 // create new attribute layer
244 maAttributeLayerHolder
.createAttributeLayer( getShape() );
246 ENSURE_OR_THROW( maAttributeLayerHolder
.get(),
247 "Could not generate shape attribute layer" );
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.
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() )
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)
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
);
285 // apply additive mode to newly created Attribute layer
286 maAttributeLayerHolder
.get()->setAdditiveMode(
287 mxAnimateNode
->getAdditive() );
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)
297 // supply Activity (and the underlying Animation) with
298 // it's AttributeLayer, to perform the animation on
299 mpActivity
->setTargets( getShape(), maAttributeLayerHolder
.get() );
301 // add to activities queue
302 getContext().mrActivitiesQueue
.addActivity( mpActivity
);
305 // Actually, DO generate the event for empty activity,
306 // to keep the chain of animations running
307 BaseNode::scheduleDeactivationEvent();
311 void AnimationBaseNode::deactivate_st( NodeState eDestState
)
313 if (eDestState
== FROZEN
) {
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)
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
334 mpShapeSubset
->disableSubsetShape();
338 if (eDestState
== ENDED
) {
340 // no shape anymore, no layer needed:
341 maAttributeLayerHolder
.reset();
343 if (! isDependentSubsettedShape()) {
345 // for all other shapes, removing the
346 // attribute layer quite possibly changes
347 // shape display. Thus, force update
348 AttributableShapeSharedPtr
const pShape( getShape() );
350 // don't anybody dare to check against
351 // pShape->isVisible() here, removing the
352 // attribute layer might actually make the
354 getContext().mpSubsettableShapeManager
->notifyShapeUpdate( pShape
);
358 // kill activity, if still running
359 mpActivity
->dispose();
365 bool AnimationBaseNode::hasPendingAnimation() const
367 // TODO(F1): This might not always be true. Are there 'inactive'
372 #if OSL_DEBUG_LEVEL >= 2 && defined(DBG_UTIL)
373 void AnimationBaseNode::showState() const
375 BaseNode::showState();
377 VERBOSE_TRACE( "AnimationBaseNode info: independent subset=%s",
378 mbIsIndependentSubset
? "y" : "n" );
382 ActivitiesFactory::CommonParameters
383 AnimationBaseNode::fillCommonParameters() const
385 double nDuration
= 0.0;
387 // TODO(F3): Duration/End handling is barely there
388 if( !(mxAnimateNode
->getDuration() >>= nDuration
) ) {
389 mxAnimateNode
->getEnd() >>= nDuration
; // Wah.
392 // minimal duration we fallback to (avoid 0 here!)
393 nDuration
= ::std::max( 0.001, nDuration
);
395 const bool bAutoReverse( mxAnimateNode
->getAutoReverse() );
397 boost::optional
<double> aRepeats
;
399 if( (mxAnimateNode
->getRepeatCount() >>= nRepeats
) ) {
400 aRepeats
.reset( nRepeats
);
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)
410 // convert duration back to repeat counts
412 aRepeats
.reset( nRepeats
/ (2.0 * nDuration
) );
414 aRepeats
.reset( nRepeats
/ nDuration
);
418 // no double value for both values - Timing::INDEFINITE?
419 animations::Timing eTiming
;
421 if( !(mxAnimateNode
->getRepeatDuration() >>= eTiming
) ||
422 eTiming
!= animations::Timing_INDEFINITE
)
424 if( !(mxAnimateNode
->getRepeatCount() >>= eTiming
) ||
425 eTiming
!= animations::Timing_INDEFINITE
)
427 // no indefinite timing, no other values given -
428 // use simple run, i.e. repeat of 1.0
429 aRepeats
.reset( 1.0 );
436 double nAcceleration
= 0.0;
437 double nDeceleration
= 0.0;
438 BaseNodeSharedPtr
const pSelf( getSelf() );
439 for ( boost::shared_ptr
<BaseNode
> pNode( pSelf
);
440 pNode
; pNode
= pNode
->getParentNode() )
442 uno::Reference
<animations::XAnimationNode
> const xAnimationNode(
443 pNode
->getXAnimationNode() );
444 nAcceleration
= std::max( nAcceleration
,
445 xAnimationNode
->getAcceleration() );
446 nDeceleration
= std::max( nDeceleration
,
447 xAnimationNode
->getDecelerate() );
450 EventSharedPtr pEndEvent
;
452 pEndEvent
= makeEvent(
453 boost::bind( &AnimationNode::deactivate
, pSelf
),
454 "AnimationBaseNode::deactivate");
457 // Calculate the minimum frame count that depends on the duration and
458 // the minimum frame count.
459 const sal_Int32
nMinFrameCount (basegfx::clamp
<sal_Int32
>(
460 basegfx::fround(nDuration
* FrameRate::MinimumFramesPerSecond
), 1, 10));
462 return ActivitiesFactory::CommonParameters(
464 getContext().mrEventQueue
,
465 getContext().mrActivitiesQueue
,
476 AttributableShapeSharedPtr
AnimationBaseNode::getShape() const
478 // any subsetting at all?
480 return mpShapeSubset
->getSubsetShape();
482 return mpShape
; // nope, plain shape always
485 } // namespace internal
486 } // namespace slideshow
488 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */