/* -*- 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/log.hxx>
#include <sal/types.h>
#include <sot/formats.hxx>
#include <utility>
#include <vcl/weld.hxx>
#include <svx/svditer.hxx>
#include <sfx2/docfile.hxx>
#include <svx/svdoole2.hxx>
#include <vcl/svapp.hxx>
#include <cusshow.hxx>

#include <sfx2/viewfrm.hxx>

#include <sdtreelb.hxx>
#include <DrawDocShell.hxx>
#include <drawdoc.hxx>
#include <sdpage.hxx>
#include <sdmod.hxx>
#include <sdresid.hxx>
#include <navigatr.hxx>
#include <strings.hrc>

#include <bitmaps.hlst>
#include <customshowlist.hxx>
#include <ViewShell.hxx>
#include <DrawController.hxx>
#include <ViewShellBase.hxx>

#include <com/sun/star/embed/XEmbedPersist.hpp>
#include <com/sun/star/embed/XEmbeddedObject.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <svtools/acceleratorexecute.hxx>
#include <svtools/embedtransfer.hxx>
#include <comphelper/servicehelper.hxx>
#include <comphelper/processfactory.hxx>

#include <vcl/commandevent.hxx>

#include <svx/svdview.hxx>
#include <DrawViewShell.hxx>

#include <svx/svdoashp.hxx>
#include <svx/sdasitm.hxx>
#include <svl/poolitem.hxx>
#include <svl/stritem.hxx>

#include <unordered_set>

#include <Window.hxx>

using namespace com::sun::star;

namespace {

sd::DrawViewShell* lcl_getDrawViewShell(const SdDrawDocument* pDoc)
{
    if (!pDoc || !pDoc->GetDocSh())
        return nullptr;
    return static_cast<sd::DrawViewShell*>(pDoc->GetDocSh()->GetViewShell());
}

}

SdPageObjsTLV::SdPageObjsTransferable::SdPageObjsTransferable(
        INetBookmark aBookmark,
    ::sd::DrawDocShell& rDocShell,
    NavigatorDragType eDragType)
    : SdTransferable(rDocShell.GetDoc(), nullptr, true),
      maBookmark(std::move( aBookmark )),
      mrDocShell( rDocShell ),
      meDragType( eDragType )
{
}

SdPageObjsTLV::SdPageObjsTransferable::~SdPageObjsTransferable()
{
}

void SdPageObjsTLV::SdPageObjsTransferable::AddSupportedFormats()
{
    AddFormat(SotClipboardFormatId::NETSCAPE_BOOKMARK);
    AddFormat(SotClipboardFormatId::TREELISTBOX);
    AddFormat(GetListBoxDropFormatId());
}

bool SdPageObjsTLV::SdPageObjsTransferable::GetData( const css::datatransfer::DataFlavor& rFlavor, const OUString& /*rDestDoc*/ )
{
    SotClipboardFormatId nFormatId = SotExchange::GetFormat( rFlavor );
    switch (nFormatId)
    {
        case SotClipboardFormatId::NETSCAPE_BOOKMARK:
            SetINetBookmark( maBookmark, rFlavor );
            return true;

        case SotClipboardFormatId::TREELISTBOX:
        {
            css::uno::Any aTreeListBoxData; // empty for now
            SetAny(aTreeListBoxData);
            return true;
        }

        default:
            return false;
    }
}

SdPageObjsTLV::SdPageObjsTransferable* SdPageObjsTLV::SdPageObjsTransferable::getImplementation( const css::uno::Reference< css::uno::XInterface >& rxData )
    noexcept
{
    return dynamic_cast<SdPageObjsTLV::SdPageObjsTransferable*>(rxData.get());
}

SotClipboardFormatId SdPageObjsTLV::SdPageObjsTransferable::GetListBoxDropFormatId()
{
    // Temporary flavor id that is used internally in the navigator
    static const SotClipboardFormatId id = SotExchange::RegisterFormatMimeType(u"application/x-openoffice-treelistbox-moveonly;windows_formatname=\"SV_LBOX_DD_FORMAT_MOVE\""_ustr);
    return id;
}

/**
 * @return true if children of the specified string are selected
 */
bool SdPageObjsTLV::HasSelectedChildren( std::u16string_view rName )
{
    bool bChildren = false;

    if( !rName.empty() )
    {
        std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
        OUString aTmp;

        if (m_xTreeView->get_iter_first(*xEntry))
        {
            do
            {
                aTmp = m_xTreeView->get_text(*xEntry);
                if (aTmp == rName)
                {

                    // see if any of the selected nodes are subchildren of this node
                    m_xTreeView->selected_foreach([this, &bChildren, &xEntry](weld::TreeIter& rEntry){
                        std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator(&rEntry));
                        while (!bChildren && m_xTreeView->iter_parent(*xParent))
                            bChildren = m_xTreeView->iter_compare(*xParent, *xEntry) == 0;
                        return bChildren;
                    });

                    break;
                }
            }
            while (m_xTreeView->iter_next(*xEntry));
        }
    }

    return bChildren;
}

void SdPageObjsTLV::SetShowAllShapes (
    const bool bShowAllShapes,
    const bool bFillList)
{
    m_bShowAllShapes = bShowAllShapes;
    if (bFillList)
    {
        if (m_pMedium == nullptr)
            Fill(m_pDoc, m_bShowAllPages, m_aDocName);
        else
            Fill(m_pDoc, m_pMedium, m_aDocName);
    }
}

void SdPageObjsTLV::SetOrderFrontToBack(const bool bOrderFrontToBack)
{
    m_bOrderFrontToBack = bOrderFrontToBack;
}

bool SdPageObjsTLV::IsEqualToShapeList(std::unique_ptr<weld::TreeIter>& rEntry, const SdrObjList& rList,
                                       std::u16string_view rListName)
{
    if (!rEntry)
        return false;
    OUString aName = m_xTreeView->get_text(*rEntry);

    if (rListName != aName)
        return false;

    if (!m_xTreeView->iter_next(*rEntry))
        rEntry.reset();

    SdrObjListIter aIter(&rList,
                         !rList.HasObjectNavigationOrder() /* use navigation order, if available */,
                         SdrIterMode::Flat);

    while (aIter.IsMore())
    {
        SdrObject* pObj = aIter.Next();

        const OUString aObjectName(GetObjectName(pObj));

        if (!aObjectName.isEmpty())
        {
            if (!rEntry)
                return false;

            aName = m_xTreeView->get_text(*rEntry);

            if (aObjectName != aName)
                return false;

            if (pObj->IsGroupObject())
            {
                bool bRet = IsEqualToShapeList(rEntry, *pObj->GetSubList(), aObjectName);
                if (!bRet)
                    return false;
            }
            else
            {
                if (!m_xTreeView->iter_next(*rEntry))
                    rEntry.reset();
            }
        }
    }

    return true;
}

