1 /************************************************************************* 2 * 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * Copyright 2000, 2010 Oracle and/or its affiliates. 6 * 7 * OpenOffice.org - a multi-platform office productivity suite 8 * 9 * This file is part of OpenOffice.org. 10 * 11 * OpenOffice.org is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU Lesser General Public License version 3 13 * only, as published by the Free Software Foundation. 14 * 15 * OpenOffice.org is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU Lesser General Public License version 3 for more details 19 * (a copy is included in the LICENSE file that accompanied this code). 20 * 21 * You should have received a copy of the GNU Lesser General Public License 22 * version 3 along with OpenOffice.org. If not, see 23 * <http://www.openoffice.org/license.html> 24 * for a copy of the LGPLv3 License. 25 * 26 ************************************************************************/ 27 28 // MARKER(update_precomp.py): autogen include statement, do not remove 29 #include "precompiled_slideshow.hxx" 30 31 // must be first 32 #include <canvas/debug.hxx> 33 #include <canvas/verbosetrace.hxx> 34 #include <cppuhelper/exc_hlp.hxx> 35 #include <comphelper/anytostring.hxx> 36 #include <com/sun/star/presentation/ParagraphTarget.hpp> 37 #include <com/sun/star/animations/Timing.hpp> 38 #include <com/sun/star/animations/AnimationAdditiveMode.hpp> 39 #include <com/sun/star/presentation/ShapeAnimationSubType.hpp> 40 41 #include "nodetools.hxx" 42 #include "doctreenode.hxx" 43 #include "animationbasenode.hxx" 44 #include "delayevent.hxx" 45 #include "framerate.hxx" 46 47 #include <boost/bind.hpp> 48 #include <boost/optional.hpp> 49 #include <algorithm> 50 51 using namespace com::sun::star; 52 53 namespace slideshow { 54 namespace internal { 55 56 AnimationBaseNode::AnimationBaseNode( 57 const uno::Reference< animations::XAnimationNode >& xNode, 58 const BaseContainerNodeSharedPtr& rParent, 59 const NodeContext& rContext ) 60 : BaseNode( xNode, rParent, rContext ), 61 mxAnimateNode( xNode, uno::UNO_QUERY_THROW ), 62 maAttributeLayerHolder(), 63 maSlideSize( rContext.maSlideSize ), 64 mpActivity(), 65 mpShape(), 66 mpShapeSubset(), 67 mpSubsetManager(rContext.maContext.mpSubsettableShapeManager), 68 mbIsIndependentSubset( rContext.mbIsIndependentSubset ) 69 { 70 // extract native node targets 71 // =========================== 72 73 // plain shape target 74 uno::Reference< drawing::XShape > xShape( mxAnimateNode->getTarget(), 75 uno::UNO_QUERY ); 76 77 // distinguish 5 cases: 78 // 79 // - plain shape target 80 // (NodeContext.mpMasterShapeSubset full set) 81 // 82 // - parent-generated subset (generate an 83 // independent subset) 84 // 85 // - parent-generated subset from iteration 86 // (generate a dependent subset) 87 // 88 // - XShape target at the XAnimatioNode (generate 89 // a plain shape target) 90 // 91 // - ParagraphTarget target at the XAnimationNode 92 // (generate an independent shape subset) 93 if( rContext.mpMasterShapeSubset ) 94 { 95 if( rContext.mpMasterShapeSubset->isFullSet() ) 96 { 97 // case 1: plain shape target from parent 98 mpShape = rContext.mpMasterShapeSubset->getSubsetShape(); 99 } 100 else 101 { 102 // cases 2 & 3: subset shape 103 mpShapeSubset = rContext.mpMasterShapeSubset; 104 } 105 } 106 else 107 { 108 // no parent-provided shape, try to extract 109 // from XAnimationNode - cases 4 and 5 110 111 if( xShape.is() ) 112 { 113 mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager, 114 xShape ); 115 } 116 else 117 { 118 // no shape provided. Maybe a ParagraphTarget? 119 presentation::ParagraphTarget aTarget; 120 121 if( !(mxAnimateNode->getTarget() >>= aTarget) ) 122 ENSURE_OR_THROW( 123 false, "could not extract any target information" ); 124 125 xShape = aTarget.Shape; 126 127 ENSURE_OR_THROW( xShape.is(), "invalid shape in ParagraphTarget" ); 128 129 mpShape = lookupAttributableShape( getContext().mpSubsettableShapeManager, 130 xShape ); 131 132 // NOTE: For shapes with ParagraphTarget, we ignore 133 // the SubItem property. We implicitely assume that it 134 // is set to ONLY_TEXT. 135 OSL_ENSURE( 136 mxAnimateNode->getSubItem() == 137 presentation::ShapeAnimationSubType::ONLY_TEXT || 138 mxAnimateNode->getSubItem() == 139 presentation::ShapeAnimationSubType::AS_WHOLE, 140 "ParagraphTarget given, but subitem not AS_TEXT or AS_WHOLE? " 141 "Make up your mind, I'll ignore the subitem." ); 142 143 // okay, found a ParagraphTarget with a valid XShape. Does the shape 144 // provide the given paragraph? 145 const DocTreeNode& rTreeNode( 146 mpShape->getTreeNodeSupplier().getTreeNode( 147 aTarget.Paragraph, 148 DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ) ); 149 150 // CAUTION: the creation of the subset shape 151 // _must_ stay in the node constructor, since 152 // Slide::prefetchShow() initializes shape 153 // attributes right after animation import (or 154 // the Slide class must be changed). 155 mpShapeSubset.reset( 156 new ShapeSubset( mpShape, 157 rTreeNode, 158 mpSubsetManager )); 159 160 // Override NodeContext, and flag this node as 161 // a special independent subset one. This is 162 // important when applying initial attributes: 163 // independent shape subsets must be setup 164 // when the slide starts, since they, as their 165 // name suggest, can have state independent to 166 // the master shape. The following example 167 // might illustrate that: a master shape has 168 // no effect, one of the text paragraphs 169 // within it has an appear effect. Now, the 170 // respective paragraph must be invisible when 171 // the slide is initially shown, and become 172 // visible only when the effect starts. 173 mbIsIndependentSubset = true; 174 175 // already enable subset right here, the 176 // setup of initial shape attributes of 177 // course needs the subset shape 178 // generated, to apply e.g. visibility 179 // changes. 180 mpShapeSubset->enableSubsetShape(); 181 } 182 } 183 } 184 185 void AnimationBaseNode::dispose() 186 { 187 if (mpActivity) { 188 mpActivity->dispose(); 189 mpActivity.reset(); 190 } 191 192 maAttributeLayerHolder.reset(); 193 mxAnimateNode.clear(); 194 mpShape.reset(); 195 mpShapeSubset.reset(); 196 197 BaseNode::dispose(); 198 } 199 200 bool AnimationBaseNode::init_st() 201 { 202 // if we've still got an old activity lying around, dispose it: 203 if (mpActivity) { 204 mpActivity->dispose(); 205 mpActivity.reset(); 206 } 207 208 // note: actually disposing the activity too early might cause problems, 209 // because on dequeued() it calls endAnimation(pAnim->end()), thus ending 210 // animation _after_ last screen update. 211 // review that end() is properly called (which calls endAnimation(), too). 212 213 try { 214 // TODO(F2): For restart functionality, we must regenerate activities, 215 // since they are not able to reset their state (or implement _that_) 216 mpActivity = createActivity(); 217 } 218 catch (uno::Exception const&) { 219 OSL_ENSURE( false, rtl::OUStringToOString( 220 comphelper::anyToString(cppu::getCaughtException()), 221 RTL_TEXTENCODING_UTF8 ) ); 222 // catch and ignore. We later handle empty activities, but for 223 // other nodes to function properly, the core functionality of 224 // this node must remain up and running. 225 } 226 return true; 227 } 228 229 bool AnimationBaseNode::resolve_st() 230 { 231 // enable shape subset for automatically generated 232 // subsets. Independent subsets are already setup 233 // during construction time. Doing it only here 234 // saves us a lot of sprites and shapes lying 235 // around. This is especially important for 236 // character-wise iterations, since the shape 237 // content (e.g. thousands of characters) would 238 // otherwise be painted character-by-character. 239 if (isDependentSubsettedShape() && mpShapeSubset) { 240 mpShapeSubset->enableSubsetShape(); 241 } 242 return true; 243 } 244 245 void AnimationBaseNode::activate_st() 246 { 247 // create new attribute layer 248 maAttributeLayerHolder.createAttributeLayer( getShape() ); 249 250 ENSURE_OR_THROW( maAttributeLayerHolder.get(), 251 "Could not generate shape attribute layer" ); 252 253 // TODO(Q2): This affects the way mpActivity 254 // works, but is performed here because of 255 // locality (we're fiddling with the additive mode 256 // here, anyway, and it's the only place where we 257 // do). OTOH, maybe the complete additive mode 258 // setup should be moved to the activities. 259 260 // for simple by-animations, the SMIL spec 261 // requires us to emulate "0,by-value" value list 262 // behaviour, with additive mode forced to "sum", 263 // no matter what the input is 264 // (http://www.w3.org/TR/smil20/animation.html#adef-by). 265 if( mxAnimateNode->getBy().hasValue() && 266 !mxAnimateNode->getTo().hasValue() && 267 !mxAnimateNode->getFrom().hasValue() ) 268 { 269 // force attribute mode to REPLACE (note the 270 // subtle discrepancy to the paragraph above, 271 // where SMIL requires SUM. This is internally 272 // handled by the FromToByActivity, and is 273 // because otherwise DOM values would not be 274 // handled correctly: the activity cannot 275 // determine whether an 276 // Activity::getUnderlyingValue() yields the 277 // DOM value, or already a summed-up conglomerate) 278 // 279 // Note that this poses problems with our 280 // hybrid activity duration (time or min number of frames), 281 // since if activities 282 // exceed their duration, wrong 'by' start 283 // values might arise ('Laser effect') 284 maAttributeLayerHolder.get()->setAdditiveMode( 285 animations::AnimationAdditiveMode::REPLACE ); 286 } 287 else 288 { 289 // apply additive mode to newly created Attribute layer 290 maAttributeLayerHolder.get()->setAdditiveMode( 291 mxAnimateNode->getAdditive() ); 292 } 293 294 // fake normal animation behaviour, even if we 295 // show nothing. This is the appropriate way to 296 // handle errors on Activity generation, because 297 // maybe all other effects on the slide are 298 // correctly initialized (but won't run, if we 299 // signal an error here) 300 if (mpActivity) { 301 // supply Activity (and the underlying Animation) with 302 // it's AttributeLayer, to perform the animation on 303 mpActivity->setTargets( getShape(), maAttributeLayerHolder.get() ); 304 305 // add to activities queue 306 getContext().mrActivitiesQueue.addActivity( mpActivity ); 307 } 308 else { 309 // Actually, DO generate the event for empty activity, 310 // to keep the chain of animations running 311 BaseNode::scheduleDeactivationEvent(); 312 } 313 } 314 315 void AnimationBaseNode::deactivate_st( NodeState eDestState ) 316 { 317 if (eDestState == FROZEN) { 318 if (mpActivity) 319 mpActivity->end(); 320 } 321 322 if (isDependentSubsettedShape()) { 323 // for dependent subsets, remove subset shape 324 // from layer, re-integrate subsetted part 325 // back into original shape. For independent 326 // subsets, we cannot make any assumptions 327 // about subset attribute state relative to 328 // master shape, thus, have to keep it. This 329 // will effectively re-integrate the subsetted 330 // part into the original shape (whose 331 // animation will hopefully have ended, too) 332 333 // this statement will save a whole lot of 334 // sprites for iterated text effects, since 335 // those sprites will only exist during the 336 // actual lifetime of the effects 337 if (mpShapeSubset) { 338 mpShapeSubset->disableSubsetShape(); 339 } 340 } 341 342 if (eDestState == ENDED) { 343 344 // no shape anymore, no layer needed: 345 maAttributeLayerHolder.reset(); 346 347 if (! isDependentSubsettedShape()) { 348 349 // for all other shapes, removing the 350 // attribute layer quite possibly changes 351 // shape display. Thus, force update 352 AttributableShapeSharedPtr const pShape( getShape() ); 353 354 // don't anybody dare to check against 355 // pShape->isVisible() here, removing the 356 // attribute layer might actually make the 357 // shape invisible! 358 getContext().mpSubsettableShapeManager->notifyShapeUpdate( pShape ); 359 } 360 361 if (mpActivity) { 362 // kill activity, if still running 363 mpActivity->dispose(); 364 mpActivity.reset(); 365 } 366 } 367 } 368 369 bool AnimationBaseNode::hasPendingAnimation() const 370 { 371 // TODO(F1): This might not always be true. Are there 'inactive' 372 // animation nodes? 373 return true; 374 } 375 376 #if defined(VERBOSE) && defined(DBG_UTIL) 377 void AnimationBaseNode::showState() const 378 { 379 BaseNode::showState(); 380 381 VERBOSE_TRACE( "AnimationBaseNode info: independent subset=%s", 382 mbIsIndependentSubset ? "y" : "n" ); 383 } 384 #endif 385 386 ActivitiesFactory::CommonParameters 387 AnimationBaseNode::fillCommonParameters() const 388 { 389 double nDuration = 0.0; 390 391 // TODO(F3): Duration/End handling is barely there 392 if( !(mxAnimateNode->getDuration() >>= nDuration) ) { 393 mxAnimateNode->getEnd() >>= nDuration; // Wah. 394 } 395 396 // minimal duration we fallback to (avoid 0 here!) 397 nDuration = ::std::max( 0.001, nDuration ); 398 399 const bool bAutoReverse( mxAnimateNode->getAutoReverse() ); 400 401 boost::optional<double> aRepeats; 402 double nRepeats = 0; 403 if( (mxAnimateNode->getRepeatCount() >>= nRepeats) ) { 404 aRepeats.reset( nRepeats ); 405 } 406 else { 407 if( (mxAnimateNode->getRepeatDuration() >>= nRepeats) ) { 408 // when repeatDuration is given, 409 // autoreverse does _not_ modify the 410 // active duration. Thus, calc repeat 411 // count with already adapted simple 412 // duration (twice the specified duration) 413 414 // convert duration back to repeat counts 415 if( bAutoReverse ) 416 aRepeats.reset( nRepeats / (2.0 * nDuration) ); 417 else 418 aRepeats.reset( nRepeats / nDuration ); 419 } 420 else { 421 // no double value for both values - Timing::INDEFINITE? 422 animations::Timing eTiming; 423 424 if( !(mxAnimateNode->getRepeatDuration() >>= eTiming) || 425 eTiming != animations::Timing_INDEFINITE ) 426 { 427 if( !(mxAnimateNode->getRepeatCount() >>= eTiming) || 428 eTiming != animations::Timing_INDEFINITE ) 429 { 430 // no indefinite timing, no other values given - 431 // use simple run, i.e. repeat of 1.0 432 aRepeats.reset( 1.0 ); 433 } 434 } 435 } 436 } 437 438 // calc accel/decel: 439 double nAcceleration = 0.0; 440 double nDeceleration = 0.0; 441 BaseNodeSharedPtr const pSelf( getSelf() ); 442 for ( boost::shared_ptr<BaseNode> pNode( pSelf ); 443 pNode; pNode = pNode->getParentNode() ) 444 { 445 uno::Reference<animations::XAnimationNode> const xAnimationNode( 446 pNode->getXAnimationNode() ); 447 nAcceleration = std::max( nAcceleration, 448 xAnimationNode->getAcceleration() ); 449 nDeceleration = std::max( nDeceleration, 450 xAnimationNode->getDecelerate() ); 451 } 452 453 EventSharedPtr pEndEvent; 454 if (pSelf) { 455 pEndEvent = makeEvent( 456 boost::bind( &AnimationNode::deactivate, pSelf ), 457 "AnimationBaseNode::deactivate"); 458 } 459 460 // Calculate the minimum frame count that depends on the duration and 461 // the minimum frame count. 462 const sal_Int32 nMinFrameCount (basegfx::clamp<sal_Int32>( 463 basegfx::fround(nDuration * FrameRate::MinimumFramesPerSecond), 1, 10)); 464 465 return ActivitiesFactory::CommonParameters( 466 pEndEvent, 467 getContext().mrEventQueue, 468 getContext().mrActivitiesQueue, 469 nDuration, 470 nMinFrameCount, 471 bAutoReverse, 472 aRepeats, 473 nAcceleration, 474 nDeceleration, 475 getShape(), 476 getSlideSize()); 477 } 478 479 AttributableShapeSharedPtr AnimationBaseNode::getShape() const 480 { 481 // any subsetting at all? 482 if (mpShapeSubset) 483 return mpShapeSubset->getSubsetShape(); 484 else 485 return mpShape; // nope, plain shape always 486 } 487 488 } // namespace internal 489 } // namespace slideshow 490 491