From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- slideshow/source/engine/slideshowimpl.cxx | 2439 +++++++++++++++++++++++++++++ 1 file changed, 2439 insertions(+) create mode 100644 slideshow/source/engine/slideshowimpl.cxx (limited to 'slideshow/source/engine/slideshowimpl.cxx') diff --git a/slideshow/source/engine/slideshowimpl.cxx b/slideshow/source/engine/slideshowimpl.cxx new file mode 100644 index 000000000..75b15e5c7 --- /dev/null +++ b/slideshow/source/engine/slideshowimpl.cxx @@ -0,0 +1,2439 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rehearsetimingsactivity.hxx" +#include "waitsymbol.hxx" +#include "effectrewinder.hxx" +#include +#include "pointersymbol.hxx" + +#include +#include +#include + +using namespace com::sun::star; +using namespace ::slideshow::internal; + +namespace box2d::utils { class box2DWorld; + typedef ::std::shared_ptr< box2DWorld > Box2DWorldSharedPtr; } + +namespace { + +/** During animations the update() method tells its caller to call it as + soon as possible. This gives us more time to render the next frame and + still maintain a steady frame rate. This class is responsible for + synchronizing the display of new frames and thus keeping the frame rate + steady. +*/ +class FrameSynchronization +{ +public: + /** Create new object with a predefined duration between two frames. + @param nFrameDuration + The preferred duration between the display of two frames in + seconds. + */ + explicit FrameSynchronization (const double nFrameDuration); + + /** Set the current time as the time at which the current frame is + displayed. From this the target time of the next frame is derived. + */ + void MarkCurrentFrame(); + + /** When there is time left until the next frame is due then wait. + Otherwise return without delay. + */ + void Synchronize(); + + /** Activate frame synchronization when an animation is active and + frames are to be displayed in a steady rate. While active + Synchronize() will wait until the frame duration time has passed. + */ + void Activate(); + + /** Deactivate frame synchronization when no animation is active and the + time between frames depends on user actions and other external + sources. While deactivated Synchronize() will return without delay. + */ + void Deactivate(); + +private: + /** The timer that is used for synchronization is independent from the + one used by SlideShowImpl: it is not paused or modified by + animations. + */ + canvas::tools::ElapsedTime maTimer; + /** Time between the display of frames. Enforced only when mbIsActive + is . + */ + const double mnFrameDuration; + /** Time (of maTimer) when the next frame shall be displayed. + Synchronize() will wait until this time. + */ + double mnNextFrameTargetTime; + /** Synchronize() will wait only when this flag is . Otherwise + it returns immediately. + */ + bool mbIsActive; +}; + +/****************************************************************************** + + SlideShowImpl + + This class encapsulates the slideshow presentation viewer. + + With an instance of this class, it is possible to statically + and dynamically show a presentation, as defined by the + constructor-provided draw model (represented by a sequence + of css::drawing::XDrawPage objects). + + It is possible to show the presentation on multiple views + simultaneously (e.g. for a multi-monitor setup). Since this + class also relies on user interaction, the corresponding + XSlideShowView interface provides means to register some UI + event listeners (mostly borrowed from awt::XWindow interface). + + Since currently (mid 2004), OOo isn't very well suited to + multi-threaded rendering, this class relies on very + frequent external update() calls, which will render the + next frame of animations. This works as follows: after the + displaySlide() has been successfully called (which setup and + starts an actual slide show), the update() method must be + called until it returns false. + Effectively, this puts the burden of providing + concurrency to the clients of this class, which, as noted + above, is currently unavoidable with the current state of + affairs (I've actually tried threading here, but failed + miserably when using the VCL canvas as the render backend - + deadlocked). + + ******************************************************************************/ + +typedef cppu::WeakComponentImplHelper SlideShowImplBase; + +typedef ::std::vector< ::cppcanvas::PolyPolygonSharedPtr> PolyPolygonVector; + +/// Maps XDrawPage for annotations persistence +typedef ::std::map< css::uno::Reference< + css::drawing::XDrawPage>, + PolyPolygonVector> PolygonMap; + +class SlideShowImpl : private cppu::BaseMutex, + public CursorManager, + public MediaFileManager, + public SlideShowImplBase +{ +public: + explicit SlideShowImpl( + uno::Reference const& xContext ); + + /** Notify that the transition phase of the current slide + has ended. + + The life of a slide has three phases: the transition + phase, when the previous slide vanishes, and the + current slide becomes visible, the shape animation + phase, when shape effects are running, and the phase + after the last shape animation has ended, but before + the next slide transition starts. + + This method notifies the end of the first phase. + + @param bPaintSlide + When true, Slide::show() is passed a true as well, denoting + explicit paint of slide content. Pass false here, if e.g. a + slide transition has already rendered the initial slide image. + */ + void notifySlideTransitionEnded( bool bPaintSlide ); + + /** Notify that the shape animation phase of the current slide + has ended. + + The life of a slide has three phases: the transition + phase, when the previous slide vanishes, and the + current slide becomes visible, the shape animation + phase, when shape effects are running, and the phase + after the last shape animation has ended, but before + the next slide transition starts. + + This method notifies the end of the second phase. + */ + void notifySlideAnimationsEnded(); + + /** Notify that the slide has ended. + + The life of a slide has three phases: the transition + phase, when the previous slide vanishes, and the + current slide becomes visible, the shape animation + phase, when shape effects are running, and the phase + after the last shape animation has ended, but before + the next slide transition starts. + + This method notifies the end of the third phase. + */ + void notifySlideEnded (const bool bReverse); + + /** Notification from eventmultiplexer that a hyperlink + has been clicked. + */ + bool notifyHyperLinkClicked( OUString const& hyperLink ); + + /** Notification from eventmultiplexer that an animation event has occurred. + This will be forwarded to all registered XSlideShowListener + */ + bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ); + + /** Obtain a MediaTempFile for the specified url. */ + virtual std::shared_ptr getMediaTempFile(const OUString& aUrl) override; + +private: + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + // XSlideShow: + virtual sal_Bool SAL_CALL nextEffect() override; + virtual sal_Bool SAL_CALL previousEffect() override; + virtual sal_Bool SAL_CALL startShapeActivity( + uno::Reference const& xShape ) override; + virtual sal_Bool SAL_CALL stopShapeActivity( + uno::Reference const& xShape ) override; + virtual sal_Bool SAL_CALL pause( sal_Bool bPauseShow ) override; + virtual uno::Reference SAL_CALL getCurrentSlide() override; + virtual void SAL_CALL displaySlide( + uno::Reference const& xSlide, + uno::Reference const& xDrawPages, + uno::Reference const& xRootNode, + uno::Sequence const& rProperties ) override; + virtual void SAL_CALL registerUserPaintPolygons( const css::uno::Reference< css::lang::XMultiServiceFactory >& xDocFactory ) override; + virtual sal_Bool SAL_CALL setProperty( + beans::PropertyValue const& rProperty ) override; + virtual sal_Bool SAL_CALL addView( + uno::Reference const& xView ) override; + virtual sal_Bool SAL_CALL removeView( + uno::Reference const& xView ) override; + virtual sal_Bool SAL_CALL update( double & nNextTimeout ) override; + virtual void SAL_CALL addSlideShowListener( + uno::Reference const& xListener ) override; + virtual void SAL_CALL removeSlideShowListener( + uno::Reference const& xListener ) override; + virtual void SAL_CALL addShapeEventListener( + uno::Reference const& xListener, + uno::Reference const& xShape ) override; + virtual void SAL_CALL removeShapeEventListener( + uno::Reference const& xListener, + uno::Reference const& xShape ) override; + virtual void SAL_CALL setShapeCursor( + uno::Reference const& xShape, sal_Int16 nPointerShape ) override; + + // CursorManager + + + virtual bool requestCursor( sal_Int16 nCursorShape ) override; + virtual void resetCursor() override; + + /** This is somewhat similar to displaySlide when called for the current + slide. It has been simplified to take advantage of that no slide + change takes place. Furthermore it does not show the slide + transition. + */ + void redisplayCurrentSlide(); + +protected: + // WeakComponentImplHelperBase + virtual void SAL_CALL disposing() override; + + bool isDisposed() const + { + return (rBHelper.bDisposed || rBHelper.bInDispose); + } + +private: + struct SeparateListenerImpl; friend struct SeparateListenerImpl; + class PrefetchPropertiesFunc; friend class PrefetchPropertiesFunc; + + /// Stop currently running show. + void stopShow(); + + ///Find a polygons vector in maPolygons (map) + PolygonMap::iterator findPolygons( uno::Reference const& xDrawPage); + + /// Creates a new slide. + SlideSharedPtr makeSlide( + uno::Reference const& xDrawPage, + uno::Reference const& xDrawPages, + uno::Reference const& xRootNode ); + + /// Checks whether the given slide/animation node matches mpPrefetchSlide + static bool matches( + SlideSharedPtr const& pSlide, + uno::Reference const& xSlide, + uno::Reference const& xNode ) + { + if (pSlide) + return (pSlide->getXDrawPage() == xSlide && + pSlide->getXAnimationNode() == xNode); + else + return (!xSlide.is() && !xNode.is()); + } + + /// Resets the current slide transition sound object with a new one: + SoundPlayerSharedPtr resetSlideTransitionSound( + uno::Any const& url, bool bLoopSound ); + + /// stops the current slide transition sound + void stopSlideTransitionSound(); + + /** Prepare a slide transition + + This method registers all necessary events and + activities for a slide transition. + + @return the slide change activity, or NULL for no transition effect + */ + ActivitySharedPtr createSlideTransition( + const uno::Reference< drawing::XDrawPage >& xDrawPage, + const SlideSharedPtr& rLeavingSlide, + const SlideSharedPtr& rEnteringSlide, + const EventSharedPtr& rTransitionEndEvent ); + + /** Request/release the wait symbol. The wait symbol is displayed when + there are more requests then releases. Locking the wait symbol + helps to avoid intermediate repaints. + + Do not call this method directly. Use WaitSymbolLock instead. + */ + void requestWaitSymbol(); + void releaseWaitSymbol(); + + class WaitSymbolLock {public: + explicit WaitSymbolLock(SlideShowImpl& rSlideShowImpl) : mrSlideShowImpl(rSlideShowImpl) + { mrSlideShowImpl.requestWaitSymbol(); } + ~WaitSymbolLock() + { mrSlideShowImpl.releaseWaitSymbol(); } + private: SlideShowImpl& mrSlideShowImpl; + }; + + /// Filter requested cursor shape against hard slideshow cursors (wait, etc.) + sal_Int16 calcActiveCursor( sal_Int16 nCursorShape ) const; + + /** This method is called asynchronously to finish the rewinding of an + effect to the previous slide that was initiated earlier. + */ + void rewindEffectToPreviousSlide(); + + /// all registered views + UnoViewContainer maViewContainer; + + /// all registered slide show listeners + comphelper::OInterfaceContainerHelper3 maListenerContainer; + + /// map of vectors, containing all registered listeners for a shape + ShapeEventListenerMap maShapeEventListeners; + /// map of sal_Int16 values, specifying the mouse cursor for every shape + ShapeCursorMap maShapeCursors; + + //map of vector of Polygons, containing polygons drawn on each slide. + PolygonMap maPolygons; + + std::optional maUserPaintColor; + + double maUserPaintStrokeWidth; + + //changed for the eraser project + std::optional maEraseAllInk; + std::optional maEraseInk; + //end changed + + std::shared_ptr mpPresTimer; + ScreenUpdater maScreenUpdater; + EventQueue maEventQueue; + EventMultiplexer maEventMultiplexer; + ActivitiesQueue maActivitiesQueue; + UserEventQueue maUserEventQueue; + SubsettableShapeManagerSharedPtr mpDummyPtr; + box2d::utils::Box2DWorldSharedPtr mpBox2DDummyPtr; + + std::shared_ptr mpListener; + + std::shared_ptr mpRehearseTimingsActivity; + std::shared_ptr mpWaitSymbol; + + std::shared_ptr mpPointerSymbol; + + /// the current slide transition sound object: + SoundPlayerSharedPtr mpCurrentSlideTransitionSound; + + uno::Reference mxComponentContext; + uno::Reference< + presentation::XTransitionFactory> mxOptionalTransitionFactory; + + /// the previously running slide + SlideSharedPtr mpPreviousSlide; + /// the currently running slide + SlideSharedPtr mpCurrentSlide; + /// the already prefetched slide: best candidate for upcoming slide + SlideSharedPtr mpPrefetchSlide; + /// slide to be prefetched: best candidate for upcoming slide + uno::Reference mxPrefetchSlide; + /// save the XDrawPagesSupplier to retrieve polygons + uno::Reference mxDrawPagesSupplier; + /// Used by MediaFileManager, for media files with package url. + uno::Reference mxSBD; + /// slide animation to be prefetched: + uno::Reference mxPrefetchAnimationNode; + + sal_Int16 mnCurrentCursor; + + sal_Int32 mnWaitSymbolRequestCount; + bool mbAutomaticAdvancementMode; + bool mbImageAnimationsAllowed; + bool mbNoSlideTransitions; + bool mbMouseVisible; + bool mbForceManualAdvance; + bool mbShowPaused; + bool mbSlideShowIdle; + bool mbDisableAnimationZOrder; + + EffectRewinder maEffectRewinder; + FrameSynchronization maFrameSynchronization; +}; + +/** Separate event listener for animation, view and hyperlink events. + + This handler is registered for slide animation end, view and + hyperlink events at the global EventMultiplexer, and forwards + notifications to the SlideShowImpl +*/ +struct SlideShowImpl::SeparateListenerImpl : public EventHandler, + public ViewRepaintHandler, + public HyperlinkHandler, + public AnimationEventHandler +{ + SlideShowImpl& mrShow; + ScreenUpdater& mrScreenUpdater; + EventQueue& mrEventQueue; + + SeparateListenerImpl( SlideShowImpl& rShow, + ScreenUpdater& rScreenUpdater, + EventQueue& rEventQueue ) : + mrShow( rShow ), + mrScreenUpdater( rScreenUpdater ), + mrEventQueue( rEventQueue ) + {} + + SeparateListenerImpl( const SeparateListenerImpl& ) = delete; + SeparateListenerImpl& operator=( const SeparateListenerImpl& ) = delete; + + // EventHandler + virtual bool handleEvent() override + { + // DON't call notifySlideAnimationsEnded() + // directly, but queue an event. handleEvent() + // might be called from e.g. + // showNext(), and notifySlideAnimationsEnded() must not be called + // in recursion. Note that the event is scheduled for the next + // frame so that its expensive execution does not come in between + // sprite hiding and shape redraw (at the end of the animation of a + // shape), which would cause a flicker. + mrEventQueue.addEventForNextRound( + makeEvent( [this] () { this->mrShow.notifySlideAnimationsEnded(); }, + "SlideShowImpl::notifySlideAnimationsEnded")); + return true; + } + + // ViewRepaintHandler + virtual void viewClobbered( const UnoViewSharedPtr& rView ) override + { + // given view needs repaint, request update + mrScreenUpdater.notifyUpdate(rView, true); + } + + // HyperlinkHandler + virtual bool handleHyperlink( OUString const& rLink ) override + { + return mrShow.notifyHyperLinkClicked(rLink); + } + + // AnimationEventHandler + virtual bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) override + { + return mrShow.handleAnimationEvent(rNode); + } +}; + +SlideShowImpl::SlideShowImpl( + uno::Reference const& xContext ) + : SlideShowImplBase(m_aMutex), + maViewContainer(), + maListenerContainer( m_aMutex ), + maShapeEventListeners(), + maShapeCursors(), + maUserPaintColor(), + maUserPaintStrokeWidth(4.0), + mpPresTimer( std::make_shared() ), + maScreenUpdater(maViewContainer), + maEventQueue( mpPresTimer ), + maEventMultiplexer( maEventQueue, + maViewContainer ), + maActivitiesQueue( mpPresTimer ), + maUserEventQueue( maEventMultiplexer, + maEventQueue, + *this ), + mpDummyPtr(), + mpBox2DDummyPtr(), + mpListener(), + mpRehearseTimingsActivity(), + mpWaitSymbol(), + mpPointerSymbol(), + mpCurrentSlideTransitionSound(), + mxComponentContext( xContext ), + mxOptionalTransitionFactory(), + mpCurrentSlide(), + mpPrefetchSlide(), + mxPrefetchSlide(), + mxDrawPagesSupplier(), + mxSBD(), + mxPrefetchAnimationNode(), + mnCurrentCursor(awt::SystemPointer::ARROW), + mnWaitSymbolRequestCount(0), + mbAutomaticAdvancementMode(false), + mbImageAnimationsAllowed( true ), + mbNoSlideTransitions( false ), + mbMouseVisible( true ), + mbForceManualAdvance( false ), + mbShowPaused( false ), + mbSlideShowIdle( true ), + mbDisableAnimationZOrder( false ), + maEffectRewinder(maEventMultiplexer, maEventQueue, maUserEventQueue), + maFrameSynchronization(1.0 / FrameRate::PreferredFramesPerSecond) + +{ + // keep care not constructing any UNO references to this inside ctor, + // shift that code to create()! + + uno::Reference xFactory( + mxComponentContext->getServiceManager() ); + + if( xFactory.is() ) + { + try + { + // #i82460# try to retrieve special transition factory + mxOptionalTransitionFactory.set( + xFactory->createInstanceWithContext( + "com.sun.star.presentation.TransitionFactory", + mxComponentContext ), + uno::UNO_QUERY ); + } + catch (loader::CannotActivateFactoryException const&) + { + } + } + + mpListener = std::make_shared( + *this, + maScreenUpdater, + maEventQueue ); + maEventMultiplexer.addSlideAnimationsEndHandler( mpListener ); + maEventMultiplexer.addViewRepaintHandler( mpListener ); + maEventMultiplexer.addHyperlinkHandler( mpListener, 0.0 ); + maEventMultiplexer.addAnimationStartHandler( mpListener ); + maEventMultiplexer.addAnimationEndHandler( mpListener ); +} + +// we are about to be disposed (someone call dispose() on us) +void SlideShowImpl::disposing() +{ + osl::MutexGuard const guard( m_aMutex ); + + maEffectRewinder.dispose(); + + // stop slide transition sound, if any: + stopSlideTransitionSound(); + + mxComponentContext.clear(); + + if( mpCurrentSlideTransitionSound ) + { + mpCurrentSlideTransitionSound->dispose(); + mpCurrentSlideTransitionSound.reset(); + } + + mpWaitSymbol.reset(); + mpPointerSymbol.reset(); + + if( mpRehearseTimingsActivity ) + { + mpRehearseTimingsActivity->dispose(); + mpRehearseTimingsActivity.reset(); + } + + if( mpListener ) + { + maEventMultiplexer.removeSlideAnimationsEndHandler(mpListener); + maEventMultiplexer.removeViewRepaintHandler(mpListener); + maEventMultiplexer.removeHyperlinkHandler(mpListener); + maEventMultiplexer.removeAnimationStartHandler( mpListener ); + maEventMultiplexer.removeAnimationEndHandler( mpListener ); + + mpListener.reset(); + } + + maUserEventQueue.clear(); + maActivitiesQueue.clear(); + maEventMultiplexer.clear(); + maEventQueue.clear(); + mpPresTimer.reset(); + maShapeCursors.clear(); + maShapeEventListeners.clear(); + + // send all listeners a disposing() that we are going down: + maListenerContainer.disposeAndClear( + lang::EventObject( static_cast(this) ) ); + + maViewContainer.dispose(); + + // release slides: + mxPrefetchAnimationNode.clear(); + mxPrefetchSlide.clear(); + mpPrefetchSlide.reset(); + mpCurrentSlide.reset(); + mpPreviousSlide.reset(); +} + +uno::Sequence< OUString > SAL_CALL SlideShowImpl::getSupportedServiceNames() +{ + return { "com.sun.star.presentation.SlideShow" }; +} + +OUString SAL_CALL SlideShowImpl::getImplementationName() +{ + return "com.sun.star.comp.presentation.SlideShow"; +} + +sal_Bool SAL_CALL SlideShowImpl::supportsService(const OUString& aServiceName) +{ + return cppu::supportsService(this, aServiceName); +} + +/// stops the current slide transition sound +void SlideShowImpl::stopSlideTransitionSound() +{ + if (mpCurrentSlideTransitionSound) + { + mpCurrentSlideTransitionSound->stopPlayback(); + mpCurrentSlideTransitionSound->dispose(); + mpCurrentSlideTransitionSound.reset(); + } + } + +SoundPlayerSharedPtr SlideShowImpl::resetSlideTransitionSound( const uno::Any& rSound, bool bLoopSound ) +{ + bool bStopSound = false; + OUString url; + + if( !(rSound >>= bStopSound) ) + bStopSound = false; + rSound >>= url; + + if( !bStopSound && url.isEmpty() ) + return SoundPlayerSharedPtr(); + + stopSlideTransitionSound(); + + if (!url.isEmpty()) + { + try + { + mpCurrentSlideTransitionSound = SoundPlayer::create( + maEventMultiplexer, url, mxComponentContext, *this); + mpCurrentSlideTransitionSound->setPlaybackLoop( bLoopSound ); + } + catch (lang::NoSupportException const&) + { + // catch possible exceptions from SoundPlayer, since + // being not able to playback the sound is not a hard + // error here (still, the slide transition should be + // shown). + } + } + return mpCurrentSlideTransitionSound; +} + +ActivitySharedPtr SlideShowImpl::createSlideTransition( + const uno::Reference< drawing::XDrawPage >& xDrawPage, + const SlideSharedPtr& rLeavingSlide, + const SlideSharedPtr& rEnteringSlide, + const EventSharedPtr& rTransitionEndEvent) +{ + ENSURE_OR_THROW( !maViewContainer.empty(), + "createSlideTransition(): No views" ); + ENSURE_OR_THROW( rEnteringSlide, + "createSlideTransition(): No entering slide" ); + + // return empty transition, if slide transitions + // are disabled. + if (mbNoSlideTransitions) + return ActivitySharedPtr(); + + // retrieve slide change parameters from XDrawPage + uno::Reference< beans::XPropertySet > xPropSet( xDrawPage, + uno::UNO_QUERY ); + + if( !xPropSet.is() ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Slide has no PropertySet - assuming no transition" ); + return ActivitySharedPtr(); + } + + sal_Int16 nTransitionType(0); + if( !getPropertyValue( nTransitionType, + xPropSet, + "TransitionType") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Could not extract slide transition type from XDrawPage - assuming no transition" ); + return ActivitySharedPtr(); + } + + sal_Int16 nTransitionSubType(0); + if( !getPropertyValue( nTransitionSubType, + xPropSet, + "TransitionSubtype") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Could not extract slide transition subtype from XDrawPage - assuming no transition" ); + return ActivitySharedPtr(); + } + + bool bTransitionDirection(false); + if( !getPropertyValue( bTransitionDirection, + xPropSet, + "TransitionDirection") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Could not extract slide transition direction from XDrawPage - assuming default direction" ); + } + + sal_Int32 aUnoColor(0); + if( !getPropertyValue( aUnoColor, + xPropSet, + "TransitionFadeColor") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Could not extract slide transition fade color from XDrawPage - assuming black" ); + } + + const RGBColor aTransitionFadeColor( unoColor2RGBColor( aUnoColor )); + + uno::Any aSound; + bool bLoopSound = false; + + if( !getPropertyValue( aSound, xPropSet, "Sound") ) + SAL_INFO("slideshow", "createSlideTransition(): Could not determine transition sound effect URL from XDrawPage - using no sound" ); + + if( !getPropertyValue( bLoopSound, xPropSet, "LoopSound" ) ) + SAL_INFO("slideshow", "createSlideTransition(): Could not get slide property 'LoopSound' - using no sound" ); + + NumberAnimationSharedPtr pTransition( + TransitionFactory::createSlideTransition( + rLeavingSlide, + rEnteringSlide, + maViewContainer, + maScreenUpdater, + maEventMultiplexer, + mxOptionalTransitionFactory, + nTransitionType, + nTransitionSubType, + bTransitionDirection, + aTransitionFadeColor, + resetSlideTransitionSound( aSound, bLoopSound ) )); + + if( !pTransition ) + return ActivitySharedPtr(); // no transition effect has been + // generated. Normally, that means + // that simply no transition is + // set on this slide. + + double nTransitionDuration(0.0); + if( !getPropertyValue( nTransitionDuration, + xPropSet, + "TransitionDuration") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "Could not extract slide transition duration from XDrawPage - assuming no transition" ); + return ActivitySharedPtr(); + } + + sal_Int32 nMinFrames(5); + if( !getPropertyValue( nMinFrames, + xPropSet, + "MinimalFrameNumber") ) + { + SAL_INFO("slideshow", "createSlideTransition(): " + "No minimal number of frames given - assuming 5" ); + } + + // prefetch slide transition bitmaps, but postpone it after + // displaySlide() has finished - sometimes, view size has not yet + // reached final size + maEventQueue.addEvent( + makeEvent( [pTransition] () { + pTransition->prefetch(); }, + "Animation::prefetch")); + + return ActivitySharedPtr( + ActivitiesFactory::createSimpleActivity( + ActivitiesFactory::CommonParameters( + rTransitionEndEvent, + maEventQueue, + maActivitiesQueue, + nTransitionDuration, + nMinFrames, + false, + std::optional(1.0), + 0.0, + 0.0, + ShapeSharedPtr(), + basegfx::B2DSize( rEnteringSlide->getSlideSize() ) ), + pTransition, + true )); +} + +PolygonMap::iterator SlideShowImpl::findPolygons( uno::Reference const& xDrawPage) +{ + // TODO(P2): optimize research in the map. + return maPolygons.find(xDrawPage); +} + +SlideSharedPtr SlideShowImpl::makeSlide( + uno::Reference const& xDrawPage, + uno::Reference const& xDrawPages, + uno::Reference const& xRootNode ) +{ + if( !xDrawPage.is() ) + return SlideSharedPtr(); + + //Retrieve polygons for the current slide + PolygonMap::iterator aIter = findPolygons(xDrawPage); + + const SlideSharedPtr pSlide( createSlide(xDrawPage, + xDrawPages, + xRootNode, + maEventQueue, + maEventMultiplexer, + maScreenUpdater, + maActivitiesQueue, + maUserEventQueue, + *this, + *this, + maViewContainer, + mxComponentContext, + maShapeEventListeners, + maShapeCursors, + (aIter != maPolygons.end()) ? aIter->second : PolyPolygonVector(), + maUserPaintColor ? *maUserPaintColor : RGBColor(), + maUserPaintStrokeWidth, + !!maUserPaintColor, + mbImageAnimationsAllowed, + mbDisableAnimationZOrder) ); + + // prefetch show content (reducing latency for slide + // bitmap and effect start later on) + pSlide->prefetch(); + + return pSlide; +} + +void SlideShowImpl::requestWaitSymbol() +{ + ++mnWaitSymbolRequestCount; + OSL_ASSERT(mnWaitSymbolRequestCount>0); + + if (mnWaitSymbolRequestCount == 1) + { + if( !mpWaitSymbol ) + { + // fall back to cursor + requestCursor(calcActiveCursor(mnCurrentCursor)); + } + else + mpWaitSymbol->show(); + } +} + +void SlideShowImpl::releaseWaitSymbol() +{ + --mnWaitSymbolRequestCount; + OSL_ASSERT(mnWaitSymbolRequestCount>=0); + + if (mnWaitSymbolRequestCount == 0) + { + if( !mpWaitSymbol ) + { + // fall back to cursor + requestCursor(calcActiveCursor(mnCurrentCursor)); + } + else + mpWaitSymbol->hide(); + } +} + +sal_Int16 SlideShowImpl::calcActiveCursor( sal_Int16 nCursorShape ) const +{ + if( mnWaitSymbolRequestCount>0 && !mpWaitSymbol ) // enforce wait cursor + nCursorShape = awt::SystemPointer::WAIT; + else if( !mbMouseVisible ) // enforce INVISIBLE + nCursorShape = awt::SystemPointer::INVISIBLE; + else if( maUserPaintColor && + nCursorShape == awt::SystemPointer::ARROW ) + nCursorShape = awt::SystemPointer::PEN; + + return nCursorShape; +} + +void SlideShowImpl::stopShow() +{ + // Force-end running animation + // =========================== + if (mpCurrentSlide) + { + mpCurrentSlide->hide(); + //Register polygons in the map + if(findPolygons(mpCurrentSlide->getXDrawPage()) != maPolygons.end()) + maPolygons.erase(mpCurrentSlide->getXDrawPage()); + + maPolygons.insert(make_pair(mpCurrentSlide->getXDrawPage(),mpCurrentSlide->getPolygons())); + } + + // clear all queues + maEventQueue.clear(); + maActivitiesQueue.clear(); + + // Attention: we MUST clear the user event queue here, + // this is because the current slide might have registered + // shape events (click or enter/leave), which might + // otherwise dangle forever in the queue (because of the + // shared ptr nature). If someone needs to change this: + // somehow unregister those shapes at the user event queue + // on notifySlideEnded(). + maUserEventQueue.clear(); + + // re-enable automatic effect advancement + // (maEventQueue.clear() above might have killed + // maEventMultiplexer's tick events) + if (mbAutomaticAdvancementMode) + { + // toggle automatic mode (enabling just again is + // ignored by EventMultiplexer) + maEventMultiplexer.setAutomaticMode( false ); + maEventMultiplexer.setAutomaticMode( true ); + } +} + +class SlideShowImpl::PrefetchPropertiesFunc +{ +public: + PrefetchPropertiesFunc( SlideShowImpl * that_, + bool& rbSkipAllMainSequenceEffects, + bool& rbSkipSlideTransition) + : mpSlideShowImpl(that_), + mrbSkipAllMainSequenceEffects(rbSkipAllMainSequenceEffects), + mrbSkipSlideTransition(rbSkipSlideTransition) + {} + + void operator()( beans::PropertyValue const& rProperty ) const { + if (rProperty.Name == "Prefetch" ) + { + uno::Sequence seq; + if ((rProperty.Value >>= seq) && seq.getLength() == 2) + { + seq[0] >>= mpSlideShowImpl->mxPrefetchSlide; + seq[1] >>= mpSlideShowImpl->mxPrefetchAnimationNode; + } + } + else if ( rProperty.Name == "SkipAllMainSequenceEffects" ) + { + rProperty.Value >>= mrbSkipAllMainSequenceEffects; + } + else if ( rProperty.Name == "SkipSlideTransition" ) + { + rProperty.Value >>= mrbSkipSlideTransition; + } + else + { + SAL_WARN( "slideshow", rProperty.Name ); + } + } +private: + SlideShowImpl *const mpSlideShowImpl; + bool& mrbSkipAllMainSequenceEffects; + bool& mrbSkipSlideTransition; +}; + +void SlideShowImpl::displaySlide( + uno::Reference const& xSlide, + uno::Reference const& xDrawPages, + uno::Reference const& xRootNode, + uno::Sequence const& rProperties ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return; + + maEffectRewinder.setRootAnimationNode(xRootNode); + maEffectRewinder.setCurrentSlide(xSlide); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + mxDrawPagesSupplier = xDrawPages; + mxSBD = uno::Reference(mxDrawPagesSupplier, uno::UNO_QUERY); + + stopShow(); // MUST call that: results in + // maUserEventQueue.clear(). What's more, + // stopShow()'s currSlide->hide() call is + // now also required, notifySlideEnded() + // relies on that + // unconditionally. Otherwise, genuine + // shape animations (drawing layer and + // GIF) will not be stopped. + + bool bSkipAllMainSequenceEffects (false); + bool bSkipSlideTransition (false); + std::for_each( rProperties.begin(), + rProperties.end(), + PrefetchPropertiesFunc(this, bSkipAllMainSequenceEffects, bSkipSlideTransition) ); + + OSL_ENSURE( !maViewContainer.empty(), "### no views!" ); + if (maViewContainer.empty()) + return; + + // this here might take some time + { + WaitSymbolLock aLock (*this); + + mpPreviousSlide = mpCurrentSlide; + mpCurrentSlide.reset(); + + if (matches( mpPrefetchSlide, xSlide, xRootNode )) + { + // prefetched slide matches: + mpCurrentSlide = mpPrefetchSlide; + } + else + mpCurrentSlide = makeSlide( xSlide, xDrawPages, xRootNode ); + + OSL_ASSERT( mpCurrentSlide ); + if (mpCurrentSlide) + { + basegfx::B2DSize oldSlideSize; + if( mpPreviousSlide ) + oldSlideSize = basegfx::B2DSize( mpPreviousSlide->getSlideSize() ); + + basegfx::B2DSize const slideSize( mpCurrentSlide->getSlideSize() ); + + // push new transformation to all views, if size changed + if( !mpPreviousSlide || oldSlideSize != slideSize ) + { + for( const auto& pView : maViewContainer ) + pView->setViewSize( slideSize ); + + // explicitly notify view change here, + // because transformation might have changed: + // optimization, this->notifyViewChange() would + // repaint slide which is not necessary. + maEventMultiplexer.notifyViewsChanged(); + } + + // create slide transition, and add proper end event + // (which then starts the slide effects + // via CURRENT_SLIDE.show()) + ActivitySharedPtr pSlideChangeActivity ( + createSlideTransition( + mpCurrentSlide->getXDrawPage(), + mpPreviousSlide, + mpCurrentSlide, + makeEvent( + [this] () { this->notifySlideTransitionEnded(false); }, + "SlideShowImpl::notifySlideTransitionEnded"))); + + if (bSkipSlideTransition) + { + // The transition activity was created for the side effects + // (like sound transitions). Because we want to skip the + // actual transition animation we do not need the activity + // anymore. + pSlideChangeActivity.reset(); + } + + if (pSlideChangeActivity) + { + // factory generated a slide transition - activate it! + maActivitiesQueue.addActivity( pSlideChangeActivity ); + } + else + { + // no transition effect on this slide - schedule slide + // effect start event right away. + maEventQueue.addEvent( + makeEvent( + [this] () { this->notifySlideTransitionEnded(true); }, + "SlideShowImpl::notifySlideTransitionEnded")); + } + } + } // finally + + maListenerContainer.forEach( + [](uno::Reference const& xListener) + { + xListener->slideTransitionStarted(); + }); + + // We are currently rewinding an effect. This lead us from the next + // slide to this one. To complete this we have to play back all main + // sequence effects on this slide. + if (bSkipAllMainSequenceEffects) + maEffectRewinder.skipAllMainSequenceEffects(); +} + +void SlideShowImpl::redisplayCurrentSlide() +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + stopShow(); + + OSL_ENSURE( !maViewContainer.empty(), "### no views!" ); + if (maViewContainer.empty()) + return; + + // No transition effect on this slide - schedule slide + // effect start event right away. + maEventQueue.addEvent( + makeEvent( [this] () { this->notifySlideTransitionEnded(true); }, + "SlideShowImpl::notifySlideTransitionEnded")); + + maListenerContainer.forEach( + [](uno::Reference const& xListener) + { + xListener->slideTransitionStarted(); + }); +} + +sal_Bool SlideShowImpl::nextEffect() +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + if (mbShowPaused) + return true; + else + return maEventMultiplexer.notifyNextEffect(); +} + +sal_Bool SlideShowImpl::previousEffect() +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + if (mbShowPaused) + return true; + else + { + return maEffectRewinder.rewind( + maScreenUpdater.createLock(), + [this]() { return this->redisplayCurrentSlide(); }, + [this]() { return this->rewindEffectToPreviousSlide(); } ); + } +} + +void SlideShowImpl::rewindEffectToPreviousSlide() +{ + // Show the wait symbol now and prevent it from showing temporary slide + // content while effects are played back. + WaitSymbolLock aLock (*this); + + // A previous call to EffectRewinder::Rewind could not rewind the current + // effect because there are no effects on the current slide or none has + // yet been displayed. Go to the previous slide. + notifySlideEnded(true); + + // Process pending events once more in order to have the following + // screen update show the last effect. Not sure whether this should be + // necessary. + maEventQueue.forceEmpty(); + + // We have to call the screen updater before the wait symbol is turned + // off. Otherwise the wait symbol would force the display of an + // intermediate state of the slide (before the effects are replayed.) + maScreenUpdater.commitUpdates(); +} + +sal_Bool SlideShowImpl::startShapeActivity( + uno::Reference const& /*xShape*/ ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + // TODO(F3): NYI + OSL_FAIL( "not yet implemented!" ); + return false; +} + +sal_Bool SlideShowImpl::stopShapeActivity( + uno::Reference const& /*xShape*/ ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + // TODO(F3): NYI + OSL_FAIL( "not yet implemented!" ); + return false; +} + +sal_Bool SlideShowImpl::pause( sal_Bool bPauseShow ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + if (bPauseShow) + mpPresTimer->pauseTimer(); + else + mpPresTimer->continueTimer(); + + maEventMultiplexer.notifyPauseMode(bPauseShow); + + mbShowPaused = bPauseShow; + return true; +} + +uno::Reference SlideShowImpl::getCurrentSlide() +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return uno::Reference(); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + if (mpCurrentSlide) + return mpCurrentSlide->getXDrawPage(); + else + return uno::Reference(); +} + +sal_Bool SlideShowImpl::addView( + uno::Reference const& xView ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + // first of all, check if view has a valid canvas + ENSURE_OR_RETURN_FALSE( xView.is(), "addView(): Invalid view" ); + ENSURE_OR_RETURN_FALSE( xView->getCanvas().is(), + "addView(): View does not provide a valid canvas" ); + + UnoViewSharedPtr const pView( createSlideView( + xView, + maEventQueue, + maEventMultiplexer )); + if (!maViewContainer.addView( pView )) + return false; // view already added + + // initialize view content + // ======================= + + if (mpCurrentSlide) + { + // set view transformation + const basegfx::B2ISize slideSize = mpCurrentSlide->getSlideSize(); + pView->setViewSize( basegfx::B2DSize( slideSize.getX(), + slideSize.getY() ) ); + } + + // clear view area (since it's newly added, + // we need a clean slate) + pView->clearAll(); + + // broadcast newly added view + maEventMultiplexer.notifyViewAdded( pView ); + + // set current mouse ptr + pView->setCursorShape( calcActiveCursor(mnCurrentCursor) ); + + return true; +} + +sal_Bool SlideShowImpl::removeView( + uno::Reference const& xView ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + ENSURE_OR_RETURN_FALSE( xView.is(), "removeView(): Invalid view" ); + + UnoViewSharedPtr const pView( maViewContainer.removeView( xView ) ); + if( !pView ) + return false; // view was not added in the first place + + // remove view from EventMultiplexer (mouse events etc.) + maEventMultiplexer.notifyViewRemoved( pView ); + + pView->_dispose(); + + return true; +} + +void SlideShowImpl::registerUserPaintPolygons( const uno::Reference< lang::XMultiServiceFactory >& xDocFactory ) +{ + //Retrieve Polygons if user ends presentation by context menu + if (mpCurrentSlide) + { + if(findPolygons(mpCurrentSlide->getXDrawPage()) != maPolygons.end()) + maPolygons.erase(mpCurrentSlide->getXDrawPage()); + + maPolygons.insert(make_pair(mpCurrentSlide->getXDrawPage(),mpCurrentSlide->getPolygons())); + } + + //Creating the layer for shapes drawn during slideshow + // query for the XLayerManager + uno::Reference< drawing::XLayerSupplier > xLayerSupplier(xDocFactory, uno::UNO_QUERY); + uno::Reference< container::XNameAccess > xNameAccess = xLayerSupplier->getLayerManager(); + uno::Reference< drawing::XLayerManager > xLayerManager(xNameAccess, uno::UNO_QUERY); + + // create layer + uno::Reference< drawing::XLayer > xDrawnInSlideshow; + uno::Any aPropLayer; + OUString sLayerName = "DrawnInSlideshow"; + if (xNameAccess->hasByName(sLayerName)) + { + xNameAccess->getByName(sLayerName) >>= xDrawnInSlideshow; + } + else + { + xDrawnInSlideshow = xLayerManager->insertNewByIndex(xLayerManager->getCount()); + aPropLayer <<= sLayerName; + xDrawnInSlideshow->setPropertyValue("Name", aPropLayer); + } + + // ODF defaults from ctor of SdrLayer are not automatically set on the here + // created XLayer. Need to be done explicitly here. + aPropLayer <<= true; + xDrawnInSlideshow->setPropertyValue("IsVisible", aPropLayer); + xDrawnInSlideshow->setPropertyValue("IsPrintable", aPropLayer); + aPropLayer <<= false; + xDrawnInSlideshow->setPropertyValue("IsLocked", aPropLayer); + + //Register polygons for each slide + for( const auto& rPoly : maPolygons ) + { + PolyPolygonVector aPolygons = rPoly.second; + //Get shapes for the slide + css::uno::Reference< css::drawing::XShapes > Shapes = rPoly.first; + //Retrieve polygons for one slide + for( const auto& pPolyPoly : aPolygons ) + { + ::basegfx::B2DPolyPolygon b2DPolyPoly = ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(pPolyPoly->getUNOPolyPolygon()); + + //Normally there is only one polygon + for(sal_uInt32 i=0; i< b2DPolyPoly.count();i++) + { + const ::basegfx::B2DPolygon& aPoly = b2DPolyPoly.getB2DPolygon(i); + sal_uInt32 nPoints = aPoly.count(); + + if( nPoints > 1) + { + //create the PolyLineShape + uno::Reference< uno::XInterface > polyshape(xDocFactory->createInstance( + "com.sun.star.drawing.PolyLineShape" ) ); + uno::Reference< drawing::XShape > rPolyShape(polyshape, uno::UNO_QUERY); + + //Add the shape to the slide + Shapes->add(rPolyShape); + + //Retrieve shape properties + uno::Reference< beans::XPropertySet > aXPropSet( rPolyShape, uno::UNO_QUERY ); + //Construct a sequence of points sequence + drawing::PointSequenceSequence aRetval; + //Create only one sequence for one polygon + aRetval.realloc( 1 ); + // Retrieve the sequence of points from aRetval + drawing::PointSequence* pOuterSequence = aRetval.getArray(); + // Create 2 points in this sequence + pOuterSequence->realloc(nPoints); + // Get these points which are in an array + awt::Point* pInnerSequence = pOuterSequence->getArray(); + for( sal_uInt32 n = 0; n < nPoints; n++ ) + { + //Create a point from the polygon + *pInnerSequence++ = awt::Point( + basegfx::fround(aPoly.getB2DPoint(n).getX()), + basegfx::fround(aPoly.getB2DPoint(n).getY())); + } + + //Fill the properties + //Give the built PointSequenceSequence. + uno::Any aParam; + aParam <<= aRetval; + aXPropSet->setPropertyValue("PolyPolygon", aParam ); + + //LineStyle : SOLID by default + drawing::LineStyle eLS; + eLS = drawing::LineStyle_SOLID; + aXPropSet->setPropertyValue("LineStyle", uno::Any(eLS) ); + + //LineColor + sal_uInt32 nLineColor; + nLineColor = pPolyPoly->getRGBALineColor(); + //Transform polygon color from RRGGBBAA to AARRGGBB + aXPropSet->setPropertyValue("LineColor", uno::Any(RGBAColor2UnoColor(nLineColor)) ); + + //LineWidth + double fLineWidth; + fLineWidth = pPolyPoly->getStrokeWidth(); + aXPropSet->setPropertyValue("LineWidth", uno::Any(static_cast(fLineWidth)) ); + + // make polygons special + xLayerManager->attachShapeToLayer(rPolyShape, xDrawnInSlideshow); + } + } + } + } +} + +sal_Bool SlideShowImpl::setProperty( beans::PropertyValue const& rProperty ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + if ( rProperty.Name == "AutomaticAdvancement" ) + { + double nTimeout(0.0); + mbAutomaticAdvancementMode = (rProperty.Value >>= nTimeout); + if (mbAutomaticAdvancementMode) + { + maEventMultiplexer.setAutomaticTimeout( nTimeout ); + } + maEventMultiplexer.setAutomaticMode( mbAutomaticAdvancementMode ); + return true; + } + + if ( rProperty.Name == "UserPaintColor" ) + { + sal_Int32 nColor(0); + if (rProperty.Value >>= nColor) + { + OSL_ENSURE( mbMouseVisible, + "setProperty(): User paint overrides invisible mouse" ); + + // enable user paint + maUserPaintColor = unoColor2RGBColor(nColor); + if( mpCurrentSlide && !mpCurrentSlide->isPaintOverlayActive() ) + mpCurrentSlide->enablePaintOverlay(); + + maEventMultiplexer.notifyUserPaintColor( *maUserPaintColor ); + } + else + { + // disable user paint + maUserPaintColor.reset(); + maEventMultiplexer.notifyUserPaintDisabled(); + } + + resetCursor(); + + return true; + } + + //adding support for erasing features in UserPaintOverlay + if ( rProperty.Name == "EraseAllInk" ) + { + bool bEraseAllInk(false); + if (rProperty.Value >>= bEraseAllInk) + { + OSL_ENSURE( mbMouseVisible, + "setProperty(): User paint overrides invisible mouse" ); + + // enable user paint + maEraseAllInk = bEraseAllInk; + maEventMultiplexer.notifyEraseAllInk( *maEraseAllInk ); + } + + return true; + } + + if ( rProperty.Name == "SwitchPenMode" ) + { + bool bSwitchPenMode(false); + if (rProperty.Value >>= bSwitchPenMode) + { + OSL_ENSURE( mbMouseVisible, + "setProperty(): User paint overrides invisible mouse" ); + + if(bSwitchPenMode){ + // Switch to Pen Mode + maEventMultiplexer.notifySwitchPenMode(); + } + } + return true; + } + + if ( rProperty.Name == "SwitchEraserMode" ) + { + bool bSwitchEraserMode(false); + if (rProperty.Value >>= bSwitchEraserMode) + { + OSL_ENSURE( mbMouseVisible, + "setProperty(): User paint overrides invisible mouse" ); + if(bSwitchEraserMode){ + // switch to Eraser mode + maEventMultiplexer.notifySwitchEraserMode(); + } + } + + return true; + } + + if ( rProperty.Name == "EraseInk" ) + { + sal_Int32 nEraseInk(100); + if (rProperty.Value >>= nEraseInk) + { + OSL_ENSURE( mbMouseVisible, + "setProperty(): User paint overrides invisible mouse" ); + + // enable user paint + maEraseInk = nEraseInk; + maEventMultiplexer.notifyEraseInkWidth( *maEraseInk ); + } + + return true; + } + + // new Property for pen's width + if ( rProperty.Name == "UserPaintStrokeWidth" ) + { + double nWidth(4.0); + if (rProperty.Value >>= nWidth) + { + OSL_ENSURE( mbMouseVisible,"setProperty(): User paint overrides invisible mouse" ); + // enable user paint stroke width + maUserPaintStrokeWidth = nWidth; + maEventMultiplexer.notifyUserPaintStrokeWidth( maUserPaintStrokeWidth ); + } + + return true; + } + + if ( rProperty.Name == "AdvanceOnClick" ) + { + bool bAdvanceOnClick = false; + if (! (rProperty.Value >>= bAdvanceOnClick)) + return false; + maUserEventQueue.setAdvanceOnClick( bAdvanceOnClick ); + return true; + } + + if ( rProperty.Name == "DisableAnimationZOrder" ) + { + bool bDisableAnimationZOrder = false; + if (! (rProperty.Value >>= bDisableAnimationZOrder)) + return false; + mbDisableAnimationZOrder = bDisableAnimationZOrder; + return true; + } + + if ( rProperty.Name == "ImageAnimationsAllowed" ) + { + if (! (rProperty.Value >>= mbImageAnimationsAllowed)) + return false; + + // TODO(F3): Forward to slides! + return true; + } + + if ( rProperty.Name == "MouseVisible" ) + { + if (! (rProperty.Value >>= mbMouseVisible)) + return false; + + requestCursor(mnCurrentCursor); + + return true; + } + + if ( rProperty.Name == "ForceManualAdvance" ) + { + return (rProperty.Value >>= mbForceManualAdvance); + } + + if ( rProperty.Name == "RehearseTimings" ) + { + bool bRehearseTimings = false; + if (! (rProperty.Value >>= bRehearseTimings)) + return false; + + if (bRehearseTimings) + { + // TODO(Q3): Move to slide + mpRehearseTimingsActivity = RehearseTimingsActivity::create( + SlideShowContext( + mpDummyPtr, + maEventQueue, + maEventMultiplexer, + maScreenUpdater, + maActivitiesQueue, + maUserEventQueue, + *this, + *this, + maViewContainer, + mxComponentContext, + mpBox2DDummyPtr ) ); + } + else if (mpRehearseTimingsActivity) + { + // removes timer from all views: + mpRehearseTimingsActivity->dispose(); + mpRehearseTimingsActivity.reset(); + } + return true; + } + + if ( rProperty.Name == "WaitSymbolBitmap" ) + { + uno::Reference xBitmap; + if (! (rProperty.Value >>= xBitmap)) + return false; + + mpWaitSymbol = WaitSymbol::create( xBitmap, + maScreenUpdater, + maEventMultiplexer, + maViewContainer ); + + return true; + } + + if ( rProperty.Name == "PointerSymbolBitmap" ) + { + uno::Reference xBitmap; + if (! (rProperty.Value >>= xBitmap)) + return false; + + mpPointerSymbol = PointerSymbol::create( xBitmap, + maScreenUpdater, + maEventMultiplexer, + maViewContainer ); + + return true; + } + + if ( rProperty.Name == "PointerVisible" ) + { + bool visible; + if (!(rProperty.Value >>= visible)) + return false; + + mpPointerSymbol->setVisible(visible); + return true; + } + + if ( rProperty.Name == "PointerPosition") + { + css::geometry::RealPoint2D pos; + if (! (rProperty.Value >>= pos)) + return false; + + mpPointerSymbol->viewsChanged(pos); + return true; + } + + if (rProperty.Name == "NoSlideTransitions" ) + { + return (rProperty.Value >>= mbNoSlideTransitions); + } + + if ( rProperty.Name == "IsSoundEnabled" ) + { + uno::Sequence aValues; + uno::Reference xView; + bool bValue (false); + if ((rProperty.Value >>= aValues) + && aValues.getLength()==2 + && (aValues[0] >>= xView) + && (aValues[1] >>= bValue)) + { + // Look up the view. + auto iView = std::find_if(maViewContainer.begin(), maViewContainer.end(), + [&xView](const UnoViewSharedPtr& rxView) { return rxView && rxView->getUnoView() == xView; }); + if (iView != maViewContainer.end()) + { + // Store the flag at the view so that media shapes have + // access to it. + (*iView)->setIsSoundEnabled(bValue); + return true; + } + } + } + + return false; +} + +void SlideShowImpl::addSlideShowListener( + uno::Reference const& xListener ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return; + + // container syncs with passed mutex ref + maListenerContainer.addInterface(xListener); +} + +void SlideShowImpl::removeSlideShowListener( + uno::Reference const& xListener ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // container syncs with passed mutex ref + maListenerContainer.removeInterface(xListener); +} + +void SlideShowImpl::addShapeEventListener( + uno::Reference const& xListener, + uno::Reference const& xShape ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + ShapeEventListenerMap::iterator aIter; + if( (aIter=maShapeEventListeners.find( xShape )) == + maShapeEventListeners.end() ) + { + // no entry for this shape -> create one + aIter = maShapeEventListeners.emplace( + xShape, + std::make_shared>( + m_aMutex)).first; + } + + // add new listener to broadcaster + if( aIter->second ) + aIter->second->addInterface( xListener ); + + maEventMultiplexer.notifyShapeListenerAdded(xShape); +} + +void SlideShowImpl::removeShapeEventListener( + uno::Reference const& xListener, + uno::Reference const& xShape ) +{ + osl::MutexGuard const guard( m_aMutex ); + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + ShapeEventListenerMap::iterator aIter; + if( (aIter = maShapeEventListeners.find( xShape )) != + maShapeEventListeners.end() ) + { + // entry for this shape found -> remove listener from + // helper object + ENSURE_OR_THROW( + aIter->second, + "SlideShowImpl::removeShapeEventListener(): " + "listener map contains NULL broadcast helper" ); + + aIter->second->removeInterface( xListener ); + } + + maEventMultiplexer.notifyShapeListenerRemoved(xShape); +} + +void SlideShowImpl::setShapeCursor( + uno::Reference const& xShape, sal_Int16 nPointerShape ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return; + + // precondition: must only be called from the main thread! + DBG_TESTSOLARMUTEX(); + + ShapeCursorMap::iterator aIter; + if( (aIter=maShapeCursors.find( xShape )) == maShapeCursors.end() ) + { + // no entry for this shape -> create one + if( nPointerShape != awt::SystemPointer::ARROW ) + { + // add new entry, unless shape shall display + // normal pointer arrow -> no need to handle that + // case + maShapeCursors.emplace(xShape, nPointerShape); + } + } + else if( nPointerShape == awt::SystemPointer::ARROW ) + { + // shape shall display normal cursor -> can disable + // the cursor and clear the entry + maShapeCursors.erase( xShape ); + } + else + { + // existing entry found, update with new cursor ID + aIter->second = nPointerShape; + } +} + +bool SlideShowImpl::requestCursor( sal_Int16 nCursorShape ) +{ + mnCurrentCursor = nCursorShape; + + const sal_Int16 nActualCursor = calcActiveCursor(mnCurrentCursor); + + // change all views to the requested cursor ID + for( const auto& pView : maViewContainer ) + pView->setCursorShape( nActualCursor ); + + return nActualCursor==nCursorShape; +} + +void SlideShowImpl::resetCursor() +{ + mnCurrentCursor = awt::SystemPointer::ARROW; + + const sal_Int16 nActualCursor = calcActiveCursor( mnCurrentCursor ); + // change all views to the default cursor ID + for( const auto& pView : maViewContainer ) + pView->setCursorShape( nActualCursor ); +} + +sal_Bool SlideShowImpl::update( double & nNextTimeout ) +{ + osl::MutexGuard const guard( m_aMutex ); + + if (isDisposed()) + return false; + + // precondition: update() must only be called from the + // main thread! + DBG_TESTSOLARMUTEX(); + + if( mbShowPaused ) + { + // commit frame (might be repaints pending) + maScreenUpdater.commitUpdates(); + + return false; + } + else + { + // TODO(F2): re-evaluate whether that timer lagging makes + // sense. + + // hold timer, while processing the queues: + // 1. when there is more than one active activity this ensures the + // same time for all activities and events + // 2. processing of events may lead to creation of further events + // that have zero delay. While the timer is stopped these events + // are processed in the same run. + { + //Get a shared-ptr that outlives the scope-guard which will + //ensure that the pointed-to-item exists in the case of a + //::dispose clearing mpPresTimer + std::shared_ptr xTimer(mpPresTimer); + comphelper::ScopeGuard scopeGuard( + [&xTimer]() { return xTimer->releaseTimer(); } ); + xTimer->holdTimer(); + + // process queues + maEventQueue.process(); + + // #i118671# the call above may execute a macro bound to an object. In + // that case this macro may have destroyed this local slideshow so that it + // is disposed (see bugdoc at task). In that case, detect this and exit + // gently from this slideshow. Do not forget to disable the scoped + // call to mpPresTimer, this will be deleted if we are disposed. + if (isDisposed()) + { + scopeGuard.dismiss(); + return false; + } + + maActivitiesQueue.process(); + + // commit frame to screen + maFrameSynchronization.Synchronize(); + maScreenUpdater.commitUpdates(); + + // TODO(Q3): remove need to call dequeued() from + // activities. feels like a wart. + + // Rationale for ActivitiesQueue::processDequeued(): when + // an activity ends, it usually pushed the end state to + // the animated shape in question, and ends the animation + // (which, in turn, will usually disable shape sprite + // mode). Disabling shape sprite mode causes shape + // repaint, which, depending on slide content, takes + // considerably more time than sprite updates. Thus, the + // last animation step tends to look delayed. To + // camouflage this, reaching end position and disabling + // sprite mode is split into two (normal Activity::end(), + // and Activity::dequeued()). Now, the reason to call + // commitUpdates() twice here is caused by the unrelated + // fact that during wait cursor display/hide, the screen + // is updated, and shows hidden sprites, but, in case of + // leaving the second commitUpdates() call out and punting + // that to the next round, no updated static slide + // content. In short, the last shape animation of a slide + // tends to blink at its end. + + // process dequeued activities _after_ commit to screen + maActivitiesQueue.processDequeued(); + + // commit frame to screen + maScreenUpdater.commitUpdates(); + } + // Time held until here + + const bool bActivitiesLeft = ! maActivitiesQueue.isEmpty(); + const bool bTimerEventsLeft = ! maEventQueue.isEmpty(); + const bool bRet = (bActivitiesLeft || bTimerEventsLeft); + + if (bRet) + { + // calc nNextTimeout value: + if (bActivitiesLeft) + { + // Activity queue is not empty. Tell caller that we would + // like to render another frame. + + // Return a zero time-out to signal our caller to call us + // back as soon as possible. The actual timing, waiting the + // appropriate amount of time between frames, is then done + // by the maFrameSynchronization object. + nNextTimeout = 0; + maFrameSynchronization.Activate(); + } + else + { + // timer events left: + // difference from current time (nota bene: + // time no longer held here!) to the next event in + // the event queue. + + // #i61190# Retrieve next timeout only _after_ + // processing activity queue + + // ensure positive value: + nNextTimeout = std::max( 0.0, maEventQueue.nextTimeout() ); + + // There is no active animation so the frame rate does not + // need to be synchronized. + maFrameSynchronization.Deactivate(); + } + + mbSlideShowIdle = false; + } + +#if defined(DBG_UTIL) + // when slideshow is idle, issue an XUpdatable::update() call + // exactly once after a previous animation sequence finished - + // this might trigger screen dumps on some canvas + // implementations + if( !mbSlideShowIdle && + (!bRet || + nNextTimeout > 1.0) ) + { + for( const auto& pView : maViewContainer ) + { + try + { + uno::Reference< presentation::XSlideShowView > xView( pView->getUnoView(), + uno::UNO_SET_THROW ); + uno::Reference const xUpdatable( + xView->getCanvas(), uno::UNO_QUERY); + if (xUpdatable.is()) // not supported in PresenterCanvas + { + xUpdatable->update(); + } + } + catch( uno::RuntimeException& ) + { + throw; + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "slideshow", "" ); + } + } + + mbSlideShowIdle = true; + } +#endif + + return bRet; + } +} + +void SlideShowImpl::notifySlideTransitionEnded( bool bPaintSlide ) +{ + osl::MutexGuard const guard( m_aMutex ); + + OSL_ENSURE( !isDisposed(), "### already disposed!" ); + OSL_ENSURE( mpCurrentSlide, + "notifySlideTransitionEnded(): Invalid current slide" ); + if (mpCurrentSlide) + { + mpCurrentSlide->update_settings( !!maUserPaintColor, maUserPaintColor ? *maUserPaintColor : RGBColor(), maUserPaintStrokeWidth ); + + // first init show, to give the animations + // the chance to register SlideStartEvents + const bool bBackgroundLayerRendered( !bPaintSlide ); + mpCurrentSlide->show( bBackgroundLayerRendered ); + maEventMultiplexer.notifySlideStartEvent(); + } +} + +void queryAutomaticSlideTransition( uno::Reference const& xDrawPage, + double& nAutomaticNextSlideTimeout, + bool& bHasAutomaticNextSlide ) +{ + // retrieve slide change parameters from XDrawPage + // =============================================== + + uno::Reference< beans::XPropertySet > xPropSet( xDrawPage, + uno::UNO_QUERY ); + + sal_Int32 nChange(0); + if( !xPropSet.is() || + !getPropertyValue( nChange, + xPropSet, + "Change") ) + { + SAL_INFO("slideshow", + "queryAutomaticSlideTransition(): " + "Could not extract slide change mode from XDrawPage - assuming " ); + } + + bHasAutomaticNextSlide = nChange == 1; + + if( !xPropSet.is() || + !getPropertyValue( nAutomaticNextSlideTimeout, + xPropSet, + "HighResDuration") ) + { + SAL_INFO("slideshow", + "queryAutomaticSlideTransition(): " + "Could not extract slide transition timeout from " + "XDrawPage - assuming 1 sec" ); + } +} + +void SlideShowImpl::notifySlideAnimationsEnded() +{ + osl::MutexGuard const guard( m_aMutex ); + + //Draw polygons above animations + mpCurrentSlide->drawPolygons(); + + OSL_ENSURE( !isDisposed(), "### already disposed!" ); + + // This struct will receive the (interruptable) event, + // that triggers the notifySlideEnded() method. + InterruptableEventPair aNotificationEvents; + + if( maEventMultiplexer.getAutomaticMode() ) + { + OSL_ENSURE( ! mpRehearseTimingsActivity, + "unexpected: RehearseTimings mode!" ); + + // schedule a slide end event, with automatic mode's + // delay + aNotificationEvents = makeInterruptableDelay( + [this]() { return this->notifySlideEnded( false ); }, + maEventMultiplexer.getAutomaticTimeout() ); + } + else + { + OSL_ENSURE( mpCurrentSlide, + "notifySlideAnimationsEnded(): Invalid current slide!" ); + + bool bHasAutomaticNextSlide=false; + double nAutomaticNextSlideTimeout=0.0; + queryAutomaticSlideTransition(mpCurrentSlide->getXDrawPage(), + nAutomaticNextSlideTimeout, + bHasAutomaticNextSlide); + + // check whether slide transition should happen + // 'automatically'. If yes, simply schedule the + // specified timeout. + // NOTE: mbForceManualAdvance and mpRehearseTimingsActivity + // override any individual slide setting, to always + // step slides manually. + if( !mbForceManualAdvance && + !mpRehearseTimingsActivity && + bHasAutomaticNextSlide ) + { + aNotificationEvents = makeInterruptableDelay( + [this]() { return this->notifySlideEnded( false ); }, + nAutomaticNextSlideTimeout); + + // TODO(F2): Provide a mechanism to let the user override + // this automatic timeout via next() + } + else + { + if (mpRehearseTimingsActivity) + mpRehearseTimingsActivity->start(); + + // generate click event. Thus, the user must + // trigger the actual end of a slide. No need to + // generate interruptable event here, there's no + // timeout involved. + aNotificationEvents.mpImmediateEvent = + makeEvent( [this] () { this->notifySlideEnded(false); }, + "SlideShowImpl::notifySlideEnded"); + } + } + + // register events on the queues. To make automatic slide + // changes interruptable, register the interruption event + // as a nextEffectEvent target. Note that the timeout + // event is optional (e.g. manual slide changes don't + // generate a timeout) + maUserEventQueue.registerNextEffectEvent( + aNotificationEvents.mpImmediateEvent ); + + if( aNotificationEvents.mpTimeoutEvent ) + maEventQueue.addEvent( aNotificationEvents.mpTimeoutEvent ); + + // current slide's main sequence is over. Now should be + // the time to prefetch the next slide (if any), and + // prepare the initial slide bitmap (speeds up slide + // change setup time a lot). Show the wait cursor, this + // indeed might take some seconds. + { + WaitSymbolLock aLock (*this); + + if (! matches( mpPrefetchSlide, + mxPrefetchSlide, mxPrefetchAnimationNode )) + { + mpPrefetchSlide = makeSlide( mxPrefetchSlide, mxDrawPagesSupplier, + mxPrefetchAnimationNode ); + } + if (mpPrefetchSlide) + { + // ignore return value, this is just to populate + // Slide's internal bitmap buffer, such that the time + // needed to generate the slide bitmap is not spent + // when the slide change is requested. + mpPrefetchSlide->getCurrentSlideBitmap( *maViewContainer.begin() ); + } + } // finally + + maListenerContainer.forEach( + [](uno::Reference const& xListener) + { + xListener->slideAnimationsEnded(); + }); +} + +void SlideShowImpl::notifySlideEnded (const bool bReverse) +{ + osl::MutexGuard const guard( m_aMutex ); + + OSL_ENSURE( !isDisposed(), "### already disposed!" ); + + if (mpRehearseTimingsActivity && !bReverse) + { + const double time = mpRehearseTimingsActivity->stop(); + if (mpRehearseTimingsActivity->hasBeenClicked()) + { + // save time at current drawpage: + uno::Reference xPropSet( + mpCurrentSlide->getXDrawPage(), uno::UNO_QUERY ); + OSL_ASSERT( xPropSet.is() ); + if (xPropSet.is()) + { + xPropSet->setPropertyValue( + "Change", + uno::Any( static_cast(1) ) ); + xPropSet->setPropertyValue( + "Duration", + uno::Any( static_cast(time) ) ); + } + } + } + + if (bReverse) + maEventMultiplexer.notifySlideEndEvent(); + + stopShow(); // MUST call that: results in + // maUserEventQueue.clear(). What's more, + // stopShow()'s currSlide->hide() call is + // now also required, notifySlideEnded() + // relies on that + // unconditionally. Otherwise, genuine + // shape animations (drawing layer and + // GIF) will not be stopped. + + maListenerContainer.forEach( + [&bReverse]( const uno::Reference< presentation::XSlideShowListener >& xListener ) + { return xListener->slideEnded( bReverse ); } ); +} + +bool SlideShowImpl::notifyHyperLinkClicked( OUString const& hyperLink ) +{ + osl::MutexGuard const guard( m_aMutex ); + + maListenerContainer.forEach( + [&hyperLink]( const uno::Reference< presentation::XSlideShowListener >& xListener ) + { return xListener->hyperLinkClicked( hyperLink ); } ); + return true; +} + +/** Notification from eventmultiplexer that an animation event has occurred. + This will be forwarded to all registered XSlideShoeListener + */ +bool SlideShowImpl::handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) +{ + osl::MutexGuard const guard( m_aMutex ); + + uno::Reference xNode( rNode->getXAnimationNode() ); + + switch( rNode->getState() ) + { + case AnimationNode::ACTIVE: + maListenerContainer.forEach( + [&xNode]( const uno::Reference< animations::XAnimationListener >& xListener ) + { return xListener->beginEvent( xNode ); } ); + break; + + case AnimationNode::FROZEN: + case AnimationNode::ENDED: + maListenerContainer.forEach( + [&xNode]( const uno::Reference< animations::XAnimationListener >& xListener ) + { return xListener->endEvent( xNode ); } ); + if(mpCurrentSlide->isPaintOverlayActive()) + mpCurrentSlide->drawPolygons(); + break; + default: + break; + } + + return true; +} + +std::shared_ptr SlideShowImpl::getMediaTempFile(const OUString& aUrl) +{ + std::shared_ptr aRet; + +#if !HAVE_FEATURE_AVMEDIA + (void)aUrl; +#else + if (!mxSBD.is()) + return aRet; + + comphelper::LifecycleProxy aProxy; + uno::Reference xStream = + comphelper::OStorageHelper::GetStreamAtPackageURL(mxSBD->getDocumentStorage(), aUrl, + css::embed::ElementModes::READ, aProxy); + + uno::Reference xInStream = xStream->getInputStream(); + if (xInStream.is()) + { + sal_Int32 nLastDot = aUrl.lastIndexOf('.'); + sal_Int32 nLastSlash = aUrl.lastIndexOf('/'); + OUString sDesiredExtension; + if (nLastDot > nLastSlash && nLastDot+1 < aUrl.getLength()) + sDesiredExtension = aUrl.copy(nLastDot); + + OUString sTempUrl; + if (::avmedia::CreateMediaTempFile(xInStream, sTempUrl, sDesiredExtension)) + aRet = std::make_shared(sTempUrl); + + xInStream->closeInput(); + } +#endif + + return aRet; +} + +//===== FrameSynchronization ================================================== + +FrameSynchronization::FrameSynchronization (const double nFrameDuration) + : maTimer(), + mnFrameDuration(nFrameDuration), + mnNextFrameTargetTime(0), + mbIsActive(false) +{ + MarkCurrentFrame(); +} + +void FrameSynchronization::MarkCurrentFrame() +{ + mnNextFrameTargetTime = maTimer.getElapsedTime() + mnFrameDuration; +} + +void FrameSynchronization::Synchronize() +{ + if (mbIsActive) + { + // Do busy waiting for now. + for(;;) + { + double remainingTime = mnNextFrameTargetTime - maTimer.getElapsedTime(); + if(remainingTime <= 0) + break; + // Try to sleep most of it. + int remainingMilliseconds = remainingTime * 1000; + if(remainingMilliseconds > 2) + osl::Thread::wait(std::chrono::milliseconds(remainingMilliseconds - 2)); + } + } + + MarkCurrentFrame(); +} + +void FrameSynchronization::Activate() +{ + mbIsActive = true; +} + +void FrameSynchronization::Deactivate() +{ + mbIsActive = false; +} + +} // anon namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +slideshow_SlideShowImpl_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence const&) +{ + return cppu::acquire(new SlideShowImpl(context)); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3