/* -*- 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 <controller/SlsSlotManager.hxx>
#include <SlideSorter.hxx>
#include <SlideSorterViewShell.hxx>
#include <controller/SlideSorterController.hxx>
#include <controller/SlsClipboard.hxx>
#include <controller/SlsCurrentSlideManager.hxx>
#include <controller/SlsInsertionIndicatorHandler.hxx>
#include <controller/SlsPageSelector.hxx>
#include <controller/SlsSelectionFunction.hxx>
#include <controller/SlsSelectionManager.hxx>
#include <model/SlideSorterModel.hxx>
#include <model/SlsPageEnumerationProvider.hxx>
#include <model/SlsPageDescriptor.hxx>
#include <view/SlideSorterView.hxx>
#include <view/SlsLayouter.hxx>
#include <framework/FrameworkHelper.hxx>
#include <Window.hxx>
#include <fupoor.hxx>
#include <fucushow.hxx>
#include <fusldlg.hxx>
#include <fuexpand.hxx>
#include <fusumry.hxx>
#include <slideshow.hxx>
#include <app.hrc>
#include <strings.hrc>
#include <sdresid.hxx>
#include <unokywds.hxx>
#include <drawdoc.hxx>
#include <DrawDocShell.hxx>
#include <ViewShellBase.hxx>
#include <ViewShellImplementation.hxx>
#include <sdpage.hxx>
#include <sdxfer.hxx>
#include <helpids.h>
#include <unmodpg.hxx>
#include <DrawViewShell.hxx>
#include <sdabstdlg.hxx>
#include <sdmod.hxx>
#include <unomodel.hxx>

#include <vcl/uitest/logger.hxx>
#include <vcl/uitest/eventdescription.hxx>

#include <sfx2/request.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/sidebar/Sidebar.hxx>
#include <svx/svxids.hrc>
#include <svx/svxdlg.hxx>
#include <svl/intitem.hxx>
#include <svl/stritem.hxx>
#include <svl/whiter.hxx>
#include <svl/itempool.hxx>
#include <com/sun/star/drawing/XMasterPagesSupplier.hpp>
#include <com/sun/star/drawing/XDrawPages.hpp>
#include <osl/diagnose.h>
#include <tools/debug.hxx>

#include <memory>

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;

namespace sd::slidesorter::controller {

namespace {

/** The state of a set of slides with respect to being excluded from the
    slide show.
*/
enum SlideExclusionState {UNDEFINED, EXCLUDED, INCLUDED, MIXED};

/** Return for the given set of slides whether they included are
    excluded from the slide show.
*/
SlideExclusionState GetSlideExclusionState (model::PageEnumeration& rPageSet);

} // end of anonymous namespace


namespace {

void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& rAction)
{
    EventDescription aDescription;
    aDescription.aID = "impress_win_or_draw_win";
    aDescription.aParameters = std::move(aParameters);
    aDescription.aAction = rAction;
    aDescription.aKeyWord = "ImpressWindowUIObject";
    aDescription.aParent = "MainWindow";

    UITestLogger::getInstance().logEvent(aDescription);
}

}

SlotManager::SlotManager (SlideSorter& rSlideSorter)
    : mrSlideSorter(rSlideSorter)
{
}

