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 #include <config_features.h> 22 #include <tools/diagnose_ex.h> 23 24 #include <cppuhelper/basemutex.hxx> 25 #include <cppuhelper/compbase.hxx> 26 #include <cppuhelper/interfacecontainer.h> 27 #include <cppuhelper/supportsservice.hxx> 28 29 #include <comphelper/interfacecontainer3.hxx> 30 #include <comphelper/scopeguard.hxx> 31 #include <comphelper/storagehelper.hxx> 32 #include <cppcanvas/polypolygon.hxx> 33 #include <osl/thread.hxx> 34 35 #include <tools/debug.hxx> 36 37 #include <basegfx/point/b2dpoint.hxx> 38 #include <basegfx/polygon/b2dpolygon.hxx> 39 #include <basegfx/utils/canvastools.hxx> 40 41 #include <sal/log.hxx> 42 43 #include <com/sun/star/beans/XPropertySet.hpp> 44 #include <com/sun/star/util/XUpdatable.hpp> 45 #include <com/sun/star/awt/SystemPointer.hpp> 46 #include <com/sun/star/presentation/XSlideShow.hpp> 47 #include <com/sun/star/presentation/XSlideShowListener.hpp> 48 #include <com/sun/star/lang/NoSupportException.hpp> 49 #include <com/sun/star/lang/XMultiServiceFactory.hpp> 50 #include <com/sun/star/lang/XServiceInfo.hpp> 51 #include <com/sun/star/drawing/PointSequenceSequence.hpp> 52 #include <com/sun/star/drawing/PointSequence.hpp> 53 #include <com/sun/star/drawing/XLayer.hpp> 54 #include <com/sun/star/drawing/XLayerSupplier.hpp> 55 #include <com/sun/star/drawing/XLayerManager.hpp> 56 #include <com/sun/star/container/XNameAccess.hpp> 57 #include <com/sun/star/document/XStorageBasedDocument.hpp> 58 59 #include <com/sun/star/uno/Reference.hxx> 60 #include <com/sun/star/loader/CannotActivateFactoryException.hpp> 61 62 #include <unoviewcontainer.hxx> 63 #include <transitionfactory.hxx> 64 #include <eventmultiplexer.hxx> 65 #include <usereventqueue.hxx> 66 #include <eventqueue.hxx> 67 #include <cursormanager.hxx> 68 #include <mediafilemanager.hxx> 69 #include <slideshowcontext.hxx> 70 #include <activitiesqueue.hxx> 71 #include <activitiesfactory.hxx> 72 #include <interruptabledelayevent.hxx> 73 #include <slide.hxx> 74 #include <shapemaps.hxx> 75 #include <slideview.hxx> 76 #include <tools.hxx> 77 #include <unoview.hxx> 78 #include "rehearsetimingsactivity.hxx" 79 #include "waitsymbol.hxx" 80 #include "effectrewinder.hxx" 81 #include <framerate.hxx> 82 #include "pointersymbol.hxx" 83 84 #include <map> 85 #include <vector> 86 #include <algorithm> 87 88 using namespace com::sun::star; 89 using namespace ::slideshow::internal; 90 91 namespace box2d::utils { class box2DWorld; 92 typedef ::std::shared_ptr< box2DWorld > Box2DWorldSharedPtr; } 93 94 namespace { 95 96 /** During animations the update() method tells its caller to call it as 97 soon as possible. This gives us more time to render the next frame and 98 still maintain a steady frame rate. This class is responsible for 99 synchronizing the display of new frames and thus keeping the frame rate 100 steady. 101 */ 102 class FrameSynchronization 103 { 104 public: 105 /** Create new object with a predefined duration between two frames. 106 @param nFrameDuration 107 The preferred duration between the display of two frames in 108 seconds. 109 */ 110 explicit FrameSynchronization (const double nFrameDuration); 111 112 /** Set the current time as the time at which the current frame is 113 displayed. From this the target time of the next frame is derived. 114 */ 115 void MarkCurrentFrame(); 116 117 /** When there is time left until the next frame is due then wait. 118 Otherwise return without delay. 119 */ 120 void Synchronize(); 121 122 /** Activate frame synchronization when an animation is active and 123 frames are to be displayed in a steady rate. While active 124 Synchronize() will wait until the frame duration time has passed. 125 */ 126 void Activate(); 127 128 /** Deactivate frame synchronization when no animation is active and the 129 time between frames depends on user actions and other external 130 sources. While deactivated Synchronize() will return without delay. 131 */ 132 void Deactivate(); 133 134 private: 135 /** The timer that is used for synchronization is independent from the 136 one used by SlideShowImpl: it is not paused or modified by 137 animations. 138 */ 139 canvas::tools::ElapsedTime maTimer; 140 /** Time between the display of frames. Enforced only when mbIsActive 141 is <TRUE/>. 142 */ 143 const double mnFrameDuration; 144 /** Time (of maTimer) when the next frame shall be displayed. 145 Synchronize() will wait until this time. 146 */ 147 double mnNextFrameTargetTime; 148 /** Synchronize() will wait only when this flag is <TRUE/>. Otherwise 149 it returns immediately. 150 */ 151 bool mbIsActive; 152 }; 153 154 /****************************************************************************** 155 156 SlideShowImpl 157 158 This class encapsulates the slideshow presentation viewer. 159 160 With an instance of this class, it is possible to statically 161 and dynamically show a presentation, as defined by the 162 constructor-provided draw model (represented by a sequence 163 of css::drawing::XDrawPage objects). 164 165 It is possible to show the presentation on multiple views 166 simultaneously (e.g. for a multi-monitor setup). Since this 167 class also relies on user interaction, the corresponding 168 XSlideShowView interface provides means to register some UI 169 event listeners (mostly borrowed from awt::XWindow interface). 170 171 Since currently (mid 2004), OOo isn't very well suited to 172 multi-threaded rendering, this class relies on <em>very 173 frequent</em> external update() calls, which will render the 174 next frame of animations. This works as follows: after the 175 displaySlide() has been successfully called (which setup and 176 starts an actual slide show), the update() method must be 177 called until it returns false. 178 Effectively, this puts the burden of providing 179 concurrency to the clients of this class, which, as noted 180 above, is currently unavoidable with the current state of 181 affairs (I've actually tried threading here, but failed 182 miserably when using the VCL canvas as the render backend - 183 deadlocked). 184 185 ******************************************************************************/ 186 187 typedef cppu::WeakComponentImplHelper<css::lang::XServiceInfo, presentation::XSlideShow> SlideShowImplBase; 188 189 typedef ::std::vector< ::cppcanvas::PolyPolygonSharedPtr> PolyPolygonVector; 190 191 /// Maps XDrawPage for annotations persistence 192 typedef ::std::map< css::uno::Reference< 193 css::drawing::XDrawPage>, 194 PolyPolygonVector> PolygonMap; 195 196 class SlideShowImpl : private cppu::BaseMutex, 197 public CursorManager, 198 public MediaFileManager, 199 public SlideShowImplBase 200 { 201 public: 202 explicit SlideShowImpl( 203 uno::Reference<uno::XComponentContext> const& xContext ); 204 205 /** Notify that the transition phase of the current slide 206 has ended. 207 208 The life of a slide has three phases: the transition 209 phase, when the previous slide vanishes, and the 210 current slide becomes visible, the shape animation 211 phase, when shape effects are running, and the phase 212 after the last shape animation has ended, but before 213 the next slide transition starts. 214 215 This method notifies the end of the first phase. 216 217 @param bPaintSlide 218 When true, Slide::show() is passed a true as well, denoting 219 explicit paint of slide content. Pass false here, if e.g. a 220 slide transition has already rendered the initial slide image. 221 */ 222 void notifySlideTransitionEnded( bool bPaintSlide ); 223 224 /** Notify that the shape animation phase of the current slide 225 has ended. 226 227 The life of a slide has three phases: the transition 228 phase, when the previous slide vanishes, and the 229 current slide becomes visible, the shape animation 230 phase, when shape effects are running, and the phase 231 after the last shape animation has ended, but before 232 the next slide transition starts. 233 234 This method notifies the end of the second phase. 235 */ 236 void notifySlideAnimationsEnded(); 237 238 /** Notify that the slide has ended. 239 240 The life of a slide has three phases: the transition 241 phase, when the previous slide vanishes, and the 242 current slide becomes visible, the shape animation 243 phase, when shape effects are running, and the phase 244 after the last shape animation has ended, but before 245 the next slide transition starts. 246 247 This method notifies the end of the third phase. 248 */ 249 void notifySlideEnded (const bool bReverse); 250 251 /** Notification from eventmultiplexer that a hyperlink 252 has been clicked. 253 */ 254 bool notifyHyperLinkClicked( OUString const& hyperLink ); 255 256 /** Notification from eventmultiplexer that an animation event has occurred. 257 This will be forwarded to all registered XSlideShowListener 258 */ 259 bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ); 260 261 /** Obtain a MediaTempFile for the specified url. */ 262 virtual std::shared_ptr<avmedia::MediaTempFile> getMediaTempFile(const OUString& aUrl) override; 263 264 private: 265 // XServiceInfo 266 virtual OUString SAL_CALL getImplementationName() override; 267 virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; 268 virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; 269 270 // XSlideShow: 271 virtual sal_Bool SAL_CALL nextEffect() override; 272 virtual sal_Bool SAL_CALL previousEffect() override; 273 virtual sal_Bool SAL_CALL startShapeActivity( 274 uno::Reference<drawing::XShape> const& xShape ) override; 275 virtual sal_Bool SAL_CALL stopShapeActivity( 276 uno::Reference<drawing::XShape> const& xShape ) override; 277 virtual sal_Bool SAL_CALL pause( sal_Bool bPauseShow ) override; 278 virtual uno::Reference<drawing::XDrawPage> SAL_CALL getCurrentSlide() override; 279 virtual void SAL_CALL displaySlide( 280 uno::Reference<drawing::XDrawPage> const& xSlide, 281 uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages, 282 uno::Reference<animations::XAnimationNode> const& xRootNode, 283 uno::Sequence<beans::PropertyValue> const& rProperties ) override; 284 virtual void SAL_CALL registerUserPaintPolygons( const css::uno::Reference< css::lang::XMultiServiceFactory >& xDocFactory ) override; 285 virtual sal_Bool SAL_CALL setProperty( 286 beans::PropertyValue const& rProperty ) override; 287 virtual sal_Bool SAL_CALL addView( 288 uno::Reference<presentation::XSlideShowView> const& xView ) override; 289 virtual sal_Bool SAL_CALL removeView( 290 uno::Reference<presentation::XSlideShowView> const& xView ) override; 291 virtual sal_Bool SAL_CALL update( double & nNextTimeout ) override; 292 virtual void SAL_CALL addSlideShowListener( 293 uno::Reference<presentation::XSlideShowListener> const& xListener ) override; 294 virtual void SAL_CALL removeSlideShowListener( 295 uno::Reference<presentation::XSlideShowListener> const& xListener ) override; 296 virtual void SAL_CALL addShapeEventListener( 297 uno::Reference<presentation::XShapeEventListener> const& xListener, 298 uno::Reference<drawing::XShape> const& xShape ) override; 299 virtual void SAL_CALL removeShapeEventListener( 300 uno::Reference<presentation::XShapeEventListener> const& xListener, 301 uno::Reference<drawing::XShape> const& xShape ) override; 302 virtual void SAL_CALL setShapeCursor( 303 uno::Reference<drawing::XShape> const& xShape, sal_Int16 nPointerShape ) override; 304 305 // CursorManager 306 307 308 virtual bool requestCursor( sal_Int16 nCursorShape ) override; 309 virtual void resetCursor() override; 310 311 /** This is somewhat similar to displaySlide when called for the current 312 slide. It has been simplified to take advantage of that no slide 313 change takes place. Furthermore it does not show the slide 314 transition. 315 */ 316 void redisplayCurrentSlide(); 317 318 protected: 319 // WeakComponentImplHelperBase 320 virtual void SAL_CALL disposing() override; 321 322 bool isDisposed() const 323 { 324 return (rBHelper.bDisposed || rBHelper.bInDispose); 325 } 326 327 private: 328 struct SeparateListenerImpl; friend struct SeparateListenerImpl; 329 class PrefetchPropertiesFunc; friend class PrefetchPropertiesFunc; 330 331 /// Stop currently running show. 332 void stopShow(); 333 334 ///Find a polygons vector in maPolygons (map) 335 PolygonMap::iterator findPolygons( uno::Reference<drawing::XDrawPage> const& xDrawPage); 336 337 /// Creates a new slide. 338 SlideSharedPtr makeSlide( 339 uno::Reference<drawing::XDrawPage> const& xDrawPage, 340 uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages, 341 uno::Reference<animations::XAnimationNode> const& xRootNode ); 342 343 /// Checks whether the given slide/animation node matches mpPrefetchSlide 344 static bool matches( 345 SlideSharedPtr const& pSlide, 346 uno::Reference<drawing::XDrawPage> const& xSlide, 347 uno::Reference<animations::XAnimationNode> const& xNode ) 348 { 349 if (pSlide) 350 return (pSlide->getXDrawPage() == xSlide && 351 pSlide->getXAnimationNode() == xNode); 352 else 353 return (!xSlide.is() && !xNode.is()); 354 } 355 356 /// Resets the current slide transition sound object with a new one: 357 SoundPlayerSharedPtr resetSlideTransitionSound( 358 uno::Any const& url, bool bLoopSound ); 359 360 /// stops the current slide transition sound 361 void stopSlideTransitionSound(); 362 363 /** Prepare a slide transition 364 365 This method registers all necessary events and 366 activities for a slide transition. 367 368 @return the slide change activity, or NULL for no transition effect 369 */ 370 ActivitySharedPtr createSlideTransition( 371 const uno::Reference< drawing::XDrawPage >& xDrawPage, 372 const SlideSharedPtr& rLeavingSlide, 373 const SlideSharedPtr& rEnteringSlide, 374 const EventSharedPtr& rTransitionEndEvent ); 375 376 /** Request/release the wait symbol. The wait symbol is displayed when 377 there are more requests then releases. Locking the wait symbol 378 helps to avoid intermediate repaints. 379 380 Do not call this method directly. Use WaitSymbolLock instead. 381 */ 382 void requestWaitSymbol(); 383 void releaseWaitSymbol(); 384 385 class WaitSymbolLock {public: 386 explicit WaitSymbolLock(SlideShowImpl& rSlideShowImpl) : mrSlideShowImpl(rSlideShowImpl) 387 { mrSlideShowImpl.requestWaitSymbol(); } 388 ~WaitSymbolLock() 389 { mrSlideShowImpl.releaseWaitSymbol(); } 390 private: SlideShowImpl& mrSlideShowImpl; 391 }; 392 393 /// Filter requested cursor shape against hard slideshow cursors (wait, etc.) 394 sal_Int16 calcActiveCursor( sal_Int16 nCursorShape ) const; 395 396 /** This method is called asynchronously to finish the rewinding of an 397 effect to the previous slide that was initiated earlier. 398 */ 399 void rewindEffectToPreviousSlide(); 400 401 /// all registered views 402 UnoViewContainer maViewContainer; 403 404 /// all registered slide show listeners 405 comphelper::OInterfaceContainerHelper3<presentation::XSlideShowListener> maListenerContainer; 406 407 /// map of vectors, containing all registered listeners for a shape 408 ShapeEventListenerMap maShapeEventListeners; 409 /// map of sal_Int16 values, specifying the mouse cursor for every shape 410 ShapeCursorMap maShapeCursors; 411 412 //map of vector of Polygons, containing polygons drawn on each slide. 413 PolygonMap maPolygons; 414 415 std::optional<RGBColor> maUserPaintColor; 416 417 double maUserPaintStrokeWidth; 418 419 //changed for the eraser project 420 std::optional<bool> maEraseAllInk; 421 std::optional<sal_Int32> maEraseInk; 422 //end changed 423 424 std::shared_ptr<canvas::tools::ElapsedTime> mpPresTimer; 425 ScreenUpdater maScreenUpdater; 426 EventQueue maEventQueue; 427 EventMultiplexer maEventMultiplexer; 428 ActivitiesQueue maActivitiesQueue; 429 UserEventQueue maUserEventQueue; 430 SubsettableShapeManagerSharedPtr mpDummyPtr; 431 box2d::utils::Box2DWorldSharedPtr mpBox2DDummyPtr; 432 433 std::shared_ptr<SeparateListenerImpl> mpListener; 434 435 std::shared_ptr<RehearseTimingsActivity> mpRehearseTimingsActivity; 436 std::shared_ptr<WaitSymbol> mpWaitSymbol; 437 438 std::shared_ptr<PointerSymbol> mpPointerSymbol; 439 440 /// the current slide transition sound object: 441 SoundPlayerSharedPtr mpCurrentSlideTransitionSound; 442 443 uno::Reference<uno::XComponentContext> mxComponentContext; 444 uno::Reference< 445 presentation::XTransitionFactory> mxOptionalTransitionFactory; 446 447 /// the previously running slide 448 SlideSharedPtr mpPreviousSlide; 449 /// the currently running slide 450 SlideSharedPtr mpCurrentSlide; 451 /// the already prefetched slide: best candidate for upcoming slide 452 SlideSharedPtr mpPrefetchSlide; 453 /// slide to be prefetched: best candidate for upcoming slide 454 uno::Reference<drawing::XDrawPage> mxPrefetchSlide; 455 /// save the XDrawPagesSupplier to retrieve polygons 456 uno::Reference<drawing::XDrawPagesSupplier> mxDrawPagesSupplier; 457 /// Used by MediaFileManager, for media files with package url. 458 uno::Reference<document::XStorageBasedDocument> mxSBD; 459 /// slide animation to be prefetched: 460 uno::Reference<animations::XAnimationNode> mxPrefetchAnimationNode; 461 462 sal_Int16 mnCurrentCursor; 463 464 sal_Int32 mnWaitSymbolRequestCount; 465 bool mbAutomaticAdvancementMode; 466 bool mbImageAnimationsAllowed; 467 bool mbNoSlideTransitions; 468 bool mbMouseVisible; 469 bool mbForceManualAdvance; 470 bool mbShowPaused; 471 bool mbSlideShowIdle; 472 bool mbDisableAnimationZOrder; 473 474 EffectRewinder maEffectRewinder; 475 FrameSynchronization maFrameSynchronization; 476 }; 477 478 /** Separate event listener for animation, view and hyperlink events. 479 480 This handler is registered for slide animation end, view and 481 hyperlink events at the global EventMultiplexer, and forwards 482 notifications to the SlideShowImpl 483 */ 484 struct SlideShowImpl::SeparateListenerImpl : public EventHandler, 485 public ViewRepaintHandler, 486 public HyperlinkHandler, 487 public AnimationEventHandler 488 { 489 SlideShowImpl& mrShow; 490 ScreenUpdater& mrScreenUpdater; 491 EventQueue& mrEventQueue; 492 493 SeparateListenerImpl( SlideShowImpl& rShow, 494 ScreenUpdater& rScreenUpdater, 495 EventQueue& rEventQueue ) : 496 mrShow( rShow ), 497 mrScreenUpdater( rScreenUpdater ), 498 mrEventQueue( rEventQueue ) 499 {} 500 501 SeparateListenerImpl( const SeparateListenerImpl& ) = delete; 502 SeparateListenerImpl& operator=( const SeparateListenerImpl& ) = delete; 503 504 // EventHandler 505 virtual bool handleEvent() override 506 { 507 // DON't call notifySlideAnimationsEnded() 508 // directly, but queue an event. handleEvent() 509 // might be called from e.g. 510 // showNext(), and notifySlideAnimationsEnded() must not be called 511 // in recursion. Note that the event is scheduled for the next 512 // frame so that its expensive execution does not come in between 513 // sprite hiding and shape redraw (at the end of the animation of a 514 // shape), which would cause a flicker. 515 mrEventQueue.addEventForNextRound( 516 makeEvent( [this] () { this->mrShow.notifySlideAnimationsEnded(); }, 517 "SlideShowImpl::notifySlideAnimationsEnded")); 518 return true; 519 } 520 521 // ViewRepaintHandler 522 virtual void viewClobbered( const UnoViewSharedPtr& rView ) override 523 { 524 // given view needs repaint, request update 525 mrScreenUpdater.notifyUpdate(rView, true); 526 } 527 528 // HyperlinkHandler 529 virtual bool handleHyperlink( OUString const& rLink ) override 530 { 531 return mrShow.notifyHyperLinkClicked(rLink); 532 } 533 534 // AnimationEventHandler 535 virtual bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) override 536 { 537 return mrShow.handleAnimationEvent(rNode); 538 } 539 }; 540 541 SlideShowImpl::SlideShowImpl( 542 uno::Reference<uno::XComponentContext> const& xContext ) 543 : SlideShowImplBase(m_aMutex), 544 maViewContainer(), 545 maListenerContainer( m_aMutex ), 546 maShapeEventListeners(), 547 maShapeCursors(), 548 maUserPaintColor(), 549 maUserPaintStrokeWidth(4.0), 550 mpPresTimer( std::make_shared<canvas::tools::ElapsedTime>() ), 551 maScreenUpdater(maViewContainer), 552 maEventQueue( mpPresTimer ), 553 maEventMultiplexer( maEventQueue, 554 maViewContainer ), 555 maActivitiesQueue( mpPresTimer ), 556 maUserEventQueue( maEventMultiplexer, 557 maEventQueue, 558 *this ), 559 mpDummyPtr(), 560 mpBox2DDummyPtr(), 561 mpListener(), 562 mpRehearseTimingsActivity(), 563 mpWaitSymbol(), 564 mpPointerSymbol(), 565 mpCurrentSlideTransitionSound(), 566 mxComponentContext( xContext ), 567 mxOptionalTransitionFactory(), 568 mpCurrentSlide(), 569 mpPrefetchSlide(), 570 mxPrefetchSlide(), 571 mxDrawPagesSupplier(), 572 mxSBD(), 573 mxPrefetchAnimationNode(), 574 mnCurrentCursor(awt::SystemPointer::ARROW), 575 mnWaitSymbolRequestCount(0), 576 mbAutomaticAdvancementMode(false), 577 mbImageAnimationsAllowed( true ), 578 mbNoSlideTransitions( false ), 579 mbMouseVisible( true ), 580 mbForceManualAdvance( false ), 581 mbShowPaused( false ), 582 mbSlideShowIdle( true ), 583 mbDisableAnimationZOrder( false ), 584 maEffectRewinder(maEventMultiplexer, maEventQueue, maUserEventQueue), 585 maFrameSynchronization(1.0 / FrameRate::PreferredFramesPerSecond) 586 587 { 588 // keep care not constructing any UNO references to this inside ctor, 589 // shift that code to create()! 590 591 uno::Reference<lang::XMultiComponentFactory> xFactory( 592 mxComponentContext->getServiceManager() ); 593 594 if( xFactory.is() ) 595 { 596 try 597 { 598 // #i82460# try to retrieve special transition factory 599 mxOptionalTransitionFactory.set( 600 xFactory->createInstanceWithContext( 601 "com.sun.star.presentation.TransitionFactory", 602 mxComponentContext ), 603 uno::UNO_QUERY ); 604 } 605 catch (loader::CannotActivateFactoryException const&) 606 { 607 } 608 } 609 610 mpListener = std::make_shared<SeparateListenerImpl>( 611 *this, 612 maScreenUpdater, 613 maEventQueue ); 614 maEventMultiplexer.addSlideAnimationsEndHandler( mpListener ); 615 maEventMultiplexer.addViewRepaintHandler( mpListener ); 616 maEventMultiplexer.addHyperlinkHandler( mpListener, 0.0 ); 617 maEventMultiplexer.addAnimationStartHandler( mpListener ); 618 maEventMultiplexer.addAnimationEndHandler( mpListener ); 619 } 620 621 // we are about to be disposed (someone call dispose() on us) 622 void SlideShowImpl::disposing() 623 { 624 osl::MutexGuard const guard( m_aMutex ); 625 626 maEffectRewinder.dispose(); 627 628 // stop slide transition sound, if any: 629 stopSlideTransitionSound(); 630 631 mxComponentContext.clear(); 632 633 if( mpCurrentSlideTransitionSound ) 634 { 635 mpCurrentSlideTransitionSound->dispose(); 636 mpCurrentSlideTransitionSound.reset(); 637 } 638 639 mpWaitSymbol.reset(); 640 mpPointerSymbol.reset(); 641 642 if( mpRehearseTimingsActivity ) 643 { 644 mpRehearseTimingsActivity->dispose(); 645 mpRehearseTimingsActivity.reset(); 646 } 647 648 if( mpListener ) 649 { 650 maEventMultiplexer.removeSlideAnimationsEndHandler(mpListener); 651 maEventMultiplexer.removeViewRepaintHandler(mpListener); 652 maEventMultiplexer.removeHyperlinkHandler(mpListener); 653 maEventMultiplexer.removeAnimationStartHandler( mpListener ); 654 maEventMultiplexer.removeAnimationEndHandler( mpListener ); 655 656 mpListener.reset(); 657 } 658 659 maUserEventQueue.clear(); 660 maActivitiesQueue.clear(); 661 maEventMultiplexer.clear(); 662 maEventQueue.clear(); 663 mpPresTimer.reset(); 664 maShapeCursors.clear(); 665 maShapeEventListeners.clear(); 666 667 // send all listeners a disposing() that we are going down: 668 maListenerContainer.disposeAndClear( 669 lang::EventObject( static_cast<cppu::OWeakObject *>(this) ) ); 670 671 maViewContainer.dispose(); 672 673 // release slides: 674 mxPrefetchAnimationNode.clear(); 675 mxPrefetchSlide.clear(); 676 mpPrefetchSlide.reset(); 677 mpCurrentSlide.reset(); 678 mpPreviousSlide.reset(); 679 } 680 681 uno::Sequence< OUString > SAL_CALL SlideShowImpl::getSupportedServiceNames() 682 { 683 return { "com.sun.star.presentation.SlideShow" }; 684 } 685 686 OUString SAL_CALL SlideShowImpl::getImplementationName() 687 { 688 return "com.sun.star.comp.presentation.SlideShow"; 689 } 690 691 sal_Bool SAL_CALL SlideShowImpl::supportsService(const OUString& aServiceName) 692 { 693 return cppu::supportsService(this, aServiceName); 694 } 695 696 /// stops the current slide transition sound 697 void SlideShowImpl::stopSlideTransitionSound() 698 { 699 if (mpCurrentSlideTransitionSound) 700 { 701 mpCurrentSlideTransitionSound->stopPlayback(); 702 mpCurrentSlideTransitionSound->dispose(); 703 mpCurrentSlideTransitionSound.reset(); 704 } 705 } 706 707 SoundPlayerSharedPtr SlideShowImpl::resetSlideTransitionSound( const uno::Any& rSound, bool bLoopSound ) 708 { 709 bool bStopSound = false; 710 OUString url; 711 712 if( !(rSound >>= bStopSound) ) 713 bStopSound = false; 714 rSound >>= url; 715 716 if( !bStopSound && url.isEmpty() ) 717 return SoundPlayerSharedPtr(); 718 719 stopSlideTransitionSound(); 720 721 if (!url.isEmpty()) 722 { 723 try 724 { 725 mpCurrentSlideTransitionSound = SoundPlayer::create( 726 maEventMultiplexer, url, mxComponentContext, *this); 727 mpCurrentSlideTransitionSound->setPlaybackLoop( bLoopSound ); 728 } 729 catch (lang::NoSupportException const&) 730 { 731 // catch possible exceptions from SoundPlayer, since 732 // being not able to playback the sound is not a hard 733 // error here (still, the slide transition should be 734 // shown). 735 } 736 } 737 return mpCurrentSlideTransitionSound; 738 } 739 740 ActivitySharedPtr SlideShowImpl::createSlideTransition( 741 const uno::Reference< drawing::XDrawPage >& xDrawPage, 742 const SlideSharedPtr& rLeavingSlide, 743 const SlideSharedPtr& rEnteringSlide, 744 const EventSharedPtr& rTransitionEndEvent) 745 { 746 ENSURE_OR_THROW( !maViewContainer.empty(), 747 "createSlideTransition(): No views" ); 748 ENSURE_OR_THROW( rEnteringSlide, 749 "createSlideTransition(): No entering slide" ); 750 751 // return empty transition, if slide transitions 752 // are disabled. 753 if (mbNoSlideTransitions) 754 return ActivitySharedPtr(); 755 756 // retrieve slide change parameters from XDrawPage 757 uno::Reference< beans::XPropertySet > xPropSet( xDrawPage, 758 uno::UNO_QUERY ); 759 760 if( !xPropSet.is() ) 761 { 762 SAL_INFO("slideshow", "createSlideTransition(): " 763 "Slide has no PropertySet - assuming no transition" ); 764 return ActivitySharedPtr(); 765 } 766 767 sal_Int16 nTransitionType(0); 768 if( !getPropertyValue( nTransitionType, 769 xPropSet, 770 "TransitionType") ) 771 { 772 SAL_INFO("slideshow", "createSlideTransition(): " 773 "Could not extract slide transition type from XDrawPage - assuming no transition" ); 774 return ActivitySharedPtr(); 775 } 776 777 sal_Int16 nTransitionSubType(0); 778 if( !getPropertyValue( nTransitionSubType, 779 xPropSet, 780 "TransitionSubtype") ) 781 { 782 SAL_INFO("slideshow", "createSlideTransition(): " 783 "Could not extract slide transition subtype from XDrawPage - assuming no transition" ); 784 return ActivitySharedPtr(); 785 } 786 787 bool bTransitionDirection(false); 788 if( !getPropertyValue( bTransitionDirection, 789 xPropSet, 790 "TransitionDirection") ) 791 { 792 SAL_INFO("slideshow", "createSlideTransition(): " 793 "Could not extract slide transition direction from XDrawPage - assuming default direction" ); 794 } 795 796 sal_Int32 aUnoColor(0); 797 if( !getPropertyValue( aUnoColor, 798 xPropSet, 799 "TransitionFadeColor") ) 800 { 801 SAL_INFO("slideshow", "createSlideTransition(): " 802 "Could not extract slide transition fade color from XDrawPage - assuming black" ); 803 } 804 805 const RGBColor aTransitionFadeColor( unoColor2RGBColor( aUnoColor )); 806 807 uno::Any aSound; 808 bool bLoopSound = false; 809 810 if( !getPropertyValue( aSound, xPropSet, "Sound") ) 811 SAL_INFO("slideshow", "createSlideTransition(): Could not determine transition sound effect URL from XDrawPage - using no sound" ); 812 813 if( !getPropertyValue( bLoopSound, xPropSet, "LoopSound" ) ) 814 SAL_INFO("slideshow", "createSlideTransition(): Could not get slide property 'LoopSound' - using no sound" ); 815 816 NumberAnimationSharedPtr pTransition( 817 TransitionFactory::createSlideTransition( 818 rLeavingSlide, 819 rEnteringSlide, 820 maViewContainer, 821 maScreenUpdater, 822 maEventMultiplexer, 823 mxOptionalTransitionFactory, 824 nTransitionType, 825 nTransitionSubType, 826 bTransitionDirection, 827 aTransitionFadeColor, 828 resetSlideTransitionSound( aSound, bLoopSound ) )); 829 830 if( !pTransition ) 831 return ActivitySharedPtr(); // no transition effect has been 832 // generated. Normally, that means 833 // that simply no transition is 834 // set on this slide. 835 836 double nTransitionDuration(0.0); 837 if( !getPropertyValue( nTransitionDuration, 838 xPropSet, 839 "TransitionDuration") ) 840 { 841 SAL_INFO("slideshow", "createSlideTransition(): " 842 "Could not extract slide transition duration from XDrawPage - assuming no transition" ); 843 return ActivitySharedPtr(); 844 } 845 846 sal_Int32 nMinFrames(5); 847 if( !getPropertyValue( nMinFrames, 848 xPropSet, 849 "MinimalFrameNumber") ) 850 { 851 SAL_INFO("slideshow", "createSlideTransition(): " 852 "No minimal number of frames given - assuming 5" ); 853 } 854 855 // prefetch slide transition bitmaps, but postpone it after 856 // displaySlide() has finished - sometimes, view size has not yet 857 // reached final size 858 maEventQueue.addEvent( 859 makeEvent( [pTransition] () { 860 pTransition->prefetch(); }, 861 "Animation::prefetch")); 862 863 return ActivitySharedPtr( 864 ActivitiesFactory::createSimpleActivity( 865 ActivitiesFactory::CommonParameters( 866 rTransitionEndEvent, 867 maEventQueue, 868 maActivitiesQueue, 869 nTransitionDuration, 870 nMinFrames, 871 false, 872 std::optional<double>(1.0), 873 0.0, 874 0.0, 875 ShapeSharedPtr(), 876 basegfx::B2DSize( rEnteringSlide->getSlideSize() ) ), 877 pTransition, 878 true )); 879 } 880 881 PolygonMap::iterator SlideShowImpl::findPolygons( uno::Reference<drawing::XDrawPage> const& xDrawPage) 882 { 883 // TODO(P2): optimize research in the map. 884 return maPolygons.find(xDrawPage); 885 } 886 887 SlideSharedPtr SlideShowImpl::makeSlide( 888 uno::Reference<drawing::XDrawPage> const& xDrawPage, 889 uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages, 890 uno::Reference<animations::XAnimationNode> const& xRootNode ) 891 { 892 if( !xDrawPage.is() ) 893 return SlideSharedPtr(); 894 895 //Retrieve polygons for the current slide 896 PolygonMap::iterator aIter = findPolygons(xDrawPage); 897 898 const SlideSharedPtr pSlide( createSlide(xDrawPage, 899 xDrawPages, 900 xRootNode, 901 maEventQueue, 902 maEventMultiplexer, 903 maScreenUpdater, 904 maActivitiesQueue, 905 maUserEventQueue, 906 *this, 907 *this, 908 maViewContainer, 909 mxComponentContext, 910 maShapeEventListeners, 911 maShapeCursors, 912 (aIter != maPolygons.end()) ? aIter->second : PolyPolygonVector(), 913 maUserPaintColor ? *maUserPaintColor : RGBColor(), 914 maUserPaintStrokeWidth, 915 !!maUserPaintColor, 916 mbImageAnimationsAllowed, 917 mbDisableAnimationZOrder) ); 918 919 // prefetch show content (reducing latency for slide 920 // bitmap and effect start later on) 921 pSlide->prefetch(); 922 923 return pSlide; 924 } 925 926 void SlideShowImpl::requestWaitSymbol() 927 { 928 ++mnWaitSymbolRequestCount; 929 OSL_ASSERT(mnWaitSymbolRequestCount>0); 930 931 if (mnWaitSymbolRequestCount == 1) 932 { 933 if( !mpWaitSymbol ) 934 { 935 // fall back to cursor 936 requestCursor(calcActiveCursor(mnCurrentCursor)); 937 } 938 else 939 mpWaitSymbol->show(); 940 } 941 } 942 943 void SlideShowImpl::releaseWaitSymbol() 944 { 945 --mnWaitSymbolRequestCount; 946 OSL_ASSERT(mnWaitSymbolRequestCount>=0); 947 948 if (mnWaitSymbolRequestCount == 0) 949 { 950 if( !mpWaitSymbol ) 951 { 952 // fall back to cursor 953 requestCursor(calcActiveCursor(mnCurrentCursor)); 954 } 955 else 956 mpWaitSymbol->hide(); 957 } 958 } 959 960 sal_Int16 SlideShowImpl::calcActiveCursor( sal_Int16 nCursorShape ) const 961 { 962 if( mnWaitSymbolRequestCount>0 && !mpWaitSymbol ) // enforce wait cursor 963 nCursorShape = awt::SystemPointer::WAIT; 964 else if( !mbMouseVisible ) // enforce INVISIBLE 965 nCursorShape = awt::SystemPointer::INVISIBLE; 966 else if( maUserPaintColor && 967 nCursorShape == awt::SystemPointer::ARROW ) 968 nCursorShape = awt::SystemPointer::PEN; 969 970 return nCursorShape; 971 } 972 973 void SlideShowImpl::stopShow() 974 { 975 // Force-end running animation 976 // =========================== 977 if (mpCurrentSlide) 978 { 979 mpCurrentSlide->hide(); 980 //Register polygons in the map 981 if(findPolygons(mpCurrentSlide->getXDrawPage()) != maPolygons.end()) 982 maPolygons.erase(mpCurrentSlide->getXDrawPage()); 983 984 maPolygons.insert(make_pair(mpCurrentSlide->getXDrawPage(),mpCurrentSlide->getPolygons())); 985 } 986 987 // clear all queues 988 maEventQueue.clear(); 989 maActivitiesQueue.clear(); 990 991 // Attention: we MUST clear the user event queue here, 992 // this is because the current slide might have registered 993 // shape events (click or enter/leave), which might 994 // otherwise dangle forever in the queue (because of the 995 // shared ptr nature). If someone needs to change this: 996 // somehow unregister those shapes at the user event queue 997 // on notifySlideEnded(). 998 maUserEventQueue.clear(); 999 1000 // re-enable automatic effect advancement 1001 // (maEventQueue.clear() above might have killed 1002 // maEventMultiplexer's tick events) 1003 if (mbAutomaticAdvancementMode) 1004 { 1005 // toggle automatic mode (enabling just again is 1006 // ignored by EventMultiplexer) 1007 maEventMultiplexer.setAutomaticMode( false ); 1008 maEventMultiplexer.setAutomaticMode( true ); 1009 } 1010 } 1011 1012 class SlideShowImpl::PrefetchPropertiesFunc 1013 { 1014 public: 1015 PrefetchPropertiesFunc( SlideShowImpl * that_, 1016 bool& rbSkipAllMainSequenceEffects, 1017 bool& rbSkipSlideTransition) 1018 : mpSlideShowImpl(that_), 1019 mrbSkipAllMainSequenceEffects(rbSkipAllMainSequenceEffects), 1020 mrbSkipSlideTransition(rbSkipSlideTransition) 1021 {} 1022 1023 void operator()( beans::PropertyValue const& rProperty ) const { 1024 if (rProperty.Name == "Prefetch" ) 1025 { 1026 uno::Sequence<uno::Any> seq; 1027 if ((rProperty.Value >>= seq) && seq.getLength() == 2) 1028 { 1029 seq[0] >>= mpSlideShowImpl->mxPrefetchSlide; 1030 seq[1] >>= mpSlideShowImpl->mxPrefetchAnimationNode; 1031 } 1032 } 1033 else if ( rProperty.Name == "SkipAllMainSequenceEffects" ) 1034 { 1035 rProperty.Value >>= mrbSkipAllMainSequenceEffects; 1036 } 1037 else if ( rProperty.Name == "SkipSlideTransition" ) 1038 { 1039 rProperty.Value >>= mrbSkipSlideTransition; 1040 } 1041 else 1042 { 1043 SAL_WARN( "slideshow", rProperty.Name ); 1044 } 1045 } 1046 private: 1047 SlideShowImpl *const mpSlideShowImpl; 1048 bool& mrbSkipAllMainSequenceEffects; 1049 bool& mrbSkipSlideTransition; 1050 }; 1051 1052 void SlideShowImpl::displaySlide( 1053 uno::Reference<drawing::XDrawPage> const& xSlide, 1054 uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages, 1055 uno::Reference<animations::XAnimationNode> const& xRootNode, 1056 uno::Sequence<beans::PropertyValue> const& rProperties ) 1057 { 1058 osl::MutexGuard const guard( m_aMutex ); 1059 1060 if (isDisposed()) 1061 return; 1062 1063 maEffectRewinder.setRootAnimationNode(xRootNode); 1064 maEffectRewinder.setCurrentSlide(xSlide); 1065 1066 // precondition: must only be called from the main thread! 1067 DBG_TESTSOLARMUTEX(); 1068 1069 mxDrawPagesSupplier = xDrawPages; 1070 mxSBD = uno::Reference<document::XStorageBasedDocument>(mxDrawPagesSupplier, uno::UNO_QUERY); 1071 1072 stopShow(); // MUST call that: results in 1073 // maUserEventQueue.clear(). What's more, 1074 // stopShow()'s currSlide->hide() call is 1075 // now also required, notifySlideEnded() 1076 // relies on that 1077 // unconditionally. Otherwise, genuine 1078 // shape animations (drawing layer and 1079 // GIF) will not be stopped. 1080 1081 bool bSkipAllMainSequenceEffects (false); 1082 bool bSkipSlideTransition (false); 1083 std::for_each( rProperties.begin(), 1084 rProperties.end(), 1085 PrefetchPropertiesFunc(this, bSkipAllMainSequenceEffects, bSkipSlideTransition) ); 1086 1087 OSL_ENSURE( !maViewContainer.empty(), "### no views!" ); 1088 if (maViewContainer.empty()) 1089 return; 1090 1091 // this here might take some time 1092 { 1093 WaitSymbolLock aLock (*this); 1094 1095 mpPreviousSlide = mpCurrentSlide; 1096 mpCurrentSlide.reset(); 1097 1098 if (matches( mpPrefetchSlide, xSlide, xRootNode )) 1099 { 1100 // prefetched slide matches: 1101 mpCurrentSlide = mpPrefetchSlide; 1102 } 1103 else 1104 mpCurrentSlide = makeSlide( xSlide, xDrawPages, xRootNode ); 1105 1106 OSL_ASSERT( mpCurrentSlide ); 1107 if (mpCurrentSlide) 1108 { 1109 basegfx::B2DSize oldSlideSize; 1110 if( mpPreviousSlide ) 1111 oldSlideSize = basegfx::B2DSize( mpPreviousSlide->getSlideSize() ); 1112 1113 basegfx::B2DSize const slideSize( mpCurrentSlide->getSlideSize() ); 1114 1115 // push new transformation to all views, if size changed 1116 if( !mpPreviousSlide || oldSlideSize != slideSize ) 1117 { 1118 for( const auto& pView : maViewContainer ) 1119 pView->setViewSize( slideSize ); 1120 1121 // explicitly notify view change here, 1122 // because transformation might have changed: 1123 // optimization, this->notifyViewChange() would 1124 // repaint slide which is not necessary. 1125 maEventMultiplexer.notifyViewsChanged(); 1126 } 1127 1128 // create slide transition, and add proper end event 1129 // (which then starts the slide effects 1130 // via CURRENT_SLIDE.show()) 1131 ActivitySharedPtr pSlideChangeActivity ( 1132 createSlideTransition( 1133 mpCurrentSlide->getXDrawPage(), 1134 mpPreviousSlide, 1135 mpCurrentSlide, 1136 makeEvent( 1137 [this] () { this->notifySlideTransitionEnded(false); }, 1138 "SlideShowImpl::notifySlideTransitionEnded"))); 1139 1140 if (bSkipSlideTransition) 1141 { 1142 // The transition activity was created for the side effects 1143 // (like sound transitions). Because we want to skip the 1144 // actual transition animation we do not need the activity 1145 // anymore. 1146 pSlideChangeActivity.reset(); 1147 } 1148 1149 if (pSlideChangeActivity) 1150 { 1151 // factory generated a slide transition - activate it! 1152 maActivitiesQueue.addActivity( pSlideChangeActivity ); 1153 } 1154 else 1155 { 1156 // no transition effect on this slide - schedule slide 1157 // effect start event right away. 1158 maEventQueue.addEvent( 1159 makeEvent( 1160 [this] () { this->notifySlideTransitionEnded(true); }, 1161 "SlideShowImpl::notifySlideTransitionEnded")); 1162 } 1163 } 1164 } // finally 1165 1166 maListenerContainer.forEach( 1167 [](uno::Reference<presentation::XSlideShowListener> const& xListener) 1168 { 1169 xListener->slideTransitionStarted(); 1170 }); 1171 1172 // We are currently rewinding an effect. This lead us from the next 1173 // slide to this one. To complete this we have to play back all main 1174 // sequence effects on this slide. 1175 if (bSkipAllMainSequenceEffects) 1176 maEffectRewinder.skipAllMainSequenceEffects(); 1177 } 1178 1179 void SlideShowImpl::redisplayCurrentSlide() 1180 { 1181 osl::MutexGuard const guard( m_aMutex ); 1182 1183 if (isDisposed()) 1184 return; 1185 1186 // precondition: must only be called from the main thread! 1187 DBG_TESTSOLARMUTEX(); 1188 stopShow(); 1189 1190 OSL_ENSURE( !maViewContainer.empty(), "### no views!" ); 1191 if (maViewContainer.empty()) 1192 return; 1193 1194 // No transition effect on this slide - schedule slide 1195 // effect start event right away. 1196 maEventQueue.addEvent( 1197 makeEvent( [this] () { this->notifySlideTransitionEnded(true); }, 1198 "SlideShowImpl::notifySlideTransitionEnded")); 1199 1200 maListenerContainer.forEach( 1201 [](uno::Reference<presentation::XSlideShowListener> const& xListener) 1202 { 1203 xListener->slideTransitionStarted(); 1204 }); 1205 } 1206 1207 sal_Bool SlideShowImpl::nextEffect() 1208 { 1209 osl::MutexGuard const guard( m_aMutex ); 1210 1211 if (isDisposed()) 1212 return false; 1213 1214 // precondition: must only be called from the main thread! 1215 DBG_TESTSOLARMUTEX(); 1216 1217 if (mbShowPaused) 1218 return true; 1219 else 1220 return maEventMultiplexer.notifyNextEffect(); 1221 } 1222 1223 sal_Bool SlideShowImpl::previousEffect() 1224 { 1225 osl::MutexGuard const guard( m_aMutex ); 1226 1227 if (isDisposed()) 1228 return false; 1229 1230 // precondition: must only be called from the main thread! 1231 DBG_TESTSOLARMUTEX(); 1232 1233 if (mbShowPaused) 1234 return true; 1235 else 1236 { 1237 return maEffectRewinder.rewind( 1238 maScreenUpdater.createLock(), 1239 [this]() { return this->redisplayCurrentSlide(); }, 1240 [this]() { return this->rewindEffectToPreviousSlide(); } ); 1241 } 1242 } 1243 1244 void SlideShowImpl::rewindEffectToPreviousSlide() 1245 { 1246 // Show the wait symbol now and prevent it from showing temporary slide 1247 // content while effects are played back. 1248 WaitSymbolLock aLock (*this); 1249 1250 // A previous call to EffectRewinder::Rewind could not rewind the current 1251 // effect because there are no effects on the current slide or none has 1252 // yet been displayed. Go to the previous slide. 1253 notifySlideEnded(true); 1254 1255 // Process pending events once more in order to have the following 1256 // screen update show the last effect. Not sure whether this should be 1257 // necessary. 1258 maEventQueue.forceEmpty(); 1259 1260 // We have to call the screen updater before the wait symbol is turned 1261 // off. Otherwise the wait symbol would force the display of an 1262 // intermediate state of the slide (before the effects are replayed.) 1263 maScreenUpdater.commitUpdates(); 1264 } 1265 1266 sal_Bool SlideShowImpl::startShapeActivity( 1267 uno::Reference<drawing::XShape> const& /*xShape*/ ) 1268 { 1269 osl::MutexGuard const guard( m_aMutex ); 1270 1271 // precondition: must only be called from the main thread! 1272 DBG_TESTSOLARMUTEX(); 1273 1274 // TODO(F3): NYI 1275 OSL_FAIL( "not yet implemented!" ); 1276 return false; 1277 } 1278 1279 sal_Bool SlideShowImpl::stopShapeActivity( 1280 uno::Reference<drawing::XShape> const& /*xShape*/ ) 1281 { 1282 osl::MutexGuard const guard( m_aMutex ); 1283 1284 // precondition: must only be called from the main thread! 1285 DBG_TESTSOLARMUTEX(); 1286 1287 // TODO(F3): NYI 1288 OSL_FAIL( "not yet implemented!" ); 1289 return false; 1290 } 1291 1292 sal_Bool SlideShowImpl::pause( sal_Bool bPauseShow ) 1293 { 1294 osl::MutexGuard const guard( m_aMutex ); 1295 1296 if (isDisposed()) 1297 return false; 1298 1299 // precondition: must only be called from the main thread! 1300 DBG_TESTSOLARMUTEX(); 1301 1302 if (bPauseShow) 1303 mpPresTimer->pauseTimer(); 1304 else 1305 mpPresTimer->continueTimer(); 1306 1307 maEventMultiplexer.notifyPauseMode(bPauseShow); 1308 1309 mbShowPaused = bPauseShow; 1310 return true; 1311 } 1312 1313 uno::Reference<drawing::XDrawPage> SlideShowImpl::getCurrentSlide() 1314 { 1315 osl::MutexGuard const guard( m_aMutex ); 1316 1317 if (isDisposed()) 1318 return uno::Reference<drawing::XDrawPage>(); 1319 1320 // precondition: must only be called from the main thread! 1321 DBG_TESTSOLARMUTEX(); 1322 1323 if (mpCurrentSlide) 1324 return mpCurrentSlide->getXDrawPage(); 1325 else 1326 return uno::Reference<drawing::XDrawPage>(); 1327 } 1328 1329 sal_Bool SlideShowImpl::addView( 1330 uno::Reference<presentation::XSlideShowView> const& xView ) 1331 { 1332 osl::MutexGuard const guard( m_aMutex ); 1333 1334 if (isDisposed()) 1335 return false; 1336 1337 // precondition: must only be called from the main thread! 1338 DBG_TESTSOLARMUTEX(); 1339 1340 // first of all, check if view has a valid canvas 1341 ENSURE_OR_RETURN_FALSE( xView.is(), "addView(): Invalid view" ); 1342 ENSURE_OR_RETURN_FALSE( xView->getCanvas().is(), 1343 "addView(): View does not provide a valid canvas" ); 1344 1345 UnoViewSharedPtr const pView( createSlideView( 1346 xView, 1347 maEventQueue, 1348 maEventMultiplexer )); 1349 if (!maViewContainer.addView( pView )) 1350 return false; // view already added 1351 1352 // initialize view content 1353 // ======================= 1354 1355 if (mpCurrentSlide) 1356 { 1357 // set view transformation 1358 const basegfx::B2ISize slideSize = mpCurrentSlide->getSlideSize(); 1359 pView->setViewSize( basegfx::B2DSize( slideSize.getX(), 1360 slideSize.getY() ) ); 1361 } 1362 1363 // clear view area (since it's newly added, 1364 // we need a clean slate) 1365 pView->clearAll(); 1366 1367 // broadcast newly added view 1368 maEventMultiplexer.notifyViewAdded( pView ); 1369 1370 // set current mouse ptr 1371 pView->setCursorShape( calcActiveCursor(mnCurrentCursor) ); 1372 1373 return true; 1374 } 1375 1376 sal_Bool SlideShowImpl::removeView( 1377 uno::Reference<presentation::XSlideShowView> const& xView ) 1378 { 1379 osl::MutexGuard const guard( m_aMutex ); 1380 1381 // precondition: must only be called from the main thread! 1382 DBG_TESTSOLARMUTEX(); 1383 1384 ENSURE_OR_RETURN_FALSE( xView.is(), "removeView(): Invalid view" ); 1385 1386 UnoViewSharedPtr const pView( maViewContainer.removeView( xView ) ); 1387 if( !pView ) 1388 return false; // view was not added in the first place 1389 1390 // remove view from EventMultiplexer (mouse events etc.) 1391 maEventMultiplexer.notifyViewRemoved( pView ); 1392 1393 pView->_dispose(); 1394 1395 return true; 1396 } 1397 1398 void SlideShowImpl::registerUserPaintPolygons( const uno::Reference< lang::XMultiServiceFactory >& xDocFactory ) 1399 { 1400 //Retrieve Polygons if user ends presentation by context menu 1401 if (mpCurrentSlide) 1402 { 1403 if(findPolygons(mpCurrentSlide->getXDrawPage()) != maPolygons.end()) 1404 maPolygons.erase(mpCurrentSlide->getXDrawPage()); 1405 1406 maPolygons.insert(make_pair(mpCurrentSlide->getXDrawPage(),mpCurrentSlide->getPolygons())); 1407 } 1408 1409 //Creating the layer for shapes drawn during slideshow 1410 // query for the XLayerManager 1411 uno::Reference< drawing::XLayerSupplier > xLayerSupplier(xDocFactory, uno::UNO_QUERY); 1412 uno::Reference< container::XNameAccess > xNameAccess = xLayerSupplier->getLayerManager(); 1413 uno::Reference< drawing::XLayerManager > xLayerManager(xNameAccess, uno::UNO_QUERY); 1414 1415 // create layer 1416 uno::Reference< drawing::XLayer > xDrawnInSlideshow; 1417 uno::Any aPropLayer; 1418 OUString sLayerName = "DrawnInSlideshow"; 1419 if (xNameAccess->hasByName(sLayerName)) 1420 { 1421 xNameAccess->getByName(sLayerName) >>= xDrawnInSlideshow; 1422 } 1423 else 1424 { 1425 xDrawnInSlideshow = xLayerManager->insertNewByIndex(xLayerManager->getCount()); 1426 aPropLayer <<= sLayerName; 1427 xDrawnInSlideshow->setPropertyValue("Name", aPropLayer); 1428 } 1429 1430 // ODF defaults from ctor of SdrLayer are not automatically set on the here 1431 // created XLayer. Need to be done explicitly here. 1432 aPropLayer <<= true; 1433 xDrawnInSlideshow->setPropertyValue("IsVisible", aPropLayer); 1434 xDrawnInSlideshow->setPropertyValue("IsPrintable", aPropLayer); 1435 aPropLayer <<= false; 1436 xDrawnInSlideshow->setPropertyValue("IsLocked", aPropLayer); 1437 1438 //Register polygons for each slide 1439 for( const auto& rPoly : maPolygons ) 1440 { 1441 PolyPolygonVector aPolygons = rPoly.second; 1442 //Get shapes for the slide 1443 css::uno::Reference< css::drawing::XShapes > Shapes = rPoly.first; 1444 //Retrieve polygons for one slide 1445 for( const auto& pPolyPoly : aPolygons ) 1446 { 1447 ::basegfx::B2DPolyPolygon b2DPolyPoly = ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(pPolyPoly->getUNOPolyPolygon()); 1448 1449 //Normally there is only one polygon 1450 for(sal_uInt32 i=0; i< b2DPolyPoly.count();i++) 1451 { 1452 const ::basegfx::B2DPolygon& aPoly = b2DPolyPoly.getB2DPolygon(i); 1453 sal_uInt32 nPoints = aPoly.count(); 1454 1455 if( nPoints > 1) 1456 { 1457 //create the PolyLineShape 1458 uno::Reference< uno::XInterface > polyshape(xDocFactory->createInstance( 1459 "com.sun.star.drawing.PolyLineShape" ) ); 1460 uno::Reference< drawing::XShape > rPolyShape(polyshape, uno::UNO_QUERY); 1461 1462 //Add the shape to the slide 1463 Shapes->add(rPolyShape); 1464 1465 //Retrieve shape properties 1466 uno::Reference< beans::XPropertySet > aXPropSet( rPolyShape, uno::UNO_QUERY ); 1467 //Construct a sequence of points sequence 1468 drawing::PointSequenceSequence aRetval; 1469 //Create only one sequence for one polygon 1470 aRetval.realloc( 1 ); 1471 // Retrieve the sequence of points from aRetval 1472 drawing::PointSequence* pOuterSequence = aRetval.getArray(); 1473 // Create 2 points in this sequence 1474 pOuterSequence->realloc(nPoints); 1475 // Get these points which are in an array 1476 awt::Point* pInnerSequence = pOuterSequence->getArray(); 1477 for( sal_uInt32 n = 0; n < nPoints; n++ ) 1478 { 1479 //Create a point from the polygon 1480 *pInnerSequence++ = awt::Point( 1481 basegfx::fround(aPoly.getB2DPoint(n).getX()), 1482 basegfx::fround(aPoly.getB2DPoint(n).getY())); 1483 } 1484 1485 //Fill the properties 1486 //Give the built PointSequenceSequence. 1487 uno::Any aParam; 1488 aParam <<= aRetval; 1489 aXPropSet->setPropertyValue("PolyPolygon", aParam ); 1490 1491 //LineStyle : SOLID by default 1492 drawing::LineStyle eLS; 1493 eLS = drawing::LineStyle_SOLID; 1494 aXPropSet->setPropertyValue("LineStyle", uno::Any(eLS) ); 1495 1496 //LineColor 1497 sal_uInt32 nLineColor; 1498 nLineColor = pPolyPoly->getRGBALineColor(); 1499 //Transform polygon color from RRGGBBAA to AARRGGBB 1500 aXPropSet->setPropertyValue("LineColor", uno::Any(RGBAColor2UnoColor(nLineColor)) ); 1501 1502 //LineWidth 1503 double fLineWidth; 1504 fLineWidth = pPolyPoly->getStrokeWidth(); 1505 aXPropSet->setPropertyValue("LineWidth", uno::Any(static_cast<sal_Int32>(fLineWidth)) ); 1506 1507 // make polygons special 1508 xLayerManager->attachShapeToLayer(rPolyShape, xDrawnInSlideshow); 1509 } 1510 } 1511 } 1512 } 1513 } 1514 1515 sal_Bool SlideShowImpl::setProperty( beans::PropertyValue const& rProperty ) 1516 { 1517 osl::MutexGuard const guard( m_aMutex ); 1518 1519 if (isDisposed()) 1520 return false; 1521 1522 // precondition: must only be called from the main thread! 1523 DBG_TESTSOLARMUTEX(); 1524 1525 if ( rProperty.Name == "AutomaticAdvancement" ) 1526 { 1527 double nTimeout(0.0); 1528 mbAutomaticAdvancementMode = (rProperty.Value >>= nTimeout); 1529 if (mbAutomaticAdvancementMode) 1530 { 1531 maEventMultiplexer.setAutomaticTimeout( nTimeout ); 1532 } 1533 maEventMultiplexer.setAutomaticMode( mbAutomaticAdvancementMode ); 1534 return true; 1535 } 1536 1537 if ( rProperty.Name == "UserPaintColor" ) 1538 { 1539 sal_Int32 nColor(0); 1540 if (rProperty.Value >>= nColor) 1541 { 1542 OSL_ENSURE( mbMouseVisible, 1543 "setProperty(): User paint overrides invisible mouse" ); 1544 1545 // enable user paint 1546 maUserPaintColor = unoColor2RGBColor(nColor); 1547 if( mpCurrentSlide && !mpCurrentSlide->isPaintOverlayActive() ) 1548 mpCurrentSlide->enablePaintOverlay(); 1549 1550 maEventMultiplexer.notifyUserPaintColor( *maUserPaintColor ); 1551 } 1552 else 1553 { 1554 // disable user paint 1555 maUserPaintColor.reset(); 1556 maEventMultiplexer.notifyUserPaintDisabled(); 1557 } 1558 1559 resetCursor(); 1560 1561 return true; 1562 } 1563 1564 //adding support for erasing features in UserPaintOverlay 1565 if ( rProperty.Name == "EraseAllInk" ) 1566 { 1567 bool bEraseAllInk(false); 1568 if (rProperty.Value >>= bEraseAllInk) 1569 { 1570 OSL_ENSURE( mbMouseVisible, 1571 "setProperty(): User paint overrides invisible mouse" ); 1572 1573 // enable user paint 1574 maEraseAllInk = bEraseAllInk; 1575 maEventMultiplexer.notifyEraseAllInk( *maEraseAllInk ); 1576 } 1577 1578 return true; 1579 } 1580 1581 if ( rProperty.Name == "SwitchPenMode" ) 1582 { 1583 bool bSwitchPenMode(false); 1584 if (rProperty.Value >>= bSwitchPenMode) 1585 { 1586 OSL_ENSURE( mbMouseVisible, 1587 "setProperty(): User paint overrides invisible mouse" ); 1588 1589 if(bSwitchPenMode){ 1590 // Switch to Pen Mode 1591 maEventMultiplexer.notifySwitchPenMode(); 1592 } 1593 } 1594 return true; 1595 } 1596 1597 if ( rProperty.Name == "SwitchEraserMode" ) 1598 { 1599 bool bSwitchEraserMode(false); 1600 if (rProperty.Value >>= bSwitchEraserMode) 1601 { 1602 OSL_ENSURE( mbMouseVisible, 1603 "setProperty(): User paint overrides invisible mouse" ); 1604 if(bSwitchEraserMode){ 1605 // switch to Eraser mode 1606 maEventMultiplexer.notifySwitchEraserMode(); 1607 } 1608 } 1609 1610 return true; 1611 } 1612 1613 if ( rProperty.Name == "EraseInk" ) 1614 { 1615 sal_Int32 nEraseInk(100); 1616 if (rProperty.Value >>= nEraseInk) 1617 { 1618 OSL_ENSURE( mbMouseVisible, 1619 "setProperty(): User paint overrides invisible mouse" ); 1620 1621 // enable user paint 1622 maEraseInk = nEraseInk; 1623 maEventMultiplexer.notifyEraseInkWidth( *maEraseInk ); 1624 } 1625 1626 return true; 1627 } 1628 1629 // new Property for pen's width 1630 if ( rProperty.Name == "UserPaintStrokeWidth" ) 1631 { 1632 double nWidth(4.0); 1633 if (rProperty.Value >>= nWidth) 1634 { 1635 OSL_ENSURE( mbMouseVisible,"setProperty(): User paint overrides invisible mouse" ); 1636 // enable user paint stroke width 1637 maUserPaintStrokeWidth = nWidth; 1638 maEventMultiplexer.notifyUserPaintStrokeWidth( maUserPaintStrokeWidth ); 1639 } 1640 1641 return true; 1642 } 1643 1644 if ( rProperty.Name == "AdvanceOnClick" ) 1645 { 1646 bool bAdvanceOnClick = false; 1647 if (! (rProperty.Value >>= bAdvanceOnClick)) 1648 return false; 1649 maUserEventQueue.setAdvanceOnClick( bAdvanceOnClick ); 1650 return true; 1651 } 1652 1653 if ( rProperty.Name == "DisableAnimationZOrder" ) 1654 { 1655 bool bDisableAnimationZOrder = false; 1656 if (! (rProperty.Value >>= bDisableAnimationZOrder)) 1657 return false; 1658 mbDisableAnimationZOrder = bDisableAnimationZOrder; 1659 return true; 1660 } 1661 1662 if ( rProperty.Name == "ImageAnimationsAllowed" ) 1663 { 1664 if (! (rProperty.Value >>= mbImageAnimationsAllowed)) 1665 return false; 1666 1667 // TODO(F3): Forward to slides! 1668 return true; 1669 } 1670 1671 if ( rProperty.Name == "MouseVisible" ) 1672 { 1673 if (! (rProperty.Value >>= mbMouseVisible)) 1674 return false; 1675 1676 requestCursor(mnCurrentCursor); 1677 1678 return true; 1679 } 1680 1681 if ( rProperty.Name == "ForceManualAdvance" ) 1682 { 1683 return (rProperty.Value >>= mbForceManualAdvance); 1684 } 1685 1686 if ( rProperty.Name == "RehearseTimings" ) 1687 { 1688 bool bRehearseTimings = false; 1689 if (! (rProperty.Value >>= bRehearseTimings)) 1690 return false; 1691 1692 if (bRehearseTimings) 1693 { 1694 // TODO(Q3): Move to slide 1695 mpRehearseTimingsActivity = RehearseTimingsActivity::create( 1696 SlideShowContext( 1697 mpDummyPtr, 1698 maEventQueue, 1699 maEventMultiplexer, 1700 maScreenUpdater, 1701 maActivitiesQueue, 1702 maUserEventQueue, 1703 *this, 1704 *this, 1705 maViewContainer, 1706 mxComponentContext, 1707 mpBox2DDummyPtr ) ); 1708 } 1709 else if (mpRehearseTimingsActivity) 1710 { 1711 // removes timer from all views: 1712 mpRehearseTimingsActivity->dispose(); 1713 mpRehearseTimingsActivity.reset(); 1714 } 1715 return true; 1716 } 1717 1718 if ( rProperty.Name == "WaitSymbolBitmap" ) 1719 { 1720 uno::Reference<rendering::XBitmap> xBitmap; 1721 if (! (rProperty.Value >>= xBitmap)) 1722 return false; 1723 1724 mpWaitSymbol = WaitSymbol::create( xBitmap, 1725 maScreenUpdater, 1726 maEventMultiplexer, 1727 maViewContainer ); 1728 1729 return true; 1730 } 1731 1732 if ( rProperty.Name == "PointerSymbolBitmap" ) 1733 { 1734 uno::Reference<rendering::XBitmap> xBitmap; 1735 if (! (rProperty.Value >>= xBitmap)) 1736 return false; 1737 1738 mpPointerSymbol = PointerSymbol::create( xBitmap, 1739 maScreenUpdater, 1740 maEventMultiplexer, 1741 maViewContainer ); 1742 1743 return true; 1744 } 1745 1746 if ( rProperty.Name == "PointerVisible" ) 1747 { 1748 bool visible; 1749 if (!(rProperty.Value >>= visible)) 1750 return false; 1751 1752 mpPointerSymbol->setVisible(visible); 1753 return true; 1754 } 1755 1756 if ( rProperty.Name == "PointerPosition") 1757 { 1758 css::geometry::RealPoint2D pos; 1759 if (! (rProperty.Value >>= pos)) 1760 return false; 1761 1762 mpPointerSymbol->viewsChanged(pos); 1763 return true; 1764 } 1765 1766 if (rProperty.Name == "NoSlideTransitions" ) 1767 { 1768 return (rProperty.Value >>= mbNoSlideTransitions); 1769 } 1770 1771 if ( rProperty.Name == "IsSoundEnabled" ) 1772 { 1773 uno::Sequence<uno::Any> aValues; 1774 uno::Reference<presentation::XSlideShowView> xView; 1775 bool bValue (false); 1776 if ((rProperty.Value >>= aValues) 1777 && aValues.getLength()==2 1778 && (aValues[0] >>= xView) 1779 && (aValues[1] >>= bValue)) 1780 { 1781 // Look up the view. 1782 auto iView = std::find_if(maViewContainer.begin(), maViewContainer.end(), 1783 [&xView](const UnoViewSharedPtr& rxView) { return rxView && rxView->getUnoView() == xView; }); 1784 if (iView != maViewContainer.end()) 1785 { 1786 // Store the flag at the view so that media shapes have 1787 // access to it. 1788 (*iView)->setIsSoundEnabled(bValue); 1789 return true; 1790 } 1791 } 1792 } 1793 1794 return false; 1795 } 1796 1797 void SlideShowImpl::addSlideShowListener( 1798 uno::Reference<presentation::XSlideShowListener> const& xListener ) 1799 { 1800 osl::MutexGuard const guard( m_aMutex ); 1801 1802 if (isDisposed()) 1803 return; 1804 1805 // container syncs with passed mutex ref 1806 maListenerContainer.addInterface(xListener); 1807 } 1808 1809 void SlideShowImpl::removeSlideShowListener( 1810 uno::Reference<presentation::XSlideShowListener> const& xListener ) 1811 { 1812 osl::MutexGuard const guard( m_aMutex ); 1813 1814 // container syncs with passed mutex ref 1815 maListenerContainer.removeInterface(xListener); 1816 } 1817 1818 void SlideShowImpl::addShapeEventListener( 1819 uno::Reference<presentation::XShapeEventListener> const& xListener, 1820 uno::Reference<drawing::XShape> const& xShape ) 1821 { 1822 osl::MutexGuard const guard( m_aMutex ); 1823 1824 if (isDisposed()) 1825 return; 1826 1827 // precondition: must only be called from the main thread! 1828 DBG_TESTSOLARMUTEX(); 1829 1830 ShapeEventListenerMap::iterator aIter; 1831 if( (aIter=maShapeEventListeners.find( xShape )) == 1832 maShapeEventListeners.end() ) 1833 { 1834 // no entry for this shape -> create one 1835 aIter = maShapeEventListeners.emplace( 1836 xShape, 1837 std::make_shared<comphelper::OInterfaceContainerHelper3<css::presentation::XShapeEventListener>>( 1838 m_aMutex)).first; 1839 } 1840 1841 // add new listener to broadcaster 1842 if( aIter->second ) 1843 aIter->second->addInterface( xListener ); 1844 1845 maEventMultiplexer.notifyShapeListenerAdded(xShape); 1846 } 1847 1848 void SlideShowImpl::removeShapeEventListener( 1849 uno::Reference<presentation::XShapeEventListener> const& xListener, 1850 uno::Reference<drawing::XShape> const& xShape ) 1851 { 1852 osl::MutexGuard const guard( m_aMutex ); 1853 1854 // precondition: must only be called from the main thread! 1855 DBG_TESTSOLARMUTEX(); 1856 1857 ShapeEventListenerMap::iterator aIter; 1858 if( (aIter = maShapeEventListeners.find( xShape )) != 1859 maShapeEventListeners.end() ) 1860 { 1861 // entry for this shape found -> remove listener from 1862 // helper object 1863 ENSURE_OR_THROW( 1864 aIter->second, 1865 "SlideShowImpl::removeShapeEventListener(): " 1866 "listener map contains NULL broadcast helper" ); 1867 1868 aIter->second->removeInterface( xListener ); 1869 } 1870 1871 maEventMultiplexer.notifyShapeListenerRemoved(xShape); 1872 } 1873 1874 void SlideShowImpl::setShapeCursor( 1875 uno::Reference<drawing::XShape> const& xShape, sal_Int16 nPointerShape ) 1876 { 1877 osl::MutexGuard const guard( m_aMutex ); 1878 1879 if (isDisposed()) 1880 return; 1881 1882 // precondition: must only be called from the main thread! 1883 DBG_TESTSOLARMUTEX(); 1884 1885 ShapeCursorMap::iterator aIter; 1886 if( (aIter=maShapeCursors.find( xShape )) == maShapeCursors.end() ) 1887 { 1888 // no entry for this shape -> create one 1889 if( nPointerShape != awt::SystemPointer::ARROW ) 1890 { 1891 // add new entry, unless shape shall display 1892 // normal pointer arrow -> no need to handle that 1893 // case 1894 maShapeCursors.emplace(xShape, nPointerShape); 1895 } 1896 } 1897 else if( nPointerShape == awt::SystemPointer::ARROW ) 1898 { 1899 // shape shall display normal cursor -> can disable 1900 // the cursor and clear the entry 1901 maShapeCursors.erase( xShape ); 1902 } 1903 else 1904 { 1905 // existing entry found, update with new cursor ID 1906 aIter->second = nPointerShape; 1907 } 1908 } 1909 1910 bool SlideShowImpl::requestCursor( sal_Int16 nCursorShape ) 1911 { 1912 mnCurrentCursor = nCursorShape; 1913 1914 const sal_Int16 nActualCursor = calcActiveCursor(mnCurrentCursor); 1915 1916 // change all views to the requested cursor ID 1917 for( const auto& pView : maViewContainer ) 1918 pView->setCursorShape( nActualCursor ); 1919 1920 return nActualCursor==nCursorShape; 1921 } 1922 1923 void SlideShowImpl::resetCursor() 1924 { 1925 mnCurrentCursor = awt::SystemPointer::ARROW; 1926 1927 const sal_Int16 nActualCursor = calcActiveCursor( mnCurrentCursor ); 1928 // change all views to the default cursor ID 1929 for( const auto& pView : maViewContainer ) 1930 pView->setCursorShape( nActualCursor ); 1931 } 1932 1933 sal_Bool SlideShowImpl::update( double & nNextTimeout ) 1934 { 1935 osl::MutexGuard const guard( m_aMutex ); 1936 1937 if (isDisposed()) 1938 return false; 1939 1940 // precondition: update() must only be called from the 1941 // main thread! 1942 DBG_TESTSOLARMUTEX(); 1943 1944 if( mbShowPaused ) 1945 { 1946 // commit frame (might be repaints pending) 1947 maScreenUpdater.commitUpdates(); 1948 1949 return false; 1950 } 1951 else 1952 { 1953 // TODO(F2): re-evaluate whether that timer lagging makes 1954 // sense. 1955 1956 // hold timer, while processing the queues: 1957 // 1. when there is more than one active activity this ensures the 1958 // same time for all activities and events 1959 // 2. processing of events may lead to creation of further events 1960 // that have zero delay. While the timer is stopped these events 1961 // are processed in the same run. 1962 { 1963 //Get a shared-ptr that outlives the scope-guard which will 1964 //ensure that the pointed-to-item exists in the case of a 1965 //::dispose clearing mpPresTimer 1966 std::shared_ptr<canvas::tools::ElapsedTime> xTimer(mpPresTimer); 1967 comphelper::ScopeGuard scopeGuard( 1968 [&xTimer]() { return xTimer->releaseTimer(); } ); 1969 xTimer->holdTimer(); 1970 1971 // process queues 1972 maEventQueue.process(); 1973 1974 // #i118671# the call above may execute a macro bound to an object. In 1975 // that case this macro may have destroyed this local slideshow so that it 1976 // is disposed (see bugdoc at task). In that case, detect this and exit 1977 // gently from this slideshow. Do not forget to disable the scoped 1978 // call to mpPresTimer, this will be deleted if we are disposed. 1979 if (isDisposed()) 1980 { 1981 scopeGuard.dismiss(); 1982 return false; 1983 } 1984 1985 maActivitiesQueue.process(); 1986 1987 // commit frame to screen 1988 maFrameSynchronization.Synchronize(); 1989 maScreenUpdater.commitUpdates(); 1990 1991 // TODO(Q3): remove need to call dequeued() from 1992 // activities. feels like a wart. 1993 1994 // Rationale for ActivitiesQueue::processDequeued(): when 1995 // an activity ends, it usually pushed the end state to 1996 // the animated shape in question, and ends the animation 1997 // (which, in turn, will usually disable shape sprite 1998 // mode). Disabling shape sprite mode causes shape 1999 // repaint, which, depending on slide content, takes 2000 // considerably more time than sprite updates. Thus, the 2001 // last animation step tends to look delayed. To 2002 // camouflage this, reaching end position and disabling 2003 // sprite mode is split into two (normal Activity::end(), 2004 // and Activity::dequeued()). Now, the reason to call 2005 // commitUpdates() twice here is caused by the unrelated 2006 // fact that during wait cursor display/hide, the screen 2007 // is updated, and shows hidden sprites, but, in case of 2008 // leaving the second commitUpdates() call out and punting 2009 // that to the next round, no updated static slide 2010 // content. In short, the last shape animation of a slide 2011 // tends to blink at its end. 2012 2013 // process dequeued activities _after_ commit to screen 2014 maActivitiesQueue.processDequeued(); 2015 2016 // commit frame to screen 2017 maScreenUpdater.commitUpdates(); 2018 } 2019 // Time held until here 2020 2021 const bool bActivitiesLeft = ! maActivitiesQueue.isEmpty(); 2022 const bool bTimerEventsLeft = ! maEventQueue.isEmpty(); 2023 const bool bRet = (bActivitiesLeft || bTimerEventsLeft); 2024 2025 if (bRet) 2026 { 2027 // calc nNextTimeout value: 2028 if (bActivitiesLeft) 2029 { 2030 // Activity queue is not empty. Tell caller that we would 2031 // like to render another frame. 2032 2033 // Return a zero time-out to signal our caller to call us 2034 // back as soon as possible. The actual timing, waiting the 2035 // appropriate amount of time between frames, is then done 2036 // by the maFrameSynchronization object. 2037 nNextTimeout = 0; 2038 maFrameSynchronization.Activate(); 2039 } 2040 else 2041 { 2042 // timer events left: 2043 // difference from current time (nota bene: 2044 // time no longer held here!) to the next event in 2045 // the event queue. 2046 2047 // #i61190# Retrieve next timeout only _after_ 2048 // processing activity queue 2049 2050 // ensure positive value: 2051 nNextTimeout = std::max( 0.0, maEventQueue.nextTimeout() ); 2052 2053 // There is no active animation so the frame rate does not 2054 // need to be synchronized. 2055 maFrameSynchronization.Deactivate(); 2056 } 2057 2058 mbSlideShowIdle = false; 2059 } 2060 2061 #if defined(DBG_UTIL) 2062 // when slideshow is idle, issue an XUpdatable::update() call 2063 // exactly once after a previous animation sequence finished - 2064 // this might trigger screen dumps on some canvas 2065 // implementations 2066 if( !mbSlideShowIdle && 2067 (!bRet || 2068 nNextTimeout > 1.0) ) 2069 { 2070 for( const auto& pView : maViewContainer ) 2071 { 2072 try 2073 { 2074 uno::Reference< presentation::XSlideShowView > xView( pView->getUnoView(), 2075 uno::UNO_SET_THROW ); 2076 uno::Reference<util::XUpdatable> const xUpdatable( 2077 xView->getCanvas(), uno::UNO_QUERY); 2078 if (xUpdatable.is()) // not supported in PresenterCanvas 2079 { 2080 xUpdatable->update(); 2081 } 2082 } 2083 catch( uno::RuntimeException& ) 2084 { 2085 throw; 2086 } 2087 catch( uno::Exception& ) 2088 { 2089 TOOLS_WARN_EXCEPTION( "slideshow", "" ); 2090 } 2091 } 2092 2093 mbSlideShowIdle = true; 2094 } 2095 #endif 2096 2097 return bRet; 2098 } 2099 } 2100 2101 void SlideShowImpl::notifySlideTransitionEnded( bool bPaintSlide ) 2102 { 2103 osl::MutexGuard const guard( m_aMutex ); 2104 2105 OSL_ENSURE( !isDisposed(), "### already disposed!" ); 2106 OSL_ENSURE( mpCurrentSlide, 2107 "notifySlideTransitionEnded(): Invalid current slide" ); 2108 if (mpCurrentSlide) 2109 { 2110 mpCurrentSlide->update_settings( !!maUserPaintColor, maUserPaintColor ? *maUserPaintColor : RGBColor(), maUserPaintStrokeWidth ); 2111 2112 // first init show, to give the animations 2113 // the chance to register SlideStartEvents 2114 const bool bBackgroundLayerRendered( !bPaintSlide ); 2115 mpCurrentSlide->show( bBackgroundLayerRendered ); 2116 maEventMultiplexer.notifySlideStartEvent(); 2117 } 2118 } 2119 2120 void queryAutomaticSlideTransition( uno::Reference<drawing::XDrawPage> const& xDrawPage, 2121 double& nAutomaticNextSlideTimeout, 2122 bool& bHasAutomaticNextSlide ) 2123 { 2124 // retrieve slide change parameters from XDrawPage 2125 // =============================================== 2126 2127 uno::Reference< beans::XPropertySet > xPropSet( xDrawPage, 2128 uno::UNO_QUERY ); 2129 2130 sal_Int32 nChange(0); 2131 if( !xPropSet.is() || 2132 !getPropertyValue( nChange, 2133 xPropSet, 2134 "Change") ) 2135 { 2136 SAL_INFO("slideshow", 2137 "queryAutomaticSlideTransition(): " 2138 "Could not extract slide change mode from XDrawPage - assuming <none>" ); 2139 } 2140 2141 bHasAutomaticNextSlide = nChange == 1; 2142 2143 if( !xPropSet.is() || 2144 !getPropertyValue( nAutomaticNextSlideTimeout, 2145 xPropSet, 2146 "HighResDuration") ) 2147 { 2148 SAL_INFO("slideshow", 2149 "queryAutomaticSlideTransition(): " 2150 "Could not extract slide transition timeout from " 2151 "XDrawPage - assuming 1 sec" ); 2152 } 2153 } 2154 2155 void SlideShowImpl::notifySlideAnimationsEnded() 2156 { 2157 osl::MutexGuard const guard( m_aMutex ); 2158 2159 //Draw polygons above animations 2160 mpCurrentSlide->drawPolygons(); 2161 2162 OSL_ENSURE( !isDisposed(), "### already disposed!" ); 2163 2164 // This struct will receive the (interruptable) event, 2165 // that triggers the notifySlideEnded() method. 2166 InterruptableEventPair aNotificationEvents; 2167 2168 if( maEventMultiplexer.getAutomaticMode() ) 2169 { 2170 OSL_ENSURE( ! mpRehearseTimingsActivity, 2171 "unexpected: RehearseTimings mode!" ); 2172 2173 // schedule a slide end event, with automatic mode's 2174 // delay 2175 aNotificationEvents = makeInterruptableDelay( 2176 [this]() { return this->notifySlideEnded( false ); }, 2177 maEventMultiplexer.getAutomaticTimeout() ); 2178 } 2179 else 2180 { 2181 OSL_ENSURE( mpCurrentSlide, 2182 "notifySlideAnimationsEnded(): Invalid current slide!" ); 2183 2184 bool bHasAutomaticNextSlide=false; 2185 double nAutomaticNextSlideTimeout=0.0; 2186 queryAutomaticSlideTransition(mpCurrentSlide->getXDrawPage(), 2187 nAutomaticNextSlideTimeout, 2188 bHasAutomaticNextSlide); 2189 2190 // check whether slide transition should happen 2191 // 'automatically'. If yes, simply schedule the 2192 // specified timeout. 2193 // NOTE: mbForceManualAdvance and mpRehearseTimingsActivity 2194 // override any individual slide setting, to always 2195 // step slides manually. 2196 if( !mbForceManualAdvance && 2197 !mpRehearseTimingsActivity && 2198 bHasAutomaticNextSlide ) 2199 { 2200 aNotificationEvents = makeInterruptableDelay( 2201 [this]() { return this->notifySlideEnded( false ); }, 2202 nAutomaticNextSlideTimeout); 2203 2204 // TODO(F2): Provide a mechanism to let the user override 2205 // this automatic timeout via next() 2206 } 2207 else 2208 { 2209 if (mpRehearseTimingsActivity) 2210 mpRehearseTimingsActivity->start(); 2211 2212 // generate click event. Thus, the user must 2213 // trigger the actual end of a slide. No need to 2214 // generate interruptable event here, there's no 2215 // timeout involved. 2216 aNotificationEvents.mpImmediateEvent = 2217 makeEvent( [this] () { this->notifySlideEnded(false); }, 2218 "SlideShowImpl::notifySlideEnded"); 2219 } 2220 } 2221 2222 // register events on the queues. To make automatic slide 2223 // changes interruptable, register the interruption event 2224 // as a nextEffectEvent target. Note that the timeout 2225 // event is optional (e.g. manual slide changes don't 2226 // generate a timeout) 2227 maUserEventQueue.registerNextEffectEvent( 2228 aNotificationEvents.mpImmediateEvent ); 2229 2230 if( aNotificationEvents.mpTimeoutEvent ) 2231 maEventQueue.addEvent( aNotificationEvents.mpTimeoutEvent ); 2232 2233 // current slide's main sequence is over. Now should be 2234 // the time to prefetch the next slide (if any), and 2235 // prepare the initial slide bitmap (speeds up slide 2236 // change setup time a lot). Show the wait cursor, this 2237 // indeed might take some seconds. 2238 { 2239 WaitSymbolLock aLock (*this); 2240 2241 if (! matches( mpPrefetchSlide, 2242 mxPrefetchSlide, mxPrefetchAnimationNode )) 2243 { 2244 mpPrefetchSlide = makeSlide( mxPrefetchSlide, mxDrawPagesSupplier, 2245 mxPrefetchAnimationNode ); 2246 } 2247 if (mpPrefetchSlide) 2248 { 2249 // ignore return value, this is just to populate 2250 // Slide's internal bitmap buffer, such that the time 2251 // needed to generate the slide bitmap is not spent 2252 // when the slide change is requested. 2253 mpPrefetchSlide->getCurrentSlideBitmap( *maViewContainer.begin() ); 2254 } 2255 } // finally 2256 2257 maListenerContainer.forEach( 2258 [](uno::Reference<presentation::XSlideShowListener> const& xListener) 2259 { 2260 xListener->slideAnimationsEnded(); 2261 }); 2262 } 2263 2264 void SlideShowImpl::notifySlideEnded (const bool bReverse) 2265 { 2266 osl::MutexGuard const guard( m_aMutex ); 2267 2268 OSL_ENSURE( !isDisposed(), "### already disposed!" ); 2269 2270 if (mpRehearseTimingsActivity && !bReverse) 2271 { 2272 const double time = mpRehearseTimingsActivity->stop(); 2273 if (mpRehearseTimingsActivity->hasBeenClicked()) 2274 { 2275 // save time at current drawpage: 2276 uno::Reference<beans::XPropertySet> xPropSet( 2277 mpCurrentSlide->getXDrawPage(), uno::UNO_QUERY ); 2278 OSL_ASSERT( xPropSet.is() ); 2279 if (xPropSet.is()) 2280 { 2281 xPropSet->setPropertyValue( 2282 "Change", 2283 uno::Any( static_cast<sal_Int32>(1) ) ); 2284 xPropSet->setPropertyValue( 2285 "Duration", 2286 uno::Any( static_cast<sal_Int32>(time) ) ); 2287 } 2288 } 2289 } 2290 2291 if (bReverse) 2292 maEventMultiplexer.notifySlideEndEvent(); 2293 2294 stopShow(); // MUST call that: results in 2295 // maUserEventQueue.clear(). What's more, 2296 // stopShow()'s currSlide->hide() call is 2297 // now also required, notifySlideEnded() 2298 // relies on that 2299 // unconditionally. Otherwise, genuine 2300 // shape animations (drawing layer and 2301 // GIF) will not be stopped. 2302 2303 maListenerContainer.forEach( 2304 [&bReverse]( const uno::Reference< presentation::XSlideShowListener >& xListener ) 2305 { return xListener->slideEnded( bReverse ); } ); 2306 } 2307 2308 bool SlideShowImpl::notifyHyperLinkClicked( OUString const& hyperLink ) 2309 { 2310 osl::MutexGuard const guard( m_aMutex ); 2311 2312 maListenerContainer.forEach( 2313 [&hyperLink]( const uno::Reference< presentation::XSlideShowListener >& xListener ) 2314 { return xListener->hyperLinkClicked( hyperLink ); } ); 2315 return true; 2316 } 2317 2318 /** Notification from eventmultiplexer that an animation event has occurred. 2319 This will be forwarded to all registered XSlideShoeListener 2320 */ 2321 bool SlideShowImpl::handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) 2322 { 2323 osl::MutexGuard const guard( m_aMutex ); 2324 2325 uno::Reference<animations::XAnimationNode> xNode( rNode->getXAnimationNode() ); 2326 2327 switch( rNode->getState() ) 2328 { 2329 case AnimationNode::ACTIVE: 2330 maListenerContainer.forEach( 2331 [&xNode]( const uno::Reference< animations::XAnimationListener >& xListener ) 2332 { return xListener->beginEvent( xNode ); } ); 2333 break; 2334 2335 case AnimationNode::FROZEN: 2336 case AnimationNode::ENDED: 2337 maListenerContainer.forEach( 2338 [&xNode]( const uno::Reference< animations::XAnimationListener >& xListener ) 2339 { return xListener->endEvent( xNode ); } ); 2340 if(mpCurrentSlide->isPaintOverlayActive()) 2341 mpCurrentSlide->drawPolygons(); 2342 break; 2343 default: 2344 break; 2345 } 2346 2347 return true; 2348 } 2349 2350 std::shared_ptr<avmedia::MediaTempFile> SlideShowImpl::getMediaTempFile(const OUString& aUrl) 2351 { 2352 std::shared_ptr<avmedia::MediaTempFile> aRet; 2353 2354 #if !HAVE_FEATURE_AVMEDIA 2355 (void)aUrl; 2356 #else 2357 if (!mxSBD.is()) 2358 return aRet; 2359 2360 comphelper::LifecycleProxy aProxy; 2361 uno::Reference<io::XStream> xStream = 2362 comphelper::OStorageHelper::GetStreamAtPackageURL(mxSBD->getDocumentStorage(), aUrl, 2363 css::embed::ElementModes::READ, aProxy); 2364 2365 uno::Reference<io::XInputStream> xInStream = xStream->getInputStream(); 2366 if (xInStream.is()) 2367 { 2368 sal_Int32 nLastDot = aUrl.lastIndexOf('.'); 2369 sal_Int32 nLastSlash = aUrl.lastIndexOf('/'); 2370 OUString sDesiredExtension; 2371 if (nLastDot > nLastSlash && nLastDot+1 < aUrl.getLength()) 2372 sDesiredExtension = aUrl.copy(nLastDot); 2373 2374 OUString sTempUrl; 2375 if (::avmedia::CreateMediaTempFile(xInStream, sTempUrl, sDesiredExtension)) 2376 aRet = std::make_shared<avmedia::MediaTempFile>(sTempUrl); 2377 2378 xInStream->closeInput(); 2379 } 2380 #endif 2381 2382 return aRet; 2383 } 2384 2385 //===== FrameSynchronization ================================================== 2386 2387 FrameSynchronization::FrameSynchronization (const double nFrameDuration) 2388 : maTimer(), 2389 mnFrameDuration(nFrameDuration), 2390 mnNextFrameTargetTime(0), 2391 mbIsActive(false) 2392 { 2393 MarkCurrentFrame(); 2394 } 2395 2396 void FrameSynchronization::MarkCurrentFrame() 2397 { 2398 mnNextFrameTargetTime = maTimer.getElapsedTime() + mnFrameDuration; 2399 } 2400 2401 void FrameSynchronization::Synchronize() 2402 { 2403 if (mbIsActive) 2404 { 2405 // Do busy waiting for now. 2406 for(;;) 2407 { 2408 double remainingTime = mnNextFrameTargetTime - maTimer.getElapsedTime(); 2409 if(remainingTime <= 0) 2410 break; 2411 // Try to sleep most of it. 2412 int remainingMilliseconds = remainingTime * 1000; 2413 if(remainingMilliseconds > 2) 2414 osl::Thread::wait(std::chrono::milliseconds(remainingMilliseconds - 2)); 2415 } 2416 } 2417 2418 MarkCurrentFrame(); 2419 } 2420 2421 void FrameSynchronization::Activate() 2422 { 2423 mbIsActive = true; 2424 } 2425 2426 void FrameSynchronization::Deactivate() 2427 { 2428 mbIsActive = false; 2429 } 2430 2431 } // anon namespace 2432 2433 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* 2434 slideshow_SlideShowImpl_get_implementation( 2435 css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) 2436 { 2437 return cppu::acquire(new SlideShowImpl(context)); 2438 } 2439 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 2440
