/* -*- 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 <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/frame/XDispatchProvider.hpp>
#include <com/sun/star/util/URL.hpp>

#include <comphelper/propertyvalue.hxx>
#include <cppuhelper/supportsservice.hxx>

#include <sal/log.hxx>
#include <vcl/svapp.hxx>
#include <vcl/wrkwin.hxx>
#include <svx/svdpool.hxx>
#include <svx/svdlayer.hxx>
#include <svl/itemprop.hxx>

#include <sfx2/bindings.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/sfxsids.hrc>

#include <framework/FrameworkHelper.hxx>
#include <framework/ConfigurationChangeEvent.hxx>
#include <comphelper/extract.hxx>

#include <FrameView.hxx>
#include <createpresentation.hxx>
#include <unomodel.hxx>
#include <slideshow.hxx>
#include "slideshowimpl.hxx"
#include <sdattr.hrc>
#include <sdmod.hxx>
#include <FactoryIds.hxx>
#include <DrawDocShell.hxx>
#include <ViewShell.hxx>
#include <ViewShellBase.hxx>
#include "SlideShowRestarter.hxx"
#include <DrawController.hxx>
#include <PresentationViewShell.hxx>
#include <customshowlist.hxx>
#include <unopage.hxx>
#include <sdpage.hxx>
#include <cusshow.hxx>
#include <optsitem.hxx>
#include <strings.hrc>
#include <sdresid.hxx>
#include <ResourceId.hxx>

using ::com::sun::star::presentation::XSlideShowController;
using ::sd::framework::FrameworkHelper;
using ::com::sun::star::awt::XWindow;
using namespace ::sd;
using namespace ::cppu;
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::drawing;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::animations;

namespace {
    /** This local version of the work window overrides DataChanged() so that it
        can restart the slide show when a display is added or removed.
    */
    class FullScreenWorkWindow : public WorkWindow
    {
    public:
        FullScreenWorkWindow (
            const ::rtl::Reference<SlideShow>& rpSlideShow,
            ViewShellBase* pViewShellBase)
            : WorkWindow(nullptr, WB_HIDE | WB_CLIPCHILDREN),
              mpRestarter(std::make_shared<SlideShowRestarter>(rpSlideShow, pViewShellBase))
        {}

        void Restart(bool bForce)
        {
            mpRestarter->Restart(bForce);
        }

        virtual void DataChanged (const DataChangedEvent& rEvent) override
        {
            if (rEvent.GetType() == DataChangedEventType::DISPLAY)
                Restart(false);
        }

    private:
        ::std::shared_ptr<SlideShowRestarter> mpRestarter;
    };
}

static std::span<const SfxItemPropertyMapEntry> ImplGetPresentationPropertyMap()
{
    // NOTE: First member must be sorted
    static const SfxItemPropertyMapEntry aPresentationPropertyMap_Impl[] =
    {
        { u"AllowAnimations"_ustr,          ATTR_PRESENT_ANIMATION_ALLOWED,       cppu::UnoType<bool>::get(),                0, 0 },
        { u"CustomShow"_ustr,               ATTR_PRESENT_CUSTOMSHOW,              ::cppu::UnoType<OUString>::get(),     0, 0 },
        { u"Display"_ustr,                  ATTR_PRESENT_DISPLAY,                 ::cppu::UnoType<sal_Int32>::get(),    0, 0 },
        { u"FirstPage"_ustr,                ATTR_PRESENT_DIANAME,                 ::cppu::UnoType<OUString>::get(),     0, 0 },
        { u"IsAlwaysOnTop"_ustr,            ATTR_PRESENT_ALWAYS_ON_TOP,           cppu::UnoType<bool>::get(),                0, 0 },
        { u"IsAutomatic"_ustr,              ATTR_PRESENT_MANUEL,                  cppu::UnoType<bool>::get(),                0, 0 },
        { u"IsEndless"_ustr,                ATTR_PRESENT_ENDLESS,                 cppu::UnoType<bool>::get(),                0, 0 },
        { u"IsFullScreen"_ustr,             ATTR_PRESENT_FULLSCREEN,              cppu::UnoType<bool>::get(),                0, 0 },
        { u"IsShowAll"_ustr,                ATTR_PRESENT_ALL,                     cppu::UnoType<bool>::get(),                0, 0 },
        { u"IsMouseVisible"_ustr,           ATTR_PRESENT_MOUSE,                   cppu::UnoType<bool>::get(),                0, 0 },
        { u"IsShowLogo"_ustr,               ATTR_PRESENT_SHOW_PAUSELOGO,          cppu::UnoType<bool>::get(),                0, 0 },
        { u"IsTransitionOnClick"_ustr,      ATTR_PRESENT_CHANGE_PAGE,             cppu::UnoType<bool>::get(),                0, 0 },
        { u"Pause"_ustr,                    ATTR_PRESENT_PAUSE_TIMEOUT,           ::cppu::UnoType<sal_Int32>::get(),    0, 0 },
        { u"StartWithNavigator"_ustr,       ATTR_PRESENT_NAVIGATOR,               cppu::UnoType<bool>::get(),                0, 0 },
        { u"UsePen"_ustr,                   ATTR_PRESENT_PEN,                     cppu::UnoType<bool>::get(),                0, 0 },
    };

    return aPresentationPropertyMap_Impl;
}