/**
 * Checks if the pages (PageKind::Standard) of a doc and the objects on the pages
 * are identical to the TreeLB.
 * If a doc is provided, this will be the used doc (important by more than
 * one document).
 */
bool SdPageObjsTLV::IsEqualToDoc( const SdDrawDocument* pInDoc )
{
    if( pInDoc )
        m_pDoc = pInDoc;

    if( !m_pDoc )
        return false;

    sd::DrawViewShell* pDrawViewShell = lcl_getDrawViewShell(m_pDoc);
    if (!pDrawViewShell)
        return false;
    PageKind eDrawViewShellPageKind = pDrawViewShell->GetPageKind();
    if (eDrawViewShellPageKind != PageKind::Standard && eDrawViewShellPageKind != PageKind::Notes)
        return false;

    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
    if (!m_xTreeView->get_iter_first(*xEntry))
        xEntry.reset();

    // compare all pages including the objects
    sal_uInt16 nPage = 0;
    const sal_uInt16 nMaxPages = m_pDoc->GetPageCount();

    while( nPage < nMaxPages )
    {
        const SdPage* pPage = static_cast<const SdPage*>( m_pDoc->GetPage( nPage ) );
        if (pPage->GetPageKind() == eDrawViewShellPageKind)
        {
            bool bRet = IsEqualToShapeList(xEntry, *pPage, pPage->GetName());
            if (!bRet)
                return false;
        }
        nPage++;
    }
    // If there are still entries in the listbox,
    // then objects (with names) or pages were deleted
    return !xEntry;
}

IMPL_LINK(SdPageObjsTLV, CommandHdl, const CommandEvent&, rCEvt, bool)
{
    if (m_bEditing)
    {
        // Set the editing flag false here because gtk3 in-place editing ends but EditedEntryHdl
        // doesn't get called. This isn't needed for sal in-place editing because EditedEntryHdl
        // gets called when focus is lost.
        m_bEditing = false;
    }

    if (rCEvt.GetCommand() == CommandEventId::ContextMenu)
    {
        m_xTreeView->grab_focus();

        // select clicked entry
        if (std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
                rCEvt.IsMouseEvent() &&  m_xTreeView->get_dest_row_at_pos(
                    rCEvt.GetMousePosPixel(), xEntry.get(), false))
        {
            m_bSelectionHandlerNavigates = true;
            m_bNavigationGrabsFocus = false;
            m_xTreeView->set_cursor(*xEntry);
            Select();
        }

        bool bRet = m_aPopupMenuHdl.Call(rCEvt);
        return bRet;
    }

    return false;
}

IMPL_LINK(SdPageObjsTLV, KeyInputHdl, const KeyEvent&, rKEvt, bool)
{
    if (m_bEditing)
        return false;

    const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();

    // tdf#167573 - Deleting an object using Navigator deletes the whole page
    if (rKeyCode.GetCode() == KEY_DELETE)
    {
        // Seems this is needed for Gtk3 with Wayland but not with X11. Other VCL plugins seem not
        // to need this before explicitly trying to grab the draw shell focus.
        if (SfxViewShell* pCurSh = SfxViewShell::Current())
        {
            if (vcl::Window* pShellWnd = pCurSh->GetWindow())
            {
                pShellWnd->GrabFocus();
            }
        }
        // The current shell may be the slide sorter. Explicitly try to grab the draw
        // shell focus so delete works with the selected object(s) and not with the whole slide.
        if (sd::DrawDocShell* pDocShell = m_pDoc->GetDocSh())
        {
            if (sd::ViewShell* pViewShell = pDocShell->GetViewShell())
            {
                if (vcl::Window* pWindow = pViewShell->GetActiveWindow())
                {
                    pWindow->GrabFocus();
                    // now grab focus back to the tree
                    m_xTreeView->grab_focus();
                }
            }
        }
    }

    if (m_xAccel->execute(rKeyCode))
    {
        // the accelerator consumed the event
        return true;
    }
    if (rKeyCode.GetCode() == KEY_RETURN)
    {
        std::unique_ptr<weld::TreeIter> xCursor(m_xTreeView->make_iterator());
        if (m_xTreeView->get_cursor(xCursor.get()) && m_xTreeView->iter_has_child(*xCursor))
        {
            if (m_xTreeView->get_row_expanded(*xCursor))
                m_xTreeView->collapse_row(*xCursor);
            else
                m_xTreeView->expand_row(*xCursor);
        }
        m_bNavigationGrabsFocus = true;
        m_aRowActivatedHdl.Call(*m_xTreeView);
        m_bNavigationGrabsFocus = false;
        return true;
    }
    bool bRet = m_aKeyPressHdl.Call(rKEvt);
    return bRet;
}

IMPL_LINK(SdPageObjsTLV, MousePressHdl, const MouseEvent&, rMEvt, bool)
{
    // Don't set m_bEditing false here. Sal in-place editing doesn't like that because the in-place
    // editing mouse presses also end up here.
    if (m_bEditing)
        return false;

    m_bSelectionHandlerNavigates = rMEvt.GetClicks() == 1;
    m_bNavigationGrabsFocus = rMEvt.GetClicks() != 1;
    return false;
}

IMPL_LINK_NOARG(SdPageObjsTLV, MouseReleaseHdl, const MouseEvent&, bool)
{
    if (m_bEditing)
        return false;

    if (m_aMouseReleaseHdl.IsSet() && m_aMouseReleaseHdl.Call(MouseEvent()))
        return false;

    m_bSelectionHandlerNavigates = false;
    m_bNavigationGrabsFocus = true;
    return false;
}

IMPL_LINK(SdPageObjsTLV, DragBeginHdl, bool&, rUnsetDragIcon, bool)
{
    rUnsetDragIcon = false;
    return StartDrag();
}

namespace
{
    bool CanDragSource(const weld::TreeView& rTreeView)
    {
        std::unique_ptr<weld::TreeIter> xSource(rTreeView.make_iterator());
        if (!rTreeView.get_selected(xSource.get()))
            return false;

        std::unique_ptr<weld::TreeIter> xSourceParent(rTreeView.make_iterator(xSource.get()));
        bool bSourceHasParent = rTreeView.iter_parent(*xSourceParent);
        // disallow root drag
        if (!bSourceHasParent)
            return false;

        SdrObject* pSourceObject = weld::fromId<SdrObject*>(rTreeView.get_id(*xSource));
        if (pSourceObject == reinterpret_cast<SdrObject*>(1))
            pSourceObject = nullptr;

        if (!pSourceObject)
            return false;

        SdrPage* pObjectList = pSourceObject->getSdrPageFromSdrObject();
        if (!pObjectList)
            return false;

        return true;
    }
}