void SlotManager::FuTemporary (SfxRequest& rRequest)
{
    SdDrawDocument* pDocument = mrSlideSorter.GetModel().GetDocument();

    SlideSorterViewShell* pShell
        = dynamic_cast<SlideSorterViewShell*>(&mrSlideSorter.GetViewShell());
    if (pShell == nullptr)
        return;

    switch (rRequest.GetSlot())
    {
        case SID_PRESENTATION:
        case SID_PRESENTATION_CURRENT_SLIDE:
        case SID_REHEARSE_TIMINGS:
            slideshowhelp::ShowSlideShow(rRequest, *mrSlideSorter.GetModel().GetDocument());
            pShell->Cancel();
            rRequest.Done();
            break;

        case SID_HIDE_SLIDE:
            ChangeSlideExclusionState(model::SharedPageDescriptor(), true);
            break;

        case SID_SHOW_SLIDE:
            ChangeSlideExclusionState(model::SharedPageDescriptor(), false);
            break;

        case SID_PAGES_PER_ROW:
            if (rRequest.GetArgs() != nullptr)
            {
                const SfxUInt16Item* pPagesPerRow = rRequest.GetArg(SID_PAGES_PER_ROW);
                if (pPagesPerRow != nullptr)
                {
                    sal_Int32 nColumnCount = pPagesPerRow->GetValue();
                    // Force the given number of columns by setting
                    // the minimal and maximal number of columns to
                    // the same value.
                    mrSlideSorter.GetView().GetLayouter().SetColumnCount (
                        nColumnCount, nColumnCount);
                    // Force a repaint and re-layout.
                    pShell->ArrangeGUIElements ();
                    // Rearrange the UI-elements controlled by the
                    // controller and force a rearrangement of the
                    // view.
                    mrSlideSorter.GetController().Rearrange(true);
                }
            }
            rRequest.Done();
            break;

        case SID_SELECTALL:
            mrSlideSorter.GetController().GetPageSelector().SelectAllPages();
            rRequest.Done();
            break;

        case SID_SLIDE_TRANSITIONS_PANEL:
        {
            // First make sure that the sidebar is visible
            if (SfxViewFrame* pFrame = pShell->GetViewFrame())
            {
                pFrame->ShowChildWindow(SID_SIDEBAR);
                ::sfx2::sidebar::Sidebar::ShowPanel(
                    u"SdSlideTransitionPanel",
                    pFrame->GetFrame().GetFrameInterface());
                rRequest.Ignore ();
            }
            break;
        }

        case SID_MASTER_SLIDES_PANEL:
        {
            // First make sure that the sidebar is visible
            if (SfxViewFrame* pFrame = pShell->GetViewFrame())
            {
                pFrame->ShowChildWindow(SID_SIDEBAR);
                ::sfx2::sidebar::Sidebar::ShowPanel(
                    u"SdAllMasterPagesPanel",
                    pFrame->GetFrame().GetFrameInterface());
                rRequest.Ignore ();
            }
            break;
        }

        case SID_PRESENTATION_DLG:
            FuSlideShowDlg::Create (
                *pShell,
                mrSlideSorter.GetContentWindow(),
                &mrSlideSorter.GetView(),
                *pDocument,
                rRequest);
            break;

        case SID_CUSTOMSHOW_DLG:
            FuCustomShowDlg::Create (
                *pShell,
                mrSlideSorter.GetContentWindow(),
                &mrSlideSorter.GetView(),
                *pDocument,
                rRequest);
                break;

        case SID_EXPAND_PAGE:
            FuExpandPage::Create (
                *pShell,
                mrSlideSorter.GetContentWindow(),
                &mrSlideSorter.GetView(),
                *pDocument,
                rRequest);
            break;

        case SID_SUMMARY_PAGE:
            FuSummaryPage::Create (
                *pShell,
                mrSlideSorter.GetContentWindow(),
                &mrSlideSorter.GetView(),
                *pDocument,
                rRequest);
            break;

        case SID_INSERTPAGE:
        case SID_INSERT_MASTER_PAGE:
            InsertSlide(rRequest);
            rRequest.Done();
            break;

        case SID_DUPLICATE_PAGE:
            DuplicateSelectedSlides(rRequest);
            rRequest.Done();
            break;

        case SID_DELETE_PAGE:
        case SID_DELETE_MASTER_PAGE:
        case SID_DELETE: // we need SID_CUT to handle the delete key
            // (DEL -> accelerator -> SID_CUT).
            if (mrSlideSorter.GetModel().GetPageCount() > 1)
            {
                mrSlideSorter.GetView().EndTextEditAllViews();
                mrSlideSorter.GetController().GetSelectionManager()->DeleteSelectedPages();
            }

            rRequest.Done();
            break;

        case SID_RENAMEPAGE:
        case SID_RENAME_MASTER_PAGE:
            RenameSlide (rRequest);
            rRequest.Done ();
            break;

        case SID_ASSIGN_LAYOUT:
        {
            pShell->mpImpl->AssignLayout( rRequest, PageKind::Standard );
            rRequest.Done ();
        }
        break;

        case SID_PHOTOALBUM:
        {
            SdAbstractDialogFactory* pFact = SdAbstractDialogFactory::Create();
            vcl::Window* pWin = mrSlideSorter.GetContentWindow();
            ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateSdPhotoAlbumDialog(
                pWin ? pWin->GetFrameWeld() : nullptr,
                *pDocument));
            pDlg->Execute();
            rRequest.Done ();
        }
        break;

        case SID_REMOTE_DLG:
        {
#ifdef ENABLE_SDREMOTE
            SdAbstractDialogFactory* pFact = SdAbstractDialogFactory::Create();
            vcl::Window* pWin = mrSlideSorter.GetContentWindow();
            ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateRemoteDialog(pWin ? pWin->GetFrameWeld() : nullptr));
            pDlg->Execute();
#endif
        }
        break;

        default:
            break;
    }
}

void SlotManager::FuPermanent (SfxRequest& rRequest)
{
    ViewShell& rShell = mrSlideSorter.GetViewShell();

    if(rShell.GetCurrentFunction().is())
    {
        rtl::Reference<FuPoor> xEmpty;
        if (rShell.GetOldFunction() == rShell.GetCurrentFunction())
            rShell.SetOldFunction(xEmpty);

        rShell.GetCurrentFunction()->Deactivate();
        rShell.SetCurrentFunction(xEmpty);
    }

    switch(rRequest.GetSlot())
    {
        case SID_OBJECT_SELECT:
            rShell.SetCurrentFunction( SelectionFunction::Create(mrSlideSorter, rRequest) );
            rRequest.Done();
            break;

        default:
                break;
    }

    if(rShell.GetOldFunction().is())
    {
        rShell.GetOldFunction()->Deactivate();
        rtl::Reference<FuPoor> xEmpty;
        rShell.SetOldFunction(xEmpty);
    }

    if(rShell.GetCurrentFunction().is())
    {
        rShell.GetCurrentFunction()->Activate();
        rShell.SetOldFunction(rShell.GetCurrentFunction());
    }

    //! that's only until ENUM-Slots ?are
    //  Invalidate( SID_OBJECT_SELECT );
}