SlideShow::SlideShow( SdDrawDocument* pDoc )
: maPropSet(ImplGetPresentationPropertyMap(), SdrObject::GetGlobalDrawObjectItemPool())
, mbIsInStartup(false)
, mpDoc( pDoc )
, mpCurrentViewShellBase( nullptr )
, mpFullScreenViewShellBase( nullptr )
, mpFullScreenFrameView( nullptr )
, mnInPlaceConfigEvent( nullptr )
{
}

void SlideShow::ThrowIfDisposed() const
{
    if( mpDoc == nullptr )
        throw DisposedException();
}

/// used by the model to create a slideshow for it
rtl::Reference< SlideShow > SlideShow::Create( SdDrawDocument* pDoc )
{
    return new SlideShow( pDoc );
}

rtl::Reference< SlideShow > SlideShow::GetSlideShow( SdDrawDocument const * pDocument )
{
    rtl::Reference< SlideShow > xRet;

    if( pDocument )
        xRet = GetSlideShow( *pDocument );

    return xRet;
}

rtl::Reference< SlideShow > SlideShow::GetSlideShow( SdDrawDocument const & rDocument )
{
    return rtl::Reference< SlideShow >(
        dynamic_cast< SlideShow* >( rDocument.getPresentation().get() ) );
}

rtl::Reference< SlideShow > SlideShow::GetSlideShow( ViewShellBase const & rBase )
{
    return GetSlideShow( rBase.GetDocument() );
}

css::uno::Reference< css::presentation::XSlideShowController > SlideShow::GetSlideShowController(ViewShellBase const & rBase )
{
    rtl::Reference< SlideShow > xSlideShow( GetSlideShow( rBase ) );

    Reference< XSlideShowController > xRet;
    if( xSlideShow.is() )
        xRet = xSlideShow->getController();

    return xRet;
}

bool SlideShow::StartPreview( ViewShellBase const & rBase,
    const css::uno::Reference< css::drawing::XDrawPage >& xDrawPage,
    const css::uno::Reference< css::animations::XAnimationNode >& xAnimationNode )
{
    rtl::Reference< SlideShow > xSlideShow( GetSlideShow( rBase ) );
    if( !xSlideShow.is() )
        return false;

    // end an already running IASS Preview (when someone is fast)
    if (xSlideShow->IsInteractiveSlideshow() && xSlideShow->isInteractiveSetup())
        xSlideShow->endInteractivePreview();

    // check if IASS re-use of running Slideshow can/should be done
    // and do it
    if (xSlideShow->IsInteractiveSlideshow() && xSlideShow->isFullScreen()) // IASS
        return xSlideShow->startInteractivePreview( xDrawPage, xAnimationNode );

    // fallback to usual mode
    xSlideShow->startPreview( xDrawPage, xAnimationNode );
    return true;
}

void SlideShow::Stop( ViewShellBase const & rBase )
{
    rtl::Reference< SlideShow > xSlideShow( GetSlideShow( rBase ) );
    if( xSlideShow.is() )
        xSlideShow->end();
}

bool SlideShow::IsRunning( ViewShellBase const & rBase )
{
    rtl::Reference< SlideShow > xSlideShow( GetSlideShow( rBase ) );
    return xSlideShow.is() && xSlideShow->isRunning();
}

bool SlideShow::IsRunning( const ViewShell& rViewShell )
{
    rtl::Reference< SlideShow > xSlideShow( GetSlideShow( rViewShell.GetViewShellBase() ) );
    return xSlideShow.is() && xSlideShow->isRunning() && (xSlideShow->mxController->getViewShell() == &rViewShell);
}

/// returns true if the interactive slideshow mode is activated
bool SlideShow::IsInteractiveSlideshow(const ViewShellBase& rViewShellBase)
{
    rtl::Reference< SlideShow > xSlideShow(GetSlideShow(rViewShellBase));
    if (!xSlideShow.is())
        return false;
    return xSlideShow->IsInteractiveSlideshow();
}

bool SlideShow::IsInteractiveSlideshow() const
{
    return mpDoc->getPresentationSettings().mbInteractive;
}

void SlideShow::CreateController(  ViewShell* pViewSh, ::sd::View* pView, vcl::Window* pParentWindow )
{
    SAL_INFO_IF( !mxController.is(), "sd.slideshow", "sd::SlideShow::CreateController(), clean up old controller first!" );

    Reference< XPresentation2 > xThis( this );

    // Reset mbIsInStartup.  From here mxController.is() is used to prevent
    // multiple slide show instances for one document.
    mxController.set(new SlideshowImpl(xThis, pViewSh, pView, mpDoc, pParentWindow));

    mbIsInStartup = false;

}

// XServiceInfo
OUString SAL_CALL SlideShow::getImplementationName(  )
{
    return u"com.sun.star.comp.sd.SlideShow"_ustr;
}

sal_Bool SAL_CALL SlideShow::supportsService( const OUString& ServiceName )
{
    return cppu::supportsService( this, ServiceName );
}

Sequence< OUString > SAL_CALL SlideShow::getSupportedServiceNames(  )
{
    return { u"com.sun.star.presentation.Presentation"_ustr };
}

// XPropertySet
Reference< XPropertySetInfo > SAL_CALL SlideShow::getPropertySetInfo()
{
    SolarMutexGuard aGuard;
    static Reference< XPropertySetInfo > xInfo = maPropSet.getPropertySetInfo();
    return xInfo;
 }