/**
 * StartDrag-Request
 */
bool SdPageObjsTLV::StartDrag()
{
    return !CanDragSource(*m_xTreeView) || DoDrag();
}

/**
 * Begin drag
 */
bool SdPageObjsTLV::DoDrag()
{
    if (!m_pNavigator)
        return true;

    if (!m_xHelper)
        return true;

    // Get the view.
    ::sd::DrawDocShell* pDocShell = m_pDoc->GetDocSh();
    ::sd::ViewShell* pViewShell = GetViewShellForDocShell(*pDocShell);
    if (pViewShell == nullptr)
    {
        OSL_ASSERT(pViewShell!=nullptr);
        return true;
    }
    sd::View* pView = pViewShell->GetView();
    if (pView == nullptr)
    {
        OSL_ASSERT(pView!=nullptr);
        return true;
    }

    m_xDropTargetHelper->SetDrawView(pViewShell->GetDrawView());
    m_xDropTargetHelper->SetOrderFrontToBack(m_bOrderFrontToBack);

    std::unique_ptr<weld::TreeIter> xEntry = m_xTreeView->make_iterator();
    bool bUserData = m_xTreeView->get_cursor(xEntry.get());

    SdrObject* pObject = nullptr;
    sal_Int64 nUserData = bUserData ? m_xTreeView->get_id(*xEntry).toInt64() : 0;
    if (nUserData != 1)
        pObject = reinterpret_cast<SdrObject*>(nUserData);
    if (pObject != nullptr)
    {
        // For shapes without a user supplied name (the automatically
        // created name does not count), a different drag and drop technique
        // is used.
        if (GetObjectName(pObject, false).isEmpty())
        {
            AddShapeToTransferable(*m_xHelper, *pObject);
            m_xHelper->SetView(pView);
            SdModule::get()->pTransferDrag = m_xHelper.get();
        }

        // Unnamed shapes have to be selected to be recognized by the
        // current drop implementation.  In order to have a consistent
        // behaviour for all shapes, every shape that is to be dragged is
        // selected first.
        SdrPageView* pPageView = pView->GetSdrPageView();
        pView->UnmarkAllObj(pPageView);
        pView->MarkObj(pObject, pPageView);
    }
    else
    {
        m_xHelper->SetView(pView);
        SdModule::get()->pTransferDrag = m_xHelper.get();
    }

    return false;
}

SdPageObjsTLVDropTarget::SdPageObjsTLVDropTarget(weld::TreeView& rTreeView)
    : DropTargetHelper(rTreeView.get_drop_target())
    , m_rTreeView(rTreeView)
    , m_pSdrView(nullptr)
{
}

/**
 * AcceptDrop-Event
 */
sal_Int8 SdPageObjsTLVDropTarget::AcceptDrop(const AcceptDropEvent& rEvt)
{
    weld::TreeView* pSource = m_rTreeView.get_drag_source();
    // only dragging within the same widget allowed
    if (!pSource || pSource != &m_rTreeView)
        return DND_ACTION_NONE;

    std::unique_ptr<weld::TreeIter> xTarget(m_rTreeView.make_iterator());
    if (!m_rTreeView.get_dest_row_at_pos(rEvt.maPosPixel, xTarget.get(), true))
        return DND_ACTION_NONE;

    // disallow when root is drop target
    if (m_rTreeView.get_iter_depth(*xTarget) == 0)
        return DND_ACTION_NONE;

    // disallow if there is no source entry selected
    std::unique_ptr<weld::TreeIter> xSource(m_rTreeView.make_iterator());
    if (!m_rTreeView.get_selected(xSource.get()))
        return DND_ACTION_NONE;

    // disallow when root is source
    if (m_rTreeView.get_iter_depth(*xSource) == 0)
        return DND_ACTION_NONE;

    // disallow when the source is the parent or ancestral parent of the target
    std::unique_ptr<weld::TreeIter> xTargetParent(m_rTreeView.make_iterator(xTarget.get()));
    while (m_rTreeView.get_iter_depth(*xTargetParent) > 1)
    {
        if (!m_rTreeView.iter_parent(*xTargetParent) ||
                m_rTreeView.iter_compare(*xSource, *xTargetParent) == 0)
            return DND_ACTION_NONE;
    }

    // disallow drop when source and target are not within the same page
    std::unique_ptr<weld::TreeIter> xSourcePage(m_rTreeView.make_iterator(xSource.get()));
    std::unique_ptr<weld::TreeIter> xTargetPage(m_rTreeView.make_iterator(xTarget.get()));
    while (m_rTreeView.get_iter_depth(*xTargetPage))
        m_rTreeView.iter_parent(*xTargetPage);
    while (m_rTreeView.get_iter_depth(*xSourcePage))
        m_rTreeView.iter_parent(*xSourcePage);
    if (m_rTreeView.iter_compare(*xTargetPage, *xSourcePage) != 0)
        return DND_ACTION_NONE;

    return DND_ACTION_MOVE;
}

/**
 * ExecuteDrop-Event
 */
