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 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 implicitely 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 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 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 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 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 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 365 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) 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 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 502 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