void SAL_CALL SlideShow::setPropertyValue( const OUString& aPropertyName, const Any& aValue )
{
    SolarMutexGuard aGuard;
    ThrowIfDisposed();

    sd::PresentationSettings& rPresSettings = mpDoc->getPresentationSettings();

    const SfxItemPropertyMapEntry* pEntry = maPropSet.getPropertyMapEntry(aPropertyName);

    if( pEntry && ((pEntry->nFlags & PropertyAttribute::READONLY) != 0) )
        throw PropertyVetoException();

    bool bValuesChanged = false;
    bool bIllegalArgument = true;

    switch( pEntry ? pEntry->nWID : -1 )
    {
    case ATTR_PRESENT_ALL:
    {
        bool bVal = false;

        if( aValue >>= bVal )
        {
            bIllegalArgument = false;

            if( rPresSettings.mbAll != bVal )
            {
                rPresSettings.mbAll = bVal;
                bValuesChanged = true;
                if( bVal )
                    rPresSettings.mbCustomShow = false;
            }
        }
        break;
    }
    case ATTR_PRESENT_CHANGE_PAGE:
    {
        bool bVal = false;

        if( aValue >>= bVal )
        {
            bIllegalArgument = false;

            if( bVal == rPresSettings.mbLockedPages )
            {
                bValuesChanged = true;
                rPresSettings.mbLockedPages = !bVal;
            }
        }
        break;
    }

    case ATTR_PRESENT_ANIMATION_ALLOWED:
    {
        bool bVal = false;

        if( aValue >>= bVal )
        {
            bIllegalArgument = false;

            if(rPresSettings.mbAnimationAllowed != bVal)
            {
                bValuesChanged = true;
                rPresSettings.mbAnimationAllowed = bVal;
            }
        }
        break;
    }
    case ATTR_PRESENT_CUSTOMSHOW:
    {
        OUString aShowName;
        if( aValue >>= aShowName )
        {
            bIllegalArgument = false;

            SdCustomShowList* pCustomShowList = mpDoc->GetCustomShowList();
            if(pCustomShowList)
            {
                SdCustomShow* pCustomShow;
                for( pCustomShow = pCustomShowList->First(); pCustomShow != nullptr; pCustomShow = pCustomShowList->Next() )
                {
                    if( pCustomShow->GetName() == aShowName )
                        break;
                }

                rPresSettings.mbCustomShow = true;
                bValuesChanged = true;
            }
        }
        break;
    }
    case ATTR_PRESENT_ENDLESS:
    {
        bool bVal = false;

        if( aValue >>= bVal )
        {
            bIllegalArgument = false;

            if( rPresSettings.mbEndless != bVal)
            {
                bValuesChanged = true;
                rPresSettings.mbEndless = bVal;
            }
        }
        break;
    }
    case ATTR_PRESENT_FULLSCREEN:
    {
        bool bVal = false;

        if( aValue >>= bVal )
        {
            bIllegalArgument = false;
            if( rPresSettings.mbFullScreen != bVal)
            {
                bValuesChanged = true;
                rPresSettings.mbFullScreen = bVal;
            }
        }
        break;
    }
    case ATTR_PRESENT_DIANAME:
    {
        OUString aPresPage;
        aValue >>= aPresPage;
        bIllegalArgument = false;
        if( (rPresSettings.maPresPage != aPresPage) || !rPresSettings.mbCustomShow || !rPresSettings.mbAll )
        {
            bValuesChanged = true;
            rPresSettings.maPresPage = getUiNameFromPageApiNameImpl(aPresPage);
            rPresSettings.mbCustomShow = false;
            rPresSettings.mbAll = false;
        }
        break;
    }
    case ATTR_PRESENT_MANUEL:
    {
        bool bVal = false;

        if( aValue >>= bVal )
        {
            bIllegalArgument = false;

            if( rPresSettings.mbManual != bVal)
            {
                bValuesChanged = true;
                rPresSettings.mbManual = bVal;
            }
        }
        break;
    }
    case ATTR_PRESENT_MOUSE:
    {
        bool bVal = false;

        if( aValue >>= bVal )
        {
            bIllegalArgument = false;
            if( rPresSettings.mbMouseVisible != bVal)
            {
                bValuesChanged = true;
                rPresSettings.mbMouseVisible = bVal;
            }
        }
        break;
    }
    case ATTR_PRESENT_ALWAYS_ON_TOP:
    {
        bool bVal = false;

        if( aValue >>= bVal )
        {
            bIllegalArgument = false;

            if( rPresSettings.mbAlwaysOnTop != bVal)
            {
                bValuesChanged = true;
                rPresSettings.mbAlwaysOnTop = bVal;
            }
        }
        break;
    }
    case ATTR_PRESENT_NAVIGATOR:
        bIllegalArgument = false;
        //ignored, but exists in some older documents
        break;
    case ATTR_PRESENT_PEN:
    {
        bool bVal = false;

        if( aValue >>= bVal )
        {
            bIllegalArgument = false;

            if(rPresSettings.mbMouseAsPen != bVal)
            {
                bValuesChanged = true;
                rPresSettings.mbMouseAsPen = bVal;
            }
        }
        break;
    }
    case ATTR_PRESENT_PAUSE_TIMEOUT:
    {
        sal_Int32 nValue = 0;
        if( (aValue >>= nValue) && (nValue >= 0) )
        {
            bIllegalArgument = false;
            if( rPresSettings.mnPauseTimeout != nValue )
            {
                bValuesChanged = true;
                rPresSettings.mnPauseTimeout = nValue;
            }
        }
        break;
    }
    case ATTR_PRESENT_SHOW_PAUSELOGO:
    {
        bool bVal = false;

        if( aValue >>= bVal )
        {
            bIllegalArgument = false;

            if( rPresSettings.mbShowPauseLogo != bVal )
            {
                bValuesChanged = true;
                rPresSettings.mbShowPauseLogo = bVal;
            }
        }
        break;
    }
    case ATTR_PRESENT_DISPLAY:
    {
        sal_Int32 nDisplay = 0;
        if( aValue >>= nDisplay )
        {
            bIllegalArgument = false;

            SdOptions* pOptions = SdModule::get()->GetSdOptions(DocumentType::Impress);
            pOptions->SetDisplay( nDisplay );

            FullScreenWorkWindow *pWin = dynamic_cast<FullScreenWorkWindow *>(GetWorkWindow());
            if( !pWin )
                return;
            pWin->Restart(true);
        }
        break;
    }

    default:
        throw UnknownPropertyException( OUString::number(pEntry ? pEntry->nWID : -1), static_cast<cppu::OWeakObject*>(this));
    }

    if( bIllegalArgument )
        throw IllegalArgumentException();

    if( bValuesChanged )
        mpDoc->SetChanged();
}