sal_Int8 SdPageObjsTLVDropTarget::ExecuteDrop( const ExecuteDropEvent& rEvt )
{
    weld::TreeView* pSource = m_rTreeView.get_drag_source();
    // only dragging within the same widget allowed
    if (!pSource || pSource != &m_rTreeView)
        return DND_ACTION_NONE;

    std::unique_ptr<weld::TreeIter> xSource(m_rTreeView.make_iterator());
    if (!m_rTreeView.get_selected(xSource.get()))
        return DND_ACTION_NONE;

    std::unique_ptr<weld::TreeIter> xTarget(m_rTreeView.make_iterator());
    if (!m_rTreeView.get_dest_row_at_pos(rEvt.maPosPixel, xTarget.get(), false))
        return DND_ACTION_NONE;

    auto nIterCompare = m_rTreeView.iter_compare(*xSource, *xTarget);
    if (nIterCompare == 0)
    {
        // drop position is the same as source position
        return DND_ACTION_NONE;
    }

    SdrObject* pTargetObject = weld::fromId<SdrObject*>(m_rTreeView.get_id(*xTarget));
    SdrObject* pSourceObject = weld::fromId<SdrObject*>(m_rTreeView.get_id(*xSource));
    if (pSourceObject == reinterpret_cast<SdrObject*>(1))
        pSourceObject = nullptr;
    if (pTargetObject == reinterpret_cast<SdrObject*>(1))
        pTargetObject = nullptr;

    if (pTargetObject != nullptr && pSourceObject != nullptr && m_pSdrView)
    {
        SdrPage* pObjectList = pSourceObject->getSdrPageFromSdrObject();

        std::unique_ptr<weld::TreeIter> xSourceParent(m_rTreeView.make_iterator(xSource.get()));
        m_rTreeView.iter_parent(*xSourceParent);
        std::unique_ptr<weld::TreeIter> xTargetParent(m_rTreeView.make_iterator(xTarget.get()));
        m_rTreeView.iter_parent(*xTargetParent);

        int nTargetPos = m_rTreeView.get_iter_index_in_parent(*xTarget);

        // Make the tree view what the model will be when it is changed below.
        m_rTreeView.move_subtree(*xSource, xTargetParent.get(), nTargetPos);
        m_rTreeView.iter_previous_sibling(*xTarget);
        m_rTreeView.set_cursor(*xTarget);

        // Remove and insert are required for moving objects into and out of groups.
        // PutMarked... by itself would suffice if this wasn't allowed.

        // Remove the source object from source parent list and insert it in the target parent list.
        SdrObject* pSourceParentObject = weld::fromId<SdrObject*>(m_rTreeView.get_id(*xSourceParent));
        SdrObject* pTargetParentObject = weld::fromId<SdrObject*>(m_rTreeView.get_id(*xTargetParent));

        // Presumably there is need for a hard reference to hold on to the removed object so it is
        // guaranteed to be valid for insert back into an object list.
        rtl::Reference<SdrObject> rSourceObject;

        // remove object
        if (pSourceParentObject == reinterpret_cast<SdrObject*>(1))
        {
            rSourceObject = pObjectList->NbcRemoveObject(pSourceObject->GetOrdNum());
        }
        else
        {
            SdrObjList* pList = pSourceParentObject->GetSubList();
            rSourceObject = pList->NbcRemoveObject(pSourceObject->GetOrdNum());
        }

        // insert object
        if (pTargetParentObject == reinterpret_cast<SdrObject*>(1))
        {
            pObjectList->NbcInsertObject(rSourceObject.get());
        }
        else
        {
            SdrObjList* pList = pTargetParentObject->GetSubList();
            pList->NbcInsertObject(rSourceObject.get());
        }

        m_bOrderFrontToBack ? m_pSdrView->PutMarkedInFrontOfObj(pTargetObject) :
                              m_pSdrView->PutMarkedBehindObj(pTargetObject);
    }

    return DND_ACTION_NONE;
}

void SdPageObjsTLV::AddShapeToTransferable (
    SdTransferable& rTransferable,
    const SdrObject& rObject) const
{
    std::unique_ptr<TransferableObjectDescriptor> pObjectDescriptor(new TransferableObjectDescriptor);
    bool bIsDescriptorFillingPending (true);

    const SdrOle2Obj* pOleObject = dynamic_cast<const SdrOle2Obj*>(&rObject);
    if (pOleObject != nullptr && pOleObject->GetObjRef().is())
    {
        // If object has no persistence it must be copied as part of the document
        try
        {
            uno::Reference< embed::XEmbedPersist > xPersObj (pOleObject->GetObjRef(), uno::UNO_QUERY );
            if (xPersObj.is() && xPersObj->hasEntry())
            {
                SvEmbedTransferHelper::FillTransferableObjectDescriptor(
                    *pObjectDescriptor,
                    pOleObject->GetObjRef(),
                    pOleObject->GetGraphic(),
                    pOleObject->GetAspect());
                bIsDescriptorFillingPending = false;
            }
        }
        catch( uno::Exception& )
        {
        }
    }

    ::sd::DrawDocShell* pDocShell = m_pDoc->GetDocSh();
    if (bIsDescriptorFillingPending && pDocShell!=nullptr)
    {
        pDocShell->FillTransferableObjectDescriptor(*pObjectDescriptor);
    }

    Point aDragPos (rObject.GetCurrentBoundRect().Center());
    pObjectDescriptor->maDragStartPos = aDragPos;
    if (pDocShell != nullptr)
        pObjectDescriptor->maDisplayName = pDocShell->GetMedium()->GetURLObject().GetURLNoPass();
    else
        pObjectDescriptor->maDisplayName.clear();

    rTransferable.SetStartPos(aDragPos);
    rTransferable.SetObjectDescriptor( std::move(pObjectDescriptor) );
}

::sd::ViewShell* SdPageObjsTLV::GetViewShellForDocShell (::sd::DrawDocShell& rDocShell)
{
    {
        ::sd::ViewShell* pViewShell = rDocShell.GetViewShell();
        if (pViewShell != nullptr)
            return pViewShell;
    }

    try
    {
        // Get a component enumeration from the desktop and search it for documents.
        const uno::Reference<uno::XComponentContext>& xContext( ::comphelper::getProcessComponentContext());

        uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(xContext);

        if ( ! xDesktop.is())
            return nullptr;

        uno::Reference<container::XIndexAccess> xFrameAccess = xDesktop->getFrames();
        if ( ! xFrameAccess.is())
            return nullptr;

        for (sal_Int32 nIndex=0,nCount=xFrameAccess->getCount(); nIndex<nCount; ++nIndex)
        {
            uno::Reference<frame::XFrame> xFrame;
            if ( ! (xFrameAccess->getByIndex(nIndex) >>= xFrame))
                continue;

            auto xController = xFrame->getController();
            ::sd::DrawController* pController = dynamic_cast<sd::DrawController*>(xController.get());
            if (pController == nullptr)
                continue;
            ::sd::ViewShellBase* pBase = pController->GetViewShellBase();
            if (pBase == nullptr)
                continue;
            if (pBase->GetDocShell() != &rDocShell)
                continue;

            const std::shared_ptr<sd::ViewShell> pViewShell (pBase->GetMainViewShell());
            if (pViewShell)
                return pViewShell.get();
        }
    }
    catch (uno::Exception &)
    {
        // When there is an exception then simply use the default value of
        // bIsEnabled and disable the controls.
    }
    return nullptr;
}

