/* -*- 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 <sal/config.h>

#include <algorithm>

#include <config_features.h>

#include <com/sun/star/frame/theAutoRecovery.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/document/XEventsSupplier.hpp>
#include <com/sun/star/drawing/XMasterPageTarget.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/awt/SystemPointer.hpp>
#include <com/sun/star/util/URLTransformer.hpp>
#include <com/sun/star/util/XURLTransformer.hpp>
#include <com/sun/star/frame/XDispatch.hpp>
#include <com/sun/star/frame/XLayoutManager.hpp>
#include <com/sun/star/presentation/SlideShow.hpp>
#include <com/sun/star/media/XPlayer.hpp>
#include <officecfg/Office/Impress.hxx>
#include <officecfg/Office/Recovery.hxx>
#include <svl/stritem.hxx>
#include <svl/urihelper.hxx>
#include <basic/sbstar.hxx>

#include <toolkit/helper/vclunohelper.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/sequence.hxx>

#include <sfx2/infobar.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/app.hxx>
#include <sfx2/viewfrm.hxx>
#include <svx/svdoole2.hxx>
#include <svx/f3dchild.hxx>
#include <svx/imapdlg.hxx>
#include <svx/fontwork.hxx>
#include <svx/SvxColorChildWindow.hxx>
#include <svx/bmpmask.hxx>
#include <svx/srchdlg.hxx>
#include <svx/hyperdlg.hxx>
#include <svx/svxids.hrc>
#include <svx/unoapi.hxx>
#include <AnimationChildWindow.hxx>
#include <notifydocumentevent.hxx>
#include "slideshowimpl.hxx"
#include "slideshowviewimpl.hxx"
#include "PaneHider.hxx"

#include <bitmaps.hlst>
#include <strings.hrc>
#include <sdresid.hxx>
#include <utility>
#include <vcl/ColorDialog.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/weldutils.hxx>

#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include <vcl/help.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/propertyvalue.hxx>
#include <rtl/ref.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <avmedia/mediawindow.hxx>
#include <DrawDocShell.hxx>
#include <ViewShellBase.hxx>
#include <PresentationViewShell.hxx>
#include <RemoteServer.hxx>
#include <customshowlist.hxx>
#include <unopage.hxx>
#include <sdpage.hxx>
#include <sdmod.hxx>
#include <app.hrc>
#include <cusshow.hxx>
#include <optsitem.hxx>
#include <unomodel.hxx>

#define CM_SLIDES       21

using ::com::sun::star::animations::XAnimationNode;
using ::com::sun::star::animations::XAnimationListener;
using ::com::sun::star::awt::XWindow;
using namespace ::com::sun::star;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::drawing;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::presentation;
using namespace ::com::sun::star::beans;

namespace sd
{
/** Slots, which will be disabled in the slide show and are managed by Sfx.
    Have to be sorted in the order of the SIDs */
sal_uInt16 const pAllowed[] =
{
    SID_OPENDOC                             , //     5501   ///< that internally jumps work
    SID_JUMPTOMARK                          , //     5598
    SID_OPENHYPERLINK                       , //     6676
    SID_PRESENTATION_END                     //    27218
};

class AnimationSlideController
{
public:
    enum Mode { ALL, FROM, CUSTOM, PREVIEW };

public:
    AnimationSlideController( Reference< XIndexAccess > const & xSlides, Mode eMode );

    void setStartSlideNumber( sal_Int32 nSlideNumber ) { mnStartSlideNumber = nSlideNumber; }
    sal_Int32 getStartSlideIndex() const;

    sal_Int32 getCurrentSlideNumber() const;
    sal_Int32 getCurrentSlideIndex() const;

    sal_Int32 getSlideIndexCount() const { return maSlideNumbers.size(); }
    sal_Int32 getSlideNumberCount() const { return mnSlideCount; }

    sal_Int32 getSlideNumber( sal_Int32 nSlideIndex ) const;

    void insertSlideNumber( sal_Int32 nSlideNumber, bool bVisible = true );
    void setPreviewNode( const Reference< XAnimationNode >& xPreviewNode );

    bool jumpToSlideIndex( sal_Int32 nNewSlideIndex );
    bool jumpToSlideNumber( sal_Int32 nNewSlideIndex );

    bool nextSlide();
    bool previousSlide();

    void displayCurrentSlide( const Reference< XSlideShow >& xShow,
                              const Reference< XDrawPagesSupplier>& xDrawPages,
                              const bool bSkipAllMainSequenceEffects );

    sal_Int32 getNextSlideIndex() const;
    sal_Int32 getPreviousSlideIndex() const;

    bool isVisibleSlideNumber( sal_Int32 nSlideNumber ) const;

    Reference< XDrawPage > getSlideByNumber( sal_Int32 nSlideNumber ) const;

    sal_Int32 getNextSlideNumber() const;

    bool hasSlides() const { return !maSlideNumbers.empty(); }

    // for InteractiveSlideShow we need to temporarily change the program
    // and mode, so allow save/restore that settings
    void pushForPreview();
    void popFromPreview();
private:
    bool getSlideAPI( sal_Int32 nSlideNumber, Reference< XDrawPage >& xSlide, Reference< XAnimationNode >& xAnimNode );
    sal_Int32 findSlideIndex( sal_Int32 nSlideNumber ) const;

    bool isValidIndex( sal_Int32 nIndex ) const { return (nIndex >= 0) && (o3tl::make_unsigned(nIndex) < maSlideNumbers.size()); }
    bool isValidSlideNumber( sal_Int32 nSlideNumber ) const { return (nSlideNumber >= 0) && (nSlideNumber < mnSlideCount); }

private:
    Mode meMode;
    sal_Int32 mnStartSlideNumber;
    std::vector< sal_Int32 > maSlideNumbers;
    std::vector< bool > maSlideVisible;
    std::vector< bool > maSlideVisited;
    Reference< XAnimationNode > mxPreviewNode;
    sal_Int32 mnSlideCount;
    sal_Int32 mnCurrentSlideIndex;
    sal_Int32 mnHiddenSlideNumber;
    Reference< XIndexAccess > mxSlides;

    // IASS data for push/pop
    std::vector< sal_Int32 > maSlideNumbers2;
    std::vector< bool > maSlideVisible2;
    std::vector< bool > maSlideVisited2;
    Reference< XAnimationNode > mxPreviewNode2;
    Mode meMode2;
};

void AnimationSlideController::pushForPreview()
{
    maSlideNumbers2 = maSlideNumbers;
    maSlideVisible2 = maSlideVisible;
    maSlideVisited2 = maSlideVisited;
    maSlideNumbers.clear();
    maSlideVisible.clear();
    maSlideVisited.clear();
    mxPreviewNode2 = mxPreviewNode;
    meMode2 = meMode;
    meMode = AnimationSlideController::PREVIEW;
}

void AnimationSlideController::popFromPreview()
{
    maSlideNumbers = maSlideNumbers2;
    maSlideVisible = maSlideVisible2;
    maSlideVisited = maSlideVisited2;
    maSlideNumbers2.clear();
    maSlideVisible2.clear();
    maSlideVisited2.clear();
    mxPreviewNode = mxPreviewNode2;
    meMode = meMode2;
}

Reference< XDrawPage > AnimationSlideController::getSlideByNumber( sal_Int32 nSlideNumber ) const
{
    Reference< XDrawPage > xSlide;
    if( mxSlides.is() && (nSlideNumber >= 0) && (nSlideNumber < mxSlides->getCount()) )
        mxSlides->getByIndex( nSlideNumber ) >>= xSlide;
    return xSlide;
}

bool AnimationSlideController::isVisibleSlideNumber( sal_Int32 nSlideNumber ) const
{
    sal_Int32 nIndex = findSlideIndex( nSlideNumber );

    if( nIndex != -1 )
        return maSlideVisible[ nIndex ];
    else
        return false;
}

void AnimationSlideController::setPreviewNode( const Reference< XAnimationNode >& xPreviewNode )
{
    mxPreviewNode = xPreviewNode;
}

AnimationSlideController::AnimationSlideController( Reference< XIndexAccess > const & xSlides, Mode eMode  )
:   meMode( eMode )
,   mnStartSlideNumber(-1)
,   mnSlideCount( 0 )
,   mnCurrentSlideIndex(0)
,   mnHiddenSlideNumber( -1 )
,   mxSlides( xSlides )
,   meMode2( eMode )
{
    if( mxSlides.is() )
        mnSlideCount = xSlides->getCount();
}

sal_Int32 AnimationSlideController::getStartSlideIndex() const
{
    if( mnStartSlideNumber >= 0 )
    {
        sal_Int32 nIndex;
        const sal_Int32 nCount = maSlideNumbers.size();

        for( nIndex = 0; nIndex < nCount; nIndex++ )
        {
            if( maSlideNumbers[nIndex] == mnStartSlideNumber )
                return nIndex;
        }
    }

    return 0;
}

sal_Int32 AnimationSlideController::getCurrentSlideNumber() const
{
    if( mnHiddenSlideNumber != -1 )
        return mnHiddenSlideNumber;
    else if( !maSlideNumbers.empty() )
        return maSlideNumbers[mnCurrentSlideIndex];
    else
        return 0;
}

sal_Int32 AnimationSlideController::getCurrentSlideIndex() const
{
    if( mnHiddenSlideNumber != -1 )
        return -1;
    else
        return mnCurrentSlideIndex;
}

bool AnimationSlideController::jumpToSlideIndex( sal_Int32 nNewSlideIndex )
{
    if( isValidIndex( nNewSlideIndex ) )
    {
        mnCurrentSlideIndex = nNewSlideIndex;
        mnHiddenSlideNumber = -1;
        maSlideVisited[mnCurrentSlideIndex] = true;
        return true;
    }
    else
    {
        return false;
    }
}

bool AnimationSlideController::jumpToSlideNumber( sal_Int32 nNewSlideNumber )
{
    sal_Int32 nIndex = findSlideIndex( nNewSlideNumber );
    if( isValidIndex( nIndex ) )
    {
        return jumpToSlideIndex( nIndex );
    }
    else if( (nNewSlideNumber >= 0) && (nNewSlideNumber < mnSlideCount) )
    {
        // jump to a hidden slide
        mnHiddenSlideNumber = nNewSlideNumber;
        return true;
    }
    else
    {
        return false;
    }
}

sal_Int32 AnimationSlideController::getSlideNumber( sal_Int32 nSlideIndex ) const
{
    if( isValidIndex( nSlideIndex ) )
        return maSlideNumbers[nSlideIndex];
    else
        return -1;
}

void AnimationSlideController::insertSlideNumber( sal_Int32 nSlideNumber, bool bVisible /* = true */ )
{
    DBG_ASSERT( isValidSlideNumber( nSlideNumber ), "sd::AnimationSlideController::insertSlideNumber(), illegal index" );
    if( isValidSlideNumber( nSlideNumber ) )
    {
        maSlideNumbers.push_back( nSlideNumber );
        maSlideVisible.push_back( bVisible );
        maSlideVisited.push_back( false );
    }
}

bool AnimationSlideController::getSlideAPI( sal_Int32 nSlideNumber, Reference< XDrawPage >& xSlide, Reference< XAnimationNode >& xAnimNode )
{
    if( isValidSlideNumber( nSlideNumber ) ) try
    {
        xSlide.set( mxSlides->getByIndex(nSlideNumber), UNO_QUERY_THROW );

        if( meMode == PREVIEW )
        {
            xAnimNode = mxPreviewNode;
        }
        else
        {
            Reference< animations::XAnimationNodeSupplier > xAnimNodeSupplier( xSlide, UNO_QUERY_THROW );
            xAnimNode = xAnimNodeSupplier->getAnimationNode();
        }

        return true;
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::AnimationSlideController::getSlideAPI()" );
    }

    return false;
}

sal_Int32 AnimationSlideController::findSlideIndex( sal_Int32 nSlideNumber ) const
{
    sal_Int32 nIndex;
    const sal_Int32 nCount = maSlideNumbers.size();

    for( nIndex = 0; nIndex < nCount; nIndex++ )
    {
        if( maSlideNumbers[nIndex] == nSlideNumber )
            return nIndex;
    }

    return -1;
}

sal_Int32 AnimationSlideController::getNextSlideIndex() const
{
    switch( meMode )
    {
    case ALL:
        {
            sal_Int32 nNewSlideIndex = mnCurrentSlideIndex + 1;
            if( isValidIndex( nNewSlideIndex ) )
            {
                // if the current slide is not excluded, make sure the
                // next slide is also not excluded.
                // if the current slide is excluded, we want to go
                // to the next slide, even if this is also excluded.
                if( maSlideVisible[mnCurrentSlideIndex] )
                {
                    while( isValidIndex( nNewSlideIndex ) )
                    {
                        if( maSlideVisible[nNewSlideIndex] )
                            break;

                        nNewSlideIndex++;
                    }
                }
            }
            return isValidIndex( nNewSlideIndex ) ? nNewSlideIndex : -1;
        }

    case FROM:
    case CUSTOM:
        return mnHiddenSlideNumber == -1 ? mnCurrentSlideIndex + 1 : mnCurrentSlideIndex;

    default:
    case PREVIEW:
        return -1;

    }
}

sal_Int32 AnimationSlideController::getNextSlideNumber() const
{
    sal_Int32 nNextSlideIndex = getNextSlideIndex();
    if( isValidIndex( nNextSlideIndex ) )
    {
        return maSlideNumbers[nNextSlideIndex];
    }
    else
    {
        return -1;
    }
}

bool AnimationSlideController::nextSlide()
{
    return jumpToSlideIndex( getNextSlideIndex() );
}

sal_Int32 AnimationSlideController::getPreviousSlideIndex() const
{
    sal_Int32 nNewSlideIndex = mnCurrentSlideIndex - 1;

    switch( meMode )
    {
        case ALL:
        {
            // make sure the previous slide is visible
            // or was already visited
            while( isValidIndex( nNewSlideIndex ) )
            {
                if( maSlideVisible[nNewSlideIndex] || maSlideVisited[nNewSlideIndex] )
                    break;

                nNewSlideIndex--;
            }

            break;
        }

        case PREVIEW:
            return -1;

        default:
            break;
    }

    return nNewSlideIndex;
}

bool AnimationSlideController::previousSlide()
{
    return jumpToSlideIndex( getPreviousSlideIndex() );
}

void AnimationSlideController::displayCurrentSlide( const Reference< XSlideShow >& xShow,
                                                    const Reference< XDrawPagesSupplier>& xDrawPages,
                                                    const bool bSkipAllMainSequenceEffects )
{
    const sal_Int32 nCurrentSlideNumber = getCurrentSlideNumber();

    if( !(xShow.is() && (nCurrentSlideNumber != -1 )) )
        return;

    Reference< XDrawPage > xSlide;
    Reference< XAnimationNode > xAnimNode;
    ::std::vector<PropertyValue> aProperties;

    const sal_Int32 nNextSlideNumber = getNextSlideNumber();
    if( getSlideAPI( nNextSlideNumber, xSlide, xAnimNode )  )
    {
        Sequence< Any > aValue{ Any(xSlide), Any(xAnimNode) };
        aProperties.emplace_back( "Prefetch" ,
                -1,
                Any(aValue),
                PropertyState_DIRECT_VALUE);
    }
    if (bSkipAllMainSequenceEffects)
    {
        // Add one property that prevents the slide transition from being
        // shown (to speed up the transition to the previous slide) and
        // one to show all main sequence effects so that the user can
        // continue to undo effects.
        aProperties.emplace_back( "SkipAllMainSequenceEffects",
                -1,
                Any(true),
                PropertyState_DIRECT_VALUE);
        aProperties.emplace_back("SkipSlideTransition",
                -1,
                Any(true),
                PropertyState_DIRECT_VALUE);
    }

    if( getSlideAPI( nCurrentSlideNumber, xSlide, xAnimNode ) )
        xShow->displaySlide( xSlide, xDrawPages, xAnimNode, comphelper::containerToSequence(aProperties) );
}