Any SAL_CALL SlideShow::getPropertyValue( const OUString& PropertyName )
{
    SolarMutexGuard aGuard;
    ThrowIfDisposed();

    const sd::PresentationSettings& rPresSettings = mpDoc->getPresentationSettings();

    const SfxItemPropertyMapEntry* pEntry = maPropSet.getPropertyMapEntry(PropertyName);

    switch( pEntry ? pEntry->nWID : -1 )
    {
    case ATTR_PRESENT_ALL:
        return Any( !rPresSettings.mbCustomShow && rPresSettings.mbAll );
    case ATTR_PRESENT_CHANGE_PAGE:
        return Any( !rPresSettings.mbLockedPages );
    case ATTR_PRESENT_ANIMATION_ALLOWED:
        return Any( rPresSettings.mbAnimationAllowed );
    case ATTR_PRESENT_CUSTOMSHOW:
        {
            SdCustomShowList* pList = mpDoc->GetCustomShowList();
            SdCustomShow* pShow = (pList && rPresSettings.mbCustomShow) ? pList->GetCurObject() : nullptr;
            OUString aShowName;

            if(pShow)
                aShowName = pShow->GetName();

            return Any( aShowName );
        }
    case ATTR_PRESENT_ENDLESS:
        return Any( rPresSettings.mbEndless );
    case ATTR_PRESENT_FULLSCREEN:
        return Any( rPresSettings.mbFullScreen );
    case ATTR_PRESENT_DIANAME:
        {
            OUString aSlideName;

            if( !rPresSettings.mbCustomShow && !rPresSettings.mbAll )
                aSlideName = getPageApiNameFromUiName( rPresSettings.maPresPage );

            return Any( aSlideName );
        }
    case ATTR_PRESENT_MANUEL:
        return Any( rPresSettings.mbManual );
    case ATTR_PRESENT_MOUSE:
        return Any( rPresSettings.mbMouseVisible );
    case ATTR_PRESENT_ALWAYS_ON_TOP:
        return Any( rPresSettings.mbAlwaysOnTop );
    case ATTR_PRESENT_NAVIGATOR:
        return Any( false );
    case ATTR_PRESENT_PEN:
        return Any( rPresSettings.mbMouseAsPen );
    case ATTR_PRESENT_PAUSE_TIMEOUT:
        return Any( rPresSettings.mnPauseTimeout );
    case ATTR_PRESENT_SHOW_PAUSELOGO:
        return Any( rPresSettings.mbShowPauseLogo );
    case ATTR_PRESENT_DISPLAY:
    {
        SdOptions* pOptions = SdModule::get()->GetSdOptions(DocumentType::Impress);
        return Any(pOptions->GetDisplay());
    }

    default:
        throw UnknownPropertyException( OUString::number(pEntry ? pEntry->nWID : -1), static_cast<cppu::OWeakObject*>(this));
    }
}

void SAL_CALL SlideShow::addPropertyChangeListener( const OUString& , const Reference< XPropertyChangeListener >&  )
{
}

void SAL_CALL SlideShow::removePropertyChangeListener( const OUString& , const Reference< XPropertyChangeListener >&  )
{
}

void SAL_CALL SlideShow::addVetoableChangeListener( const OUString& , const Reference< XVetoableChangeListener >&  )
{
}

void SAL_CALL SlideShow::removeVetoableChangeListener( const OUString& , const Reference< XVetoableChangeListener >&  )
{
}

// XPresentation

void SAL_CALL SlideShow::start()
{
    const Sequence< PropertyValue > aArguments;
    startWithArguments( aArguments );
}

WorkWindow *SlideShow::GetWorkWindow()
{
    if( !mpFullScreenViewShellBase )
        return nullptr;

    PresentationViewShell* pShell = dynamic_cast<PresentationViewShell*>(mpFullScreenViewShellBase->GetMainViewShell().get());

    if( !pShell)
        return nullptr;

    SfxViewFrame* pFrame = pShell->GetViewFrame();
    if (!pFrame)
        return nullptr;

    return dynamic_cast<WorkWindow*>(pFrame->GetFrame().GetWindow().GetParent());
}