SdPageObjsTLV::SdPageObjsTLV(std::unique_ptr<weld::TreeView> xTreeView)
    : m_xTreeView(std::move(xTreeView))
    , m_xScratchIter(m_xTreeView->make_iterator())
    , m_xDropTargetHelper(new SdPageObjsTLVDropTarget(*m_xTreeView))
    , m_xAccel(::svt::AcceleratorExecute::createAcceleratorHelper())
    , m_pNavigator(nullptr)
    , m_pDoc(nullptr)
    , m_pBookmarkDoc(nullptr)
    , m_pMedium(nullptr)
    , m_pOwnMedium(nullptr)
    , m_bLinkableSelected(false)
    , m_bShowAllShapes(false)
    , m_bOrderFrontToBack(false)
    , m_bShowAllPages(false)
    , m_bSelectionHandlerNavigates(false)
    , m_bNavigationGrabsFocus(true)
    , m_eSelectionMode(SelectionMode::Single)
    , m_nSelectEventId(nullptr)
    , m_nRowActivateEventId(nullptr)
{
    m_xTreeView->connect_expanding(LINK(this, SdPageObjsTLV, RequestingChildrenHdl));
    m_xTreeView->connect_selection_changed(LINK(this, SdPageObjsTLV, SelectHdl));
    m_xTreeView->connect_row_activated(LINK(this, SdPageObjsTLV, RowActivatedHdl));
    m_xTreeView->connect_drag_begin(LINK(this, SdPageObjsTLV, DragBeginHdl));
    m_xTreeView->connect_key_press(LINK(this, SdPageObjsTLV, KeyInputHdl));
    m_xTreeView->connect_mouse_press(LINK(this, SdPageObjsTLV, MousePressHdl));
    m_xTreeView->connect_mouse_release(LINK(this, SdPageObjsTLV, MouseReleaseHdl));
    m_xTreeView->connect_editing(LINK(this, SdPageObjsTLV, EditingEntryHdl),
                                 LINK(this, SdPageObjsTLV, EditedEntryHdl));
    m_xTreeView->connect_popup_menu(LINK(this, SdPageObjsTLV, CommandHdl));

    m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * 28,
                                  m_xTreeView->get_text_height() * 8);
    m_xTreeView->set_column_editables({true});
}

IMPL_LINK(SdPageObjsTLV, EditEntryAgain, void*, pTreeIter, void)
{
    m_xTreeView->grab_focus();
    std::unique_ptr<weld::TreeIter> xEntry(static_cast<weld::TreeIter*>(pTreeIter));
    m_xTreeView->start_editing(*xEntry);
}

IMPL_LINK_NOARG(SdPageObjsTLV, EditingEntryHdl, const weld::TreeIter&, bool)
{
    m_bEditing = true;
    return true;
}

IMPL_LINK(SdPageObjsTLV, EditedEntryHdl, const IterString&, rIterString, bool)
{
    m_bEditing = false;

    // Did the name change?
    if (m_xTreeView->get_text(rIterString.first) == rIterString.second)
        return true;

    // If the new name is empty or not unique, start editing again.
    if (rIterString.second.isEmpty() || m_pDoc->GetObj(rIterString.second)
        || m_pDoc->IsPageNameUnique(rIterString.second))
    {
        std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator(&rIterString.first));
        Application::PostUserEvent(LINK(this, SdPageObjsTLV, EditEntryAgain), xEntry.release());
        return false;
    }

    // set the new name
    const auto aEntryId = m_xTreeView->get_id(rIterString.first);
    if (aEntryId.toInt64() == 1)
    {
        // page name
        if (::sd::DrawDocShell* pDocShell = m_pDoc->GetDocSh())
        {
            if (::sd::ViewShell* pViewShell = GetViewShellForDocShell(*pDocShell))
            {
                SdPage* pPage = pViewShell->GetActualPage();
                pPage->SetName(rIterString.second);
            }
        }
    }
    else if (SdrObject* pCursorEntryObject = weld::fromId<SdrObject*>(aEntryId))
    {
        // object name
        pCursorEntryObject->SetName(rIterString.second);
    }

    return true;
}

IMPL_LINK_NOARG(SdPageObjsTLV, SelectHdl, weld::TreeView&, void)
{
    if (m_nSelectEventId)
        Application::RemoveUserEvent(m_nSelectEventId);
    // post the event to process select event after mouse press event
    m_nSelectEventId = Application::PostUserEvent(LINK(this, SdPageObjsTLV, AsyncSelectHdl));
}

IMPL_LINK_NOARG(SdPageObjsTLV, RowActivatedHdl, weld::TreeView&, bool)
{
    if (m_nRowActivateEventId)
        Application::RemoveUserEvent(m_nRowActivateEventId);
    // post the event to process row activate after mouse press event
    m_nRowActivateEventId = Application::PostUserEvent(LINK(this, SdPageObjsTLV, AsyncRowActivatedHdl));
    return false;
}

IMPL_LINK_NOARG(SdPageObjsTLV, AsyncSelectHdl, void*, void)
{
    Select();
}

void SdPageObjsTLV::Select()
{
    m_nSelectEventId = nullptr;

    if (m_bEditing)
        return;

    m_bLinkableSelected = true;

    m_xTreeView->selected_foreach([this](weld::TreeIter& rEntry){
        if (m_xTreeView->get_id(rEntry).toInt64() == 0)
            m_bLinkableSelected = false;
        return false;
    });

    m_aChangeHdl.Call(*m_xTreeView);

    if (m_bSelectionHandlerNavigates)
        m_aRowActivatedHdl.Call(*m_xTreeView);

    if (!m_pNavigator)
    {
        m_xHelper.clear();
        return;
    }

    ::sd::DrawDocShell* pDocShell = m_pDoc->GetDocSh();
    OUString aURL = INetURLObject(pDocShell->GetMedium()->GetPhysicalName(), INetProtocol::File).GetMainURL(INetURLObject::DecodeMechanism::NONE);
    NavigatorDragType eDragType = m_pNavigator->GetNavigatorDragType();

    OUString sSelectedEntry = get_cursor_text(); // what about multiple selections?
    aURL += "#" + sSelectedEntry;

    INetBookmark aBookmark(aURL, sSelectedEntry);
    sal_Int8 nDNDActions = DND_ACTION_COPYMOVE;

    if( eDragType == NAVIGATOR_DRAGTYPE_LINK )
        nDNDActions = DND_ACTION_LINK;  // Either COPY *or* LINK, never both!
    else
    {
        // Only for page/slide entry and only if page/slide entry dragging is re-enabled.
        // Commit 1b031eb1ba6c529ce67ff8f471afee414d64a098 disabled page/slide entry dragging.
        std::unique_ptr<weld::TreeIter> xIter(m_xTreeView->make_iterator());
        if (m_xTreeView->get_cursor(xIter.get()) && m_xTreeView->get_iter_depth(*xIter) == 0
            && m_pDoc->GetSdPageCount(PageKind::Standard) == 1)
        {
            // Can not move away the last slide in a document.
            nDNDActions = DND_ACTION_COPY;
        }
    }

    // object is destroyed by internal reference mechanism
    m_xHelper.set(new SdPageObjsTLV::SdPageObjsTransferable(std::move(aBookmark), *pDocShell, eDragType));
    rtl::Reference<TransferDataContainer> xHelper(m_xHelper);
    m_xTreeView->enable_drag_source(xHelper, nDNDActions);
}