void SlotManager::FuSupport (SfxRequest& rRequest)
{
    switch (rRequest.GetSlot())
    {
        case SID_STYLE_FAMILY:
            if (rRequest.GetArgs() != nullptr)
            {
                SdDrawDocument* pDocument
                    = mrSlideSorter.GetModel().GetDocument();
                if (pDocument != nullptr)
                {
                    const SfxPoolItem& rItem (
                        rRequest.GetArgs()->Get(SID_STYLE_FAMILY));
                    pDocument->GetDocSh()->SetStyleFamily(
                        static_cast<SfxStyleFamily>(static_cast<const SfxUInt16Item&>(rItem).GetValue()));
                }
            }
            break;

        case SID_PASTE:
        case SID_PASTE_SLIDE:
        {
            SdTransferable* pTransferClip = SdModule::get()->pTransferClip;
            if (!pTransferClip)
            {
                if (mrSlideSorter.GetController().GetClipboard().PasteSlidesFromSystemClipboard())
                    return;
            }
            if( pTransferClip )
            {
                SfxObjectShell* pTransferDocShell = pTransferClip->GetDocShell().get();

                DrawDocShell* pDocShell = dynamic_cast<DrawDocShell*>(pTransferDocShell);
                if (pDocShell && pDocShell->GetDoc()->GetPageCount() > 1)
                {
                    mrSlideSorter.GetController().GetClipboard().HandleSlotCall(rRequest);
                    break;
                }
            }
            ViewShellBase* pBase = mrSlideSorter.GetViewShellBase();
            if (pBase != nullptr)
            {
                std::shared_ptr<DrawViewShell> pDrawViewShell (
                    std::dynamic_pointer_cast<DrawViewShell>(pBase->GetMainViewShell()));
                if (pDrawViewShell != nullptr)
                    pDrawViewShell->FuSupport(rRequest);
            }
        }
        break;

        case SID_CUT:
        case SID_COPY:
        case SID_DELETE:
        case SID_COPY_SLIDE:
            mrSlideSorter.GetView().EndTextEditAllViews();
            mrSlideSorter.GetController().GetClipboard().HandleSlotCall(rRequest);
            break;

        case SID_DRAWINGMODE:
        case SID_NOTES_MODE:
        case SID_HANDOUT_MASTER_MODE:
        case SID_SLIDE_SORTER_MODE:
        case SID_OUTLINE_MODE:
        {
            ViewShellBase* pBase = mrSlideSorter.GetViewShellBase();
            if (pBase != nullptr)
            {
                framework::FrameworkHelper::Instance(*pBase)->HandleModeChangeSlot(
                    rRequest.GetSlot(), rRequest);
                rRequest.Done();
            }
            break;
        }

        case SID_UNDO:
        {
            SlideSorterViewShell* pViewShell
                = dynamic_cast<SlideSorterViewShell*>(&mrSlideSorter.GetViewShell());
            if (pViewShell != nullptr)
            {
                pViewShell->ImpSidUndo (rRequest);
            }
            break;
        }

        case SID_REDO:
        {
            SlideSorterViewShell* pViewShell
                = dynamic_cast<SlideSorterViewShell*>(&mrSlideSorter.GetViewShell());
            if (pViewShell != nullptr)
            {
                pViewShell->ImpSidRedo (rRequest);
            }
            break;
        }

        default:
            break;
    }
}

void SlotManager::ExecCtrl (SfxRequest& rRequest)
{
    ViewShell& rViewShell = mrSlideSorter.GetViewShell();
    sal_uInt16 nSlot = rRequest.GetSlot();
    switch (nSlot)
    {
        case SID_RELOAD:
        {
            // empty Undo-Manager
            mrSlideSorter.GetModel().GetDocument()->GetDocSh()->ClearUndoBuffer();

            // normal forwarding to ViewFrame for execution
            rViewShell.GetViewFrame()->ExecuteSlot(rRequest);

            // has to be finished right away
            return;
        }

        case SID_OUTPUT_QUALITY_COLOR:
        case SID_OUTPUT_QUALITY_GRAYSCALE:
        case SID_OUTPUT_QUALITY_BLACKWHITE:
        case SID_OUTPUT_QUALITY_CONTRAST:
        {
            // flush page cache
            rViewShell.ExecReq (rRequest);
            break;
        }

        case SID_MAIL_SCROLLBODY_PAGEDOWN:
        {
            rViewShell.ExecReq (rRequest);
            break;
        }

        case SID_OPT_LOCALE_CHANGED:
        {
            mrSlideSorter.GetController().UpdateAllPages();
            rViewShell.UpdatePreview (rViewShell.GetActualPage());
            rRequest.Done();
            break;
        }

        case SID_SEARCH_DLG:
            // We have to handle the SID_SEARCH_DLG slot explicitly because
            // in some cases (when the slide sorter is displayed in the
            // center pane) we want to disable the search dialog.  Therefore
            // we have to handle the execution of that slot as well.
            // We try to do that by forwarding the request to the view frame
            // of the view shell.
            rViewShell.GetViewFrame()->ExecuteSlot(rRequest);
            break;

        default:
            break;
    }
}

void SlotManager::GetAttrState (SfxItemSet& rSet)
{
    // Iterate over all items.
    SfxWhichIter aIter (rSet);
    sal_uInt16 nWhich = aIter.FirstWhich();
    while (nWhich)
    {
        sal_uInt16 nSlotId (nWhich);
        if (SfxItemPool::IsWhich(nWhich))
            nSlotId = mrSlideSorter.GetViewShell().GetPool().GetSlotId(nWhich);
        switch (nSlotId)
        {
            case SID_PAGES_PER_ROW:
                rSet.Put (
                    SfxUInt16Item (
                        nSlotId,
                        static_cast<sal_uInt16>(mrSlideSorter.GetView().GetLayouter().GetColumnCount())
                        )
                    );
            break;
        }
        nWhich = aIter.NextWhich();
    }
}