bool SlideShow::IsExitAfterPresenting() const
{
    SolarMutexGuard aGuard;
    ThrowIfDisposed();
    return mpDoc->IsExitAfterPresenting();
}

void SlideShow::SetExitAfterPresenting(bool bExit)
{
    SolarMutexGuard aGuard;
    ThrowIfDisposed();
    mpDoc->SetExitAfterPresenting(bExit);
}

void SAL_CALL SlideShow::end()
{
    SolarMutexGuard aGuard;

    if (IsInteractiveSlideshow() && isInteractiveSetup())
    {
        // If IASS was active clean that up, but do not end SlideShow
        endInteractivePreview();
        return;
    }

    // The mbIsInStartup flag should have been reset during the start of the
    // slide show.  Reset it here just in case that something has horribly
    // gone wrong.
    assert(!mbIsInStartup);

    rtl::Reference< SlideshowImpl > xController( mxController );
    if( !xController.is() )
        return;

    mxController.clear();

    if( mpFullScreenFrameView )
    {
        delete mpFullScreenFrameView;
        mpFullScreenFrameView = nullptr;
    }

    ViewShellBase* pFullScreenViewShellBase = mpFullScreenViewShellBase;
    mpFullScreenViewShellBase = nullptr;

    // dispose before fullscreen window changes screens
    // (potentially). If this needs to be moved behind
    // pWorkWindow->StartPresentationMode() again, read issue
    // pWorkWindow->i94007 & implement the solution outlined
    // there.
    xController->dispose();

    if( pFullScreenViewShellBase )
    {
        PresentationViewShell* pShell = dynamic_cast<PresentationViewShell*>(pFullScreenViewShellBase->GetMainViewShell().get());

        if( pShell && pShell->GetViewFrame() )
        {
            WorkWindow* pWorkWindow = dynamic_cast<WorkWindow*>(pShell->GetViewFrame()->GetFrame().GetWindow().GetParent());
            if( pWorkWindow )
            {
                pWorkWindow->StartPresentationMode(   (mxController.is() && mxController->maPresSettings.mbAlwaysOnTop)
                                                    ? PresentationFlags::HideAllApps : PresentationFlags::NONE );
            }
        }
    }

    if( pFullScreenViewShellBase )
    {
        PresentationViewShell* pShell = nullptr;
        {
            // Get the shell pointer in its own scope to be sure that
            // the shared_ptr to the shell is released before DoClose()
            // is called.
            ::std::shared_ptr<ViewShell> pSharedView (pFullScreenViewShellBase->GetMainViewShell());
            pShell = dynamic_cast<PresentationViewShell*>(pSharedView.get());
        }
        if( pShell && pShell->GetViewFrame() )
            pShell->GetViewFrame()->DoClose();
    }
    else if( mpCurrentViewShellBase )
    {
        ViewShell* pViewShell = mpCurrentViewShellBase->GetMainViewShell().get();

        if( pViewShell )
        {
            FrameView* pFrameView = pViewShell->GetFrameView();

            if( pFrameView && (pFrameView->GetPresentationViewShellId() != SID_VIEWSHELL0) )
            {
                ViewShell::ShellType ePreviousType (pFrameView->GetPreviousViewShellType());
                pFrameView->SetPreviousViewShellType(ViewShell::ST_NONE);

                pFrameView->SetPresentationViewShellId(SID_VIEWSHELL0);
                pFrameView->SetPreviousViewShellType(pViewShell->GetShellType());

                framework::FrameworkHelper::Instance(*mpCurrentViewShellBase)->RequestView(
                    framework::FrameworkHelper::GetViewURL(ePreviousType),
                    framework::FrameworkHelper::msCenterPaneURL);

                pViewShell->GetViewFrame()->GetBindings().InvalidateAll( true );
            }
        }
    }

    if( mpCurrentViewShellBase )
    {
        if (ViewShell* const pViewShell = mpCurrentViewShellBase->GetMainViewShell().get())
        {
            // invalidate the view shell so the presentation slot will be re-enabled
            // and the rehearsing will be updated
            pViewShell->Invalidate();

            if( xController->meAnimationMode ==ANIMATIONMODE_SHOW )
            {
                // switch to the previously visible Slide
                DrawViewShell* pDrawViewShell = dynamic_cast<DrawViewShell*>( pViewShell );
                if( pDrawViewShell )
                    pDrawViewShell->SwitchPage( static_cast<sal_uInt16>(xController->getRestoreSlide()) );
                else
                {
                    DrawController& rDrawController =
                        *mpCurrentViewShellBase->GetDrawController();
                    rDrawController.setCurrentPage(
                        Reference<XDrawPage>(
                            mpDoc->GetSdPage(xController->getRestoreSlide(), PageKind::Standard)->getUnoPage(),
                            UNO_QUERY));
                }
            }

            if( pViewShell->GetDoc()->IsExitAfterPresenting() )
            {
                pViewShell->GetDoc()->SetExitAfterPresenting( false );

                Reference<frame::XDispatchProvider> xProvider(pViewShell->GetViewShellBase().GetController()->getFrame(),
                                                              UNO_QUERY);
                if( xProvider.is() )
                {
                    util::URL aURL;
                    aURL.Complete = ".uno:CloseFrame";

                    uno::Reference< frame::XDispatch > xDispatch(
                        xProvider->queryDispatch(
                            aURL, OUString(), 0));
                    if( xDispatch.is() )
                    {
                        xDispatch->dispatch(aURL,
                                            uno::Sequence< beans::PropertyValue >());
                    }
                }
            }

            // In case mbMouseAsPen was set, a new layer DrawnInSlideshow might have been generated
            // during slideshow, which is not known to FrameView yet.
            if (any2bool(getPropertyValue(u"UsePen"_ustr))
                && pViewShell->GetDoc()->GetLayerAdmin().GetLayer(u"DrawnInSlideshow"_ustr))
            {
                SdrLayerIDSet aDocLayerIDSet;
                pViewShell->GetDoc()->GetLayerAdmin().getVisibleLayersODF(aDocLayerIDSet);
                if (pViewShell->GetFrameView()->GetVisibleLayers() != aDocLayerIDSet)
                {
                    pViewShell->GetFrameView()->SetVisibleLayers(aDocLayerIDSet);
                }
                pViewShell->GetDoc()->GetLayerAdmin().getPrintableLayersODF(aDocLayerIDSet);
                if (pViewShell->GetFrameView()->GetPrintableLayers() != aDocLayerIDSet)
                {
                    pViewShell->GetFrameView()->SetPrintableLayers(aDocLayerIDSet);
                }
                pViewShell->GetDoc()->GetLayerAdmin().getLockedLayersODF(aDocLayerIDSet);
                if (pViewShell->GetFrameView()->GetLockedLayers() != aDocLayerIDSet)
                {
                    pViewShell->GetFrameView()->SetLockedLayers(aDocLayerIDSet);
                }
                pViewShell->InvalidateWindows();
            }

            // Fire the acc focus event when focus is switched back. The above method
            // mpCurrentViewShellBase->GetWindow()->GrabFocus() will set focus to WorkWindow
            // instead of the sd::window, so here call Shell's method to fire the focus event
            pViewShell->SwitchActiveViewFireFocus();
        }
    }
    mpCurrentViewShellBase = nullptr;
}

