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