IMPL_LINK_NOARG(SdPageObjsTLV, AsyncRowActivatedHdl, void*, void)
{
    m_nRowActivateEventId = nullptr;
    m_aRowActivatedHdl.Call(*m_xTreeView);
}

OUString SdPageObjsTLV::GetObjectName(
    const SdrObject* pObject,
    const bool bCreate) const
{
    OUString aRet;

    if ( pObject )
    {
        aRet = pObject->GetName();

        if (aRet.isEmpty())
            if (auto pOleObj = dynamic_cast<const SdrOle2Obj* >(pObject))
                aRet = pOleObj->GetPersistName();
    }

    if (bCreate
        && m_bShowAllShapes
        && aRet.isEmpty()
        && pObject!=nullptr)
    {
        OUString sObjName;
        if (pObject->GetObjIdentifier() == SdrObjKind::CustomShape)
        {
            // taken from SdrObjCustomShape::GetCustomShapeName
            OUString aEngine(pObject->GetMergedItem(SDRATTR_CUSTOMSHAPE_ENGINE).GetValue());
            if(aEngine.isEmpty() || aEngine == "com.sun.star.drawing.EnhancedCustomShapeEngine")
            {
                OUString sShapeType;
                const SdrCustomShapeGeometryItem& rGeometryItem
                    = pObject->GetMergedItem(SDRATTR_CUSTOMSHAPE_GEOMETRY);
                const uno::Any* pAny = rGeometryItem.GetPropertyValueByName(u"Type"_ustr);
                if (pAny && (*pAny >>= sShapeType))
                    sObjName = SdResId(STR_NAVIGATOR_CUSTOMSHAPE) + u": " + sShapeType;
            }
        }
        else
            sObjName = pObject->TakeObjNameSingul();

        aRet = SdResId(STR_NAVIGATOR_SHAPE_BASE_NAME) + " (" + sObjName +")";
        aRet = aRet.replaceFirst("%1", OUString::number(pObject->GetOrdNum() + 1));
    }

    return aRet;
}

std::vector<OUString> SdPageObjsTLV::GetSelectEntryList(const int nDepth) const
{
    std::vector<OUString> aEntries;

    m_xTreeView->selected_foreach([this, nDepth, &aEntries](weld::TreeIter& rEntry){
        int nListDepth = m_xTreeView->get_iter_depth(rEntry);
        if (nListDepth == nDepth)
            aEntries.push_back(m_xTreeView->get_text(rEntry));
        return false;
    });

    return aEntries;
}

std::vector<OUString> SdPageObjsTLV::GetSelectedEntryIds() const
{
    std::vector<OUString> vEntryIds;

    m_xTreeView->selected_foreach([this, &vEntryIds](weld::TreeIter& rEntry){
        vEntryIds.push_back(m_xTreeView->get_id(rEntry));
        return false;
    });

    return vEntryIds;
}

/**
 * Checks if it is a draw file and opens the BookmarkDoc depending of
 * the provided Docs
 */
SdDrawDocument* SdPageObjsTLV::GetBookmarkDoc(SfxMedium* pMed)
{
    if (
       !m_pBookmarkDoc ||
         (pMed && (!m_pOwnMedium || m_pOwnMedium->GetName() != pMed->GetName()))
      )
    {
        // create a new BookmarkDoc if now one exists or if a new Medium is provided
        if (m_pOwnMedium != pMed)
        {
            CloseBookmarkDoc();
        }

        if (pMed)
        {
            // it looks that it is undefined if a Medium was set by Fill() already
            DBG_ASSERT( !m_pMedium, "SfxMedium confusion!" );
            delete m_pMedium;
            m_pMedium = nullptr;

            // take over this Medium (currently used only be Navigator)
            m_pOwnMedium = pMed;
        }

        DBG_ASSERT( m_pMedium || pMed, "No SfxMedium provided!" );

        if( pMed )
        {
            // in this mode the document is also owned and controlled by this instance
            m_xBookmarkDocShRef = new ::sd::DrawDocShell(SfxObjectCreateMode::STANDARD, true, DocumentType::Impress);
            if (m_xBookmarkDocShRef->DoLoad(pMed))
                m_pBookmarkDoc = m_xBookmarkDocShRef->GetDoc();
            else
                m_pBookmarkDoc = nullptr;
        }
        else if ( m_pMedium )
            // in this mode the document is owned and controlled by the SdDrawDocument
            // it can be released by calling the corresponding CloseBookmarkDoc method
            // successful creation of a document makes this the owner of the medium
            m_pBookmarkDoc = const_cast<SdDrawDocument*>(m_pDoc)->OpenBookmarkDoc(m_pMedium);

        if ( !m_pBookmarkDoc )
        {
            std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(m_xTreeView.get(),
                                                           VclMessageType::Warning, VclButtonsType::Ok, SdResId(STR_READ_DATA_ERROR)));
            xErrorBox->run();
            m_pMedium = nullptr; //On failure the SfxMedium is invalid
        }
    }

    return m_pBookmarkDoc;
}

/**
 * Entries are inserted only by request (double click)
 */