void SAL_CALL SlideShow::rehearseTimings()
{
    Sequence< PropertyValue > aArguments{ comphelper::makePropertyValue(u"RehearseTimings"_ustr, true) };
    startWithArguments( aArguments );
}

// XPresentation2

void SAL_CALL SlideShow::startWithArguments(const Sequence< PropertyValue >& rArguments)
{
    SolarMutexGuard aGuard;
    ThrowIfDisposed();

    // Stop a running show before starting a new one.
    if( mxController.is() )
    {
        assert(!mbIsInStartup);
        end();
    }
    else if (mbIsInStartup)
    {
        // We are already somewhere in process of starting a slide show but
        // have not yet got to the point where mxController is set.  There
        // is not yet a slide show to end so return silently.
        return;
    }

    // Prevent multiple instance of the SlideShow class for one document.
    mbIsInStartup = true;

    mxCurrentSettings = std::make_shared<PresentationSettingsEx>( mpDoc->getPresentationSettings() );
    mxCurrentSettings->SetArguments( rArguments );

    // if there is no view shell base set, use the current one or the first using this document
    if( mpCurrentViewShellBase == nullptr )
    {
        // first check current
        ::sd::ViewShellBase* pBase = ::sd::ViewShellBase::GetViewShellBase( SfxViewFrame::Current() );
        if( pBase && pBase->GetDocument() == mpDoc )
        {
            mpCurrentViewShellBase = pBase;
        }
        else
        {
            // current is not ours, so get first from ours
            mpCurrentViewShellBase = ::sd::ViewShellBase::GetViewShellBase( SfxViewFrame::GetFirst( mpDoc->GetDocSh() ) );
        }
    }

    // #i118456# make sure TextEdit changes get pushed to model.
    // mpDrawView is tested against NULL above already.
    if(mpCurrentViewShellBase)
    {
        ViewShell* pViewShell = mpCurrentViewShellBase->GetMainViewShell().get();

        if(pViewShell && pViewShell->GetView())
        {
            pViewShell->GetView()->SdrEndTextEdit();
        }
    }

    // Start either a full-screen or an in-place show.
    if(mxCurrentSettings->mbFullScreen && !mxCurrentSettings->mbPreview)
        StartFullscreenPresentation();
    else
        StartInPlacePresentation();

}

sal_Bool SAL_CALL SlideShow::isRunning(  )
{
    SolarMutexGuard aGuard;
    return mxController.is() && mxController->isRunning();
}

Reference< XSlideShowController > SAL_CALL SlideShow::getController(  )
{
    ThrowIfDisposed();

    return mxController;
}

// XComponent

void SlideShow::disposing(std::unique_lock<std::mutex>&)
{
    SolarMutexGuard aGuard;

    if( mnInPlaceConfigEvent )
    {
        Application::RemoveUserEvent( mnInPlaceConfigEvent );
        mnInPlaceConfigEvent = nullptr;
    }

    if( mxController.is() )
    {
        mxController->dispose();
        mxController.clear();
    }

    mpCurrentViewShellBase = nullptr;
    mpFullScreenViewShellBase = nullptr;
    mpDoc = nullptr;
}

bool SlideShow::startInteractivePreview( const Reference< XDrawPage >& xDrawPage, const Reference< XAnimationNode >& xAnimationNode )
{
    if (!mxController.is())
        return false;

    mxController->startInteractivePreview(xDrawPage, xAnimationNode);
    return mxController->isInteractiveSetup();
}