void SlotManager::GetMenuState (SfxItemSet& rSet)
{
    EditMode eEditMode = mrSlideSorter.GetModel().GetEditMode();
    ViewShell& rShell = mrSlideSorter.GetViewShell();
    DrawDocShell* pDocShell = mrSlideSorter.GetModel().GetDocument()->GetDocSh();

    if (rShell.GetCurrentFunction().is())
    {
        sal_uInt16 nSId = rShell.GetCurrentFunction()->GetSlotID();

        rSet.Put( SfxBoolItem( nSId, true ) );
    }
    rSet.Put( SfxBoolItem( SID_DRAWINGMODE, false ) );
    rSet.Put( SfxBoolItem( SID_SLIDE_SORTER_MODE, true ) );
    rSet.Put( SfxBoolItem( SID_OUTLINE_MODE, false ) );
    rSet.Put( SfxBoolItem( SID_NOTES_MODE, false ) );
    rSet.Put( SfxBoolItem( SID_HANDOUT_MASTER_MODE, false ) );

    if (rShell.IsMainViewShell())
    {
        rSet.DisableItem(SID_SPELL_DIALOG);
        rSet.DisableItem(SID_SEARCH_DLG);
    }

    if (SfxItemState::DEFAULT == rSet.GetItemState(SID_EXPAND_PAGE))
    {
        bool bDisable = true;
        if (eEditMode == EditMode::Page)
        {
            // At least one of the selected pages has to contain an outline
            // presentation objects in order to enable the expand page menu
            // entry.
            model::PageEnumeration aSelectedPages (
                model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
                    mrSlideSorter.GetModel()));
            while (aSelectedPages.HasMoreElements())
            {
                SdPage* pPage = aSelectedPages.GetNextElement()->GetPage();
                SdrObject* pObj = pPage->GetPresObj(PresObjKind::Outline);
                if (pObj!=nullptr )
                {
                    if( !pObj->IsEmptyPresObj() )
                    {
                        bDisable = false;
                    }
                    else
                    {
                        // check if the object is in edit, then if it's temporarily not empty
                        SdrTextObj* pTextObj = DynCastSdrTextObj( pObj );
                        if( pTextObj )
                        {
                            if( pTextObj->CanCreateEditOutlinerParaObject() )
                            {
                                bDisable = false;
                            }
                        }
                    }
                }
            }
        }

        if (bDisable)
            rSet.DisableItem (SID_EXPAND_PAGE);
    }

    if (SfxItemState::DEFAULT == rSet.GetItemState(SID_SUMMARY_PAGE))
    {
        bool bDisable = true;
        if (eEditMode == EditMode::Page)
        {
            // At least one of the selected pages has to contain a title
            // presentation objects in order to enable the summary page menu
            // entry.
            model::PageEnumeration aSelectedPages (
                model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
                    mrSlideSorter.GetModel()));
            while (aSelectedPages.HasMoreElements())
            {
                SdPage* pPage = aSelectedPages.GetNextElement()->GetPage();
                SdrObject* pObj = pPage->GetPresObj(PresObjKind::Title);

                if (pObj!=nullptr && !pObj->IsEmptyPresObj())
                    bDisable = false;
            }
        }
        if (bDisable)
            rSet.DisableItem (SID_SUMMARY_PAGE);
    }

    // starting of presentation possible?
    if( SfxItemState::DEFAULT == rSet.GetItemState( SID_PRESENTATION ) ||
        SfxItemState::DEFAULT == rSet.GetItemState( SID_REHEARSE_TIMINGS ) )
    {
        bool bDisable = true;
        model::PageEnumeration aAllPages (
            model::PageEnumerationProvider::CreateAllPagesEnumeration(mrSlideSorter.GetModel()));
        while (aAllPages.HasMoreElements())
        {
            SdPage* pPage = aAllPages.GetNextElement()->GetPage();

            if( !pPage->IsExcluded() )
                bDisable = false;
        }
        if( bDisable || pDocShell->IsPreview())
        {
            rSet.DisableItem( SID_PRESENTATION );
            rSet.DisableItem( SID_REHEARSE_TIMINGS );
        }
    }

    // Disable the rename slots when there are no or more than one slides/master
    // pages selected; disable the duplicate slot when there are no slides
    // selected:
    if (rSet.GetItemState(SID_RENAMEPAGE) == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_RENAME_MASTER_PAGE) == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_DUPLICATE_PAGE) == SfxItemState::DEFAULT)
    {
        int n = mrSlideSorter.GetController().GetPageSelector()
            .GetSelectedPageCount();
        if (n != 1)
        {
            rSet.DisableItem(SID_RENAMEPAGE);
            rSet.DisableItem(SID_RENAME_MASTER_PAGE);
        }
        if (n == 0)
        {
            rSet.DisableItem(SID_DUPLICATE_PAGE);
        }
    }

    if (rSet.GetItemState(SID_HIDE_SLIDE) == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_SHOW_SLIDE)  == SfxItemState::DEFAULT)
    {
        model::PageEnumeration aSelectedPages (
            model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
                mrSlideSorter.GetModel()));
        const SlideExclusionState eState (GetSlideExclusionState(aSelectedPages));
        switch (eState)
        {
            case MIXED:
                // Show both entries.
                break;

            case EXCLUDED:
                rSet.DisableItem(SID_HIDE_SLIDE);
                break;

            case INCLUDED:
                rSet.DisableItem(SID_SHOW_SLIDE);
                break;

            case UNDEFINED:
                rSet.DisableItem(SID_HIDE_SLIDE);
                rSet.DisableItem(SID_SHOW_SLIDE);
                break;
        }
    }

    if (eEditMode == EditMode::MasterPage)
    {
        // Disable some slots when in master page mode.
        rSet.DisableItem(SID_ASSIGN_LAYOUT);
        rSet.DisableItem(SID_INSERTPAGE);

        if (rSet.GetItemState(SID_DUPLICATE_PAGE) == SfxItemState::DEFAULT)
            rSet.DisableItem(SID_DUPLICATE_PAGE);
    }
}