constexpr OUString gsOnClick( u"OnClick"_ustr );
constexpr OUString gsBookmark( u"Bookmark"_ustr );
constexpr OUString gsVerb( u"Verb"_ustr );

SlideshowImpl::SlideshowImpl( const Reference< XPresentation2 >& xPresentation, ViewShell* pViewSh, ::sd::View* pView, SdDrawDocument* pDoc, vcl::Window* pParentWindow )
: mxShow()
, mxView()
, mxModel(pDoc->getUnoModel())
, maUpdateTimer("SlideShowImpl maUpdateTimer")
, maInputFreezeTimer("SlideShowImpl maInputFreezeTimer")
, maDeactivateTimer("SlideShowImpl maDeactivateTimer")
, mpView(pView)
, mpViewShell(pViewSh)
, mpDocSh(pDoc->GetDocSh())
, mpDoc(pDoc)
, mpParentWindow(pParentWindow)
, mpShowWindow(nullptr)
, mpSlideController()
, mnRestoreSlide(0)
, maPopupMousePos()
, maPresSize( -1, -1 )
, meAnimationMode(ANIMATIONMODE_SHOW)
, maCharBuffer()
, mpOldActiveWindow(nullptr)
, maStarBASICGlobalErrorHdl()
, mnChildMask( 0 )
, mbDisposed(false)
, mbAutoSaveWasOn(false)
, mbRehearseTimings(false)
, mbIsPaused(false)
, mbWasPaused(false)
, mbInputFreeze(false)
, mbActive(false)
, maPresSettings( pDoc->getPresentationSettings() )
, mnUserPaintColor( 0x80ff0000L )
, mbUsePen(false)
, mdUserPaintStrokeWidth ( 150.0 )
, maShapeEventMap()
, mxPreviewDrawPage()
, mxPreviewAnimationNode()
, mxPlayer()
, mpPaneHider()
, mnEndShowEvent(nullptr)
, mnContextMenuEvent(nullptr)
, mnEventObjectChange(nullptr)
, mnEventObjectInserted(nullptr)
, mnEventObjectRemoved(nullptr)
, mnEventPageOrderChange(nullptr)
, mxPresentation( xPresentation )
, mxListenerProxy()
, mxShow2()
, mxView2()
, meAnimationMode2()
, mbInterActiveSetup(false)
, maPresSettings2()
, mxPreviewDrawPage2()
, mxPreviewAnimationNode2()
, mnSlideIndex(0)
{
    if( mpViewShell )
        mpOldActiveWindow = mpViewShell->GetActiveWindow();

    maUpdateTimer.SetInvokeHandler(LINK(this, SlideshowImpl, updateHdl));
    // Priority must not be too high or we'll starve input handling etc.
    maUpdateTimer.SetPriority(TaskPriority::REPAINT);

    maDeactivateTimer.SetInvokeHandler(LINK(this, SlideshowImpl, deactivateHdl));
    maDeactivateTimer.SetTimeout( 20 );

    maInputFreezeTimer.SetInvokeHandler( LINK( this, SlideshowImpl, ReadyForNextInputHdl ) );
    maInputFreezeTimer.SetTimeout( 20 );

        // no autosave during show
    if (officecfg::Office::Recovery::AutoSave::Enabled::get())
        mbAutoSaveWasOn = true;

    Application::AddEventListener( LINK( this, SlideshowImpl, EventListenerHdl ) );

    mbUsePen = maPresSettings.mbMouseAsPen;

    SdOptions* pOptions = SdModule::get()->GetSdOptions(DocumentType::Impress);
    if( pOptions )
    {
        mnUserPaintColor = pOptions->GetPresentationPenColor();
        mdUserPaintStrokeWidth = pOptions->GetPresentationPenWidth();
    }

    // to be able to react on various changes in the DrawModel, this class
    // is now derived from SfxListener and registers itself at the DrawModel
    if (nullptr != mpDoc)
        StartListening(*mpDoc);
}

SlideshowImpl::~SlideshowImpl()
{
    // stop listening to DrawModel (see above)
    if (nullptr != mpDoc)
        EndListening(*mpDoc);

    SdModule* pModule = SdModule::get();
    //rhbz#806663 SlideshowImpl can outlive SdModule
    SdOptions* pOptions = pModule ?
        pModule->GetSdOptions(DocumentType::Impress) : nullptr;
    if( pOptions )
    {
        pOptions->SetPresentationPenColor(mnUserPaintColor);
        pOptions->SetPresentationPenWidth(mdUserPaintStrokeWidth);
    }

    Application::RemoveEventListener( LINK( this, SlideshowImpl, EventListenerHdl ) );

    maDeactivateTimer.Stop();

    if( !mbDisposed )
    {
        OSL_FAIL("SlideshowImpl::~SlideshowImpl(), component was not disposed!");
        std::unique_lock g(m_aMutex);
        disposing(g);
    }
}

void SlideshowImpl::disposing(std::unique_lock<std::mutex>&)
{
#ifdef ENABLE_SDREMOTE
    RemoteServer::presentationStopped();
#endif
    // IASS: This is the central methodology to 'steer' the
    // PresenterConsole - in this case, to shut it down
    if( mxShow.is() && mpDoc )
        NotifyDocumentEvent(
            *mpDoc,
            u"OnEndPresentation"_ustr );

    if( mbAutoSaveWasOn )
        setAutoSaveState( true );

    if( mnEndShowEvent )
        Application::RemoveUserEvent( mnEndShowEvent );
    if( mnContextMenuEvent )
        Application::RemoveUserEvent( mnContextMenuEvent );
    if( mnEventObjectChange )
        Application::RemoveUserEvent( mnEventObjectChange );
    if( mnEventObjectInserted )
        Application::RemoveUserEvent( mnEventObjectInserted );
    if( mnEventObjectRemoved )
        Application::RemoveUserEvent( mnEventObjectRemoved );
    if( mnEventPageOrderChange )
        Application::RemoveUserEvent( mnEventPageOrderChange );

    maInputFreezeTimer.Stop();

    SolarMutexGuard aSolarGuard;

    if( !mxShow.is() )
        return;

    if( mxPresentation.is() )
        mxPresentation->end();

    maUpdateTimer.Stop();

    removeShapeEvents();

    if( mxListenerProxy.is() )
        mxListenerProxy->removeAsSlideShowListener();

    try
    {
        if( mxView.is() )
            mxShow->removeView( mxView );

        Reference< XComponent > xComponent( mxShow, UNO_QUERY );
        if( xComponent.is() )
            xComponent->dispose();

        if( mxView.is() )
            mxView->dispose();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::stop()" );
    }

    mxShow.clear();
    mxView.clear();
    mxListenerProxy.clear();
    mpSlideController.reset();

    // take DrawView from presentation window, but give the old window back
    if( mpShowWindow && mpView )
        mpView->DeleteDeviceFromPaintView( *mpShowWindow->GetOutDev() );

    if( mpView )
        mpView->SetAnimationPause( false );

    if( mpViewShell )
    {
        mpViewShell->SetActiveWindow(mpOldActiveWindow);
        if (mpShowWindow)
            mpShowWindow->SetViewShell( nullptr );
    }

    if( mpView )
        mpView->InvalidateAllWin();

    if( maPresSettings.mbFullScreen )
    {
#if HAVE_FEATURE_SCRIPTING
        // restore StarBASICErrorHdl
        StarBASIC::SetGlobalErrorHdl(maStarBASICGlobalErrorHdl);
        maStarBASICGlobalErrorHdl = Link<StarBASIC*,bool>();
#endif
    }
    else
    {
        if( mpShowWindow )
            mpShowWindow->Hide();
    }

    if( meAnimationMode == ANIMATIONMODE_SHOW )
    {
        mpDocSh->SetSlotFilter();
        mpDocSh->ApplySlotFilter();

        Help::EnableContextHelp();
        Help::EnableExtHelp();

        showChildWindows();
        mnChildMask = 0;
    }

    // show current window again
    if( mpViewShell && dynamic_cast< PresentationViewShell *>( mpViewShell ) ==  nullptr)
    {
        if( meAnimationMode == ANIMATIONMODE_SHOW )
        {
            mpViewShell->GetViewShellBase().ShowUIControls (true);
            mpPaneHider.reset();
        }
        else if( meAnimationMode == ANIMATIONMODE_PREVIEW )
        {
            mpViewShell->ShowUIControls(true);
        }
    }

    if( mpShowWindow )
        mpShowWindow->Hide();
    mpShowWindow.disposeAndClear();

    if ( mpViewShell )
    {
        if( meAnimationMode == ANIMATIONMODE_SHOW )
        {
            ::sd::Window* pActWin = mpViewShell->GetActiveWindow();

            if (pActWin)
            {
                Size aVisSizePixel = pActWin->GetOutputSizePixel();
                ::tools::Rectangle aVisAreaWin = pActWin->PixelToLogic( ::tools::Rectangle( Point(0,0), aVisSizePixel) );
                mpViewShell->VisAreaChanged(aVisAreaWin);
                if (mpView)
                    mpView->VisAreaChanged(pActWin->GetOutDev());
                pActWin->GrabFocus();
            }
        }

        // restart the custom show dialog if he started us
        if( mpViewShell->IsStartShowWithDialog() && getDispatcher() )
        {
            mpViewShell->SetStartShowWithDialog( false );
            getDispatcher()->Execute( SID_CUSTOMSHOW_DLG, SfxCallMode::ASYNCHRON | SfxCallMode::RECORD );
        }

        mpViewShell->GetViewShellBase().UpdateBorder(true);
    }

    if( mpShowWindow )
    {
        mpShowWindow.disposeAndClear();
    }

    setActiveXToolbarsVisible( true );

    mbDisposed = true;
}

bool SlideshowImpl::isInteractiveSetup() const
{
    return mbInterActiveSetup;
}

void SlideshowImpl::startInteractivePreview( const Reference< XDrawPage >& xDrawPage, const Reference< XAnimationNode >& xAnimationNode )
{
    // set flag that we are in IASS mode
    mbInterActiveSetup = true;

    // save stuff that will be replaced temporarily
    mxShow2 = mxShow;
    mxView2 = mxView;
    mxPreviewDrawPage2 = mxPreviewDrawPage;
    mxPreviewAnimationNode2 = mxPreviewAnimationNode;
    meAnimationMode2 = meAnimationMode;
    maPresSettings2 = maPresSettings;

    // remember slide shown before preview
    mnSlideIndex = getCurrentSlideIndex();

    // set DrawPage/AnimationNode
    mxPreviewDrawPage = xDrawPage;
    mxPreviewAnimationNode = xAnimationNode;
    meAnimationMode = ANIMATIONMODE_PREVIEW;

    // set PresSettings for preview
    maPresSettings.mbAll = false;
    maPresSettings.mbEndless = false;
    maPresSettings.mbCustomShow = false;
    maPresSettings.mbManual = false;
    maPresSettings.mbMouseVisible = false;
    maPresSettings.mbMouseAsPen = false;
    maPresSettings.mbLockedPages = false;
    maPresSettings.mbAlwaysOnTop = false;
    maPresSettings.mbFullScreen = false;
    maPresSettings.mbAnimationAllowed = true;
    maPresSettings.mnPauseTimeout = 0;
    maPresSettings.mbShowPauseLogo = false;

    // create a new temporary AnimationSlideController
    mpSlideController->pushForPreview();
    // Reference< XDrawPagesSupplier > xDrawPages( mpDoc->getUnoModel(), UNO_QUERY_THROW );
    // Reference< XIndexAccess > xSlides( xDrawPages->getDrawPages(), UNO_QUERY_THROW );
    // mpSlideController = std::make_shared<AnimationSlideController>( xSlides, AnimationSlideController::PREVIEW );
    sal_Int32 nSlideNumber = 0;
    Reference< XPropertySet > xSet( xDrawPage, UNO_QUERY_THROW );
    xSet->getPropertyValue( u"Number"_ustr ) >>= nSlideNumber;
    mpSlideController->insertSlideNumber( nSlideNumber-1 );
    mpSlideController->setPreviewNode( xAnimationNode );

    // prepare properties
    sal_Int32 nPropertyCount = 1;
    if( xAnimationNode.is() )
        nPropertyCount++;
    Sequence< beans::PropertyValue > aProperties(nPropertyCount);
    auto pProperties = aProperties.getArray();
    pProperties[0].Name = "AutomaticAdvancement";
    pProperties[0].Value <<= 1.0; // one second timeout

    if( xAnimationNode.is() )
    {
        pProperties[1].Name = "NoSlideTransitions";
        pProperties[1].Value <<= true;
    }

    // start preview
    startShowImpl( aProperties );
}

void SlideshowImpl::endInteractivePreview()
{
    if (!mbInterActiveSetup)
        // not in use, nothing to do
        return;

    // cleanup Show/View
    try
    {
        if( mxView.is() )
            mxShow->removeView( mxView );

        Reference< XComponent > xComponent( mxShow, UNO_QUERY );
        if( xComponent.is() )
            xComponent->dispose();

        if( mxView.is() )
            mxView->dispose();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::stop()" );
    }
    mxShow.clear();
    mxView.clear();
    mxView = mxView2;
    mxShow = mxShow2;

    // restore SlideController
    mpSlideController->popFromPreview();

    // restore other settings and cleanup temporary incarnations
    maPresSettings = maPresSettings2;
    meAnimationMode = meAnimationMode2;
    mxPreviewAnimationNode = mxPreviewAnimationNode2;
    mxPreviewAnimationNode2.clear();
    mxPreviewDrawPage = mxPreviewDrawPage2;
    mxPreviewDrawPage2.clear();

    // go back to slide shown before preview
    gotoSlideIndex(mnSlideIndex);

    // reset IASS mode flag
    mbInterActiveSetup = false;
}