bool SlideShow::isInteractiveSetup() const
{
    if (!mxController.is())
        return false;

    return mxController->isInteractiveSetup();
}

void SlideShow::endInteractivePreview()
{
    mxController->endInteractivePreview();
}

void SlideShow::startPreview( const Reference< XDrawPage >& xDrawPage, const Reference< XAnimationNode >& xAnimationNode )
{
    Sequence< PropertyValue > aArguments{
        comphelper::makePropertyValue(u"Preview"_ustr, true),
        comphelper::makePropertyValue(u"FirstPage"_ustr, xDrawPage),
        comphelper::makePropertyValue(u"AnimationNode"_ustr, xAnimationNode),
        comphelper::makePropertyValue(u"ParentWindow"_ustr, Reference< XWindow >()),
    };

    startWithArguments( aArguments );
}

OutputDevice* SlideShow::getShowWindow()
{
    return mxController.is() ? mxController->mpShowWindow->GetOutDev() : nullptr;
}

int SlideShow::getAnimationMode() const
{
    return mxController.is() ? mxController->meAnimationMode : ANIMATIONMODE_SHOW;
}

void SlideShow::jumpToPageIndex( sal_Int32 nPageIndex )
{
    if( mxController.is() )
        mxController->displaySlideIndex( nPageIndex );
}

void SlideShow::jumpToPageNumber( sal_Int32 nPageNumber )
{
    if( mxController.is() )
        mxController->displaySlideNumber( nPageNumber );
}

sal_Int32 SlideShow::getCurrentPageNumber() const
{
    return mxController.is() ? mxController->getCurrentSlideNumber() : 0;
}

void SlideShow::jumpToBookmark( const OUString& sBookmark )
{
    if( mxController.is() )
        mxController->jumpToBookmark( sBookmark );
}

bool SlideShow::isFullScreen() const
{
    return mxController.is() && mxController->maPresSettings.mbFullScreen;
}

void SlideShow::resize( const Size &rSize )
{
    if( mxController.is() )
        mxController->resize( rSize );
}

bool SlideShow::activate( ViewShellBase& rBase )
{
    if( (mpFullScreenViewShellBase == &rBase) && !mxController.is() )
    {
        ::std::shared_ptr<PresentationViewShell> pShell = std::dynamic_pointer_cast<PresentationViewShell>(rBase.GetMainViewShell());
        if (pShell != nullptr)
        {
            pShell->FinishInitialization( mpFullScreenFrameView );
            mpFullScreenFrameView = nullptr;

            CreateController( pShell.get(), pShell->GetView(), rBase.GetViewWindow() );

            if (!mxController->startShow(mxCurrentSettings.get()))
                return false;

            pShell->Resize();
            // Defer the sd::ShowWindow's GrabFocus to here. so that the accessible event can be fired correctly.
            pShell->GetActiveWindow()->GrabFocus();
        }
    }

    if( mxController.is() )
        mxController->activate();

    return true;
}

void SlideShow::deactivate()
{
    mxController->deactivate();
}

bool SlideShow::keyInput(const KeyEvent& rKEvt)
{
    return mxController.is() && mxController->keyInput(rKEvt);
}

void SlideShow::paint()
{
    if( mxController.is() )
        mxController->paint();
}

void SlideShow::pause( bool bPause )
{
    if( mxController.is() )
    {
        if( bPause )
            mxController->pause();
        else
            mxController->resume();
    }
}

bool SlideShow::swipe(const CommandGestureSwipeData& rSwipeData)
{
    return mxController.is() && mxController->swipe(rSwipeData);
}

bool SlideShow::longpress(const CommandGestureLongPressData& rLongPressData)
{
    return mxController.is() && mxController->longpress(rLongPressData);
}

void SlideShow::StartInPlacePresentationConfigurationCallback()
{
    if( mnInPlaceConfigEvent != nullptr )
        Application::RemoveUserEvent( mnInPlaceConfigEvent );

    mnInPlaceConfigEvent = Application::PostUserEvent( LINK( this, SlideShow, StartInPlacePresentationConfigurationHdl ) );
}

IMPL_LINK_NOARG(SlideShow, StartInPlacePresentationConfigurationHdl, void*, void)
{
    mnInPlaceConfigEvent = nullptr;
    StartInPlacePresentation();
}