void SlotManager::GetClipboardState ( SfxItemSet& rSet)
{
    SdTransferable* pTransferClip = SdModule::get()->pTransferClip;

    if (rSet.GetItemState(SID_PASTE) == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_PASTE_SPECIAL) == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_PASTE_SLIDE) == SfxItemState::DEFAULT)
    {
        // no own clipboard data?
        if ( !pTransferClip || !pTransferClip->GetDocShell().is() )
        {
            rSet.DisableItem(SID_PASTE);
            rSet.DisableItem(SID_PASTE_SPECIAL);
            rSet.DisableItem(SID_PASTE_SLIDE);
        }
        else
        {
            SfxObjectShell* pTransferDocShell = pTransferClip->GetDocShell().get();

            if( !pTransferDocShell || static_cast<DrawDocShell*>(pTransferDocShell)->GetDoc()->GetPageCount() <= 1 )
            {
                bool bIsPastingSupported (false);

                // No or just one page.  Check if there is anything that can be
                // pasted via a DrawViewShell.
                ViewShellBase* pBase = mrSlideSorter.GetViewShellBase();
                if (pBase != nullptr)
                {
                    std::shared_ptr<DrawViewShell> pDrawViewShell (
                        std::dynamic_pointer_cast<DrawViewShell>(pBase->GetMainViewShell()));
                    if (pDrawViewShell != nullptr)
                    {
                        TransferableDataHelper aDataHelper (
                            TransferableDataHelper::CreateFromSystemClipboard(
                                pDrawViewShell->GetActiveWindow()));
                        if (aDataHelper.GetFormatCount() > 0)
                            bIsPastingSupported = true;
                    }
                }

                if ( ! bIsPastingSupported)
                {
                    rSet.DisableItem(SID_PASTE);
                    rSet.DisableItem(SID_PASTE_SPECIAL);
                }
            }
        }
    }

    // Cut, copy and paste of master pages is not yet implemented properly
    if (rSet.GetItemState(SID_COPY) == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_PASTE)  == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_PASTE_SPECIAL)  == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_CUT)  == SfxItemState::DEFAULT)
    {
        if (mrSlideSorter.GetModel().GetEditMode() == EditMode::MasterPage)
        {
            if (rSet.GetItemState(SID_CUT) == SfxItemState::DEFAULT)
                rSet.DisableItem(SID_CUT);
            if (rSet.GetItemState(SID_PASTE_SPECIAL) == SfxItemState::DEFAULT)
                rSet.DisableItem(SID_PASTE_SPECIAL);
        }
    }

    ViewShellBase* pBase = mrSlideSorter.GetViewShellBase();
    if (pBase && pBase->GetObjectShell()->isContentExtractionLocked())
    {
        rSet.DisableItem(SID_COPY);
        rSet.DisableItem(SID_CUT);
    }

    // Cut, copy, and delete page are disabled when there is no selection.
    if (!(rSet.GetItemState(SID_CUT) == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_COPY)  == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_DELETE) == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_DELETE_PAGE) == SfxItemState::DEFAULT
        || rSet.GetItemState(SID_DELETE_MASTER_PAGE) == SfxItemState::DEFAULT))
        return;

    model::PageEnumeration aSelectedPages (
        model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
            mrSlideSorter.GetModel()));

    // For copy to work we have to have at least one selected page.
    if ( ! aSelectedPages.HasMoreElements())
        rSet.DisableItem(SID_COPY);

    bool bDisable = false;
    // The operations that lead to the deletion of a page are valid if
    // a) there is at least one selected page
    // b) deleting the selected pages leaves at least one page in the
    // document
    // c) selected master pages must not be used by slides.

    // Test a).
    if ( ! aSelectedPages.HasMoreElements())
        bDisable = true;
    // Test b): Count the number of selected pages.  It has to be less
    // than the number of all pages.
    else if (mrSlideSorter.GetController().GetPageSelector().GetSelectedPageCount()
        >= mrSlideSorter.GetController().GetPageSelector().GetPageCount())
        bDisable = true;
    // Test c): Iterate over the selected pages and look for a master
    // page that is used by at least one page.
    else while (aSelectedPages.HasMoreElements())
    {
        SdPage* pPage = aSelectedPages.GetNextElement()->GetPage();
        int nUseCount (mrSlideSorter.GetModel().GetDocument()
            ->GetMasterPageUserCount(pPage));
        if (nUseCount > 0)
        {
            bDisable = true;
            break;
        }
    }

    if (bDisable)
    {
        rSet.DisableItem(SID_CUT);
        rSet.DisableItem(SID_DELETE_PAGE);
        rSet.DisableItem(SID_DELETE_MASTER_PAGE);
    }
}

void SlotManager::GetStatusBarState (SfxItemSet& rSet)
{
    // page view and layout
    SdPage* pPage      = nullptr;
    sal_uInt16 nSelectedPages = mrSlideSorter.GetController().GetPageSelector().GetSelectedPageCount();
    View* pDrView = &mrSlideSorter.GetView();

    //Set number of slides
    if (nSelectedPages > 0)
    {
        model::PageEnumeration aSelectedPages (
            model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
                mrSlideSorter.GetModel()));
        model::SharedPageDescriptor pDescriptor (aSelectedPages.GetNextElement());
        OUString aPageStr;
        if (pDescriptor)
        {
            pPage = pDescriptor->GetPage();
            sal_uInt16 nFirstPage = (pPage->GetPageNum()/2) + 1;
            sal_Int32 nPageCount = mrSlideSorter.GetModel().GetPageCount();
            sal_Int32 nActivePageCount = static_cast<sal_Int32>(mrSlideSorter.GetModel().GetDocument()->GetActiveSdPageCount());

            if (pDrView->GetDoc().GetDocumentType() == DocumentType::Draw)
                aPageStr = (nPageCount == nActivePageCount) ? SdResId(STR_SD_PAGE_COUNT_DRAW) : SdResId(STR_SD_PAGE_COUNT_CUSTOM_DRAW);
            else
                aPageStr = (nPageCount == nActivePageCount) ? SdResId(STR_SD_PAGE_COUNT) : SdResId(STR_SD_PAGE_COUNT_CUSTOM);

            aPageStr = aPageStr.replaceFirst("%1", OUString::number(nFirstPage));
            aPageStr = aPageStr.replaceFirst("%2", OUString::number(nPageCount));
            if(nPageCount != nActivePageCount)
                aPageStr = aPageStr.replaceFirst("%3", OUString::number(nActivePageCount));
        }
        rSet.Put( SfxStringItem( SID_STATUS_PAGE, aPageStr ) );
    }
    //Set layout
    if (nSelectedPages == 1 && pPage != nullptr)
    {
        SdPage* pFirstPage = pPage;
        OUString aLayoutStr = pFirstPage->GetLayoutName();
        sal_Int32 nIndex = aLayoutStr.indexOf( SD_LT_SEPARATOR );
        if( nIndex != -1 )
            aLayoutStr = aLayoutStr.copy(0, nIndex);
        rSet.Put( SfxStringItem( SID_STATUS_LAYOUT, aLayoutStr ) );
    }
    //Scale value
    const Fraction& aUIScale = mrSlideSorter.GetModel().GetDocument()->GetUIScale();
    OUString aString = OUString::number(aUIScale.GetNumerator()) +
        ":" + OUString::number(aUIScale.GetDenominator());
    rSet.Put( SfxStringItem( SID_SCALE, aString ) );
}

