Branch data Line data Source code
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 : : // must be first
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>
31 : :
32 : : #include "nodetools.hxx"
33 : : #include "doctreenode.hxx"
34 : : #include "animationbasenode.hxx"
35 : : #include "delayevent.hxx"
36 : : #include "framerate.hxx"
37 : :
38 : : #include <boost/bind.hpp>
39 : : #include <boost/optional.hpp>
40 : : #include <algorithm>
41 : :
42 : : using namespace com::sun::star;
43 : :
44 : : namespace slideshow {
45 : : namespace internal {
46 : :
47 : 0 : 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 ),
55 : : mpActivity(),
56 : : mpShape(),
57 : : mpShapeSubset(),
58 : : mpSubsetManager(rContext.maContext.mpSubsettableShapeManager),
59 : 0 : mbIsIndependentSubset( rContext.mbIsIndependentSubset )
60 : : {
61 : : // extract native node targets
62 : : // ===========================
63 : :
64 : : // plain shape target
65 : 0 : uno::Reference< drawing::XShape > xShape( mxAnimateNode->getTarget(),
66 : 0 : uno::UNO_QUERY );
67 : :
68 : : // distinguish 5 cases:
69 : : //
70 : : // - plain shape target
71 : : // (NodeContext.mpMasterShapeSubset full set)
72 : : //
73 : : // - parent-generated subset (generate an
74 : : // independent subset)
75 : : //
76 : : // - parent-generated subset from iteration
77 : : // (generate a dependent subset)
78 : : //
79 : : // - XShape target at the XAnimatioNode (generate
80 : : // a plain shape target)
81 : : //
82 : : // - ParagraphTarget target at the XAnimationNode
83 : : // (generate an independent shape subset)
84 : 0 : if( rContext.mpMasterShapeSubset )
85 : : {
86 : 0 : if( rContext.mpMasterShapeSubset->isFullSet() )
87 : : {
88 : : // case 1: plain shape target from parent
89 : 0 : mpShape = rContext.mpMasterShapeSubset->getSubsetShape();
90 : : }
91 : : else
92 : : {
93 : : // cases 2 & 3: subset shape
94 : 0 : mpShapeSubset = rContext.mpMasterShapeSubset;
95 : : }
96 : : }
97 : : else
98 : : {
99 : : // no parent-provided shape, try to extract
100 : : // from XAnimationNode - cases 4 and 5
101 : :
102 : 0 : if( xShape.is() )
103 : : {
104 : 0 : mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager,
105 : 0 : xShape );
106 : : }
107 : : else
108 : : {
109 : : // no shape provided. Maybe a ParagraphTarget?
110 : 0 : presentation::ParagraphTarget aTarget;
111 : :
112 : 0 : if( !(mxAnimateNode->getTarget() >>= aTarget) )
113 : 0 : ENSURE_OR_THROW(
114 : : false, "could not extract any target information" );
115 : :
116 : 0 : xShape = aTarget.Shape;
117 : :
118 : 0 : ENSURE_OR_THROW( xShape.is(), "invalid shape in ParagraphTarget" );
119 : :
120 : 0 : mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager,
121 : 0 : xShape );
122 : :
123 : : // NOTE: For shapes with ParagraphTarget, we ignore
124 : : // the SubItem property. We implicitely assume that it
125 : : // is set to ONLY_TEXT.
126 : : OSL_ENSURE(
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." );
133 : :
134 : : // okay, found a ParagraphTarget with a valid XShape. Does the shape
135 : : // provide the given paragraph?
136 : 0 : if( aTarget.Paragraph >= 0 &&
137 : 0 : mpShape->getTreeNodeSupplier().getNumberOfTreeNodes(
138 : 0 : DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH) > aTarget.Paragraph )
139 : : {
140 : : const DocTreeNode& rTreeNode(
141 : 0 : mpShape->getTreeNodeSupplier().getTreeNode(
142 : : aTarget.Paragraph,
143 : 0 : DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ) );
144 : :
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).
150 : : mpShapeSubset.reset(
151 : : new ShapeSubset( mpShape,
152 : : rTreeNode,
153 : 0 : mpSubsetManager ));
154 : :
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 : 0 : mbIsIndependentSubset = true;
169 : :
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
174 : : // changes.
175 : 0 : mpShapeSubset->enableSubsetShape();
176 : 0 : }
177 : : }
178 : 0 : }
179 : 0 : }
180 : :
181 : 0 : void AnimationBaseNode::dispose()
182 : : {
183 : 0 : if (mpActivity) {
184 : 0 : mpActivity->dispose();
185 : 0 : mpActivity.reset();
186 : : }
187 : :
188 : 0 : maAttributeLayerHolder.reset();
189 : 0 : mxAnimateNode.clear();
190 : 0 : mpShape.reset();
191 : 0 : mpShapeSubset.reset();
192 : :
193 : 0 : BaseNode::dispose();
194 : 0 : }
195 : :
196 : 0 : bool AnimationBaseNode::init_st()
197 : : {
198 : : // if we've still got an old activity lying around, dispose it:
199 : 0 : if (mpActivity) {
200 : 0 : mpActivity->dispose();
201 : 0 : 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 : 0 : mpActivity = createActivity();
213 : : }
214 : 0 : catch (uno::Exception const&) {
215 : : OSL_FAIL( rtl::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.
221 : : }
222 : 0 : return true;
223 : : }
224 : :
225 : 0 : 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 : 0 : if (isDependentSubsettedShape() && mpShapeSubset) {
236 : 0 : mpShapeSubset->enableSubsetShape();
237 : : }
238 : 0 : return true;
239 : : }
240 : :
241 : 0 : void AnimationBaseNode::activate_st()
242 : : {
243 : : // create new attribute layer
244 : 0 : maAttributeLayerHolder.createAttributeLayer( getShape() );
245 : :
246 : 0 : 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 : 0 : if( mxAnimateNode->getBy().hasValue() &&
262 : 0 : !mxAnimateNode->getTo().hasValue() &&
263 : 0 : !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 : 0 : animations::AnimationAdditiveMode::REPLACE );
282 : : }
283 : : else
284 : : {
285 : : // apply additive mode to newly created Attribute layer
286 : : maAttributeLayerHolder.get()->setAdditiveMode(
287 : 0 : 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 : 0 : if (mpActivity) {
297 : : // supply Activity (and the underlying Animation) with
298 : : // it's AttributeLayer, to perform the animation on
299 : 0 : mpActivity->setTargets( getShape(), maAttributeLayerHolder.get() );
300 : :
301 : : // add to activities queue
302 : 0 : 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 : 0 : BaseNode::scheduleDeactivationEvent();
308 : : }
309 : 0 : }
310 : :
311 : 0 : void AnimationBaseNode::deactivate_st( NodeState eDestState )
312 : : {
313 : 0 : if (eDestState == FROZEN) {
314 : 0 : if (mpActivity)
315 : 0 : mpActivity->end();
316 : : }
317 : :
318 : 0 : 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 : 0 : if (mpShapeSubset) {
334 : 0 : mpShapeSubset->disableSubsetShape();
335 : : }
336 : : }
337 : :
338 : 0 : if (eDestState == ENDED) {
339 : :
340 : : // no shape anymore, no layer needed:
341 : 0 : maAttributeLayerHolder.reset();
342 : :
343 : 0 : if (! isDependentSubsettedShape()) {
344 : :
345 : : // for all other shapes, removing the
346 : : // attribute layer quite possibly changes
347 : : // shape display. Thus, force update
348 : 0 : 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 : 0 : getContext().mpSubsettableShapeManager->notifyShapeUpdate( pShape );
355 : : }
356 : :
357 : 0 : if (mpActivity) {
358 : : // kill activity, if still running
359 : 0 : mpActivity->dispose();
360 : 0 : mpActivity.reset();
361 : : }
362 : : }
363 : 0 : }
364 : :
365 : 0 : bool AnimationBaseNode::hasPendingAnimation() const
366 : : {
367 : : // TODO(F1): This might not always be true. Are there 'inactive'
368 : : // animation nodes?
369 : 0 : return true;
370 : : }
371 : :
372 : : #if OSL_DEBUG_LEVEL >= 2 && 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 : 0 : AnimationBaseNode::fillCommonParameters() const
384 : : {
385 : 0 : double nDuration = 0.0;
386 : :
387 : : // TODO(F3): Duration/End handling is barely there
388 : 0 : if( !(mxAnimateNode->getDuration() >>= nDuration) ) {
389 : 0 : mxAnimateNode->getEnd() >>= nDuration; // Wah.
390 : : }
391 : :
392 : : // minimal duration we fallback to (avoid 0 here!)
393 : 0 : nDuration = ::std::max( 0.001, nDuration );
394 : :
395 : 0 : const bool bAutoReverse( mxAnimateNode->getAutoReverse() );
396 : :
397 : 0 : boost::optional<double> aRepeats;
398 : 0 : double nRepeats = 0;
399 : 0 : if( (mxAnimateNode->getRepeatCount() >>= nRepeats) ) {
400 : 0 : aRepeats.reset( nRepeats );
401 : : }
402 : : else {
403 : 0 : 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 : 0 : if( bAutoReverse )
412 : 0 : aRepeats.reset( nRepeats / (2.0 * nDuration) );
413 : : else
414 : 0 : aRepeats.reset( nRepeats / nDuration );
415 : : }
416 : : else
417 : : {
418 : : // no double value for both values - Timing::INDEFINITE?
419 : : animations::Timing eTiming;
420 : :
421 : 0 : if( !(mxAnimateNode->getRepeatDuration() >>= eTiming) ||
422 : : eTiming != animations::Timing_INDEFINITE )
423 : : {
424 : 0 : if( !(mxAnimateNode->getRepeatCount() >>= eTiming) ||
425 : : eTiming != animations::Timing_INDEFINITE )
426 : : {
427 : : // no indefinite timing, no other values given -
428 : : // use simple run, i.e. repeat of 1.0
429 : 0 : aRepeats.reset( 1.0 );
430 : : }
431 : : }
432 : : }
433 : : }
434 : :
435 : : // calc accel/decel:
436 : 0 : double nAcceleration = 0.0;
437 : 0 : double nDeceleration = 0.0;
438 : 0 : BaseNodeSharedPtr const pSelf( getSelf() );
439 : 0 : for ( boost::shared_ptr<BaseNode> pNode( pSelf );
440 : 0 : pNode; pNode = pNode->getParentNode() )
441 : : {
442 : : uno::Reference<animations::XAnimationNode> const xAnimationNode(
443 : 0 : pNode->getXAnimationNode() );
444 : : nAcceleration = std::max( nAcceleration,
445 : 0 : xAnimationNode->getAcceleration() );
446 : : nDeceleration = std::max( nDeceleration,
447 : 0 : xAnimationNode->getDecelerate() );
448 : 0 : }
449 : :
450 : 0 : EventSharedPtr pEndEvent;
451 : 0 : if (pSelf) {
452 : 0 : pEndEvent = makeEvent(
453 : : boost::bind( &AnimationNode::deactivate, pSelf ),
454 : 0 : "AnimationBaseNode::deactivate");
455 : : }
456 : :
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 : 0 : basegfx::fround(nDuration * FrameRate::MinimumFramesPerSecond), 1, 10));
461 : :
462 : : return ActivitiesFactory::CommonParameters(
463 : : pEndEvent,
464 : 0 : getContext().mrEventQueue,
465 : 0 : getContext().mrActivitiesQueue,
466 : : nDuration,
467 : : nMinFrameCount,
468 : : bAutoReverse,
469 : : aRepeats,
470 : : nAcceleration,
471 : : nDeceleration,
472 : : getShape(),
473 : 0 : getSlideSize());
474 : : }
475 : :
476 : 0 : AttributableShapeSharedPtr AnimationBaseNode::getShape() const
477 : : {
478 : : // any subsetting at all?
479 : 0 : if (mpShapeSubset)
480 : 0 : return mpShapeSubset->getSubsetShape();
481 : : else
482 : 0 : return mpShape; // nope, plain shape always
483 : : }
484 : :
485 : : } // namespace internal
486 : 0 : } // namespace slideshow
487 : :
488 : : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|