void SlideShow::StartInPlacePresentation()
{
    if( mpCurrentViewShellBase )
    {
        // Save the current view shell type so that it can be restored after the
        // show has ended.  If there already is a saved shell type then that is
        // not overwritten.

        ViewShell::ShellType eShell = ViewShell::ST_NONE;

        ::std::shared_ptr<FrameworkHelper> pHelper(FrameworkHelper::Instance(*mpCurrentViewShellBase));
        ::std::shared_ptr<ViewShell> pMainViewShell(pHelper->GetViewShell(FrameworkHelper::msCenterPaneURL));

        if( pMainViewShell )
            eShell = pMainViewShell->GetShellType();

        if( eShell != ViewShell::ST_IMPRESS )
        {
            // Switch temporary to a DrawViewShell which supports the in-place presentation.

            if( pMainViewShell )
            {
                FrameView* pFrameView = pMainViewShell->GetFrameView();
                pFrameView->SetPresentationViewShellId(SID_VIEWSHELL1);
                pFrameView->SetPreviousViewShellType (pMainViewShell->GetShellType());
                pFrameView->SetPageKind (PageKind::Standard);
            }

            pHelper->RequestView( FrameworkHelper::msImpressViewURL, FrameworkHelper::msCenterPaneURL );
            pHelper->RunOnConfigurationEvent(
                framework::ConfigurationChangeEventType::ConfigurationUpdateEnd,
                [this] (bool const) { return this->StartInPlacePresentationConfigurationCallback(); } );
            return;
        }
        else
        {
            vcl::Window* pParentWindow = mxCurrentSettings->mpParentWindow;
            if( pParentWindow == nullptr )
                pParentWindow = mpCurrentViewShellBase->GetViewWindow();

            CreateController( pMainViewShell.get(), pMainViewShell->GetView(), pParentWindow );
        }
    }
    else if( mxCurrentSettings->mpParentWindow )
    {
        // no current view shell, but parent window
        CreateController( nullptr, nullptr, mxCurrentSettings->mpParentWindow );
    }

    if( !mxController.is() )
        return;

    bool bSuccess = false;
    if( mxCurrentSettings && mxCurrentSettings->mbPreview )
    {
        bSuccess = mxController->startPreview(mxCurrentSettings->mxStartPage, mxCurrentSettings->mxAnimationNode, mxCurrentSettings->mpParentWindow );
    }
    else
    {
        bSuccess = mxController->startShow(mxCurrentSettings.get());
    }

    if( !bSuccess )
        end();
    else
    {
        if( mpCurrentViewShellBase && ( !mxCurrentSettings || ( mxCurrentSettings && !mxCurrentSettings->mbPreview ) ) )
            mpCurrentViewShellBase->GetWindow()->GrabFocus();
    }
}

void SlideShow::StartFullscreenPresentation( )
{
    // Create the top level window in which the PresentationViewShell(Base)
    // will be created.  This is done here explicitly so that we can make it
    // fullscreen.
    const sal_Int32 nDisplay (GetDisplay());
    VclPtr<WorkWindow> pWorkWindow = VclPtr<FullScreenWorkWindow>::Create(this, mpCurrentViewShellBase);
    pWorkWindow->SetBackground(Wallpaper(COL_BLACK));
    OUString Title(SdResId(STR_FULLSCREEN_SLIDESHOW));
    Title = Title.replaceFirst("%s",
                               mpCurrentViewShellBase->GetDocShell()->GetTitle(SFX_TITLE_DETECT));
    pWorkWindow->SetText(Title);
    pWorkWindow->StartPresentationMode( true, mpDoc->getPresentationSettings().mbAlwaysOnTop ? PresentationFlags::HideAllApps : PresentationFlags::NONE, nDisplay);
    //    pWorkWindow->ShowFullScreenMode(sal_False, nDisplay);

    if (!pWorkWindow->IsVisible())
        return;

    // Initialize the new presentation view shell with a copy of the
    // frame view of the current view shell.  This avoids that
    // changes made by the presentation have an effect on the other
    // view shells.
    FrameView* pOriginalFrameView = nullptr;
    ::std::shared_ptr<ViewShell> xShell(mpCurrentViewShellBase->GetMainViewShell());
    if (xShell)
        pOriginalFrameView = xShell->GetFrameView();

    delete mpFullScreenFrameView;
    mpFullScreenFrameView = new FrameView(mpDoc, pOriginalFrameView);

    // The new frame is created hidden.  To make it visible and activate the
    // new view shell--a prerequisite to process slot calls and initialize
    // its panes--a GrabFocus() has to be called later on.
    SfxFrame* pNewFrame = SfxFrame::CreateHidden( *mpDoc->GetDocSh(), *pWorkWindow, PRESENTATION_FACTORY_ID );
    pNewFrame->SetPresentationMode(true);

    mpFullScreenViewShellBase = static_cast<ViewShellBase*>(pNewFrame->GetCurrentViewFrame()->GetViewShell());
    if(mpFullScreenViewShellBase != nullptr)
    {
        // The following GrabFocus() is responsible for activating the
        // new view shell.  Without it the screen remains blank (under
        // Windows and some Linux variants.)
        mpFullScreenViewShellBase->GetWindow()->GrabFocus();
    }
}

/// convert configuration setting display concept to real screens
sal_Int32 SlideShow::GetDisplay()
{
    sal_Int32 nDisplay = 0;

    SdOptions* pOptions = SdModule::get()->GetSdOptions(DocumentType::Impress);
    if( pOptions )
        nDisplay = pOptions->GetDisplay();

    if( nDisplay < 0 )
        nDisplay = -1;
    else if( nDisplay == 0)
        nDisplay = static_cast<sal_Int32>(Application::GetDisplayExternalScreen());
    else
        nDisplay--;

    SAL_INFO("sd", "Presenting on real screen " << nDisplay);

    return nDisplay;
}

bool SlideShow::dependsOn( ViewShellBase const * pViewShellBase )
{
    return mxController.is() && (pViewShellBase == mpCurrentViewShellBase) && mpFullScreenViewShellBase;
}

Reference< presentation::XPresentation2 > CreatePresentation( const SdDrawDocument& rDocument )
{
    return Reference< presentation::XPresentation2 >( SlideShow::Create( const_cast< SdDrawDocument* >( &rDocument ) ) );
}

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