void SlotManager::RenameSlide(const SfxRequest& rRequest)
{
    View* pDrView = &mrSlideSorter.GetView();

    if ( pDrView->IsTextEdit() )
    {
        pDrView->SdrEndTextEdit();
    }

    SdPage* pSelectedPage = nullptr;
    model::PageEnumeration aSelectedPages (
            model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
                mrSlideSorter.GetModel()));
    if (aSelectedPages.HasMoreElements())
        pSelectedPage = aSelectedPages.GetNextElement()->GetPage();
    if (pSelectedPage == nullptr)
        return;

    // tdf#107183 Set different dialog titles when renaming
    // master slides or normal ones
    OUString aTitle;
    if( rRequest.GetSlot() == SID_RENAME_MASTER_PAGE )
    {
        if (pDrView->GetDoc().GetDocumentType() == DocumentType::Draw)
            aTitle = SdResId( STR_TITLE_RENAMEMASTERPAGE );
        else
            aTitle = SdResId( STR_TITLE_RENAMEMASTERSLIDE );
    }
    else
    {
        if (pDrView->GetDoc().GetDocumentType() == DocumentType::Draw)
            aTitle = SdResId( STR_TITLE_RENAMEPAGE );
        else
            aTitle = SdResId( STR_TITLE_RENAMESLIDE );
    }

    OUString aDescr( SdResId( STR_DESC_RENAMESLIDE ) );
    OUString aPageName = pSelectedPage->GetName();

    if(rRequest.GetArgs())
    {
       OUString aName = rRequest.GetArgs()->GetItem<const SfxStringItem>(SID_RENAMEPAGE)->GetValue();

       bool bResult =  RenameSlideFromDrawViewShell(pSelectedPage->GetPageNum()/2, aName );
       DBG_ASSERT( bResult, "Couldn't rename slide or page" );
    }
    else
    {
        SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
        vcl::Window* pWin = mrSlideSorter.GetContentWindow();
        ScopedVclPtr<AbstractSvxNameDialog> aNameDlg(pFact->CreateSvxNameDialog(
                pWin ? pWin->GetFrameWeld() : nullptr,
                aPageName, aDescr));
        OUString aOldName = aNameDlg->GetName();
        aNameDlg->SetText( aTitle );
        aNameDlg->SetCheckNameHdl( LINK( this, SlotManager, RenameSlideHdl ) );
        aNameDlg->SetCheckNameTooltipHdl( LINK( this, SlotManager, RenameSlideTooltipHdl ) );
        aNameDlg->SetEditHelpId( HID_SD_NAMEDIALOG_PAGE );

        if( aNameDlg->Execute() == RET_OK )
        {
            OUString aNewName = aNameDlg->GetName();
            if (aNewName != aPageName)
            {
                bool bResult =
                        RenameSlideFromDrawViewShell(
                          pSelectedPage->GetPageNum()/2, aNewName );
                DBG_ASSERT( bResult, "Couldn't rename slide or page" );
            }
        }
        OUString aNewName = aNameDlg->GetName();
        collectUIInformation({{"OldName", aOldName}, {"NewName", aNewName}}, u"RENAME"_ustr);
        aNameDlg.disposeAndClear();
    }
    // Tell the slide sorter about the name change (necessary for
    // accessibility.)
    mrSlideSorter.GetController().PageNameHasChanged(
            (pSelectedPage->GetPageNum()-1)/2, aPageName);
}

IMPL_LINK(SlotManager, RenameSlideHdl, AbstractSvxNameDialog&, rDialog, bool)
{
    OUString aNewName = rDialog.GetName();

    model::SharedPageDescriptor pDescriptor (
        mrSlideSorter.GetController().GetCurrentSlideManager().GetCurrentSlide());
    SdPage* pCurrentPage = nullptr;
    if (pDescriptor)
        pCurrentPage = pDescriptor->GetPage();

    return (pCurrentPage!=nullptr && aNewName == pCurrentPage->GetName())
        || (mrSlideSorter.GetViewShell().GetDocSh()->IsNewPageNameValid( aNewName ) );
}

IMPL_STATIC_LINK_NOARG(SlotManager, RenameSlideTooltipHdl, AbstractSvxNameDialog&, OUString)
{
    return SdResId(STR_TOOLTIP_RENAME);
}