bool SlideshowImpl::startPreview(
        const Reference< XDrawPage >& xDrawPage,
        const Reference< XAnimationNode >& xAnimationNode,
        vcl::Window * pParent )
{
    bool bRet = false;

    try
    {
        const Reference<lang::XServiceInfo> xServiceInfo( xDrawPage, UNO_QUERY );
        if (xServiceInfo.is()) {
            const Sequence<OUString> supportedServices(
                xServiceInfo->getSupportedServiceNames() );
            if (comphelper::findValue(supportedServices, "com.sun.star.drawing.MasterPage") != -1) {
                OSL_FAIL("sd::SlideshowImpl::startPreview() "
                          "not allowed on master page!");
                return false;
            }
        }

        mxPreviewDrawPage = xDrawPage;
        mxPreviewAnimationNode = xAnimationNode;
        meAnimationMode = ANIMATIONMODE_PREVIEW;

        maPresSettings.mbAll = false;
        maPresSettings.mbEndless = false;
        maPresSettings.mbCustomShow = false;
        maPresSettings.mbManual = false;
        maPresSettings.mbMouseVisible = false;
        maPresSettings.mbMouseAsPen = false;
        maPresSettings.mbLockedPages = false;
        maPresSettings.mbAlwaysOnTop = false;
        maPresSettings.mbFullScreen = false;
        maPresSettings.mbAnimationAllowed = true;
        maPresSettings.mnPauseTimeout = 0;
        maPresSettings.mbShowPauseLogo = false;

        rtl::Reference< SdXImpressDocument > xDrawPages( mpDoc->getUnoModel() );
        Reference< XIndexAccess > xSlides( xDrawPages->getDrawPages(), UNO_QUERY_THROW );
        mpSlideController = std::make_unique<AnimationSlideController>( xSlides, AnimationSlideController::PREVIEW );

        sal_Int32 nSlideNumber = 0;
        Reference< XPropertySet > xSet( mxPreviewDrawPage, UNO_QUERY_THROW );
        xSet->getPropertyValue( u"Number"_ustr ) >>= nSlideNumber;
        mpSlideController->insertSlideNumber( nSlideNumber-1 );
        mpSlideController->setPreviewNode( xAnimationNode );

        mpShowWindow = VclPtr<ShowWindow>::Create( this, ((pParent == nullptr) && mpViewShell) ?  mpParentWindow.get() : pParent );
        if( mpViewShell )
        {
            mpViewShell->SetActiveWindow( mpShowWindow );
            mpShowWindow->SetViewShell (mpViewShell);
            mpViewShell->ShowUIControls (false);
        }

        if( mpView )
        {
            mpView->AddDeviceToPaintView( *mpShowWindow->GetOutDev(), nullptr );
            mpView->SetAnimationPause( true );
        }

        // call resize handler
        if( pParent )
        {
            maPresSize = pParent->GetSizePixel();
        }
        else if( mpViewShell )
        {
            ::tools::Rectangle aContentRect (mpViewShell->GetViewShellBase().getClientRectangle());
            if (AllSettings::GetLayoutRTL())
            {
                aContentRect.SetLeft( aContentRect.Right() );
                aContentRect.AdjustRight(aContentRect.Right() );
            }
            maPresSize = aContentRect.GetSize();
            mpShowWindow->SetPosPixel( aContentRect.TopLeft() );
        }
        else
        {
            OSL_FAIL("sd::SlideshowImpl::startPreview(), I need either a parent window or a viewshell!");
        }
        resize( maPresSize );

        sal_Int32 nPropertyCount = 1;
        if( mxPreviewAnimationNode.is() )
            nPropertyCount++;

        Sequence< beans::PropertyValue > aProperties(nPropertyCount);
        auto pProperties = aProperties.getArray();
        pProperties[0].Name = "AutomaticAdvancement";
        pProperties[0].Value <<= 1.0; // one second timeout

        if( mxPreviewAnimationNode.is() )
        {
            pProperties[1].Name = "NoSlideTransitions";
            pProperties[1].Value <<= true;
        }

        bRet = startShowImpl( aProperties );

        if( mpShowWindow != nullptr && meAnimationMode == ANIMATIONMODE_PREVIEW )
            mpShowWindow->SetPreviewMode();

    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::startPreview()" );
        bRet = false;
    }

    return bRet;
}

bool SlideshowImpl::startShow( PresentationSettingsEx const * pPresSettings )
{
    const rtl::Reference<SlideshowImpl> xKeepAlive(this);

    DBG_ASSERT( !mxShow.is(), "sd::SlideshowImpl::startShow(), called twice!" );
    if( mxShow.is() )
        return true;
    DBG_ASSERT( mpParentWindow!=nullptr, "sd::SlideshowImpl::startShow() called without parent window" );
    if (mpParentWindow == nullptr)
        return false;

    // Autoplay (pps/ppsx)
    if (mpViewShell->GetDoc()->GetStartWithPresentation())
    {
        mpViewShell->GetDoc()->SetExitAfterPresenting(true);
    }

    bool bRet = false;

    try
    {
        if( pPresSettings )
        {
            maPresSettings = *pPresSettings;
            mbRehearseTimings = pPresSettings->mbRehearseTimings;
        }

        OUString  aPresSlide( maPresSettings.maPresPage );
        SdPage* pStartPage = mpViewShell->GetActualPage();
        bool    bStartWithActualSlide =  pStartPage;

        // times should be measured?
        if( mbRehearseTimings )
        {
            maPresSettings.mbEndless = false;
            maPresSettings.mbManual = true;
            maPresSettings.mbMouseVisible = true;
            maPresSettings.mbMouseAsPen = false;
            maPresSettings.mnPauseTimeout = 0;
            maPresSettings.mbShowPauseLogo = false;
        }

        if( pStartPage )
        {
            if( pStartPage->GetPageKind() == PageKind::Notes )
            {
                // we are in notes page mode, so get
                // the corresponding draw page
                const sal_uInt16 nNotePgNum = pStartPage->GetPageNum();
                assert(nNotePgNum >= 2);
                const sal_uInt16 nPgNum = ( nNotePgNum - 2 ) >> 1;
                pStartPage = mpDoc->GetSdPage( nPgNum, PageKind::Standard );
            }
        }

        if( bStartWithActualSlide )
        {
            if ( aPresSlide.isEmpty())
            {
                // no preset slide yet, so pick current on one
                aPresSlide = pStartPage->GetName();
                // if the starting slide is hidden, we can't set slide controller to ALL mode
                maPresSettings.mbAll = !pStartPage->IsExcluded();
            }

            if( meAnimationMode != ANIMATIONMODE_SHOW )
            {
                if( pStartPage->GetPageKind() == PageKind::Standard )
                {
                    maPresSettings.mbAll = false;
                }
            }
        }

        // build page list
        createSlideList( maPresSettings.mbAll, aPresSlide );

        // remember Slide number from where the show was started
        if( pStartPage )
            mnRestoreSlide = ( pStartPage->GetPageNum() - 1 ) / 2;

        if( mpSlideController->hasSlides() )
        {
            // hide child windows
            hideChildWindows();

            mpShowWindow = VclPtr<ShowWindow>::Create( this, mpParentWindow );
            mpShowWindow->SetMouseAutoHide( !maPresSettings.mbMouseVisible );
            mpViewShell->SetActiveWindow( mpShowWindow );
            mpShowWindow->SetViewShell (mpViewShell);
            mpViewShell->GetViewShellBase().ShowUIControls (false);
            // Hide the side panes for in-place presentations.
            if ( ! maPresSettings.mbFullScreen)
                mpPaneHider.reset(new PaneHider(*mpViewShell,this));

            // these Slots are forbidden in other views for this document
            if( mpDocSh && pPresSettings && !pPresSettings->mbInteractive) // IASS
            {
                mpDocSh->SetSlotFilter( true, pAllowed );
                mpDocSh->ApplySlotFilter();
            }

            Help::DisableContextHelp();
            Help::DisableExtHelp();

            if( maPresSettings.mbFullScreen )
            {
#if HAVE_FEATURE_SCRIPTING
                // disable basic ide error handling
                maStarBASICGlobalErrorHdl = StarBASIC::GetGlobalErrorHdl();
                StarBASIC::SetGlobalErrorHdl( Link<StarBASIC*,bool>() );
#endif
            }

            // call resize handler
            maPresSize = mpParentWindow->GetSizePixel();
            if (!maPresSettings.mbFullScreen)
            {
                const ::tools::Rectangle& aClientRect = mpViewShell->GetViewShellBase().getClientRectangle();
                maPresSize = aClientRect.GetSize();
                mpShowWindow->SetPosPixel( aClientRect.TopLeft() );
                resize( maPresSize );
            }

            // #i41824#
            // Note: In FullScreen Mode the OS (window manager) sends a resize to
            // the WorkWindow once it actually resized it to full size.  The
            // WorkWindow propagates the resize to the DrawViewShell which calls
            // resize() at the SlideShow (this).  Calling resize here results in a
            // temporary display of a black window in the window's default size

            if( mpView )
            {
                mpView->AddDeviceToPaintView( *mpShowWindow->GetOutDev(), nullptr );
                mpView->SetAnimationPause( true );
            }

            SfxBindings* pBindings = getBindings();
            if( pBindings )
            {
                pBindings->Invalidate( SID_PRESENTATION );
                pBindings->Invalidate( SID_REHEARSE_TIMINGS );
            }

            // Defer the sd::ShowWindow's GrabFocus to SlideShow::activate. so that the accessible event can be fired correctly.
            //mpShowWindow->GrabFocus();

            std::vector<beans::PropertyValue> aProperties;
            aProperties.reserve( 4 );

            aProperties.emplace_back( "AdvanceOnClick" ,
                    -1, Any( !maPresSettings.mbLockedPages ),
                    beans::PropertyState_DIRECT_VALUE );

            aProperties.emplace_back( "ImageAnimationsAllowed" ,
                    -1, Any( maPresSettings.mbAnimationAllowed ),
                    beans::PropertyState_DIRECT_VALUE );

            const bool bZOrderEnabled(
                SdModule::get()->GetSdOptions( mpDoc->GetDocumentType() )->IsSlideshowRespectZOrder() );
            aProperties.emplace_back( "DisableAnimationZOrder" ,
                    -1, Any( !bZOrderEnabled ),
                    beans::PropertyState_DIRECT_VALUE );

            aProperties.emplace_back( "ForceManualAdvance" ,
                    -1, Any( maPresSettings.mbManual ),
                    beans::PropertyState_DIRECT_VALUE );

            if( mbUsePen )
            {
                aProperties.emplace_back( "UserPaintColor" ,
                        // User paint color is black by default.
                        -1, Any( mnUserPaintColor ),
                        beans::PropertyState_DIRECT_VALUE );

                aProperties.emplace_back( "UserPaintStrokeWidth" ,
                        // User paint color is black by default.
                        -1, Any( mdUserPaintStrokeWidth ),
                        beans::PropertyState_DIRECT_VALUE );
            }

            if (mbRehearseTimings) {
                aProperties.emplace_back( "RehearseTimings" ,
                        -1, Any(true), beans::PropertyState_DIRECT_VALUE );
            }

            bRet = startShowImpl( Sequence<beans::PropertyValue>(
                                      aProperties.data(), aProperties.size() ) );

        }

        setActiveXToolbarsVisible( false );
    }
    catch (const Exception&)
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::startShow()" );
        bRet = false;
    }

    return bRet;
}

bool SlideshowImpl::startShowImpl( const Sequence< beans::PropertyValue >& aProperties )
{
    try
    {
        mxShow.set( createSlideShow(), UNO_SET_THROW );

        mxView = new SlideShowView(
                                             *mpShowWindow,
                                             mpDoc,
                                             meAnimationMode,
                                             this,
                                             maPresSettings.mbFullScreen);

        // try add wait symbol to properties:
        const Reference<rendering::XSpriteCanvas> xSpriteCanvas(
            mxView->getCanvas() );
        if (xSpriteCanvas.is())
        {
            Bitmap waitSymbolBitmap(BMP_WAIT_ICON);
            const Reference<rendering::XBitmap> xBitmap(
                vcl::unotools::xBitmapFromBitmap( waitSymbolBitmap ) );
            if (xBitmap.is())
            {
                mxShow->setProperty(
                    beans::PropertyValue( u"WaitSymbolBitmap"_ustr ,
                        -1,
                        Any( xBitmap ),
                        beans::PropertyState_DIRECT_VALUE ) );
            }

            Bitmap pointerSymbolBitmap(BMP_POINTER_ICON);
            const Reference<rendering::XBitmap> xPointerBitmap(
                vcl::unotools::xBitmapFromBitmap( pointerSymbolBitmap ) );
            if (xPointerBitmap.is())
            {
                mxShow->setProperty(
                    beans::PropertyValue( u"PointerSymbolBitmap"_ustr ,
                        -1,
                        Any( xPointerBitmap ),
                        beans::PropertyState_DIRECT_VALUE ) );
            }
            if (officecfg::Office::Impress::Misc::Start::ShowNavigationPanel::get())
            {
                NavbarButtonSize btnScale = static_cast<NavbarButtonSize>(officecfg::Office::Impress::Layout::Display::NavigationBtnScale::get());
                OUString prevSlidePath = u""_ustr;
                OUString nextSlidePath = u""_ustr;
                OUString menuPath = u""_ustr;
                switch (btnScale)
                {
                    case NavbarButtonSize::Large:
                    {
                        prevSlidePath = BMP_PREV_SLIDE_LARGE;
                        nextSlidePath = BMP_NEXT_SLIDE_LARGE;
                        menuPath = BMP_MENU_SLIDE_LARGE;
                        break;
                    }
                    case NavbarButtonSize::XLarge:
                    {
                        prevSlidePath = BMP_PREV_SLIDE_EXTRALARGE;
                        nextSlidePath = BMP_NEXT_SLIDE_EXTRALARGE;
                        menuPath = BMP_MENU_SLIDE_EXTRALARGE;
                        break;
                    }
                    case NavbarButtonSize::Auto:
                    case NavbarButtonSize::Small:
                    default:
                    {
                        prevSlidePath = BMP_PREV_SLIDE_SMALL;
                        nextSlidePath = BMP_NEXT_SLIDE_SMALL;
                        menuPath = BMP_MENU_SLIDE_SMALL;
                        break;
                    }
                }
                Bitmap prevSlideBm(prevSlidePath);
                const Reference<rendering::XBitmap> xPrevSBitmap(
                    vcl::unotools::xBitmapFromBitmap(prevSlideBm));
                if (xPrevSBitmap.is())
                {
                    mxShow->setProperty(beans::PropertyValue(u"NavigationSlidePrev"_ustr, -1,
                                                             Any(xPrevSBitmap),
                                                             beans::PropertyState_DIRECT_VALUE));
                }
                Bitmap menuSlideBm(menuPath);
                const Reference<rendering::XBitmap> xMenuSBitmap(
                    vcl::unotools::xBitmapFromBitmap(menuSlideBm));
                if (xMenuSBitmap.is())
                {
                    mxShow->setProperty(beans::PropertyValue(u"NavigationSlideMenu"_ustr, -1,
                                                             Any(xMenuSBitmap),
                                                             beans::PropertyState_DIRECT_VALUE));
                }
                Bitmap nextSlideBm(nextSlidePath);
                const Reference<rendering::XBitmap> xNextSBitmap(
                    vcl::unotools::xBitmapFromBitmap(nextSlideBm));
                if (xNextSBitmap.is())
                {
                    mxShow->setProperty(beans::PropertyValue(u"NavigationSlideNext"_ustr, -1,
                                                             Any(xNextSBitmap),
                                                             beans::PropertyState_DIRECT_VALUE));
                }
            }
        }

        for( const auto& rProp : aProperties )
            mxShow->setProperty( rProp );

        mxShow->addView( mxView );

        mxListenerProxy.set( new SlideShowListenerProxy( this, mxShow ) );
        mxListenerProxy->addAsSlideShowListener();

        // IASS: Do only startup the PresenterConsole if this is not
        // the SlideShow Preview mode (else would be double)
        if (!mbInterActiveSetup)
        {
            // IASS: This is the central methodology to 'steer' the
            // PresenterConsole - in this case, to start it up and make
            // it visible (if activated)
            NotifyDocumentEvent(
                *mpDoc,
                u"OnStartPresentation"_ustr);
        }

        displaySlideIndex( mpSlideController->getStartSlideIndex() );

        return true;
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::startShowImpl()" );
        return false;
    }
}

/** called only by the slideshow view when the first paint event occurs.
    This actually starts the slideshow. */
void SlideshowImpl::onFirstPaint()
{
    if( mpShowWindow )
    {
        /*
        mpShowWindow->SetBackground( Wallpaper( COL_BLACK ) );
        mpShowWindow->Erase();
        mpShowWindow->SetBackground();
        */
    }

    SolarMutexGuard aSolarGuard;
    maUpdateTimer.SetTimeout( sal_uLong(100) );
    maUpdateTimer.Start();
}

