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