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