void SlideshowImpl::paint()
{
    if( mxView.is() ) try
    {
        awt::PaintEvent aEvt;
        // aEvt.UpdateRect = TODO
        mxView->paint( aEvt );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::paint()" );
    }
}

void SAL_CALL SlideshowImpl::addSlideShowListener( const Reference< XSlideShowListener >& xListener )
{
    if( mxListenerProxy.is() )
        mxListenerProxy->addSlideShowListener( xListener );
}

void SAL_CALL SlideshowImpl::removeSlideShowListener( const Reference< XSlideShowListener >& xListener )
{
    if( mxListenerProxy.is() )
        mxListenerProxy->removeSlideShowListener( xListener );
}

void SlideshowImpl::slideEnded(const bool bReverse)
{
    if (bReverse)
        gotoPreviousSlide(true);
    else
        gotoNextSlide();
}

bool SlideshowImpl::swipe(const CommandGestureSwipeData &rSwipeData)
{
    if (mbUsePen || mnContextMenuEvent)
        return false;
    double nVelocityX = rSwipeData.getVelocityX();
    // tdf#108475 make it swipe only if some reasonable movement was involved
    if (fabs(nVelocityX) < 50)
        return false;
    if (nVelocityX > 0)
    {
        gotoPreviousSlide();
    }
    else
    {
        gotoNextEffect();
    }
    //a swipe is followed by a mouse up, tell the view to ignore that mouse up as we've reacted
    //to the swipe instead
    mxView->ignoreNextMouseReleased();
    return true;
}

bool SlideshowImpl::longpress(const CommandGestureLongPressData &rLongPressData)
{
    if (mnContextMenuEvent)
        return false;

    maPopupMousePos = Point(rLongPressData.getX(), rLongPressData.getY());
    mnContextMenuEvent = Application::PostUserEvent( LINK( this, SlideshowImpl, ContextMenuHdl ) );

    return true;
}

void SlideshowImpl::removeShapeEvents()
{
    if( !(mxShow.is() && mxListenerProxy.is()) )
        return;

    try
    {
        for( const auto& rEntry : maShapeEventMap )
        {
            mxListenerProxy->removeShapeEventListener( rEntry.first );
            mxShow->setShapeCursor( rEntry.first, awt::SystemPointer::ARROW );
        }

        maShapeEventMap.clear();
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::removeShapeEvents()" );
    }
}

