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