bool SlotManager::RenameSlideFromDrawViewShell( sal_uInt16 nPageId, const OUString & rName  )
{
    bool   bOutDummy;
    SdDrawDocument* pDocument = mrSlideSorter.GetModel().GetDocument();
    if( pDocument->GetPageByName( rName, bOutDummy ) != SDRPAGE_NOTFOUND )
        return false;

    SdPage* pPageToRename = nullptr;

    SfxUndoManager* pManager = pDocument->GetDocSh()->GetUndoManager();

    if( mrSlideSorter.GetModel().GetEditMode() == EditMode::Page )
    {
        model::SharedPageDescriptor pDescriptor (
            mrSlideSorter.GetController().GetCurrentSlideManager().GetCurrentSlide());
        if (pDescriptor)
            pPageToRename = pDescriptor->GetPage();

        if (pPageToRename != nullptr)
        {
            // Undo
            SdPage* pUndoPage = pPageToRename;
            SdrLayerAdmin &  rLayerAdmin = pDocument->GetLayerAdmin();
            SdrLayerID nBackground = rLayerAdmin.GetLayerID(sUNO_LayerName_background);
            SdrLayerID nBgObj = rLayerAdmin.GetLayerID(sUNO_LayerName_background_objects);
            SdrLayerIDSet aVisibleLayers = pPageToRename->TRG_GetMasterPageVisibleLayers();

            // (#67720#)
            pManager->AddUndoAction(
                std::make_unique<ModifyPageUndoAction>(
                    *pDocument, pUndoPage, rName, pUndoPage->GetAutoLayout(),
                    aVisibleLayers.IsSet( nBackground ),
                    aVisibleLayers.IsSet( nBgObj )));

            // rename
            pPageToRename->SetName( rName );

            // also rename notes-page
            SdPage* pNotesPage = pDocument->GetSdPage( nPageId, PageKind::Notes );
            if (pNotesPage != nullptr)
                pNotesPage->SetName (rName);
        }
    }
    else
    {
        // rename MasterPage -> rename LayoutTemplate
        pPageToRename = pDocument->GetMasterSdPage( nPageId, PageKind::Standard );
        if (pPageToRename != nullptr)
        {
            const OUString aOldLayoutName( pPageToRename->GetLayoutName() );
            pManager->AddUndoAction( std::make_unique<RenameLayoutTemplateUndoAction>( *pDocument, aOldLayoutName, rName ) );
            pDocument->RenameLayoutTemplate( aOldLayoutName, rName );
        }
    }

    bool bSuccess = pPageToRename!=nullptr && ( rName == pPageToRename->GetName() );

    if( bSuccess )
    {
        // user edited page names may be changed by the page so update control
        //        aTabControl.SetPageText( nPageId, rName );

        // set document to modified state
        pDocument->SetChanged();

        // inform navigator about change
        if (mrSlideSorter.GetViewShell().GetViewFrame())
            mrSlideSorter.GetViewShell().GetViewFrame()->GetBindings().Invalidate(
                        SID_NAVIGATOR_STATE, true);
    }

    return bSuccess;
}

/** Insert a slide.  The insertion position depends on a) the selection and
    b) the mouse position when there is no selection.

    When there is a selection then insertion takes place after the last
    slide of the selection.  For this to work all but the last selected
    slide are deselected first.

    Otherwise, when there is no selection but the insertion marker is visible
    the slide is inserted at that position.  The slide before that marker is
    selected first.

    When both the selection and the insertion marker are not visible--can
    that happen?--the new slide is inserted after the last slide.
*/
void SlotManager::InsertSlide (SfxRequest& rRequest)
{
    const sal_Int32 nInsertionIndex (GetInsertionPosition());

    PageSelector::BroadcastLock aBroadcastLock (mrSlideSorter);

    SdPage* pNewPage = nullptr;
    if (mrSlideSorter.GetModel().GetEditMode() == EditMode::Page)
    {
        SlideSorterViewShell* pShell = dynamic_cast<SlideSorterViewShell*>(
            &mrSlideSorter.GetViewShell());
        if (pShell != nullptr)
        {
            pNewPage = pShell->CreateOrDuplicatePage (
                rRequest,
                PageKind::Standard,
                nInsertionIndex>=0
                    ? mrSlideSorter.GetModel().GetPageDescriptor(nInsertionIndex)->GetPage()
                        : nullptr);
        }
    }
    else
    {
        // Use the API to create a new page.
        SdDrawDocument* pDocument = mrSlideSorter.GetModel().GetDocument();
        rtl::Reference<SdXImpressDocument> xMasterPagesSupplier (
            pDocument->getUnoModel());
        if (xMasterPagesSupplier.is())
        {
            Reference<drawing::XDrawPages> xMasterPages (
                xMasterPagesSupplier->getMasterPages());
            if (xMasterPages.is())
            {
                xMasterPages->insertNewByIndex (nInsertionIndex+1);

                // Create shapes for the default layout.
                pNewPage = pDocument->GetMasterSdPage(
                    static_cast<sal_uInt16>(nInsertionIndex+1), PageKind::Standard);
                pNewPage->CreateTitleAndLayout (true,true);
            }
        }
    }
    if (pNewPage == nullptr)
        return;

    // When a new page has been inserted then select it, make it the
    // current page, and focus it.
    view::SlideSorterView::DrawLock aDrawLock (mrSlideSorter);
    PageSelector::UpdateLock aUpdateLock (mrSlideSorter);
    mrSlideSorter.GetController().GetPageSelector().DeselectAllPages();
    mrSlideSorter.GetController().GetPageSelector().SelectPage(pNewPage);
    collectUIInformation({{"POS", OUString::number(nInsertionIndex + 2)}}, u"Insert_New_Page_or_Slide"_ustr);
}

void SlotManager::DuplicateSelectedSlides (SfxRequest& rRequest)
{
    // Create a list of the pages that are to be duplicated.  The process of
    // duplication alters the selection.
    sal_Int32 nInsertPosition (0);
    ::std::vector<SdPage*> aPagesToDuplicate;
    model::PageEnumeration aSelectedPages (
        model::PageEnumerationProvider::CreateSelectedPagesEnumeration(mrSlideSorter.GetModel()));
    while (aSelectedPages.HasMoreElements())
    {
        model::SharedPageDescriptor pDescriptor (aSelectedPages.GetNextElement());
        if (pDescriptor && pDescriptor->GetPage())
        {
            aPagesToDuplicate.push_back(pDescriptor->GetPage());
            nInsertPosition = pDescriptor->GetPage()->GetPageNum()+2;
        }
    }

    // Duplicate the pages in aPagesToDuplicate and collect the newly
    // created pages in aPagesToSelect.
    const bool bUndo (aPagesToDuplicate.size()>1 && mrSlideSorter.GetView().IsUndoEnabled());
    if (bUndo)
        mrSlideSorter.GetView().BegUndo(SdResId(STR_INSERTPAGE));

    ::std::vector<SdPage*> aPagesToSelect;
    for(const auto& rpPage : aPagesToDuplicate)
    {
        aPagesToSelect.push_back(
            mrSlideSorter.GetViewShell().CreateOrDuplicatePage(
                rRequest, PageKind::Standard, rpPage, nInsertPosition));
        nInsertPosition += 2;
    }
    aPagesToDuplicate.clear();

    if (bUndo)
        mrSlideSorter.GetView().EndUndo();

    // Set the selection to the pages in aPagesToSelect.
    PageSelector& rSelector (mrSlideSorter.GetController().GetPageSelector());
    rSelector.DeselectAllPages();
    for (auto const& it: aPagesToSelect)
    {
        rSelector.SelectPage(it);
    }

    collectUIInformation({{"POS", OUString::number(nInsertPosition + 2)}}, u"Duplicate"_ustr);
}