void SlideshowImpl::registerShapeEvents(sal_Int32 nSlideNumber)
{
    if( nSlideNumber < 0 )
        return;

    try
    {
        Reference< XIndexAccess > xPages( mxModel->getDrawPages(), UNO_QUERY_THROW );

        Reference< XShapes > xDrawPage;
        xPages->getByIndex(nSlideNumber) >>= xDrawPage;

        if( xDrawPage.is() )
        {
            Reference< XMasterPageTarget > xMasterPageTarget( xDrawPage, UNO_QUERY );
            if( xMasterPageTarget.is() )
            {
                Reference< XShapes > xMasterPage = xMasterPageTarget->getMasterPage();
                if( xMasterPage.is() )
                    registerShapeEvents( xMasterPage );
            }
            registerShapeEvents( xDrawPage );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::registerShapeEvents()" );
    }
}

void SlideshowImpl::registerShapeEvents( Reference< XShapes > const & xShapes )
{
    try
    {
        const sal_Int32 nShapeCount = xShapes->getCount();
        sal_Int32 nShape;
        for( nShape = 0; nShape < nShapeCount; nShape++ )
        {
            Reference< XShape > xShape;
            xShapes->getByIndex( nShape ) >>= xShape;

            if( xShape.is() && xShape->getShapeType() == "com.sun.star.drawing.GroupShape" )
            {
                Reference< XShapes > xSubShapes( xShape, UNO_QUERY );
                if( xSubShapes.is() )
                    registerShapeEvents( xSubShapes );
            }

            Reference< XPropertySet > xSet( xShape, UNO_QUERY );
            if( !xSet.is() )
                continue;

            Reference< XPropertySetInfo > xSetInfo( xSet->getPropertySetInfo() );
            if( !xSetInfo.is() || !xSetInfo->hasPropertyByName( gsOnClick ) )
                continue;

            WrappedShapeEventImpl aEvent;
            xSet->getPropertyValue( gsOnClick ) >>= aEvent.meClickAction;

            switch( aEvent.meClickAction )
            {
            case ClickAction_PREVPAGE:
            case ClickAction_NEXTPAGE:
            case ClickAction_FIRSTPAGE:
            case ClickAction_LASTPAGE:
            case ClickAction_STOPPRESENTATION:
                break;
            case ClickAction_BOOKMARK:
                if( xSetInfo->hasPropertyByName( gsBookmark ) )
                    xSet->getPropertyValue( gsBookmark ) >>= aEvent.maStrBookmark;
                if( getSlideNumberForBookmark( aEvent.maStrBookmark ) == -1 )
                    continue;
                break;
            case ClickAction_DOCUMENT:
            case ClickAction_SOUND:
            case ClickAction_PROGRAM:
            case ClickAction_MACRO:
                if( xSetInfo->hasPropertyByName( gsBookmark ) )
                    xSet->getPropertyValue( gsBookmark ) >>= aEvent.maStrBookmark;
                break;
            case ClickAction_VERB:
                if( xSetInfo->hasPropertyByName( gsVerb ) )
                    xSet->getPropertyValue( gsVerb ) >>= aEvent.mnVerb;
                break;
            default:
                continue; // skip all others
            }

            maShapeEventMap[ xShape ] = std::move(aEvent);

            if( mxListenerProxy.is() )
                mxListenerProxy->addShapeEventListener( xShape );
            mxShow->setShapeCursor( xShape, awt::SystemPointer::REFHAND );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::registerShapeEvents()" );
    }
}

void SlideshowImpl::displayCurrentSlide (const bool bSkipAllMainSequenceEffects)
{
    stopSound();
    removeShapeEvents();

    if( mpSlideController && mxShow.is() )
    {
        rtl::Reference< SdXImpressDocument > xDrawPages( mpDoc->getUnoModel() );
        mpSlideController->displayCurrentSlide( mxShow, xDrawPages, bSkipAllMainSequenceEffects );
        registerShapeEvents(mpSlideController->getCurrentSlideNumber());
        update();

    }
    // send out page change event and notify to update all acc info for current page
    if (mpViewShell)
    {
        sal_Int32 currentPageIndex = getCurrentSlideIndex();
        mpViewShell->fireSwitchCurrentPage(currentPageIndex);
        mpViewShell->NotifyAccUpdate();
    }
}

void SlideshowImpl::endPresentation()
{
    if( maPresSettings.mbMouseAsPen)
    {
        rtl::Reference< SdXImpressDocument > xDocFactory(mpDoc->getUnoModel() );
        if( xDocFactory.is() )
            mxShow->registerUserPaintPolygons(xDocFactory);
    }

    if( !mnEndShowEvent )
        mnEndShowEvent = Application::PostUserEvent( LINK(this, SlideshowImpl, endPresentationHdl) );
}

IMPL_LINK_NOARG(SlideshowImpl, endPresentationHdl, void*, void)
{
    mnEndShowEvent = nullptr;

    stopSound();

    if( mxPresentation.is() )
        mxPresentation->end();
}

void SAL_CALL SlideshowImpl::pause()
{
    SolarMutexGuard aSolarGuard;

    if( mbIsPaused )
        return;

    try
    {
        mbIsPaused = true;
        if( mxShow.is() )
        {
            mxShow->pause(true);

            if( mxListenerProxy.is() )
                mxListenerProxy->paused();
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::pause()" );
    }
}

void SAL_CALL SlideshowImpl::resume()
{
    SolarMutexGuard aSolarGuard;

    if( mbIsPaused ) try
    {
        if( mpShowWindow->GetShowWindowMode() == SHOWWINDOWMODE_BLANK || mpShowWindow->GetShowWindowMode() == SHOWWINDOWMODE_END )
        {
            mpShowWindow->RestartShow();
        }
        else
        {
            mbIsPaused = false;
            if( mxShow.is() )
            {
                mxShow->pause(false);
                update();

                if( mxListenerProxy.is() )
                    mxListenerProxy->resumed();
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::resume()" );
    }
#ifdef ENABLE_SDREMOTE
    RemoteServer::presentationStarted( this );
#endif
}

sal_Bool SAL_CALL SlideshowImpl::isPaused()
{
    SolarMutexGuard aSolarGuard;
    return mbIsPaused;
}

void SAL_CALL SlideshowImpl::blankScreen( sal_Int32 nColor )
{
    SolarMutexGuard aSolarGuard;

    if( mpShowWindow && mpSlideController )
    {
        if( mpShowWindow->SetBlankMode( mpSlideController->getCurrentSlideIndex(), Color(ColorTransparency, nColor) ) )
        {
            pause();
        }
    }
}

// XShapeEventListener

void SlideshowImpl::click( const Reference< XShape >& xShape )
{
    SolarMutexGuard aSolarGuard;

    auto it = maShapeEventMap.find(xShape);
    if (it == maShapeEventMap.end())
        return;
    WrappedShapeEventImpl* pEvent = &it->second;

    switch( pEvent->meClickAction )
    {
    case ClickAction_PREVPAGE:          gotoPreviousSlide();        break;
    case ClickAction_NEXTPAGE:          gotoNextSlide();            break;
    case ClickAction_FIRSTPAGE:         gotoFirstSlide();           break;
    case ClickAction_LASTPAGE:          gotoLastSlide();            break;
    case ClickAction_STOPPRESENTATION:  endPresentation();          break;
    case ClickAction_BOOKMARK:
    {
        gotoBookmark( pEvent->maStrBookmark );
    }
    break;
    case ClickAction_SOUND:
    {
#if HAVE_FEATURE_AVMEDIA
        try
        {
            mxPlayer.set(avmedia::MediaWindow::createPlayer(pEvent->maStrBookmark, u""_ustr/*TODO?*/), uno::UNO_SET_THROW );
            mxPlayer->start();
        }
        catch( uno::Exception& )
        {
            TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::click()" );
        }
#endif
    }
    break;

    case ClickAction_DOCUMENT:
    {
        OUString aBookmark( pEvent->maStrBookmark );

        sal_Int32 nPos = aBookmark.indexOf( '#' );
        if( nPos >= 0 )
        {
            OUString aURL( aBookmark.copy( 0, nPos+1 ) );
            OUString aName( aBookmark.copy( nPos+1 ) );
            aURL += getUiNameFromPageApiNameImpl( aName );
            aBookmark = aURL;
        }

        mpDocSh->OpenBookmark( aBookmark );
    }
    break;

    case ClickAction_PROGRAM:
    {
        INetURLObject aURL(
            ::URIHelper::SmartRel2Abs(
                INetURLObject(mpDocSh->GetMedium()->GetBaseURL()),
                pEvent->maStrBookmark, ::URIHelper::GetMaybeFileHdl(), true,
                false, INetURLObject::EncodeMechanism::WasEncoded,
                INetURLObject::DecodeMechanism::Unambiguous ) );

        if( INetProtocol::File == aURL.GetProtocol() )
        {
            SfxStringItem aUrl( SID_FILE_NAME, aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
            SfxBoolItem aBrowsing( SID_BROWSE, true );

            if (SfxViewFrame* pViewFrm = SfxViewFrame::Current())
            {
                SfxUnoFrameItem aDocFrame(SID_FILLFRAME, pViewFrm->GetFrame().GetFrameInterface());
                pViewFrm->GetDispatcher()->ExecuteList( SID_OPENDOC,
                    SfxCallMode::ASYNCHRON | SfxCallMode::RECORD,
                    { &aUrl, &aBrowsing }, { &aDocFrame } );
            }
        }
    }
    break;

#if HAVE_FEATURE_SCRIPTING
    case presentation::ClickAction_MACRO:
    {
        const OUString aMacro( pEvent->maStrBookmark );

        if ( SfxApplication::IsXScriptURL( aMacro ) )
        {
            Any aRet;
            Sequence< sal_Int16 > aOutArgsIndex;
            Sequence< Any > aOutArgs;
            Sequence< Any >* pInArgs = new Sequence< Any >(0);
            mpDocSh->CallXScript( aMacro, *pInArgs, aRet, aOutArgsIndex, aOutArgs);
        }
        else
        {
            // aMacro has the following syntax:
            // "Macroname.Modulname.Libname.Documentname" or
            // "Macroname.Modulname.Libname.Applicationname"
            sal_Int32 nIdx{ 0 };
            const std::u16string_view aMacroName = o3tl::getToken(aMacro, 0, '.', nIdx);
            const std::u16string_view aModulName = o3tl::getToken(aMacro, 0, '.', nIdx);

            // todo: is the limitation still given that only
            // Modulname+Macroname can be used here?
            OUString aExecMacro = OUString::Concat(aModulName) + "." + aMacroName;
            mpDocSh->GetBasic()->Call(aExecMacro);
        }
    }
    break;
#endif

    case ClickAction_VERB:
    {
        // todo, better do it async?
        SdrObject* pObj = SdrObject::getSdrObjectFromXShape(xShape);
        SdrOle2Obj* pOleObject = dynamic_cast< SdrOle2Obj* >(pObj);
        if (pOleObject && mpViewShell )
            mpViewShell->ActivateObject(pOleObject, pEvent->mnVerb);
    }
    break;
    default:
        break;
    }
}

sal_Int32 SlideshowImpl::getSlideNumberForBookmark( const OUString& rStrBookmark )
{
    bool bIsMasterPage;
    OUString aBookmark = getUiNameFromPageApiNameImpl( rStrBookmark );
    sal_uInt16 nPgNum = mpDoc->GetPageByName( aBookmark, bIsMasterPage );

    if( nPgNum == SDRPAGE_NOTFOUND )
    {
        // Is the bookmark an object?
        SdrObject* pObj = mpDoc->GetObj( aBookmark );

        if( pObj )
        {
            nPgNum = pObj->getSdrPageFromSdrObject()->GetPageNum();
            bIsMasterPage = pObj->getSdrPageFromSdrObject()->IsMasterPage();
        }
    }

    if( (nPgNum == SDRPAGE_NOTFOUND) || bIsMasterPage || static_cast<SdPage*>(mpDoc->GetPage(nPgNum))->GetPageKind() != PageKind::Standard )
        return -1;

    return ( nPgNum - 1) >> 1;
}

void SlideshowImpl::contextMenuShow(const css::awt::Point& point)
{
    maPopupMousePos = { point.X, point.Y };
    mnContextMenuEvent = Application::PostUserEvent(LINK(this, SlideshowImpl, ContextMenuHdl));
}

void SlideshowImpl::hyperLinkClicked( OUString const& aHyperLink )
{
    OUString aBookmark( aHyperLink );

    sal_Int32 nPos = aBookmark.indexOf( '#' );
    if( nPos >= 0 )
    {
        OUString aURL( aBookmark.copy( 0, nPos+1 ) );
        OUString aName( aBookmark.copy( nPos+1 ) );
        aURL += getUiNameFromPageApiNameImpl( aName );
        aBookmark = aURL;
    }

    mpDocSh->OpenBookmark( aBookmark );
}

void SlideshowImpl::displaySlideNumber( sal_Int32 nSlideNumber )
{
    if( mpSlideController )
    {
        if( mpSlideController->jumpToSlideNumber( nSlideNumber ) )
        {
            displayCurrentSlide();
        }
    }
}

/** nSlideIndex == -1 displays current slide again */
void SlideshowImpl::displaySlideIndex( sal_Int32 nSlideIndex )
{
    if( mpSlideController )
    {
        if( (nSlideIndex == -1) || mpSlideController->jumpToSlideIndex( nSlideIndex ) )
        {
            displayCurrentSlide();
        }
    }
}

void SlideshowImpl::jumpToBookmark( const OUString& sBookmark )
{
    sal_Int32 nSlideNumber = getSlideNumberForBookmark( sBookmark );
    if( nSlideNumber != -1 )
        displaySlideNumber( nSlideNumber );
}

sal_Int32 SlideshowImpl::getCurrentSlideNumber() const
{
    return mpSlideController ? mpSlideController->getCurrentSlideNumber() : -1;
}

sal_Bool SAL_CALL SlideshowImpl::isEndless()
{
    SolarMutexGuard aSolarGuard;
    return maPresSettings.mbEndless;
}

void SlideshowImpl::update()
{
    startUpdateTimer();
}

void SlideshowImpl::startUpdateTimer()
{
    SolarMutexGuard aSolarGuard;
    maUpdateTimer.SetTimeout( 0 );
    maUpdateTimer.Start();
}

/** this timer is called 20ms after a new slide was displayed.
    This is used to unfreeze user input that was disabled after
    slide change to skip input that was buffered during slide
    transition preparation */
IMPL_LINK_NOARG(SlideshowImpl, ReadyForNextInputHdl, Timer *, void)
{
    mbInputFreeze = false;
}

/** if I catch someone someday who calls this method by hand
    and not by using the timer, I will personally punish this
    person seriously, even if this person is me.
*/
IMPL_LINK_NOARG(SlideshowImpl, updateHdl, Timer *, void)
{
    updateSlideShow();
}

void SlideshowImpl::updateSlideShow()
{
    // prevent me from deletion when recursing (App::EnableYieldMode does)
    const rtl::Reference<SlideshowImpl> xKeepAlive(this);

    Reference< XSlideShow > xShow( mxShow );
    if ( ! xShow.is())
        return;

    try
    {
        double fUpdate = 0.0;
        if( !xShow->update(fUpdate) )
            fUpdate = -1.0;

        if (mxShow.is() && (fUpdate >= 0.0))
        {
            if (::basegfx::fTools::equalZero(fUpdate))
            {
                // Make sure idle tasks don't starve when we don't have to wait.
                // Don't process any events generated after invoking the function.
                Application::Reschedule(/*bHandleAllCurrentEvents=*/true);
            }
            else
            {
                // Avoid busy loop when the previous call to update()
                // returns a small positive number but not 0 (which is
                // handled above).  Also, make sure that calls to update()
                // have a minimum frequency.
                // => Allow up to 60 frames per second.  Call at least once
                // every 4 seconds.
                const static sal_Int32 nMaximumFrameCount (60);
                const static double nMinimumTimeout (1.0 / nMaximumFrameCount);
                const static double nMaximumTimeout (4.0);
                fUpdate = std::clamp(fUpdate, nMinimumTimeout, nMaximumTimeout);

                // Make sure that the maximum frame count has not been set
                // too high (only then conversion to milliseconds and long
                // integer may lead to zero value.)
                OSL_ASSERT(static_cast<sal_uLong>(fUpdate * 1000.0) > 0);
            }

            // Use our high resolution timers for the asynchronous callback.
            maUpdateTimer.SetTimeout(static_cast<sal_uLong>(fUpdate * 1000.0));
            maUpdateTimer.Start();
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::updateSlideShow()" );
    }
}

bool SlideshowImpl::keyInput(const KeyEvent& rKEvt)
{
    if( !mxShow.is() || mbInputFreeze )
        return false;

    bool bRet = true;

    try
    {
        const int nKeyCode = rKEvt.GetKeyCode().GetCode();
        switch( nKeyCode )
        {
            case awt::Key::CONTEXTMENU:
                if( !mnContextMenuEvent )
                {
                    if( mpShowWindow )
                        maPopupMousePos = mpShowWindow->GetPointerState().maPos;
                    mnContextMenuEvent = Application::PostUserEvent( LINK( this, SlideshowImpl, ContextMenuHdl ) );
                }
                break;

            // cancel show
            case KEY_ESCAPE:
            case KEY_SUBTRACT:
                // in case the user cancels the presentation, switch to current slide
                // in edit mode
                if( mpSlideController && (ANIMATIONMODE_SHOW == meAnimationMode) )
                {
                    if( mpSlideController->getCurrentSlideNumber() != -1 )
                        mnRestoreSlide = mpSlideController->getCurrentSlideNumber();
                }
                endPresentation();
                break;

            // advance show
            case KEY_PAGEDOWN:
                if(rKEvt.GetKeyCode().IsMod2())
                {
                    gotoNextSlide();
                    break;
                }
                [[fallthrough]];
            case KEY_SPACE:
            case KEY_RIGHT:
            case KEY_DOWN:
            case KEY_XF86FORWARD:
                gotoNextEffect();
                break;

            case KEY_RETURN:
            {
                if( !maCharBuffer.isEmpty() )
                {
                    if( mpSlideController )
                    {
                        if( mpSlideController->jumpToSlideNumber( maCharBuffer.toInt32() - 1 ) )
                            displayCurrentSlide();
                    }
                    maCharBuffer.clear();
                }
                else
                {
                    gotoNextEffect();
                }
            }
            break;

            // numeric: add to buffer
            case KEY_0:
            case KEY_1:
            case KEY_2:
            case KEY_3:
            case KEY_4:
            case KEY_5:
            case KEY_6:
            case KEY_7:
            case KEY_8:
            case KEY_9:
                maCharBuffer += OUStringChar( rKEvt.GetCharCode() );
                break;

            case KEY_PAGEUP:
                if(rKEvt.GetKeyCode().IsMod2())
                {
                    gotoPreviousSlide();
                    break;
                }
                [[fallthrough]];
            case KEY_LEFT:
            case KEY_UP:
            case KEY_BACKSPACE:
            case KEY_XF86BACK:
                gotoPreviousEffect();
                break;

            case KEY_P:
                setUsePen( !mbUsePen );
                break;

            // tdf#149351 Ctrl+A disables pointer as pen mode
            case KEY_A:
                if(rKEvt.GetKeyCode().IsMod1())
                {
                    setUsePen( false );
                    break;
                }
            break;

            case KEY_E:
                setEraseAllInk( true );
                updateSlideShow();
                break;

            case KEY_HOME:
                gotoFirstSlide();
                break;

            case KEY_END:
                gotoLastSlide();
                break;

            case KEY_B:
            case KEY_W:
            case KEY_POINT:
            case KEY_COMMA:
            {
                blankScreen( ((nKeyCode == KEY_W ) || (nKeyCode == KEY_COMMA)) ? 0x00ffffff : 0x00000000 );
            }
            break;

            default:
                bRet = false;
            break;
        }
    }
    catch( Exception& )
    {
        bRet = false;
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::keyInput()" );
    }

    return bRet;
}

IMPL_LINK( SlideshowImpl, EventListenerHdl, VclSimpleEvent&, rSimpleEvent, void )
{
    if( !mxShow.is() || mbInputFreeze )
        return;

    if( !((rSimpleEvent.GetId() == VclEventId::WindowCommand) && static_cast<VclWindowEvent*>(&rSimpleEvent)->GetData()) )
        return;

    const CommandEvent& rEvent = *static_cast<const CommandEvent*>(static_cast<VclWindowEvent*>(&rSimpleEvent)->GetData());

    if( rEvent.GetCommand() != CommandEventId::Media )
        return;

    CommandMediaData* pMediaData = rEvent.GetMediaData();
    pMediaData->SetPassThroughToOS(false);
    switch (pMediaData->GetMediaId())
    {
#if defined( MACOSX )
    case MediaCommand::Menu:
        if( !mnContextMenuEvent )
        {
            if( mpShowWindow )
                maPopupMousePos = mpShowWindow->GetPointerState().maPos;
            mnContextMenuEvent = Application::PostUserEvent( LINK( this, SlideshowImpl, ContextMenuHdl ) );
        }
        break;
    case MediaCommand::VolumeDown:
        gotoPreviousSlide();
        break;
    case MediaCommand::VolumeUp:
        gotoNextEffect();
        break;
#endif
    case MediaCommand::NextTrack:
        gotoNextEffect();
        break;
    case MediaCommand::Pause:
        if( !mbIsPaused )
            blankScreen(0);
        break;
    case MediaCommand::Play:
        if( mbIsPaused )
            resume();
        break;

    case MediaCommand::PlayPause:
        if( mbIsPaused )
            resume();
        else
            blankScreen(0);
        break;
    case MediaCommand::PreviousTrack:
        gotoPreviousSlide();
        break;
    case MediaCommand::NextTrackHold:
        gotoLastSlide();
        break;

    case MediaCommand::Rewind:
        gotoFirstSlide();
        break;
    case MediaCommand::Stop:
        // in case the user cancels the presentation, switch to current slide
        // in edit mode
        if( mpSlideController && (ANIMATIONMODE_SHOW == meAnimationMode) )
        {
            if( mpSlideController->getCurrentSlideNumber() != -1 )
                mnRestoreSlide = mpSlideController->getCurrentSlideNumber();
        }
        endPresentation();
        break;
    default:
        pMediaData->SetPassThroughToOS(true);
        break;
    }
}

void SlideshowImpl::mouseButtonUp(const MouseEvent& rMEvt)
{
    if( rMEvt.IsRight() && !mnContextMenuEvent )
    {
        maPopupMousePos = rMEvt.GetPosPixel();
        mnContextMenuEvent = Application::PostUserEvent( LINK( this, SlideshowImpl, ContextMenuHdl ) );
    }
}

IMPL_LINK_NOARG(SlideshowImpl, ContextMenuHdl, void*, void)
{
    mnContextMenuEvent = nullptr;

    if (mpSlideController == nullptr)
        return;

    mbWasPaused = mbIsPaused;
    if( !mbWasPaused )
        pause();

    std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(nullptr, u"modules/simpress/ui/slidecontextmenu.ui"_ustr));
    std::unique_ptr<weld::Menu> xMenu(xBuilder->weld_menu(u"menu"_ustr));
    OUString sNextImage(BMP_MENU_NEXT), sPrevImage(BMP_MENU_PREV);
    xMenu->insert(0, u"next"_ustr, SdResId(RID_SVXSTR_MENU_NEXT), &sNextImage, nullptr, nullptr, TRISTATE_INDET);
    xMenu->insert(1, u"prev"_ustr, SdResId(RID_SVXSTR_MENU_PREV), &sPrevImage, nullptr, nullptr, TRISTATE_INDET);

    // Adding button to display if in Pen  mode
    xMenu->set_active(u"pen"_ustr, mbUsePen);

    const ShowWindowMode eMode = mpShowWindow->GetShowWindowMode();
    xMenu->set_visible(u"next"_ustr, mpSlideController->getNextSlideIndex() != -1);
    xMenu->set_visible(u"prev"_ustr, (mpSlideController->getPreviousSlideIndex() != -1 ) || (eMode == SHOWWINDOWMODE_END) || (eMode == SHOWWINDOWMODE_PAUSE) || (eMode == SHOWWINDOWMODE_BLANK));
    xMenu->set_visible(u"edit"_ustr, mpViewShell->GetDoc()->GetStartWithPresentation() != 0);

    std::unique_ptr<weld::Menu> xPageMenu(xBuilder->weld_menu(u"gotomenu"_ustr));
    OUString sFirstImage(BMP_MENU_FIRST), sLastImage(BMP_MENU_LAST);
    xPageMenu->insert(0, u"first"_ustr, SdResId(RID_SVXSTR_MENU_FIRST), &sFirstImage, nullptr, nullptr, TRISTATE_INDET);
    xPageMenu->insert(1, u"last"_ustr, SdResId(RID_SVXSTR_MENU_LAST), &sLastImage, nullptr, nullptr, TRISTATE_INDET);

    // populate slide goto list
    const sal_Int32 nPageNumberCount = mpSlideController->getSlideNumberCount();
    if( nPageNumberCount <= 1 )
    {
        xMenu->set_visible(u"goto"_ustr, false);
    }
    else
    {
        sal_Int32 nCurrentSlideNumber = mpSlideController->getCurrentSlideNumber();
        if( (eMode == SHOWWINDOWMODE_END) || (eMode == SHOWWINDOWMODE_PAUSE) || (eMode == SHOWWINDOWMODE_BLANK) )
            nCurrentSlideNumber = -1;

        xPageMenu->set_visible(u"first"_ustr, mpSlideController->getSlideNumber(0) != nCurrentSlideNumber);
        xPageMenu->set_visible(u"last"_ustr, mpSlideController->getSlideNumber(mpSlideController->getSlideIndexCount() - 1) != nCurrentSlideNumber);

        sal_Int32 nPageNumber;

        for( nPageNumber = 0; nPageNumber < nPageNumberCount; nPageNumber++ )
        {
            if( mpSlideController->isVisibleSlideNumber( nPageNumber ) )
            {
                SdPage* pPage = mpDoc->GetSdPage(static_cast<sal_uInt16>(nPageNumber), PageKind::Standard);
                if (pPage)
                {
                    OUString sId(OUString::number(CM_SLIDES + nPageNumber));
                    xPageMenu->append_check(sId, pPage->GetName());
                    if (nPageNumber == nCurrentSlideNumber)
                        xPageMenu->set_active(sId, true);
                }
            }
        }
    }

    std::unique_ptr<weld::Menu> xBlankMenu(xBuilder->weld_menu(u"screenmenu"_ustr));

    if (mpShowWindow->GetShowWindowMode() == SHOWWINDOWMODE_BLANK)
    {
        xBlankMenu->set_active((mpShowWindow->GetBlankColor() == COL_WHITE) ? "white" : "black", true);
    }

    std::unique_ptr<weld::Menu> xWidthMenu(xBuilder->weld_menu(u"widthmenu"_ustr));

    // populate color width list
    sal_Int32 nIterator;
    double nWidth;

    nWidth = 4.0;
    for( nIterator = 1; nIterator < 6; nIterator++)
    {
        switch(nIterator)
        {
            case 1:
                nWidth = 4.0;
                break;
            case 2:
                nWidth = 100.0;
                break;
            case 3:
                nWidth = 150.0;
                break;
            case 4:
                nWidth = 200.0;
                break;
            case 5:
                nWidth = 400.0;
                break;
            default:
                break;
        }

        if (nWidth == mdUserPaintStrokeWidth)
            xWidthMenu->set_active(OUString::number(nWidth), true);
    }

    ::tools::Rectangle aRect(maPopupMousePos, Size(1,1));
    weld::Window* pParent = weld::GetPopupParent(*mpShowWindow, aRect);
    ContextMenuSelectHdl(xMenu->popup_at_rect(pParent, aRect));

    if( mxView.is() )
        mxView->ignoreNextMouseReleased();

    if( !mbWasPaused )
        resume();
}

void SlideshowImpl::ContextMenuSelectHdl(std::u16string_view rMenuId)
{
    if (rMenuId == u"prev")
    {
        gotoPreviousSlide();
        mbWasPaused = false;
    }
    else if(rMenuId == u"next")
    {
        gotoNextSlide();
        mbWasPaused = false;
    }
    else if (rMenuId == u"first")
    {
        gotoFirstSlide();
        mbWasPaused = false;
    }
    else if (rMenuId == u"last")
    {
        gotoLastSlide();
        mbWasPaused = false;
    }
    else if (rMenuId == u"black" || rMenuId == u"white")
    {
        const Color aBlankColor(rMenuId == u"white" ? COL_WHITE : COL_BLACK);
        if( mbWasPaused )
        {
            if( mpShowWindow->GetShowWindowMode() == SHOWWINDOWMODE_BLANK )
            {
                if( mpShowWindow->GetBlankColor() == aBlankColor )
                {
                    mbWasPaused = false;
                    mpShowWindow->RestartShow();
                    return;
                }
            }
            mpShowWindow->RestartShow();
        }
        if( mpShowWindow->SetBlankMode( mpSlideController->getCurrentSlideIndex(), aBlankColor ) )
        {
            pause();
            mbWasPaused = true;
        }
    }
    else if (rMenuId == u"color")
    {
        //Open a color picker based on SvColorDialog
        ::Color aColor( ColorTransparency, mnUserPaintColor );
        ColorDialog aColorDlg(mpShowWindow->GetFrameWeld());
        aColorDlg.SetColor( aColor );

        if (aColorDlg.Execute())
        {
            aColor = aColorDlg.GetColor();
            setPenColor(sal_Int32(aColor));
        }
        mbWasPaused = false;
    }
    else if (rMenuId == u"4")
    {
        setPenWidth(4.0);
        mbWasPaused = false;
    }
    else if (rMenuId == u"100")
    {
        setPenWidth(100.0);
        mbWasPaused = false;
    }
    else if (rMenuId == u"150")
    {
        setPenWidth(150.0);
        mbWasPaused = false;
    }
    else if (rMenuId == u"200")
    {
        setPenWidth(200.0);
        mbWasPaused = false;
    }
    else if (rMenuId == u"400")
    {
        setPenWidth(400.0);
        mbWasPaused = false;
    }
    else if (rMenuId == u"erase")
    {
        setEraseAllInk(true);
        mbWasPaused = false;
    }
    else if (rMenuId == u"pen")
    {
        setUsePen(!mbUsePen);
        mbWasPaused = false;
    }
    else if (rMenuId == u"edit")
    {
        // When in autoplay mode (pps/ppsx), offer editing of the presentation
        // Turn autostart off, else Impress will close when exiting the Presentation
        mpViewShell->GetDoc()->SetExitAfterPresenting(false);
        if( mpSlideController && (ANIMATIONMODE_SHOW == meAnimationMode) )
        {
            if( mpSlideController->getCurrentSlideNumber() != -1 )
            {
                mnRestoreSlide = mpSlideController->getCurrentSlideNumber();
            }
        }
        endPresentation();
    }
    else if (rMenuId == u"end")
    {
        // in case the user cancels the presentation, switch to current slide
        // in edit mode
        if( mpSlideController && (ANIMATIONMODE_SHOW == meAnimationMode) )
        {
            if( mpSlideController->getCurrentSlideNumber() != -1 )
            {
                mnRestoreSlide = mpSlideController->getCurrentSlideNumber();
            }
        }
        endPresentation();
    }
    else if (!rMenuId.empty())
    {
        sal_Int32 nPageNumber = o3tl::toInt32(rMenuId) - CM_SLIDES;
        const ShowWindowMode eMode = mpShowWindow->GetShowWindowMode();
        if( (eMode == SHOWWINDOWMODE_END) || (eMode == SHOWWINDOWMODE_PAUSE) || (eMode == SHOWWINDOWMODE_BLANK) )
        {
            mpShowWindow->RestartShow( nPageNumber );
        }
        else if( nPageNumber != mpSlideController->getCurrentSlideNumber() )
        {
            displaySlideNumber( nPageNumber );
        }
        mbWasPaused = false;
    }
}

Reference< XSlideShow > SlideshowImpl::createSlideShow()
{
    Reference< XSlideShow > xShow;

    try
    {
        const Reference< uno::XComponentContext >& xContext =
            ::comphelper::getProcessComponentContext();

        xShow.set( presentation::SlideShow::create(xContext), UNO_SET_THROW );
    }
    catch( uno::Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::createSlideShow()" );
    }

    return xShow;
}

void SlideshowImpl::createSlideList( bool bAll, std::u16string_view rPresSlide )
{
    const sal_uInt16 nSlideCount = mpDoc->GetSdPageCount( PageKind::Standard );

    if( !nSlideCount )
        return;

    SdCustomShow*   pCustomShow;

    if( mpDoc->GetCustomShowList() && maPresSettings.mbCustomShow )
        pCustomShow = mpDoc->GetCustomShowList()->GetCurObject();
    else
        pCustomShow = nullptr;

    // create animation slide controller
    AnimationSlideController::Mode eMode =
        ( pCustomShow && !pCustomShow->PagesVector().empty() ) ? AnimationSlideController::CUSTOM :
            (bAll ? AnimationSlideController::ALL : AnimationSlideController::FROM);

    rtl::Reference< SdXImpressDocument > xDrawPages( mpDoc->getUnoModel() );
    Reference< XIndexAccess > xSlides( xDrawPages->getDrawPages(), UNO_QUERY_THROW );
    mpSlideController = std::make_unique<AnimationSlideController>( xSlides, eMode );

    if( eMode != AnimationSlideController::CUSTOM )
    {
        sal_Int32 nFirstVisibleSlide = 0;

        // normal presentation
        if( !rPresSlide.empty() )
        {
            sal_Int32 nSlide;
            bool bTakeNextAvailable = false;

            for( nSlide = 0, nFirstVisibleSlide = -1;
                ( nSlide < nSlideCount ) && ( -1 == nFirstVisibleSlide ); nSlide++ )
            {
                SdPage* pTestSlide = mpDoc->GetSdPage( static_cast<sal_uInt16>(nSlide), PageKind::Standard );

                if( pTestSlide->GetName() == rPresSlide )
                {
                    if( pTestSlide->IsExcluded() )
                        bTakeNextAvailable = true;
                    else
                        nFirstVisibleSlide = nSlide;
                }
                else if( bTakeNextAvailable && !pTestSlide->IsExcluded() )
                    nFirstVisibleSlide = nSlide;
            }

            if( -1 == nFirstVisibleSlide )
                nFirstVisibleSlide = 0;
        }

        for( sal_Int32 i = 0; i < nSlideCount; i++ )
        {
            bool bVisible = ! mpDoc->GetSdPage( static_cast<sal_uInt16>(i), PageKind::Standard )->IsExcluded();
            if( bVisible || (eMode == AnimationSlideController::ALL) )
                mpSlideController->insertSlideNumber( i, bVisible );
        }

        mpSlideController->setStartSlideNumber( nFirstVisibleSlide );
    }
    else
    {
        if( meAnimationMode != ANIMATIONMODE_SHOW && !rPresSlide.empty() )
        {
            sal_Int32 nSlide;
            for( nSlide = 0; nSlide < nSlideCount; nSlide++ )
                if( rPresSlide == mpDoc->GetSdPage( static_cast<sal_uInt16>(nSlide), PageKind::Standard )->GetName() )
                    break;

            if( nSlide < nSlideCount )
                mpSlideController->insertSlideNumber( static_cast<sal_uInt16>(nSlide) );
        }

        for( const auto& rpPage : pCustomShow->PagesVector() )
        {
            const sal_uInt16 nSdSlide = ( rpPage->GetPageNum() - 1 ) / 2;

            if( ! mpDoc->GetSdPage( nSdSlide, PageKind::Standard )->IsExcluded())
                mpSlideController->insertSlideNumber( nSdSlide );
        }
    }
}

typedef sal_uInt16 (*FncGetChildWindowId)();

const FncGetChildWindowId aShowChildren[] =
{
    &AnimationChildWindow::GetChildWindowId,
    &Svx3DChildWindow::GetChildWindowId,
    &SvxFontWorkChildWindow::GetChildWindowId,
    &SvxColorChildWindow::GetChildWindowId,
    &SvxSearchDialogWrapper::GetChildWindowId,
    &SvxBmpMaskChildWindow::GetChildWindowId,
    &SvxIMapDlgChildWindow::GetChildWindowId,
    &SvxHlinkDlgWrapper::GetChildWindowId,
    &SfxInfoBarContainerChild::GetChildWindowId
};

void SlideshowImpl::hideChildWindows()
{
    mnChildMask = 0;

    if( ANIMATIONMODE_SHOW != meAnimationMode )
        return;

    SfxViewFrame* pViewFrame = getViewFrame();

    if( !pViewFrame )
        return;

    for( sal_uLong i = 0; i < SAL_N_ELEMENTS( aShowChildren ); i++ )
    {
        const sal_uInt16 nId = ( *aShowChildren[ i ] )();

        if( pViewFrame->GetChildWindow( nId ) )
        {
            pViewFrame->SetChildWindow( nId, false );
            mnChildMask |= ::tools::ULong(1) << i;
        }
    }
}

void SlideshowImpl::showChildWindows()
{
    if( ANIMATIONMODE_SHOW == meAnimationMode )
    {
        SfxViewFrame* pViewFrame = getViewFrame();
        if( pViewFrame )
        {
            for( sal_uLong i = 0; i < SAL_N_ELEMENTS(aShowChildren); i++ )
            {
                if( mnChildMask & ( ::tools::ULong(1) << i ) )
                    pViewFrame->SetChildWindow( ( *aShowChildren[ i ] )(), true );
            }
        }
    }
}

SfxViewFrame* SlideshowImpl::getViewFrame() const
{
    return mpViewShell ? mpViewShell->GetViewFrame() : nullptr;
}

SfxDispatcher* SlideshowImpl::getDispatcher() const
{
    return (mpViewShell && mpViewShell->GetViewFrame()) ? mpViewShell->GetViewFrame()->GetDispatcher() : nullptr;
}

SfxBindings* SlideshowImpl::getBindings() const
{
    return (mpViewShell && mpViewShell->GetViewFrame()) ? &mpViewShell->GetViewFrame()->GetBindings() : nullptr;
}

void SlideshowImpl::resize( const Size& rSize )
{
    maPresSize = rSize;

    if(mpShowWindow)
    {
        mpShowWindow->SetSizePixel( maPresSize );
        mpShowWindow->Show();
    }

    if( mxView.is() ) try
    {
        awt::WindowEvent aEvt;
        mxView->windowResized(aEvt);
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::resize()" );
    }
}

void SlideshowImpl::setActiveXToolbarsVisible( bool bVisible )
{
    // in case of ActiveX control the toolbars should not be visible if slide show runs in window mode
    // actually it runs always in window mode in case of ActiveX control
    if ( !(!maPresSettings.mbFullScreen && mpDocSh && mpDocSh->GetMedium()) )
        return;

    const SfxBoolItem* pItem = mpDocSh->GetMedium()->GetItemSet().GetItem(SID_VIEWONLY, false);
    if ( !(pItem && pItem->GetValue()) )
        return;

    // this is a plugin/activex mode, no toolbars should be visible during slide show
    // after the end of slide show they should be visible again
    SfxViewFrame* pViewFrame = getViewFrame();
    if( !pViewFrame )
        return;

    try
    {
        Reference< frame::XLayoutManager > xLayoutManager;
        Reference< beans::XPropertySet > xFrameProps( pViewFrame->GetFrame().GetFrameInterface(), UNO_QUERY_THROW );
        if ( ( xFrameProps->getPropertyValue( u"LayoutManager"_ustr )
                    >>= xLayoutManager )
          && xLayoutManager.is() )
        {
            xLayoutManager->setVisible( bVisible );
        }
    }
    catch( uno::Exception& )
    {}
}

void SAL_CALL SlideshowImpl::activate()
{
    SolarMutexGuard aSolarGuard;

    maDeactivateTimer.Stop();

    if( mbActive || !mxShow.is() )
        return;

    mbActive = true;

    if( ANIMATIONMODE_SHOW == meAnimationMode )
    {
        if( mbAutoSaveWasOn )
            setAutoSaveState( false );

        if( mpShowWindow )
        {
            SfxViewFrame* pViewFrame = getViewFrame();
            SfxDispatcher* pDispatcher = pViewFrame ? pViewFrame->GetDispatcher() : nullptr;

            hideChildWindows();

            if( pDispatcher )
            {
                // filter all forbidden slots
                pDispatcher->SetSlotFilter( SfxSlotFilterState::ENABLED, pAllowed );
            }

            if( getBindings() )
                getBindings()->InvalidateAll(true);

            mpShowWindow->GrabFocus();
        }
    }

    resume();
}

void SAL_CALL SlideshowImpl::deactivate()
{
    SolarMutexGuard aSolarGuard;

    if( mbActive && mxShow.is() )
    {
        maDeactivateTimer.Start();
    }
}

IMPL_LINK_NOARG(SlideshowImpl, deactivateHdl, Timer *, void)
{
    if( !(mbActive && mxShow.is()) )
        return;

    mbActive = false;

    pause();

    if( ANIMATIONMODE_SHOW == meAnimationMode )
    {
        if( mbAutoSaveWasOn )
            setAutoSaveState( true );

        if( mpShowWindow )
        {
            showChildWindows();
        }
    }
}

sal_Bool SAL_CALL SlideshowImpl::isActive()
{
    SolarMutexGuard aSolarGuard;
    return mbActive;
}

void SlideshowImpl::setAutoSaveState( bool bOn)
{
    try
    {
        const uno::Reference<uno::XComponentContext>& xContext( ::comphelper::getProcessComponentContext() );

        uno::Reference< util::XURLTransformer > xParser(util::URLTransformer::create(xContext));
        util::URL aURL;
        aURL.Complete = "vnd.sun.star.autorecovery:/setAutoSaveState";
        xParser->parseStrict(aURL);

        Sequence< beans::PropertyValue > aArgs{ comphelper::makePropertyValue(u"AutoSaveState"_ustr, bOn) };

        uno::Reference< frame::XDispatch > xAutoSave = frame::theAutoRecovery::get(xContext);
        xAutoSave->dispatch(aURL, aArgs);
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::setAutoSaveState()");
    }
}

Reference< XDrawPage > SAL_CALL SlideshowImpl::getCurrentSlide()
{
    SolarMutexGuard aSolarGuard;

    Reference< XDrawPage > xSlide;
    if( mxShow.is() && mpSlideController )
    {
        sal_Int32 nSlide = getCurrentSlideNumber();
        if( (nSlide >= 0) && (nSlide < mpSlideController->getSlideNumberCount() ) )
            xSlide = mpSlideController->getSlideByNumber( nSlide );
    }

    return xSlide;
}

sal_Int32 SAL_CALL SlideshowImpl::getNextSlideIndex()
{
    SolarMutexGuard aSolarGuard;

    if( mxShow.is() )
    {
        return mpSlideController->getNextSlideIndex();
    }
    else
    {
        return -1;
    }
}

sal_Int32 SAL_CALL SlideshowImpl::getCurrentSlideIndex()
{
    return mpSlideController ? mpSlideController->getCurrentSlideIndex() : -1;
}

// css::presentation::XSlideShowController:

::sal_Int32 SAL_CALL SlideshowImpl::getSlideCount()
{
    return mpSlideController ? mpSlideController->getSlideIndexCount() : 0;
}

Reference< XDrawPage > SAL_CALL SlideshowImpl::getSlideByIndex(::sal_Int32 Index)
{
    if ((mpSlideController == nullptr) || (Index < 0)
        || (Index >= mpSlideController->getSlideIndexCount()))
        throw IndexOutOfBoundsException();

    return mpSlideController->getSlideByNumber( mpSlideController->getSlideNumber( Index ) );
}

sal_Bool SAL_CALL SlideshowImpl::getAlwaysOnTop()
{
    SolarMutexGuard aSolarGuard;
    return maPresSettings.mbAlwaysOnTop;
}

void SAL_CALL SlideshowImpl::setAlwaysOnTop( sal_Bool bAlways )
{
    SolarMutexGuard aSolarGuard;
    if( maPresSettings.mbAlwaysOnTop != bool(bAlways) )
    {
        maPresSettings.mbAlwaysOnTop = bAlways;
        // todo, can this be changed while running?
    }
}

sal_Bool SAL_CALL SlideshowImpl::isFullScreen()
{
    SolarMutexGuard aSolarGuard;
    return maPresSettings.mbFullScreen;
}

sal_Bool SAL_CALL SlideshowImpl::getMouseVisible()
{
    SolarMutexGuard aSolarGuard;
    return maPresSettings.mbMouseVisible;
}

void SAL_CALL SlideshowImpl::setMouseVisible( sal_Bool bVisible )
{
    SolarMutexGuard aSolarGuard;
    if( maPresSettings.mbMouseVisible != bool(bVisible) )
    {
        maPresSettings.mbMouseVisible = bVisible;
        if( mpShowWindow )
            mpShowWindow->SetMouseAutoHide( !maPresSettings.mbMouseVisible );
    }
}

sal_Bool SAL_CALL SlideshowImpl::getUsePen()
{
    SolarMutexGuard aSolarGuard;
    return mbUsePen;
}

void SAL_CALL SlideshowImpl::setUsePen( sal_Bool bMouseAsPen )
{
    SolarMutexGuard aSolarGuard;
    mbUsePen = bMouseAsPen;
    if( !mxShow.is() )
        return;

    try
    {
        // For Pencolor;
        beans::PropertyValue aPenProp;
        aPenProp.Name = "UserPaintColor";
        if( mbUsePen )
            aPenProp.Value <<= mnUserPaintColor;
        mxShow->setProperty( aPenProp );

        //for StrokeWidth :
        if( mbUsePen )
        {
            beans::PropertyValue aPenPropWidth;
            aPenPropWidth.Name = "UserPaintStrokeWidth";
            aPenPropWidth.Value <<= mdUserPaintStrokeWidth;
            mxShow->setProperty( aPenPropWidth );

            // for Pen Mode
            beans::PropertyValue aPenPropSwitchPenMode;
            aPenPropSwitchPenMode.Name = "SwitchPenMode";
            aPenPropSwitchPenMode.Value <<= true;
            mxShow->setProperty( aPenPropSwitchPenMode );
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::setUsePen()" );
    }
}

double SAL_CALL SlideshowImpl::getPenWidth()
{
    SolarMutexGuard aSolarGuard;
    return mdUserPaintStrokeWidth;
}

void SAL_CALL SlideshowImpl::setPenWidth( double dStrokeWidth )
{
    SolarMutexGuard aSolarGuard;
    mdUserPaintStrokeWidth = dStrokeWidth;
    setUsePen( true ); // enable pen mode, update color and width
}

sal_Int32 SAL_CALL SlideshowImpl::getPenColor()
{
    SolarMutexGuard aSolarGuard;
    return mnUserPaintColor;
}

void SAL_CALL SlideshowImpl::setPenColor( sal_Int32 nColor )
{
    SolarMutexGuard aSolarGuard;
    mnUserPaintColor = nColor;
    setUsePen( true ); // enable pen mode, update color
}

void SAL_CALL SlideshowImpl::setEraseAllInk(sal_Bool bEraseAllInk)
{
    if( !bEraseAllInk )
        return;

    SolarMutexGuard aSolarGuard;
    if( !mxShow.is() )
        return;

    try
    {
        beans::PropertyValue aPenPropEraseAllInk;
        aPenPropEraseAllInk.Name = "EraseAllInk";
        aPenPropEraseAllInk.Value <<= bEraseAllInk;
        mxShow->setProperty( aPenPropEraseAllInk );
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd.slideshow", "sd::SlideshowImpl::setEraseAllInk()" );
    }
}

// XSlideShowController Methods
sal_Bool SAL_CALL SlideshowImpl::isRunning(  )
{
    SolarMutexGuard aSolarGuard;
    return mxShow.is();
}

void SAL_CALL SlideshowImpl::gotoNextEffect(  )
{
    SolarMutexGuard aSolarGuard;

    if( !(mxShow.is() && mpSlideController && mpShowWindow) )
        return;

    if( mbIsPaused && mpShowWindow->GetShowWindowMode() != SHOWWINDOWMODE_END )
        resume();

    const ShowWindowMode eMode = mpShowWindow->GetShowWindowMode();
    if( eMode == SHOWWINDOWMODE_END )
    {
        endPresentation();
    }
    else if( (eMode == SHOWWINDOWMODE_PAUSE) || (eMode == SHOWWINDOWMODE_BLANK) )
    {
        mpShowWindow->RestartShow();
    }
    else
    {
        mxShow->nextEffect();
        update();
    }
}

void SAL_CALL SlideshowImpl::gotoPreviousEffect(  )
{
    SolarMutexGuard aSolarGuard;

    if( !(mxShow.is() && mpSlideController && mpShowWindow) )
        return;

    const ShowWindowMode eMode = mpShowWindow->GetShowWindowMode();
    if( (eMode == SHOWWINDOWMODE_PAUSE) || (eMode == SHOWWINDOWMODE_BLANK) || mbIsPaused )
    {
        resume();
    }
    else
    {
        mxShow->previousEffect();
        update();
    }
}

void SAL_CALL SlideshowImpl::gotoFirstSlide(  )
{
    SolarMutexGuard aSolarGuard;

    if( !(mpShowWindow && mpSlideController) )
        return;

    if( mbIsPaused )
        resume();

    if( mpShowWindow->GetShowWindowMode() == SHOWWINDOWMODE_END )
    {
        if( mpSlideController->getSlideIndexCount() )
            mpShowWindow->RestartShow( 0);
    }
    else
    {
        displaySlideIndex( 0 );
    }
}

void SAL_CALL SlideshowImpl::gotoNextSlide(  )
{
    SolarMutexGuard aSolarGuard;

    if( mbIsPaused )
        resume();

    const ShowWindowMode eMode = mpShowWindow->GetShowWindowMode();
    if( (eMode == SHOWWINDOWMODE_PAUSE) || (eMode == SHOWWINDOWMODE_BLANK) )
    {
        mpShowWindow->RestartShow();
    }
    else
    {
        // if this is a show, ignore user inputs and
        // start 20ms timer to reenable inputs to filter
        // buffered inputs during slide transition
        if( meAnimationMode == ANIMATIONMODE_SHOW )
        {
            mbInputFreeze = true;
            maInputFreezeTimer.Start();
        }

        if( mpSlideController )
        {
            if( mpSlideController->nextSlide() )
            {
                displayCurrentSlide();
            }
            else
            {
                stopSound();

                if( meAnimationMode == ANIMATIONMODE_PREVIEW )
                {
                    endPresentation();
                }
                else if( maPresSettings.mbEndless )
                {
                    if( maPresSettings.mnPauseTimeout )
                    {
                        if( mpShowWindow )
                        {
                            if ( maPresSettings.mbShowPauseLogo )
                            {
                                Graphic aGraphic(SfxApplication::GetApplicationLogo(360));
                                mpShowWindow->SetPauseMode( maPresSettings.mnPauseTimeout, &aGraphic );
                            }
                            else
                                mpShowWindow->SetPauseMode( maPresSettings.mnPauseTimeout );
                        }
                    }
                    else
                    {
                        displaySlideIndex( 0 );
                    }
                }
                else
                {
                    if( mpShowWindow )
                    {
                        mpShowWindow->SetEndMode();
                        if (!mpViewShell->GetDoc()->GetStartWithPresentation())
                            pause();
                    }
                }
            }
        }
    }
}

void SAL_CALL SlideshowImpl::gotoPreviousSlide(  )
{
    gotoPreviousSlide(false);
}

void SlideshowImpl::gotoPreviousSlide (const bool bSkipAllMainSequenceEffects)
{
    SolarMutexGuard aSolarGuard;

    if( !(mxShow.is() && mpSlideController) )
        return;

    try
    {
        if( mbIsPaused )
            resume();

        const ShowWindowMode eMode = mpShowWindow->GetShowWindowMode();
        if( eMode == SHOWWINDOWMODE_END )
        {
            mpShowWindow->RestartShow( mpSlideController->getCurrentSlideIndex() );
        }
        else if( (eMode == SHOWWINDOWMODE_PAUSE) || (eMode == SHOWWINDOWMODE_BLANK) )
        {
            mpShowWindow->RestartShow();
        }
        else
        {
            if( mpSlideController->previousSlide())
                displayCurrentSlide(bSkipAllMainSequenceEffects);
            else if (bSkipAllMainSequenceEffects)
            {
                // We could not go to the previous slide (probably because
                // the current slide is already the first one).  We still
                // have to call displayCurrentSlide because the calling
                // slideshow can not determine whether there is a previous
                // slide or not and has already prepared for a slide change.
                // This slide change has to be completed now, even when
                // changing to the same slide.
                // Note that in this special case we do NOT pass
                // bSkipAllMainSequenceEffects because we display the same
                // slide as before and do not want to show all its effects.
                displayCurrentSlide();
            }
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::gotoPreviousSlide()" );
    }
}

void SAL_CALL SlideshowImpl::gotoLastSlide()
{
    SolarMutexGuard aSolarGuard;

    if( !mpSlideController )
        return;

    if( mbIsPaused )
        resume();

    const sal_Int32 nLastSlideIndex = mpSlideController->getSlideIndexCount() - 1;
    if( nLastSlideIndex >= 0 )
    {
        if( mpShowWindow->GetShowWindowMode() == SHOWWINDOWMODE_END )
        {
            mpShowWindow->RestartShow( nLastSlideIndex );
        }
        else
        {
            displaySlideIndex( nLastSlideIndex );
        }
    }
}

void SAL_CALL SlideshowImpl::gotoBookmark( const OUString& rBookmark )
{
    SolarMutexGuard aSolarGuard;

    if( mbIsPaused )
        resume();

    sal_Int32 nSlideNumber = getSlideNumberForBookmark( rBookmark );
    if( nSlideNumber != -1 )
        displaySlideNumber( nSlideNumber );
}

void SAL_CALL SlideshowImpl::gotoSlide( const Reference< XDrawPage >& xSlide )
{
    SolarMutexGuard aSolarGuard;

    if( !(mpSlideController && xSlide.is()) )
        return;

    if( mbIsPaused )
        resume();

    const sal_Int32 nSlideCount = mpSlideController->getSlideNumberCount();
    for( sal_Int32 nSlide = 0; nSlide < nSlideCount; nSlide++ )
    {
        if( mpSlideController->getSlideByNumber( nSlide ) == xSlide )
        {
            displaySlideNumber( nSlide );
        }
    }
}

void SAL_CALL SlideshowImpl::gotoSlideIndex( sal_Int32 nIndex )
{
    SolarMutexGuard aSolarGuard;

    if( mbIsPaused )
        resume();

    displaySlideIndex( nIndex );
}

void SAL_CALL SlideshowImpl::stopSound(  )
{
    SolarMutexGuard aSolarGuard;

    try
    {
        if( mxPlayer.is() )
        {
            mxPlayer->stop();
            mxPlayer.clear();
        }
    }
    catch( Exception& )
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::SlideshowImpl::stopSound()" );
    }
}

// XIndexAccess

::sal_Int32 SAL_CALL SlideshowImpl::getCount(  )
{
    return getSlideCount();
}

css::uno::Any SAL_CALL SlideshowImpl::getByIndex( ::sal_Int32 Index )
{
    return Any( getSlideByIndex( Index ) );
}

css::uno::Type SAL_CALL SlideshowImpl::getElementType(  )
{
    return cppu::UnoType<XDrawPage>::get();
}

sal_Bool SAL_CALL SlideshowImpl::hasElements(  )
{
    return getSlideCount() != 0;
}

namespace
{
    class AsyncUpdateSlideshow_Impl
    {
    public:
        struct AsyncUpdateSlideshowData
        {
            SlideshowImpl* pSlideshowImpl;
            uno::Reference< css::drawing::XDrawPage > XCurrentSlide;
            SdrHintKind eHintKind;
        };

        static ImplSVEvent* AsyncUpdateSlideshow(
            SlideshowImpl* pSlideshowImpl,
            const uno::Reference< css::drawing::XDrawPage >& rXCurrentSlide,
            SdrHintKind eHintKind)
        {
            AsyncUpdateSlideshowData* pNew(new AsyncUpdateSlideshowData);
            pNew->pSlideshowImpl = pSlideshowImpl;
            pNew->XCurrentSlide = rXCurrentSlide;
            pNew->eHintKind = eHintKind;
            return Application::PostUserEvent(LINK(nullptr, AsyncUpdateSlideshow_Impl, Update), pNew);
            // coverity[leaked_storage] - pDisruptor takes care of its own destruction at idle time
        }

        DECL_STATIC_LINK(AsyncUpdateSlideshow_Impl, Update, void*, void);
    };

    IMPL_STATIC_LINK(AsyncUpdateSlideshow_Impl, Update, void*, pData, void)
    {
        AsyncUpdateSlideshowData* pSlideData(static_cast<AsyncUpdateSlideshowData*>(pData));
        pSlideData->pSlideshowImpl->AsyncNotifyEvent(pSlideData->XCurrentSlide, pSlideData->eHintKind);
        delete pSlideData;
    }
}

void SlideshowImpl::AsyncNotifyEvent(
    const uno::Reference< css::drawing::XDrawPage >& rXCurrentSlide,
    const SdrHintKind eHintKind)
{
    switch (eHintKind)
    {
        case SdrHintKind::ObjectInserted:
        {
            mnEventObjectInserted = nullptr;

            // refresh single slide
            gotoSlide(rXCurrentSlide);
            break;
        }
        case SdrHintKind::ObjectRemoved:
        {
            mnEventObjectRemoved = nullptr;

            // refresh single slide
            gotoSlide(rXCurrentSlide);
            break;
        }
        case SdrHintKind::ObjectChange:
        {
            mnEventObjectChange = nullptr;

            // refresh single slide
            gotoSlide(rXCurrentSlide);
            break;
        }
        case SdrHintKind::PageOrderChange:
        {
            mnEventPageOrderChange = nullptr;

            // order of pages (object pages or master pages) changed (Insert/Remove/ChangePos)
            // rXCurrentSlide is the current slide before the change.
            rtl::Reference< SdXImpressDocument > xDrawPages( mpDoc->getUnoModel() );
            Reference< XIndexAccess > xSlides( xDrawPages->getDrawPages(), UNO_QUERY_THROW );
            const sal_Int32 nNewSlideCount(xSlides.is() ? xSlides->getCount() : 0);

            if (nNewSlideCount != mpSlideController->getSlideNumberCount())
            {
                // need to reinitialize AnimationSlideController
                OUString aPresSlide( maPresSettings.maPresPage );
                createSlideList( maPresSettings.mbAll, aPresSlide );
            }

            // Check if current slide before change is still valid (maybe removed)
            const sal_Int32 nSlideCount(mpSlideController->getSlideNumberCount());
            bool bSlideStillValid(false);

            for (sal_Int32 nSlide(0); !bSlideStillValid && nSlide < nSlideCount; nSlide++)
            {
                if (rXCurrentSlide == mpSlideController->getSlideByNumber(nSlide))
                {
                    bSlideStillValid = true;
                }
            }

            if(bSlideStillValid)
            {
                // stay on that slide
                gotoSlide(rXCurrentSlide);
            }
            else
            {
                // not possible to stay on that slide, go to 1st slide (kinda restart)
                gotoFirstSlide();
            }
            break;
        }
        default:
            break;
    }
}

bool SlideshowImpl::isCurrentSlideInvolved(const SdrHint& rHint)
{
    // get current slide
    uno::Reference< css::drawing::XDrawPage > XCurrentSlide(getCurrentSlide());
    if (!XCurrentSlide.is())
        return false;

    SdrPage* pCurrentSlide(GetSdrPageFromXDrawPage(XCurrentSlide));
    if (nullptr == pCurrentSlide)
        return false;

    const SdrPage* pHintPage(rHint.GetPage());
    if (nullptr == pHintPage)
        return false;

    if (pHintPage->IsMasterPage())
    {
        if (pCurrentSlide->TRG_HasMasterPage())
        {
            // current slide uses MasterPage on which the change happened
            return pHintPage == &pCurrentSlide->TRG_GetMasterPage();
        }
    }

    // object on current slide was changed
    return pHintPage == pCurrentSlide;
}

void SlideshowImpl::sendHintSlideChanged(const SdrPage* pChangedPage) const
{
    if (nullptr == pChangedPage)
        return;

    if (!mxShow.is())
        return;

    mxShow->setProperty(
        beans::PropertyValue( u"HintSlideChanged"_ustr ,
            -1,
            Any( GetXDrawPageForSdrPage(const_cast<SdrPage*>(pChangedPage)) ),
            beans::PropertyState_DIRECT_VALUE ) );
}

void SlideshowImpl::Notify(SfxBroadcaster& /*rBC*/, const SfxHint& rHint)
{
    if (SfxHintId::ThisIsAnSdrHint != rHint.GetId())
        // nothing to do for non-SdrHints
        return;

    if (nullptr == mpDoc)
        // better do nothing when no DrawModel (should not happen)
        return;

    // tdf#158664 I am surprised, but the 'this' instance keeps incarnated
    // when the slideshow was running once, so need to check for
    // SlideShow instance/running to be safe.
    // NOTE: isRunning() checks mxShow.is(), that is what we want
    if (!isRunning())
        // no SlideShow instance or not running, nothing to do
        return;

    const SdrHint& rSdrHint(static_cast<const SdrHint&>(rHint));
    const SdrHintKind eHintKind(rSdrHint.GetKind());

    switch (eHintKind)
    {
        case SdrHintKind::ObjectInserted:
        {
            if (nullptr != mnEventObjectInserted)
                // avoid multiple events
                return;

            // tdf#160669 IASS: inform about ALL changed slides due to prefetch
            sendHintSlideChanged(rSdrHint.GetPage());

            if (!isCurrentSlideInvolved(rSdrHint))
                // nothing to do when current slide is not involved
                return;

            // Refresh current slide
            uno::Reference< css::drawing::XDrawPage > XCurrentSlide(getCurrentSlide());
            mnEventObjectInserted = AsyncUpdateSlideshow_Impl::AsyncUpdateSlideshow(this, XCurrentSlide, eHintKind);
            break;
        }
        case SdrHintKind::ObjectRemoved:
        {
            if (nullptr != mnEventObjectRemoved)
                // avoid multiple events
                return;

            // tdf#160669 IASS: inform about ALL changed slides due to prefetch
            sendHintSlideChanged(rSdrHint.GetPage());

            if (!isCurrentSlideInvolved(rSdrHint))
                // nothing to do when current slide is not involved
                return;

            // Refresh current slide
            uno::Reference< css::drawing::XDrawPage > XCurrentSlide(getCurrentSlide());
            mnEventObjectRemoved = AsyncUpdateSlideshow_Impl::AsyncUpdateSlideshow(this, XCurrentSlide, eHintKind);
            break;
        }
        case SdrHintKind::ObjectChange:
        {
            if (nullptr != mnEventObjectChange)
                // avoid multiple events
                return;

            // tdf#160669 IASS: inform about ALL changed slides due to prefetch
            sendHintSlideChanged(rSdrHint.GetPage());

            if (!isCurrentSlideInvolved(rSdrHint))
                // nothing to do when current slide is not involved
                return;

            // Refresh current slide. Need to do that asynchronous, else e.g.
            // text edit changes EditEngine/Outliner are not progressed far
            // enough (ObjectChanged broadcast which we are in here seems
            // too early for some cases)
            uno::Reference< css::drawing::XDrawPage > XCurrentSlide(getCurrentSlide());
            mnEventObjectChange = AsyncUpdateSlideshow_Impl::AsyncUpdateSlideshow(this, XCurrentSlide, eHintKind);
            break;
        }
        case SdrHintKind::PageOrderChange:
        {
            // Unfortunately we get multiple events, e.g. when drag/drop position change in
            // slide sorter on left side of EditView. This includes some with page number +1,
            // then again -1 (it's a position change). Problem is that in-between already
            // a re-schedule seems to happen, so indeed AsyncNotifyEvent will change to +1/-1
            // already. Since we get even more, at least try to take the last one. I found no
            // good solution yet for this.
            if (nullptr != mnEventPageOrderChange)
                Application::RemoveUserEvent( mnEventPageOrderChange );

            // tdf#160669 IASS: inform about ALL changed slides due to prefetch
            sendHintSlideChanged(rSdrHint.GetPage());

            // order of pages (object pages or master pages) changed (Insert/Remove/ChangePos)
            uno::Reference< css::drawing::XDrawPage > XCurrentSlide(getCurrentSlide());
            mnEventPageOrderChange = AsyncUpdateSlideshow_Impl::AsyncUpdateSlideshow(this, XCurrentSlide, eHintKind);
            break;
        }
        case SdrHintKind::ModelCleared:
        {
            // immediately end presentation
            endPresentation();
            break;
        }
        default:
            break;
    }
}

Reference< XSlideShow > SAL_CALL SlideshowImpl::getSlideShow()
{
    return mxShow;
}

PresentationSettingsEx::PresentationSettingsEx( const PresentationSettingsEx& r )
: PresentationSettings( r )
, mbRehearseTimings(r.mbRehearseTimings)
, mbPreview(r.mbPreview)
, mpParentWindow( nullptr )
{
}

PresentationSettingsEx::PresentationSettingsEx( PresentationSettings const & r )
: PresentationSettings( r )
, mbRehearseTimings(false)
, mbPreview(false)
, mpParentWindow(nullptr)
{
}

void PresentationSettingsEx::SetArguments( const Sequence< PropertyValue >& rArguments )
{
    for( const PropertyValue& rValue : rArguments )
    {
        SetPropertyValue( rValue.Name, rValue.Value );
    }
}

void PresentationSettingsEx::SetPropertyValue( std::u16string_view rProperty, const Any& rValue )
{
    if ( rProperty == u"RehearseTimings" )
    {
        if( rValue >>= mbRehearseTimings )
            return;
    }
    else if ( rProperty == u"Preview" )
    {
        if( rValue >>= mbPreview )
            return;
    }
    else if ( rProperty == u"AnimationNode" )
    {
        if( rValue >>= mxAnimationNode )
            return;
    }
    else if ( rProperty == u"ParentWindow" )
    {
        Reference< XWindow > xWindow;
        if( rValue >>= xWindow )
        {
            mpParentWindow = xWindow.is() ? VCLUnoHelper::GetWindow( xWindow )
                                          : nullptr;
            return;
        }
    }
    else if ( rProperty == u"AllowAnimations" )
    {
        if( rValue >>= mbAnimationAllowed )
            return;
    }
    else if ( rProperty == u"FirstPage" )
    {
        OUString aPresPage;
        if( rValue >>= aPresPage )
        {
            maPresPage = getUiNameFromPageApiNameImpl(aPresPage);
            mbCustomShow = false;
            mbAll = false;
            return;
        }
        else
        {
            if( rValue >>= mxStartPage )
                return;
        }
    }
    else if ( rProperty == u"IsAlwaysOnTop" )
    {
        if( rValue >>= mbAlwaysOnTop )
            return;
    }
    else if ( rProperty == u"IsAutomatic" )
    {
        if( rValue >>= mbManual )
            return;
    }
    else if ( rProperty == u"IsEndless" )
    {
        if( rValue >>= mbEndless )
            return;
    }
    else if ( rProperty == u"IsFullScreen" )
    {
        if( rValue >>= mbFullScreen )
            return;
    }
    else if ( rProperty == u"IsMouseVisible" )
    {
        if( rValue >>= mbMouseVisible )
            return;
    }
    else if ( rProperty == u"Pause" )
    {
        sal_Int32 nPause = -1;
        if( (rValue >>= nPause) && (nPause >= 0) )
        {
            mnPauseTimeout = nPause;
            return;
        }
    }
    else if ( rProperty == u"UsePen" )
    {
        if( rValue >>= mbMouseAsPen )
            return;
    }
    throw IllegalArgumentException();
}

// XAnimationListener

SlideShowListenerProxy::SlideShowListenerProxy( rtl::Reference< SlideshowImpl > xController, css::uno::Reference< css::presentation::XSlideShow > xSlideShow )
: mxController(std::move( xController ))
, mxSlideShow(std::move( xSlideShow ))
{
}

SlideShowListenerProxy::~SlideShowListenerProxy()
{
}

void SlideShowListenerProxy::addAsSlideShowListener()
{
    if( mxSlideShow.is() )
    {
        Reference< XSlideShowListener > xSlideShowListener( this );
        mxSlideShow->addSlideShowListener( xSlideShowListener );
    }
}

void SlideShowListenerProxy::removeAsSlideShowListener()
{
    if( mxSlideShow.is() )
    {
        Reference< XSlideShowListener > xSlideShowListener( this );
        mxSlideShow->removeSlideShowListener( xSlideShowListener );
    }
}

void SlideShowListenerProxy::addShapeEventListener( const css::uno::Reference< css::drawing::XShape >& xShape )
{
    if( mxSlideShow.is() )
    {
        Reference< XShapeEventListener > xListener( this );
        mxSlideShow->addShapeEventListener( xListener, xShape );
    }
}

void SlideShowListenerProxy::removeShapeEventListener( const css::uno::Reference< css::drawing::XShape >& xShape )
{
    if( mxSlideShow.is() )
    {
        Reference< XShapeEventListener > xListener( this );
        mxSlideShow->removeShapeEventListener( xListener, xShape );
    }
}

void SlideShowListenerProxy::addSlideShowListener( const css::uno::Reference< css::presentation::XSlideShowListener >& xListener )
{
    std::unique_lock g(m_aMutex);
    maListeners.addInterface(g, xListener);
}

void SlideShowListenerProxy::removeSlideShowListener( const css::uno::Reference< css::presentation::XSlideShowListener >& xListener )
{
    std::unique_lock g(m_aMutex);
    maListeners.removeInterface(g, xListener);
}

void SAL_CALL SlideShowListenerProxy::beginEvent( const Reference< XAnimationNode >& xNode )
{
    std::unique_lock aGuard( m_aMutex );

    if( maListeners.getLength(aGuard) >= 0 )
    {
        maListeners.forEach(aGuard,
            [&] (Reference<XAnimationListener> const& xListener) {
                return xListener->beginEvent(xNode);
            } );
    }
}

void SAL_CALL SlideShowListenerProxy::endEvent( const Reference< XAnimationNode >& xNode )
{
    std::unique_lock aGuard( m_aMutex );

    if( maListeners.getLength(aGuard) >= 0 )
    {
        maListeners.forEach(aGuard,
            [&] (Reference<XAnimationListener> const& xListener) {
                return xListener->endEvent(xNode);
            } );
    }
}

void SAL_CALL SlideShowListenerProxy::repeat( const Reference< XAnimationNode >& xNode, ::sal_Int32 nRepeat )
{
    std::unique_lock aGuard( m_aMutex );

    if( maListeners.getLength(aGuard) >= 0 )
    {
        maListeners.forEach(aGuard,
            [&] (Reference<XAnimationListener> const& xListener) {
                return xListener->repeat(xNode, nRepeat);
            } );
    }
}

// css::presentation::XSlideShowListener:

void SAL_CALL SlideShowListenerProxy::paused(  )
{
    std::unique_lock aGuard( m_aMutex );

    maListeners.forEach(aGuard,
        [](uno::Reference<presentation::XSlideShowListener> const& xListener)
        {
            xListener->paused();
        });
}

void SAL_CALL SlideShowListenerProxy::resumed(  )
{
    std::unique_lock aGuard( m_aMutex );

    maListeners.forEach(aGuard,
        [](uno::Reference<presentation::XSlideShowListener> const& xListener)
        {
            xListener->resumed();
        });
}

void SAL_CALL SlideShowListenerProxy::slideTransitionStarted( )
{
    std::unique_lock aGuard( m_aMutex );

    maListeners.forEach(aGuard,
        [](uno::Reference<presentation::XSlideShowListener> const& xListener)
        {
            xListener->slideTransitionStarted();
        });
}

void SAL_CALL SlideShowListenerProxy::slideTransitionEnded( )
{
    std::unique_lock aGuard( m_aMutex );

    maListeners.forEach(aGuard,
        [](uno::Reference<presentation::XSlideShowListener> const& xListener)
        {
            xListener->slideTransitionEnded ();
        });
}

void SAL_CALL SlideShowListenerProxy::slideAnimationsEnded(  )
{
    std::unique_lock aGuard( m_aMutex );

    maListeners.forEach(aGuard,
        [](uno::Reference<presentation::XSlideShowListener> const& xListener)
        {
            xListener->slideAnimationsEnded ();
        });
}

void SlideShowListenerProxy::slideEnded(sal_Bool bReverse)
{
    {
        std::unique_lock aGuard( m_aMutex );

        if( maListeners.getLength(aGuard) >= 0 )
        {
            maListeners.forEach(aGuard,
                [&] (Reference<XSlideShowListener> const& xListener) {
                    return xListener->slideEnded(bReverse);
                } );
        }
    }

    {
        SolarMutexGuard aSolarGuard;
        if( mxController.is() )
            mxController->slideEnded(bReverse);
    }
}

void SlideShowListenerProxy::hyperLinkClicked( OUString const& aHyperLink )
{
    {
        std::unique_lock aGuard( m_aMutex );

        if( maListeners.getLength(aGuard) >= 0 )
        {
            maListeners.forEach(aGuard,
                [&] (Reference<XSlideShowListener> const& xListener) {
                    return xListener->hyperLinkClicked(aHyperLink);
                } );
        }
    }

    {
        SolarMutexGuard aSolarGuard;
        if( mxController.is() )
            mxController->hyperLinkClicked(aHyperLink);
    }
}

// XEventListener

void SAL_CALL SlideShowListenerProxy::disposing( const css::lang::EventObject& aDisposeEvent )
{
    std::unique_lock g(m_aMutex);
    maListeners.disposeAndClear( g, aDisposeEvent );
    mxController.clear();
    mxSlideShow.clear();
}

// XShapeEventListener

void SAL_CALL SlideShowListenerProxy::click( const Reference< XShape >& xShape, const css::awt::MouseEvent& /*aOriginalEvent*/ )
{
    SolarMutexGuard aSolarGuard;
    if( mxController.is() )
        mxController->click(xShape );
}

void SAL_CALL SlideShowListenerProxy::contextMenuShow(const css::awt::Point& point)
{
    SolarMutexGuard aSolarGuard;
    if (mxController.is())
        mxController->contextMenuShow(point);
}

} // namespace ::sd

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
