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