IMPL_LINK(SdPageObjsTLV, RequestingChildrenHdl, const weld::TreeIter&, rFileEntry, bool)
{
    if (!m_xTreeView->iter_has_child(rFileEntry))
    {
        if (GetBookmarkDoc())
        {
            SdrObject*   pObj = nullptr;

            OUString sImgPage(BMP_PAGE);
            OUString sImgPageObjs(BMP_PAGEOBJS);
            OUString sImgObjects(BMP_OBJECTS);
            OUString sImgOle(BMP_OLE);
            OUString sImgGraphic(BMP_GRAPHIC);

            // document name already inserted

            // only insert all "normal" ? slides with objects
            sal_uInt16 nPage = 0;
            const sal_uInt16 nMaxPages = m_pBookmarkDoc->GetPageCount();

            std::unique_ptr<weld::TreeIter> xPageEntry;
            while (nPage < nMaxPages)
            {
                SdPage* pPage = static_cast<SdPage*>(m_pBookmarkDoc->GetPage(nPage));
                if (pPage->GetPageKind() == PageKind::Standard)
                {
                    OUString sId(OUString::number(1));
                    m_xTreeView->insert(&rFileEntry, -1, &pPage->GetName(), &sId,
                                        nullptr, nullptr, false, m_xScratchIter.get());
                    m_xTreeView->set_image(*m_xScratchIter, sImgPage);

                    if (!xPageEntry)
                    {
                        xPageEntry = m_xTreeView->make_iterator(&rFileEntry);
                        (void)m_xTreeView->iter_children(*xPageEntry);
                    }
                    else
                        (void)m_xTreeView->iter_next_sibling(*xPageEntry);

                    SdrObjListIter aIter( pPage, SdrIterMode::DeepWithGroups );

                    while( aIter.IsMore() )
                    {
                        pObj = aIter.Next();
                        OUString aStr( GetObjectName( pObj ) );
                        if( !aStr.isEmpty() )
                        {
                            if( pObj->GetObjInventor() == SdrInventor::Default && pObj->GetObjIdentifier() == SdrObjKind::OLE2 )
                            {
                                m_xTreeView->insert(xPageEntry.get(), -1, &aStr, nullptr,
                                                    nullptr, nullptr, false, m_xScratchIter.get());
                                m_xTreeView->set_image(*m_xScratchIter, sImgOle);
                            }
                            else if( pObj->GetObjInventor() == SdrInventor::Default && pObj->GetObjIdentifier() == SdrObjKind::Graphic )
                            {
                                m_xTreeView->insert(xPageEntry.get(), -1, &aStr, nullptr,
                                                    nullptr, nullptr, false, m_xScratchIter.get());
                                m_xTreeView->set_image(*m_xScratchIter, sImgGraphic);
                            }
                            else
                            {
                                m_xTreeView->insert(xPageEntry.get(), -1, &aStr, nullptr,
                                                    nullptr, nullptr, false, m_xScratchIter.get());
                                m_xTreeView->set_image(*m_xScratchIter, sImgObjects);
                            }
                        }
                    }
                    if (m_xTreeView->iter_has_child(*xPageEntry))
                    {
                        m_xTreeView->set_image(*xPageEntry, sImgPageObjs);
                    }
                }
                nPage++;
            }
        }
    }
    return true;
}

void SdPageObjsTLV::SetSdNavigator(SdNavigatorWin* pNavigator)
{
    m_pNavigator = pNavigator;
}

void SdPageObjsTLV::SetViewFrame(const SfxViewFrame* pViewFrame)
{
    if (sd::ViewShellBase* pBase = sd::ViewShellBase::GetViewShellBase(pViewFrame))
    {
        css::uno::Reference< css::frame::XFrame > xFrame;
        if (std::shared_ptr<sd::ViewShell> xViewShell = pBase->GetMainViewShell())
        {
            if (SfxViewFrame* pFrame = xViewShell->GetViewFrame())
                xFrame = pFrame->GetFrame().GetFrameInterface();
        }
        m_xAccel->init(::comphelper::getProcessComponentContext(), xFrame);
    }
}

/**
 * Close and delete bookmark document
 */
void SdPageObjsTLV::CloseBookmarkDoc()
{
    if (m_xBookmarkDocShRef.is())
    {
        m_xBookmarkDocShRef->DoClose();
        m_xBookmarkDocShRef.clear();

        // Medium is owned by document, so it's destroyed already
        m_pOwnMedium = nullptr;
    }
    else if (m_pBookmarkDoc)
    {
        DBG_ASSERT(!m_pOwnMedium, "SfxMedium confusion!");
        if (m_pDoc)
        {
            // The document owns the Medium, so the Medium will be invalid after closing the document
            const_cast<SdDrawDocument*>(m_pDoc)->CloseBookmarkDoc();
            m_pMedium = nullptr;
        }
    }
    else
    {
        // perhaps mpOwnMedium provided, but no successful creation of BookmarkDoc
        delete m_pOwnMedium;
        m_pOwnMedium = nullptr;
    }

    m_pBookmarkDoc = nullptr;
}

bool SdPageObjsTLV::PageBelongsToCurrentShow(const SdPage* pPage) const
{
    // Return <TRUE/> as default when there is no custom show or when none
    // is used.  The page does then belong to the standard show.
    bool bBelongsToShow = true;

    if (m_pDoc->getPresentationSettings().mbCustomShow)
    {
        // Get the current custom show.
        SdCustomShow* pCustomShow = nullptr;
        SdCustomShowList* pShowList = const_cast<SdDrawDocument*>(m_pDoc)->GetCustomShowList();
        if (pShowList != nullptr)
        {
            sal_uLong nCurrentShowIndex = pShowList->GetCurPos();
            pCustomShow = (*pShowList)[nCurrentShowIndex].get();
        }

        // Check whether the given page is part of that custom show.
        if (pCustomShow != nullptr)
        {
            bBelongsToShow = false;
            size_t nPageCount = pCustomShow->PagesVector().size();
            for (size_t i=0; i<nPageCount && !bBelongsToShow; i++)
                if (pPage == pCustomShow->PagesVector()[i])
                    bBelongsToShow = true;
        }
    }

    return bBelongsToShow;
}

void SdPageObjsTLV::AddShapeList (
    const SdrObjList& rList,
    const SdrObject* pShape,
    const OUString& rsName,
    const bool bIsExcluded,
    const weld::TreeIter* pParentEntry)
{
    OUString aIcon(BMP_PAGE);
    if (bIsExcluded)
        aIcon = BMP_PAGE_EXCLUDED;
    else if (pShape != nullptr)
        aIcon = BMP_GROUP;

    OUString aUserData(u"1"_ustr);
    if (pShape != nullptr)
        aUserData = weld::toId(pShape);

    std::unique_ptr<weld::TreeIter> xEntry = m_xTreeView->make_iterator();
    InsertEntry(pParentEntry, aUserData, rsName, aIcon, xEntry.get());

    SdrObjListIter aIter(
        &rList,
        !rList.HasObjectNavigationOrder() /* use navigation order, if available */,
        SdrIterMode::Flat);

    while( aIter.IsMore() )
    {
        SdrObject* pObj = aIter.Next();
        assert(pObj!=nullptr);

        // Get the shape name.
        OUString aStr (GetObjectName( pObj ) );
        OUString sId(weld::toId(pObj));

        if( !aStr.isEmpty() )
        {
            if( pObj->GetObjInventor() == SdrInventor::Default && pObj->GetObjIdentifier() == SdrObjKind::OLE2 )
            {
                InsertEntry(xEntry.get(), sId, aStr, BMP_OLE);
            }
            else if( pObj->GetObjInventor() == SdrInventor::Default && pObj->GetObjIdentifier() == SdrObjKind::Graphic )
            {
                InsertEntry(xEntry.get(), sId, aStr, BMP_GRAPHIC);
            }
            else if (pObj->IsGroupObject())
            {
                AddShapeList(
                    *pObj->GetSubList(),
                    pObj,
                    aStr,
                    false,
                    xEntry.get());
            }
            else
            {
                InsertEntry(xEntry.get(), sId, aStr, BMP_OBJECTS); // BMP_OBJECTS
            }
        }
    }

    if (!m_xTreeView->iter_has_child(*xEntry))
        return;

    if (bIsExcluded)
        m_xTreeView->set_image(*xEntry, BMP_PAGEOBJS_EXCLUDED);
    else
        m_xTreeView->set_image(*xEntry, BMP_PAGEOBJS);
    m_xTreeView->expand_row(*xEntry);
}