void SlotManager::ChangeSlideExclusionState (
    const model::SharedPageDescriptor& rpDescriptor,
    const bool bExcludeSlide)
{
    SdDrawDocument* pDocument = mrSlideSorter.GetModel().GetDocument();
    SfxUndoManager* pManager = pDocument->GetDocSh()->GetUndoManager();
    if (rpDescriptor)
    {
        mrSlideSorter.GetView().SetState(
            rpDescriptor,
            model::PageDescriptor::ST_Excluded,
            bExcludeSlide);
        pManager->AddUndoAction(std::make_unique<ChangeSlideExclusionStateUndoAction>(
            *pDocument, rpDescriptor, model::PageDescriptor::ST_Excluded, !bExcludeSlide));
    }
    else
    {
        model::PageEnumeration aSelectedPages (
            model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
                mrSlideSorter.GetModel()));
        std::unique_ptr<ChangeSlideExclusionStateUndoAction> pChangeSlideExclusionStateUndoAction(
            new ChangeSlideExclusionStateUndoAction(*pDocument, model::PageDescriptor::ST_Excluded,
                                                    !bExcludeSlide));
        while (aSelectedPages.HasMoreElements())
        {
            model::SharedPageDescriptor pDescriptor (aSelectedPages.GetNextElement());
            mrSlideSorter.GetView().SetState(
                pDescriptor,
                model::PageDescriptor::ST_Excluded,
                bExcludeSlide);
            pChangeSlideExclusionStateUndoAction->AddPageDescriptor(pDescriptor);
        }
        pManager->AddUndoAction(std::move(pChangeSlideExclusionStateUndoAction));
    }

    SfxBindings& rBindings (mrSlideSorter.GetViewShell().GetViewFrame()->GetBindings());
    rBindings.Invalidate(SID_PRESENTATION);
    rBindings.Invalidate(SID_REHEARSE_TIMINGS);
    rBindings.Invalidate(SID_HIDE_SLIDE);
    rBindings.Invalidate(SID_SHOW_SLIDE);
    mrSlideSorter.GetModel().GetDocument()->SetChanged();
}

sal_Int32 SlotManager::GetInsertionPosition() const
{
    PageSelector& rSelector (mrSlideSorter.GetController().GetPageSelector());

    // The insertion indicator is preferred.  After all the user explicitly
    // used it to define the insertion position.
    if (mrSlideSorter.GetController().GetInsertionIndicatorHandler()->IsActive())
    {
        // Select the page before the insertion indicator.
        return mrSlideSorter.GetController().GetInsertionIndicatorHandler()->GetInsertionPageIndex()
            - 1;
    }

    // Is there a stored insertion position?
    else if (mrSlideSorter.GetController().GetSelectionManager()->GetInsertionPosition() >= 0)
    {
        return mrSlideSorter.GetController().GetSelectionManager()->GetInsertionPosition() - 1;
    }

    // Use the index of the last selected slide.
    else if (rSelector.GetSelectedPageCount() > 0)
    {
        for (int nIndex=rSelector.GetPageCount()-1; nIndex>=0; --nIndex)
            if (rSelector.IsPageSelected(nIndex))
                return nIndex;

        // We should never get here.
        OSL_ASSERT(false);
        return rSelector.GetPageCount() - 1;
    }

    // Select the last page when there is at least one page.
    else if (rSelector.GetPageCount() > 0)
    {
        return rSelector.GetPageCount() - 1;
    }

    // Hope for the best that CreateOrDuplicatePage() can cope with an empty
    // selection.
    else
    {
        // We should never get here because there has to be at least one page.
        OSL_ASSERT(false);
        return -1;
    }
}

void SlotManager::NotifyEditModeChange()
{
    SfxBindings& rBindings (mrSlideSorter.GetViewShell().GetViewFrame()->GetBindings());
    rBindings.Invalidate(SID_PRESENTATION);
    rBindings.Invalidate(SID_INSERTPAGE);
    rBindings.Invalidate(SID_DUPLICATE_PAGE);
}

namespace {

SlideExclusionState GetSlideExclusionState (model::PageEnumeration& rPageSet)
{
    SlideExclusionState eState (UNDEFINED);

    // Get toggle state of the selected pages.
    while (rPageSet.HasMoreElements() && eState!=MIXED)
    {
        const bool bState = rPageSet.GetNextElement()->GetPage()->IsExcluded();
        switch (eState)
        {
            case UNDEFINED:
                // Use the first selected page to set the initial value.
                eState = bState ? EXCLUDED : INCLUDED;
                break;

            case EXCLUDED:
                // The pages before where all not part of the show,
                // this one is.
                if ( ! bState)
                    eState = MIXED;
                break;

            case INCLUDED:
                // The pages before where all part of the show,
                // this one is not.
                if (bState)
                    eState = MIXED;
                break;

            default:
                // No need to change anything.
                break;
        }
    }

    return eState;
}

} // end of anonymous namespace

} // end of namespace ::sd::slidesorter::controller

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