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 31 #include <com/sun/star/drawing/XShape.hpp> 32 #include <com/sun/star/animations/XAnimate.hpp> 33 #include <com/sun/star/animations/AnimationNodeType.hpp> 34 #include <com/sun/star/presentation/EffectNodeType.hpp> 35 #include <com/sun/star/presentation/TextAnimationType.hpp> 36 #include <com/sun/star/animations/XAnimateSet.hpp> 37 #include <com/sun/star/animations/XIterateContainer.hpp> 38 #include <com/sun/star/presentation/ShapeAnimationSubType.hpp> 39 #include <com/sun/star/animations/XAnimateMotion.hpp> 40 #include <com/sun/star/animations/XAnimateColor.hpp> 41 #include <com/sun/star/animations/XAnimateTransform.hpp> 42 #include <com/sun/star/animations/AnimationTransformType.hpp> 43 #include <com/sun/star/animations/XTransitionFilter.hpp> 44 #include <com/sun/star/animations/XAudio.hpp> 45 #include <com/sun/star/presentation/ParagraphTarget.hpp> 46 #include <com/sun/star/beans/XPropertySet.hpp> 47 #include <animations/animationnodehelper.hxx> 48 #include <basegfx/numeric/ftools.hxx> 49 50 #include "animationnodefactory.hxx" 51 #include "paralleltimecontainer.hxx" 52 #include "sequentialtimecontainer.hxx" 53 #include "propertyanimationnode.hxx" 54 #include "animationsetnode.hxx" 55 #include "animationpathmotionnode.hxx" 56 #include "animationcolornode.hxx" 57 #include "animationtransformnode.hxx" 58 #include "animationtransitionfilternode.hxx" 59 #include "animationaudionode.hxx" 60 #include "animationcommandnode.hxx" 61 #include "nodetools.hxx" 62 #include "tools.hxx" 63 64 #include <boost/bind.hpp> 65 66 using namespace ::com::sun::star; 67 68 namespace slideshow { 69 namespace internal { 70 71 namespace { 72 73 // forward declaration needed by NodeCreator 74 BaseNodeSharedPtr implCreateAnimationNode( 75 const uno::Reference< animations::XAnimationNode >& xNode, 76 const BaseContainerNodeSharedPtr& rParent, 77 const NodeContext& rContext ); 78 79 class NodeCreator 80 { 81 public: 82 NodeCreator( BaseContainerNodeSharedPtr& rParent, 83 const NodeContext& rContext ) 84 : mrParent( rParent ), mrContext( rContext ) {} 85 86 void operator()( 87 const uno::Reference< animations::XAnimationNode >& xChildNode ) const 88 { 89 createChild( xChildNode, mrContext ); 90 } 91 92 protected: 93 void createChild( 94 const uno::Reference< animations::XAnimationNode >& xChildNode, 95 const NodeContext& rContext ) const 96 { 97 BaseNodeSharedPtr pChild( implCreateAnimationNode( xChildNode, 98 mrParent, 99 rContext ) ); 100 101 OSL_ENSURE( pChild, 102 "NodeCreator::operator(): child creation failed" ); 103 104 // TODO(Q1): This yields circular references, which, it seems, is 105 // unavoidable here 106 if( pChild ) 107 mrParent->appendChildNode( pChild ); 108 } 109 110 BaseContainerNodeSharedPtr& mrParent; 111 const NodeContext& mrContext; 112 }; 113 114 /** Same as NodeCreator, only that NodeContext's 115 SubsetShape is cloned for every child node. 116 117 This is used for iterated animation node generation 118 */ 119 class CloningNodeCreator : private NodeCreator 120 { 121 public: 122 CloningNodeCreator( BaseContainerNodeSharedPtr& rParent, 123 const NodeContext& rContext ) 124 : NodeCreator( rParent, rContext ) {} 125 126 void operator()( 127 const uno::Reference< animations::XAnimationNode >& xChildNode ) const 128 { 129 NodeContext aContext( mrContext ); 130 131 // TODO(Q1): There's a catch here. If you clone a 132 // subset whose actual subsetting has already been 133 // realized (i.e. if enableSubsetShape() has been 134 // called already), and the original of your clone 135 // goes out of scope, then your subset will be 136 // gone (SubsettableShapeManager::revokeSubset() be 137 // called). As of now, this behaviour is not 138 // triggered here (we either clone, XOR we enable 139 // subset initially), but one might consider 140 // reworking DrawShape/ShapeSubset to avoid this. 141 142 // clone ShapeSubset, since each node needs their 143 // own version of the ShapeSubset (otherwise, 144 // e.g. activity counting does not work - subset 145 // would be removed after first animation node 146 // disables it). 147 // 148 // NOTE: this is only a problem for animation 149 // nodes that explicitely call 150 // disableSubsetShape(). Independent shape subsets 151 // (like those created for ParagraphTargets) 152 // solely rely on the ShapeSubset destructor to 153 // normalize things, which does the right thing 154 // here: the subset is only removed after _the 155 // last_ animation node releases the shared ptr. 156 aContext.mpMasterShapeSubset.reset( 157 new ShapeSubset( *aContext.mpMasterShapeSubset ) ); 158 159 createChild( xChildNode, aContext ); 160 } 161 }; 162 163 /** Create animation nodes for text iterations 164 165 This method clones the animation nodes below xIterNode 166 for every iterated shape entity. 167 */ 168 bool implCreateIteratedNodes( 169 const uno::Reference< animations::XIterateContainer >& xIterNode, 170 BaseContainerNodeSharedPtr& rParent, 171 const NodeContext& rContext ) 172 { 173 ENSURE_OR_THROW( xIterNode.is(), 174 "implCreateIteratedNodes(): Invalid node" ); 175 176 const double nIntervalTimeout( xIterNode->getIterateInterval() ); 177 178 // valid iterate interval? We're ruling out monstrous 179 // values here, to avoid pseudo 'hangs' in the 180 // presentation 181 if( nIntervalTimeout < 0.0 || 182 nIntervalTimeout > 1000.0 ) 183 { 184 return false; // not an active iteration 185 } 186 187 if( ::basegfx::fTools::equalZero( nIntervalTimeout ) ) 188 OSL_TRACE( "implCreateIteratedNodes(): " 189 "iterate interval close to zero, there's " 190 "no point in defining such an effect " 191 "(visually equivalent to whole-shape effect)" ); 192 193 // Determine target shape (or subset) 194 // ================================== 195 196 // TODO(E1): I'm not too sure what to expect here... 197 ENSURE_OR_RETURN_FALSE( 198 xIterNode->getTarget().hasValue(), 199 "implCreateIteratedNodes(): no target on ITERATE node" ); 200 201 uno::Reference< drawing::XShape > xTargetShape( xIterNode->getTarget(), 202 uno::UNO_QUERY ); 203 204 presentation::ParagraphTarget aTarget; 205 sal_Int16 nSubItem( xIterNode->getSubItem() ); 206 bool bParagraphTarget( false ); 207 208 if( !xTargetShape.is() ) 209 { 210 // no shape provided. Maybe a ParagraphTarget? 211 if( !(xIterNode->getTarget() >>= aTarget) ) 212 ENSURE_OR_RETURN_FALSE( 213 false, 214 "implCreateIteratedNodes(): could not extract any " 215 "target information" ); 216 217 xTargetShape = aTarget.Shape; 218 219 ENSURE_OR_RETURN_FALSE( 220 xTargetShape.is(), 221 "implCreateIteratedNodes(): invalid shape in ParagraphTarget" ); 222 223 // we've a paragraph target to iterate over, thus, 224 // the whole animation container refers only to 225 // the text 226 nSubItem = presentation::ShapeAnimationSubType::ONLY_TEXT; 227 228 bParagraphTarget = true; 229 } 230 231 // Lookup shape, and fill NodeContext 232 // ================================== 233 234 AttributableShapeSharedPtr pTargetShape( 235 lookupAttributableShape( rContext.maContext.mpSubsettableShapeManager, 236 xTargetShape ) ); 237 238 const DocTreeNodeSupplier& rTreeNodeSupplier( 239 pTargetShape->getTreeNodeSupplier() ); 240 241 ShapeSubsetSharedPtr pTargetSubset; 242 243 NodeContext aContext( rContext ); 244 245 // paragraph targets already need a subset as the 246 // master shape (they're representing only a single 247 // paragraph) 248 if( bParagraphTarget ) 249 { 250 ENSURE_OR_RETURN_FALSE( 251 aTarget.Paragraph >= 0 && 252 rTreeNodeSupplier.getNumberOfTreeNodes( 253 DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ) > aTarget.Paragraph, 254 "implCreateIteratedNodes(): paragraph index out of range" ); 255 256 pTargetSubset.reset( 257 new ShapeSubset( 258 pTargetShape, 259 // retrieve index aTarget.Paragraph of 260 // type PARAGRAPH from this shape 261 rTreeNodeSupplier.getTreeNode( 262 aTarget.Paragraph, 263 DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH ), 264 rContext.maContext.mpSubsettableShapeManager ) ); 265 266 // iterate target is not the whole shape, but only 267 // the selected paragraph - subset _must_ be 268 // independent, to be able to affect visibility 269 // independent of master shape 270 aContext.mbIsIndependentSubset = true; 271 272 // already enable parent subset right here, to 273 // make potentially generated subsets subtract 274 // their content from the parent subset (and not 275 // the original shape). Otherwise, already 276 // subsetted parents (e.g. paragraphs) would not 277 // have their characters removed, when the child 278 // iterations start. 279 // Furthermore, the setup of initial shape 280 // attributes of course needs the subset shape 281 // generated, to apply e.g. visibility changes. 282 pTargetSubset->enableSubsetShape(); 283 } 284 else 285 { 286 pTargetSubset.reset( 287 new ShapeSubset( pTargetShape, 288 rContext.maContext.mpSubsettableShapeManager )); 289 } 290 291 aContext.mpMasterShapeSubset = pTargetSubset; 292 uno::Reference< animations::XAnimationNode > xNode( xIterNode, 293 uno::UNO_QUERY_THROW ); 294 295 // Generate subsets 296 // ================ 297 298 if( bParagraphTarget || 299 nSubItem != presentation::ShapeAnimationSubType::ONLY_TEXT ) 300 { 301 // prepend with animations for 302 // full Shape (will be subtracted 303 // from the subset parts within 304 // the Shape::createSubset() 305 // method). For ONLY_TEXT effects, 306 // we skip this part, to animate 307 // only the text. 308 // 309 // OR 310 // 311 // prepend with subset animation for full 312 // _paragraph_, from which the individual 313 // paragraph subsets are subtracted. Note that the 314 // subitem is superfluous here, we always assume 315 // ONLY_TEXT, if a paragraph is referenced as the 316 // master of an iteration effect. 317 NodeCreator aCreator( rParent, aContext ); 318 if( !::anim::for_each_childNode( xNode, 319 aCreator ) ) 320 { 321 ENSURE_OR_RETURN_FALSE( 322 false, 323 "implCreateIteratedNodes(): iterated child node creation failed" ); 324 } 325 } 326 327 // TODO(F2): This does not do the correct 328 // thing. Having nSubItem be set to ONLY_BACKGROUND 329 // should result in the text staying unanimated in the 330 // foreground, while the shape moves in the background 331 // (this behaviour is perfectly possible with the 332 // slideshow engine, only that the text won't be 333 // currently visible, because animations are always in 334 // the foreground) 335 if( nSubItem != presentation::ShapeAnimationSubType::ONLY_BACKGROUND ) 336 { 337 // determine type of subitem iteration (logical 338 // text unit to animate) 339 DocTreeNode::NodeType eIterateNodeType( 340 DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL ); 341 342 switch( xIterNode->getIterateType() ) 343 { 344 case presentation::TextAnimationType::BY_PARAGRAPH: 345 eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_PARAGRAPH; 346 break; 347 348 case presentation::TextAnimationType::BY_WORD: 349 eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_WORD; 350 break; 351 352 case presentation::TextAnimationType::BY_LETTER: 353 eIterateNodeType = DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL; 354 break; 355 356 default: 357 ENSURE_OR_THROW( 358 false, "implCreateIteratedNodes(): " 359 "Unexpected IterateType on XIterateContainer"); 360 break; 361 } 362 363 if( bParagraphTarget && 364 eIterateNodeType != DocTreeNode::NODETYPE_LOGICAL_WORD && 365 eIterateNodeType != DocTreeNode::NODETYPE_LOGICAL_CHARACTER_CELL ) 366 { 367 // will not animate the whole paragraph, when 368 // only the paragraph is animated at all. 369 OSL_ENSURE( false, 370 "implCreateIteratedNodes(): Ignoring paragraph iteration for paragraph master" ); 371 } 372 else 373 { 374 // setup iteration parameters 375 // -------------------------- 376 377 // iterate target is the whole shape (or the 378 // whole parent subshape), thus, can save 379 // loads of subset shapes by generating them 380 // only when the effects become active - 381 // before and after the effect active 382 // duration, all attributes are shared by 383 // master shape and subset (since the iterated 384 // effects are all the same). 385 aContext.mbIsIndependentSubset = false; 386 387 // determine number of nodes for given subitem 388 // type 389 sal_Int32 nTreeNodes( 0 ); 390 if( bParagraphTarget ) 391 { 392 // create the iterated subset _relative_ to 393 // the given paragraph index (i.e. animate the 394 // given subset type, but only when it's part 395 // of the given paragraph) 396 nTreeNodes = rTreeNodeSupplier.getNumberOfSubsetTreeNodes( 397 pTargetSubset->getSubset(), 398 eIterateNodeType ); 399 } 400 else 401 { 402 // generate normal subset 403 nTreeNodes = rTreeNodeSupplier.getNumberOfTreeNodes( 404 eIterateNodeType ); 405 } 406 407 408 // iterate node, generate copies of the children for each subset 409 // ------------------------------------------------------------- 410 411 // NodeContext::mnStartDelay contains additional node delay. 412 // This will make the duplicated nodes for each iteration start 413 // increasingly later. 414 aContext.mnStartDelay = nIntervalTimeout; 415 416 for( sal_Int32 i=0; i<nTreeNodes; ++i ) 417 { 418 // create subset with the corresponding tree nodes 419 if( bParagraphTarget ) 420 { 421 // create subsets relative to paragraph subset 422 aContext.mpMasterShapeSubset.reset( 423 new ShapeSubset( 424 pTargetSubset, 425 rTreeNodeSupplier.getSubsetTreeNode( 426 pTargetSubset->getSubset(), 427 i, 428 eIterateNodeType ) ) ); 429 } 430 else 431 { 432 // create subsets from main shape 433 aContext.mpMasterShapeSubset.reset( 434 new ShapeSubset( pTargetSubset, 435 rTreeNodeSupplier.getTreeNode( 436 i, 437 eIterateNodeType ) ) ); 438 } 439 440 CloningNodeCreator aCreator( rParent, aContext ); 441 if( !::anim::for_each_childNode( xNode, 442 aCreator ) ) 443 { 444 ENSURE_OR_RETURN_FALSE( 445 false, "implCreateIteratedNodes(): " 446 "iterated child node creation failed" ); 447 } 448 449 aContext.mnStartDelay += nIntervalTimeout; 450 } 451 } 452 } 453 454 // done with iterate child generation 455 return true; 456 } 457 458 BaseNodeSharedPtr implCreateAnimationNode( 459 const uno::Reference< animations::XAnimationNode >& xNode, 460 const BaseContainerNodeSharedPtr& rParent, 461 const NodeContext& rContext ) 462 { 463 ENSURE_OR_THROW( xNode.is(), 464 "implCreateAnimationNode(): invalid XAnimationNode" ); 465 466 BaseNodeSharedPtr pCreatedNode; 467 BaseContainerNodeSharedPtr pCreatedContainer; 468 469 // create the internal node, corresponding to xNode 470 switch( xNode->getType() ) 471 { 472 case animations::AnimationNodeType::CUSTOM: 473 OSL_ENSURE( false, "implCreateAnimationNode(): " 474 "CUSTOM not yet implemented" ); 475 return pCreatedNode; 476 477 case animations::AnimationNodeType::PAR: 478 pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr( 479 new ParallelTimeContainer( xNode, rParent, rContext ) ); 480 break; 481 482 case animations::AnimationNodeType::ITERATE: 483 // map iterate container to ParallelTimeContainer. 484 // the iterating functionality is to be found 485 // below, (see method implCreateIteratedNodes) 486 pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr( 487 new ParallelTimeContainer( xNode, rParent, rContext ) ); 488 break; 489 490 case animations::AnimationNodeType::SEQ: 491 pCreatedNode = pCreatedContainer = BaseContainerNodeSharedPtr( 492 new SequentialTimeContainer( xNode, rParent, rContext ) ); 493 break; 494 495 case animations::AnimationNodeType::ANIMATE: 496 pCreatedNode.reset( new PropertyAnimationNode( 497 xNode, rParent, rContext ) ); 498 break; 499 500 case animations::AnimationNodeType::SET: 501 pCreatedNode.reset( new AnimationSetNode( 502 xNode, rParent, rContext ) ); 503 break; 504 505 case animations::AnimationNodeType::ANIMATEMOTION: 506 pCreatedNode.reset( new AnimationPathMotionNode( 507 xNode, rParent, rContext ) ); 508 break; 509 510 case animations::AnimationNodeType::ANIMATECOLOR: 511 pCreatedNode.reset( new AnimationColorNode( 512 xNode, rParent, rContext ) ); 513 break; 514 515 case animations::AnimationNodeType::ANIMATETRANSFORM: 516 pCreatedNode.reset( new AnimationTransformNode( 517 xNode, rParent, rContext ) ); 518 break; 519 520 case animations::AnimationNodeType::TRANSITIONFILTER: 521 pCreatedNode.reset( new AnimationTransitionFilterNode( 522 xNode, rParent, rContext ) ); 523 break; 524 525 case animations::AnimationNodeType::AUDIO: 526 pCreatedNode.reset( new AnimationAudioNode( 527 xNode, rParent, rContext ) ); 528 break; 529 530 case animations::AnimationNodeType::COMMAND: 531 pCreatedNode.reset( new AnimationCommandNode( 532 xNode, rParent, rContext ) ); 533 break; 534 535 default: 536 OSL_ENSURE( false, "implCreateAnimationNode(): " 537 "invalid AnimationNodeType" ); 538 return pCreatedNode; 539 } 540 541 // TODO(Q1): This yields circular references, which, it seems, is 542 // unavoidable here 543 544 // HACK: node objects need shared_ptr to themselves, 545 // which we pass them here. 546 pCreatedNode->setSelf( pCreatedNode ); 547 548 // if we've got a container node object, recursively add 549 // its children 550 if( pCreatedContainer ) 551 { 552 uno::Reference< animations::XIterateContainer > xIterNode( 553 xNode, uno::UNO_QUERY ); 554 555 // when this node is an XIterateContainer with 556 // active iterations, this method will generate 557 // the appropriate children 558 if( xIterNode.is() ) 559 { 560 // note that implCreateIteratedNodes() might 561 // choose not to generate any child nodes 562 // (e.g. when the iterate timeout is outside 563 // sensible limits). Then, no child nodes are 564 // generated at all, since typically, child 565 // node attribute are incomplete for iteration 566 // children. 567 implCreateIteratedNodes( xIterNode, 568 pCreatedContainer, 569 rContext ); 570 } 571 else 572 { 573 // no iterate subset node, just plain child generation now 574 NodeCreator aCreator( pCreatedContainer, rContext ); 575 if( !::anim::for_each_childNode( xNode, aCreator ) ) 576 { 577 OSL_ENSURE( false, "implCreateAnimationNode(): " 578 "child node creation failed" ); 579 return BaseNodeSharedPtr(); 580 } 581 } 582 } 583 584 return pCreatedNode; 585 } 586 587 } // anon namespace 588 589 AnimationNodeSharedPtr AnimationNodeFactory::createAnimationNode( 590 const uno::Reference< animations::XAnimationNode >& xNode, 591 const ::basegfx::B2DVector& rSlideSize, 592 const SlideShowContext& rContext ) 593 { 594 ENSURE_OR_THROW( 595 xNode.is(), 596 "AnimationNodeFactory::createAnimationNode(): invalid XAnimationNode" ); 597 598 return BaseNodeSharedPtr( implCreateAnimationNode( 599 xNode, 600 BaseContainerNodeSharedPtr(), // no parent 601 NodeContext( rContext, 602 rSlideSize ))); 603 } 604 605 #if defined(VERBOSE) && defined(DBG_UTIL) 606 void AnimationNodeFactory::showTree( AnimationNodeSharedPtr& pRootNode ) 607 { 608 if( pRootNode ) 609 DEBUG_NODES_SHOWTREE( boost::dynamic_pointer_cast<BaseContainerNode>( 610 pRootNode).get() ); 611 } 612 #endif 613 614 } // namespace internal 615 } // namespace slideshow 616 617