/**
 * Fill TreeLB with pages and objects
 */
void SdPageObjsTLV::Fill(const SdDrawDocument* pInDoc, bool bAllPages, const OUString& rDocName)
{
    // record collapsed page/slide name entries to reset after the tree is refilled
    std::unordered_set<OUString> aCollapsedEntriesSet;
    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
    if (m_xTreeView->get_iter_first(*xEntry))
    {
        do
        {
            if (!m_xTreeView->get_row_expanded(*xEntry))
                aCollapsedEntriesSet.insert(m_xTreeView->get_text(*xEntry));
        } while (m_xTreeView->iter_next_sibling(*xEntry));
    }

    OUString aSelection = m_xTreeView->get_selected_text();
    clear();

    m_pDoc = pInDoc;
    m_aDocName = rDocName;
    m_bShowAllPages = bAllPages;
    m_pMedium = nullptr;

    // first insert all pages including objects
    sal_uInt16 nPage = 0;
    const sal_uInt16 nMaxPages = m_pDoc->GetPageCount();

    PageKind eDrawViewShellPageKind = PageKind::Standard;
    if (sd::DrawViewShell* pDrawViewShell = lcl_getDrawViewShell(m_pDoc))
        eDrawViewShellPageKind = pDrawViewShell->GetPageKind();

    while( nPage < nMaxPages )
    {
        const SdPage* pPage = static_cast<const SdPage*>( m_pDoc->GetPage( nPage ) );
        PageKind ePagePageKind = pPage->GetPageKind();
        if ((m_bShowAllPages ||
             (ePagePageKind == PageKind::Standard &&
              eDrawViewShellPageKind == PageKind::Standard) ||
             (ePagePageKind == PageKind::Notes &&
              eDrawViewShellPageKind == PageKind::Notes)) &&
                ePagePageKind != PageKind::Handout) //#94954# never list the normal handout page ( handout-masterpage is used instead )
        {
            bool bPageExcluded = pPage->IsExcluded();

            bool bPageBelongsToShow = PageBelongsToCurrentShow (pPage);
            bPageExcluded |= !bPageBelongsToShow;

            AddShapeList(*pPage, nullptr, pPage->GetName(), bPageExcluded, nullptr);
        }
        nPage++;
    }

    // then insert all master pages including objects
    if( m_bShowAllPages )
    {
        nPage = 0;
        const sal_uInt16 nMaxMasterPages = m_pDoc->GetMasterPageCount();

        while( nPage < nMaxMasterPages )
        {
            const SdPage* pPage = static_cast<const SdPage*>( m_pDoc->GetMasterPage( nPage ) );
            AddShapeList(*pPage, nullptr, pPage->GetName(), false, nullptr);
            nPage++;
        }
    }
    if (!aSelection.isEmpty())
    {
        m_xTreeView->all_foreach([this, &aSelection](weld::TreeIter& rEntry){
            if (m_xTreeView->get_text(rEntry) == aSelection)
            {
                m_xTreeView->select(rEntry);
                return true;
            }
            return false;
        });
    }
    // collapse page/slide entries
    if (m_xTreeView->get_iter_first(*xEntry))
    {
        do
        {
            if (aCollapsedEntriesSet.contains(m_xTreeView->get_text(*xEntry)))
                m_xTreeView->collapse_row(*xEntry);
        } while (m_xTreeView->iter_next_sibling(*xEntry));
    }
}

/**
 * We insert only the first entry. Children are created on demand.
 */
void SdPageObjsTLV::Fill( const SdDrawDocument* pInDoc, SfxMedium* pInMedium,
                          const OUString& rDocName )
{
    m_pDoc = pInDoc;

    // this object now owns the Medium
    m_pMedium = pInMedium;
    m_aDocName = rDocName;

    OUString sId(OUString::number(1));
    // insert document name
    m_xTreeView->insert(nullptr, -1, &m_aDocName, &sId, nullptr, nullptr, true, m_xScratchIter.get());
    m_xTreeView->set_image(*m_xScratchIter, BMP_DOC_OPEN);
}

/**
 * select an entry in TreeLB
 */
bool SdPageObjsTLV::SelectEntry( std::u16string_view rName )
{
    bool bFound = false;

    if (!rName.empty())
    {
        std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
        OUString aTmp;

        if (m_xTreeView->get_iter_first(*xEntry))
        {
            do
            {
                aTmp = m_xTreeView->get_text(*xEntry);
                if (aTmp == rName)
                {
                    m_xTreeView->set_cursor(*xEntry);
                    m_xTreeView->select(*xEntry);
                    bFound = true;
                    break;
                }
            }
            while (m_xTreeView->iter_next(*xEntry));
        }
    }

    return bFound;
}

void SdPageObjsTLV::SelectEntry(const SdrObject *pObj)
{
    if (pObj)
    {
        m_xTreeView->all_foreach([this, &pObj](weld::TreeIter& rEntry){
            if (weld::fromId<SdrObject*>(m_xTreeView->get_id(rEntry)) == pObj)
            {
                // Only scroll to the row of the first selected. And only when the treeview
                // doesn't have the focus.
                if (!m_xTreeView->has_focus() && m_xTreeView->get_selected_rows().empty())
                    m_xTreeView->set_cursor(rEntry);
                m_xTreeView->select(rEntry);
                return true;
            }
            return false;
        });
    }
}

SdPageObjsTLV::~SdPageObjsTLV()
{
    if (m_nSelectEventId)
        Application::RemoveUserEvent(m_nSelectEventId);
    if (m_nRowActivateEventId)
        Application::RemoveUserEvent(m_nRowActivateEventId);

    if (m_pBookmarkDoc)
        CloseBookmarkDoc();
    else
    {
        // no document was created from m_pMedium, so this object is still the owner of it
        delete m_pMedium;
    }
    m_xAccel.reset();
}

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