/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sal/config.h>

#include <string_view>

#include <svx/svditer.hxx>
#include <svx/svdobj.hxx>
#include <svx/svdview.hxx>
#include <sfx2/linkmgr.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/viewfrm.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/svapp.hxx>
#include <osl/diagnose.h>
#include <tools/urlobj.hxx>
#include <sal/log.hxx>
#include <unotools/charclass.hxx>

#include <content.hxx>
#include <navipi.hxx>
#include <global.hxx>
#include <docsh.hxx>
#include <docfunc.hxx>
#include <scmod.hxx>
#include <rangenam.hxx>
#include <dbdata.hxx>
#include <drwlayer.hxx>
#include <transobj.hxx>
#include <drwtrans.hxx>
#include <lnktrans.hxx>
#include <strings.hrc>
#include <scresid.hxx>
#include <bitmaps.hlst>
#include <arealink.hxx>
#include <navicfg.hxx>
#include <navsett.hxx>
#include <postit.hxx>
#include <tabvwsh.hxx>
#include <drawview.hxx>
#include <clipparam.hxx>
#include <markdata.hxx>

using namespace com::sun::star;

//  order of the categories in navigator -------------------------------------

const ScContentId pTypeList[int(ScContentId::LAST) + 1] =
{
    ScContentId::ROOT,            // ROOT (0) has to be at the front
    ScContentId::TABLE,
    ScContentId::RANGENAME,
    ScContentId::DBAREA,
    ScContentId::AREALINK,
    ScContentId::GRAPHIC,
    ScContentId::OLEOBJECT,
    ScContentId::NOTE,
    ScContentId::DRAWING
};

constexpr OUString aContentBmps[]=
{
    RID_BMP_CONTENT_TABLE,
    RID_BMP_CONTENT_RANGENAME,
    RID_BMP_CONTENT_DBAREA,
    RID_BMP_CONTENT_GRAPHIC,
    RID_BMP_CONTENT_OLEOBJECT,
    RID_BMP_CONTENT_NOTE,
    RID_BMP_CONTENT_AREALINK,
    RID_BMP_CONTENT_DRAWING
};

ScDocShell* ScContentTree::GetManualOrCurrent()
{
    ScDocShell* pSh = nullptr;
    if ( !aManualDoc.isEmpty() )
    {
        SfxObjectShell* pObjSh = SfxObjectShell::GetFirst( checkSfxObjectShell<ScDocShell> );
        while ( pObjSh && !pSh )
        {
            if ( pObjSh->GetTitle() == aManualDoc )
                pSh = dynamic_cast<ScDocShell*>( pObjSh  );
            pObjSh = SfxObjectShell::GetNext( *pObjSh, checkSfxObjectShell<ScDocShell> );
        }
    }
    else
    {
        //  only current when manual isn't set
        //  (so it's detected when the documents don't exists any longer)

        SfxViewShell* pViewSh = SfxViewShell::Current();
        if ( pViewSh )
        {
            SfxObjectShell* pObjSh = pViewSh->GetViewFrame().GetObjectShell();
            pSh = dynamic_cast<ScDocShell*>( pObjSh  );
        }
    }

    return pSh;
}

//          ScContentTree

ScContentTree::ScContentTree(std::unique_ptr<weld::TreeView> xTreeView, ScNavigatorDlg* pNavigatorDlg)
    : m_xTreeView(std::move(xTreeView))
    , m_xScratchIter(m_xTreeView->make_iterator())
    , m_xTransferObj(new ScLinkTransferObj)
    , pParentWindow(pNavigatorDlg)
    , nRootType(ScContentId::ROOT)
    , bIsInNavigatorDlg(false)
    , m_bFreeze(false)
    , m_nAsyncMouseReleaseId(nullptr)
{
    for (sal_uInt16 i = 0; i <= int(ScContentId::LAST); ++i)
        pPosList[pTypeList[i]] = i;         // inverse for searching

    m_aRootNodes[ScContentId::ROOT] = nullptr;
    for (sal_uInt16 i = 1; i < int(ScContentId::LAST); ++i)
        InitRoot(static_cast<ScContentId>(i));

    m_xTreeView->connect_row_activated(LINK(this, ScContentTree, ContentDoubleClickHdl));
    m_xTreeView->connect_mouse_release(LINK(this, ScContentTree, MouseReleaseHdl));
    m_xTreeView->connect_key_press(LINK(this, ScContentTree, KeyInputHdl));
    m_xTreeView->connect_popup_menu(LINK(this, ScContentTree, CommandHdl));
    m_xTreeView->connect_query_tooltip(LINK(this, ScContentTree, QueryTooltipHdl));

    rtl::Reference<TransferDataContainer> xHelper(m_xTransferObj);
    m_xTreeView->enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK);

    m_xTreeView->connect_drag_begin(LINK(this, ScContentTree, DragBeginHdl));

    m_xTreeView->set_selection_mode( SelectionMode::Single );

    m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * 30,
                                  m_xTreeView->get_text_height() * 13);
}

ScContentTree::~ScContentTree()
{
    if (m_nAsyncMouseReleaseId)
    {
        Application::RemoveUserEvent(m_nAsyncMouseReleaseId);
        m_nAsyncMouseReleaseId = nullptr;
    }
}

const TranslateId SCSTR_CONTENT_ARY[] =
{
    SCSTR_CONTENT_ROOT,
    SCSTR_CONTENT_TABLE,
    SCSTR_CONTENT_RANGENAME,
    SCSTR_CONTENT_DBAREA,
    SCSTR_CONTENT_GRAPHIC,
    SCSTR_CONTENT_OLEOBJECT,
    SCSTR_CONTENT_NOTE,
    SCSTR_CONTENT_AREALINK,
    SCSTR_CONTENT_DRAWING
};

void ScContentTree::InitRoot( ScContentId nType )
{
    if ( nType == ScContentId::ROOT )
        return;

    if ( nRootType != ScContentId::ROOT && nRootType != nType )              // hidden ?
    {
        m_aRootNodes[nType] = nullptr;
        return;
    }

    auto const & aImage = aContentBmps[static_cast<int>(nType) - 1];

    OUString aName;
    if(comphelper::LibreOfficeKit::isActive())
    {
        //In case of LOK we may have many different ScContentTrees in different languages.
        //At creation time, we store what language we use, and then use it later too.
        //It does not work in the constructor, that is why it is here.
        if (!m_pResLocaleForLOK)
        {
            m_pResLocaleForLOK = std::make_unique<std::locale>(ScModule::get()->GetResLocale());
        }
        aName = Translate::get(SCSTR_CONTENT_ARY[static_cast<int>(nType)], *m_pResLocaleForLOK);
    }
    else
    {
        aName = ScResId(SCSTR_CONTENT_ARY[static_cast<int>(nType)]);
    }
    // back to the correct position:
    sal_uInt16 nPos = nRootType != ScContentId::ROOT ? 0 : pPosList[nType]-1;
    m_aRootNodes[nType] = m_xTreeView->make_iterator();
    m_xTreeView->insert(nullptr, nPos, &aName, nullptr, nullptr, nullptr, false, m_aRootNodes[nType].get());
    m_xTreeView->set_image(*m_aRootNodes[nType], aImage);
}

void ScContentTree::ClearAll()
{
    //There are one method in Control::SetUpdateMode(), and one override method SvTreeListBox::SetUpdateMode(). Here although
    //SvTreeListBox::SetUpdateMode() is called in refresh method, it only call SvTreeListBox::SetUpdateMode(), not Control::SetUpdateMode().
    //In m_xTreeView->clear(), Broadcast( LISTACTION_CLEARED ) will be called and finally, it will be trapped into the event yield() loop. And
    //the InitRoot() method won't be called. Then if a user click or press key to update the navigator tree, crash happens.
    //So the solution is to disable the UpdateMode of Control, then call Clear(), then recover the update mode
    bool bWasFrozen = m_bFreeze;
    if (!bWasFrozen)
        freeze();
    m_xTreeView->clear();
    if (!bWasFrozen)
        thaw();
    for (sal_uInt16 i=1; i<=int(ScContentId::LAST); i++)
        InitRoot(static_cast<ScContentId>(i));
}

void ScContentTree::ClearType(ScContentId nType)
{
    if (nType == ScContentId::ROOT)
        ClearAll();
    else
    {
        weld::TreeIter* pParent = m_aRootNodes[nType].get();
        if (!pParent || m_xTreeView->iter_has_child(*pParent)) // not if no children existing
        {
            if (pParent)
                m_xTreeView->remove(*pParent);          // with all children
            InitRoot( nType );                          // if needed insert anew
        }
    }
}

void ScContentTree::InsertContent( ScContentId nType, const OUString& rValue )
{
    weld::TreeIter* pParent = m_aRootNodes[nType].get();
    if (pParent)
    {
        m_xTreeView->insert(pParent, -1, &rValue, nullptr, nullptr, nullptr, false, m_xScratchIter.get());
        m_xTreeView->set_sensitive(*m_xScratchIter, true);
    }
    else
    {
        OSL_FAIL("InsertContent without parent");
    }
}

void ScContentTree::GetEntryIndexes(ScContentId& rnRootIndex, sal_uLong& rnChildIndex, const weld::TreeIter* pEntry) const
{
    rnRootIndex = ScContentId::ROOT;
    rnChildIndex = SC_CONTENT_NOCHILD;

    if( !pEntry ) {
        SAL_WARN("sc", "got a null TreeIter");
        return;
    }

    std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator(pEntry));
    if (!m_xTreeView->iter_parent(*xParent))
        xParent.reset();
    bool bFound = false;
    for( int i = 1; !bFound && (i <= int(ScContentId::LAST)); ++i )
    {
        ScContentId nRoot = static_cast<ScContentId>(i);
        if (!m_aRootNodes[nRoot])
            continue;
        if (m_xTreeView->iter_compare(*pEntry, *m_aRootNodes[nRoot]) == 0)
        {
            rnRootIndex = nRoot;
            rnChildIndex = ~0UL;
            bFound = true;
        }
        else if (xParent && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[nRoot]) == 0)
        {
            rnRootIndex = nRoot;

            // search the entry in all child entries of the parent
            sal_uLong nEntry = 0;
            std::unique_ptr<weld::TreeIter> xIterEntry(m_xTreeView->make_iterator(xParent.get()));
            bool bIterEntry = m_xTreeView->iter_children(*xIterEntry);
            while (!bFound && bIterEntry)
            {
                if (m_xTreeView->iter_compare(*pEntry, *xIterEntry) == 0)
                {
                    rnChildIndex = nEntry;
                    bFound = true;  // exit the while loop
                }
                bIterEntry = m_xTreeView->iter_next_sibling(*xIterEntry);
                ++nEntry;
            }

            bFound = true;  // exit the for loop
        }
    }
}

sal_uLong ScContentTree::GetChildIndex(const weld::TreeIter* pEntry) const
{
    ScContentId nRoot;
    sal_uLong nChild;
    GetEntryIndexes(nRoot, nChild, pEntry);
    return nChild;
}

static OUString lcl_GetDBAreaRange( const ScDocument* pDoc, const OUString& rDBName )
{
    OUString aRet;
    if (pDoc)
    {
        ScDBCollection* pDbNames = pDoc->GetDBCollection();
        const ScDBData* pData = pDbNames->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rDBName));
        if (pData)
        {
            ScRange aRange;
            pData->GetArea(aRange);
            aRet = aRange.Format(*pDoc, ScRefFlags::RANGE_ABS_3D);
        }
    }
    return aRet;
}

IMPL_LINK_NOARG(ScContentTree, ContentDoubleClickHdl, weld::TreeView&, bool)
{
    ScContentId nType;
    sal_uLong nChild;
    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
    if (!m_xTreeView->get_cursor(xEntry.get()))
        xEntry.reset();
    GetEntryIndexes(nType, nChild, xEntry.get());

    if (xEntry && (nType != ScContentId::ROOT) && (nChild != SC_CONTENT_NOCHILD))
    {
        OUString aText(m_xTreeView->get_text(*xEntry));

        if ( !aManualDoc.isEmpty() )
            pParentWindow->SetCurrentDoc( aManualDoc );

        switch( nType )
        {
            case ScContentId::TABLE:
            {
                // tdf#133159 store current config before changing sheet
                // plausible that this should be done for all cases, but this
                // is the known case that needs it
                StoreNavigatorSettings();
                pParentWindow->SetCurrentTableStr( aText );
            }
            break;

            case ScContentId::RANGENAME:
                pParentWindow->SetCurrentCellStr( aText );
            break;

            case ScContentId::DBAREA:
            {
                //  If the same names of area and DB exists, then
                //  SID_CURRENTCELL takes the area name.
                //  Therefore for DB areas access them directly via address.

                OUString aRangeStr = lcl_GetDBAreaRange( GetSourceDocument(), aText );
                if (!aRangeStr.isEmpty())
                    pParentWindow->SetCurrentCellStr( aRangeStr );
            }
            break;

            case ScContentId::OLEOBJECT:
            case ScContentId::GRAPHIC:
            case ScContentId::DRAWING:
                pParentWindow->SetCurrentObject( aText );
            break;

            case ScContentId::NOTE:
            {
                ScAddress aPos = GetNotePos( nChild );
                pParentWindow->SetCurrentTable( aPos.Tab() );
                pParentWindow->SetCurrentCell( aPos.Col(), aPos.Row() );
                // Check whether the comment is currently visible and toggle its visibility
                ScDocument* pSrcDoc = GetSourceDocument();
                if (ScPostIt* pNote = pSrcDoc ? pSrcDoc->GetNote(aPos.Col(), aPos.Row(), aPos.Tab()) : nullptr)
                {
                    bool bVisible = pNote->IsCaptionShown();
                    // Effectivelly set the visibility of the comment
                    GetManualOrCurrent()->GetDocFunc().ShowNote(aPos, !bVisible);
                    // Put the note in edit mode
                    ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell();
                    pScTabViewShell->EditNote();
                }
            }
            break;

            case ScContentId::AREALINK:
            {
                const ScAreaLink* pLink = GetLink(nChild);
                ScDocument* pSrcDoc = GetSourceDocument();
                if (pLink && pSrcDoc)
                {
                    const ScRange& aRange = pLink->GetDestArea();
                    OUString aRangeStr(aRange.Format(*pSrcDoc, ScRefFlags::RANGE_ABS_3D, pSrcDoc->GetAddressConvention()));
                    pParentWindow->SetCurrentCellStr( aRangeStr );
                }
            }
            break;
            default: break;
        }

        ScNavigatorDlg::ReleaseFocus();     // set focus into document
    }

    return false;
}

void ScContentTree::LaunchAsyncStoreNavigatorSettings()
{
    if (!m_nAsyncMouseReleaseId)
        m_nAsyncMouseReleaseId = Application::PostUserEvent(LINK(this, ScContentTree, AsyncStoreNavigatorSettings));
}

IMPL_LINK_NOARG(ScContentTree, MouseReleaseHdl, const MouseEvent&, bool)
{
    LaunchAsyncStoreNavigatorSettings();
    return false;
}

IMPL_LINK_NOARG(ScContentTree, AsyncStoreNavigatorSettings, void*, void)
{
    m_nAsyncMouseReleaseId = nullptr;
    StoreNavigatorSettings();
}

IMPL_LINK(ScContentTree, KeyInputHdl, const KeyEvent&, rKEvt, bool)
{
    bool bUsed = false;

    const vcl::KeyCode aCode = rKEvt.GetKeyCode();
    if (aCode.GetCode() == KEY_RETURN)
    {
        switch (aCode.GetModifier())
        {
            case KEY_MOD1:
                ToggleRoot();       // toggle root mode (as in Writer)
                bUsed = true;
                break;
            case 0:
            {
                std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
                if (!m_xTreeView->get_cursor(xEntry.get()))
                    xEntry.reset();
                if (xEntry)
                {
                    ScContentId nType;
                    sal_uLong nChild;
                    GetEntryIndexes(nType, nChild, xEntry.get());

                    if (nType != ScContentId::ROOT && nChild == SC_CONTENT_NOCHILD)
                    {
                        if (m_xTreeView->get_row_expanded(*xEntry))
                            m_xTreeView->collapse_row(*xEntry);
                        else
                            m_xTreeView->expand_row(*xEntry);
                    }
                    else
                        ContentDoubleClickHdl(*m_xTreeView);      // select content as if double clicked
                }

                bUsed = true;
            }
            break;
        }
    }
    //Make KEY_SPACE has same function as DoubleClick, and realize
    //multi-selection.
    if ( bIsInNavigatorDlg )
    {
        if(aCode.GetCode() == KEY_SPACE )
        {
            bUsed = true;
            ScContentId nType;
            sal_uLong nChild;
            std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
            if (!m_xTreeView->get_cursor(xEntry.get()))
                xEntry.reset();
            GetEntryIndexes(nType, nChild, xEntry.get());

            if (xEntry && (nType != ScContentId::ROOT) && (nChild != SC_CONTENT_NOCHILD))
            {
                OUString aText(m_xTreeView->get_text(*xEntry));
                if (!aManualDoc.isEmpty())
                    pParentWindow->SetCurrentDoc( aManualDoc );
                switch (nType)
                {
                    case ScContentId::OLEOBJECT:
                    case ScContentId::GRAPHIC:
                    case ScContentId::DRAWING:
                    {
                        ScDrawView* pScDrawView = nullptr;
                        ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell();
                        if (pScTabViewShell)
                            pScDrawView = pScTabViewShell->GetViewData().GetScDrawView();
                        if (pScDrawView)
                        {
                            pScDrawView->SelectCurrentViewObject(aText);
                            bool bHasMakredObject = false;
                            weld::TreeIter* pParent = m_aRootNodes[nType].get();
                            std::unique_ptr<weld::TreeIter> xBeginEntry(m_xTreeView->make_iterator(pParent));
                            bool bBeginEntry = false;
                            if (pParent)
                                bBeginEntry = m_xTreeView->iter_children(*xBeginEntry);
                            while (bBeginEntry)
                            {
                                OUString aTempText(m_xTreeView->get_text(*xBeginEntry));
                                if( pScDrawView->GetObjectIsMarked( pScDrawView->GetObjectByName( aTempText ) ) )
                                {
                                    bHasMakredObject = true;
                                    break;
                                }
                                bBeginEntry = m_xTreeView->iter_next(*xBeginEntry);
                            }
                            if (!bHasMakredObject && pScTabViewShell)
                                pScTabViewShell->SetDrawShell(false);
                        }
                        break;
                    }
                    default:
                        break;
                }
            }
        }
    }

    if (!bUsed)
    {
        if (aCode.GetCode() == KEY_F5)
            StoreNavigatorSettings();
        else
            LaunchAsyncStoreNavigatorSettings();
    }

    return bUsed;
}

IMPL_LINK(ScContentTree, CommandHdl, const CommandEvent&, rCEvt, bool)
{
    bool bDone = false;

    ScContentId nType;
    sal_uLong nChild;
    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
    if (!m_xTreeView->get_cursor(xEntry.get()))
        xEntry.reset();
    GetEntryIndexes(nType, nChild, xEntry.get());

    switch ( rCEvt.GetCommand() )
    {
        case CommandEventId::ContextMenu:
            {
                if (comphelper::LibreOfficeKit::isActive())
                    break;

                //  drag-and-drop mode
                std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(m_xTreeView.get(), u"modules/scalc/ui/dropmenu.ui"_ustr));
                std::unique_ptr<weld::Menu> xPop(xBuilder->weld_menu(u"contextmenu"_ustr));
                std::unique_ptr<weld::Menu> xDropMenu(xBuilder->weld_menu(u"dragmodesubmenu"_ustr));

                switch (pParentWindow->GetDropMode())
                {
                    case 0:
                        xDropMenu->set_active(u"hyperlink"_ustr, true);
                        break;
                    case 1:
                        xDropMenu->set_active(u"link"_ustr, true);
                        break;
                    case 2:
                        xDropMenu->set_active(u"copy"_ustr, true);
                        break;
                }

                //  displayed document
                std::unique_ptr<weld::Menu> xDocMenu(xBuilder->weld_menu(u"displaymenu"_ustr));
                sal_uInt16 i=0;
                OUString sActive;
                OUString sId;
                //  loaded documents
                ScDocShell* pCurrentSh = dynamic_cast<ScDocShell*>( SfxObjectShell::Current()  );
                SfxObjectShell* pSh = SfxObjectShell::GetFirst();
                while ( pSh )
                {
                    if ( dynamic_cast<const ScDocShell*>( pSh) !=  nullptr )
                    {
                        OUString aName = pSh->GetTitle();
                        OUString aEntry = aName;
                        if ( pSh == pCurrentSh )
                            aEntry += pParentWindow->aStrActive;
                        else
                            aEntry += pParentWindow->aStrNotActive;
                        ++i;
                        sId = "document" + OUString::number(i);
                        xDocMenu->append_radio(sId, aEntry);
                        if (aName == aManualDoc)
                            sActive = sId;
                    }
                    pSh = SfxObjectShell::GetNext( *pSh );
                }
                //  "active window"
                ++i;
                sId = "document" + OUString::number(i);
                xDocMenu->append_radio(sId, pParentWindow->aStrActiveWin);
                if (aManualDoc.isEmpty())
                    sActive = sId;
                xDocMenu->set_active(sActive, true);

                // Edit/Delete Comments are only visible for comments
                if (nType != ScContentId::NOTE)
                {
                    xPop->set_visible(u"edit"_ustr, false);
                    xPop->set_visible(u"delete"_ustr, false);
                }

                OUString sIdent = xPop->popup_at_rect(m_xTreeView.get(), tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1, 1)));
                if (sIdent == "hyperlink")
                    pParentWindow->SetDropMode(0);
                else if (sIdent == "link")
                    pParentWindow->SetDropMode(1);
                else if (sIdent == "copy")
                    pParentWindow->SetDropMode(2);
                else if (sIdent.startsWith("document"))
                {
                    OUString aName = xDocMenu->get_label(sIdent);
                    SelectDoc(aName);
                }
                else if (sIdent == "edit")
                {
                    ScAddress aPos = GetNotePos( nChild );
                    pParentWindow->SetCurrentTable( aPos.Tab() );
                    pParentWindow->SetCurrentCell( aPos.Col(), aPos.Row() );
                    ScDocument* pSrcDoc = GetSourceDocument();
                    if (pSrcDoc->GetNote(aPos.Col(), aPos.Row(), aPos.Tab()))
                    {
                        // Make the note visible and put it in edit mode
                        GetManualOrCurrent()->GetDocFunc().ShowNote(aPos, true);
                        ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell();
                        pScTabViewShell->EditNote();
                        bDone = true;
                    }
                }
                else if (sIdent == "delete")
                {
                    ScAddress aPos = GetNotePos(nChild);
                    pParentWindow->SetCurrentTable(aPos.Tab());
                    pParentWindow->SetCurrentCell(aPos.Col(), aPos.Row());
                    ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell();
                    pScTabViewShell->DeleteContents(InsertDeleteFlags::NOTE);
                }
            }
            break;
            default: break;
    }

    return bDone;
}

IMPL_LINK(ScContentTree, QueryTooltipHdl, const weld::TreeIter&, rEntry, OUString)
{
    OUString aHelpText;

    std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator(&rEntry));
    if (!m_xTreeView->iter_parent(*xParent))
        xParent.reset();

    if (!xParent)                                 // Top-Level ?
    {
        aHelpText = OUString::number(m_xTreeView->iter_n_children(rEntry)) +
                    " " + m_xTreeView->get_text(rEntry);
    }
    else if (m_aRootNodes[ScContentId::NOTE] && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[ScContentId::NOTE]) == 0)
    {
        aHelpText = m_xTreeView->get_text(rEntry);     // notes as help text
    }
    else if (m_aRootNodes[ScContentId::AREALINK] && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[ScContentId::AREALINK]) == 0)
    {
        auto nIndex = GetChildIndex(&rEntry);
        if (nIndex != SC_CONTENT_NOCHILD)
        {
            const ScAreaLink* pLink = GetLink(nIndex);
            if (pLink)
            {
                aHelpText = pLink->GetFile();           // source file as help text
            }
        }
    }

    return aHelpText;
}

ScDocument* ScContentTree::GetSourceDocument()
{
    ScDocShell* pSh = GetManualOrCurrent();
    if (pSh)
        return &pSh->GetDocument();
    return nullptr;
}

void ScContentTree::Refresh( ScContentId nType )
{
    //  if nothing has changed the cancel right away (against flicker)
    if ( nType == ScContentId::NOTE )
        if (!NoteStringsChanged())
            return;
    if ( nType == ScContentId::GRAPHIC )
        if (!DrawNamesChanged(ScContentId::GRAPHIC))
            return;
    if ( nType == ScContentId::OLEOBJECT )
        if (!DrawNamesChanged(ScContentId::OLEOBJECT))
            return;
    if ( nType == ScContentId::DRAWING )
        if (!DrawNamesChanged(ScContentId::DRAWING))
            return;

    freeze();

    ClearType( nType );

    if ( nType == ScContentId::ROOT || nType == ScContentId::TABLE )
        GetTableNames();
    if ( nType == ScContentId::ROOT || nType == ScContentId::RANGENAME )
        GetAreaNames();
    if ( nType == ScContentId::ROOT || nType == ScContentId::DBAREA )
        GetDbNames();
    if ( nType == ScContentId::ROOT || nType == ScContentId::GRAPHIC )
        GetGraphicNames();
    if ( nType == ScContentId::ROOT || nType == ScContentId::OLEOBJECT )
        GetOleNames();
    if ( nType == ScContentId::ROOT || nType == ScContentId::DRAWING )
        GetDrawingNames();
    if ( nType == ScContentId::ROOT || nType == ScContentId::NOTE )
        GetNoteStrings();
    if ( nType == ScContentId::ROOT || nType == ScContentId::AREALINK )
        GetLinkNames();

    thaw();

    ApplyNavigatorSettings();
}

void ScContentTree::GetTableNames()
{
    if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::TABLE )       // hidden ?
        return;

    ScDocument* pDoc = GetSourceDocument();
    if (!pDoc)
        return;

    OUString aName;
    SCTAB nCount = pDoc->GetTableCount();
    for ( SCTAB i=0; i<nCount; i++ )
    {
        pDoc->GetName( i, aName );
        InsertContent( ScContentId::TABLE, aName );
    }
}

namespace {

OUString createLocalRangeName(std::u16string_view rName, std::u16string_view rTableName)
{
    return OUString::Concat(rName) + " (" + rTableName + ")";
}
}

void ScContentTree::GetAreaNames()
{
    if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::RANGENAME )       // hidden ?
        return;

    ScDocument* pDoc = GetSourceDocument();
    if (!pDoc)
        return;

    ScRange aDummy;
    std::set<OUString> aSet;
    ScRangeName* pRangeNames = pDoc->GetRangeName();
    for (const auto& rEntry : *pRangeNames)
    {
        if (rEntry.second->IsValidReference(aDummy))
            aSet.insert(rEntry.second->GetName());
    }
    for (SCTAB i = 0; i < pDoc->GetTableCount(); ++i)
    {
        ScRangeName* pLocalRangeName = pDoc->GetRangeName(i);
        if (pLocalRangeName && !pLocalRangeName->empty())
        {
            OUString aTableName;
            pDoc->GetName(i, aTableName);
            for (const auto& rEntry : *pLocalRangeName)
            {
                if (rEntry.second->IsValidReference(aDummy))
                    aSet.insert(createLocalRangeName(rEntry.second->GetName(), aTableName));
            }
        }
    }

    for (const auto& rItem : aSet)
    {
        InsertContent(ScContentId::RANGENAME, rItem);
    }
}

void ScContentTree::GetDbNames()
{
    if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::DBAREA )      // hidden ?
        return;

    ScDocument* pDoc = GetSourceDocument();
    if (!pDoc)
        return;

    ScDBCollection* pDbNames = pDoc->GetDBCollection();
    const ScDBCollection::NamedDBs& rDBs = pDbNames->getNamedDBs();
    for (const auto& rxDB : rDBs)
    {
        const OUString& aStrName = rxDB->GetName();
        InsertContent(ScContentId::DBAREA, aStrName);
    }
}

bool ScContentTree::IsPartOfType( ScContentId nContentType, SdrObjKind nObjIdentifier )
{
    bool bRet = false;
    switch ( nContentType )
    {
        case ScContentId::GRAPHIC:
            bRet = ( nObjIdentifier == SdrObjKind::Graphic );
            break;
        case ScContentId::OLEOBJECT:
            bRet = ( nObjIdentifier == SdrObjKind::OLE2 );
            break;
        case ScContentId::DRAWING:
            bRet = ( nObjIdentifier != SdrObjKind::Graphic && nObjIdentifier != SdrObjKind::OLE2 );    // everything else
            break;
        default:
            OSL_FAIL("unknown content type");
    }
    return bRet;
}

constexpr int MAX_TREE_NODES = 1000;

void ScContentTree::GetDrawNames( ScContentId nType )
{
    if (!bIsInNavigatorDlg)
        return;

    if ( nRootType != ScContentId::ROOT && nRootType != nType )              // hidden ?
        return;

    ScDocument* pDoc = GetSourceDocument();
    if (!pDoc)
        return;

    ScDrawLayer* pDrawLayer = pDoc->GetDrawLayer();
    if (!pDrawLayer)
        return;

    ScDocShell* pShell = pDoc->GetDocumentShell();
    if (!pShell)
        return;

    // iterate in flat mode for groups
    SdrIterMode eIter = ( nType == ScContentId::DRAWING ) ? SdrIterMode::Flat : SdrIterMode::DeepNoGroups;

    std::vector<OUString> aNames;
    SCTAB nTabCount = pDoc->GetTableCount();
    for (SCTAB nTab=0; nTab<nTabCount; nTab++)
    {
        SdrPage* pPage = pDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
        OSL_ENSURE(pPage,"Page ?");
        if (!pPage)
            continue;
        SdrObjListIter aIter(pPage, eIter);
        SdrObject* pObject = aIter.Next();
        while (pObject)
        {
            if (IsPartOfType(nType, pObject->GetObjIdentifier()))
            {
                OUString aName = ScDrawLayer::GetVisibleName(pObject);
                if (!aName.isEmpty())
                    aNames.push_back(aName);
                if (aNames.size() > MAX_TREE_NODES)
                {
                    SAL_WARN("sc", "too many tree nodes, ignoring the rest");
                    break;
                }
            }
            pObject = aIter.Next();
        }
    }

    weld::TreeIter* pParent = m_aRootNodes[nType].get();
    assert(pParent && "InsertContent without parent");
    // insert all of these in one go under pParent
    m_xTreeView->bulk_insert_for_each(aNames.size(), [this, &aNames](weld::TreeIter& rIter, int nIndex) {
        m_xTreeView->set_text(rIter, aNames[nIndex], 0);
        m_xTreeView->set_sensitive(rIter, true);
    }, pParent);
}

void ScContentTree::GetGraphicNames()
{
    GetDrawNames( ScContentId::GRAPHIC );
}

void ScContentTree::GetOleNames()
{
    GetDrawNames( ScContentId::OLEOBJECT );
}

void ScContentTree::GetDrawingNames()
{
    GetDrawNames( ScContentId::DRAWING );
}

void ScContentTree::GetLinkNames()
{
    if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::AREALINK )                // hidden ?
        return;

    ScDocument* pDoc = GetSourceDocument();
    if (!pDoc)
        return;

    sfx2::LinkManager* pLinkManager = pDoc->GetLinkManager();
    assert(pLinkManager && "no LinkManager on document?");
    const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks();
    sal_uInt16 nCount = rLinks.size();
    for (sal_uInt16 i=0; i<nCount; i++)
    {
        ::sfx2::SvBaseLink* pBase = rLinks[i].get();
        if (auto pScAreaLink = dynamic_cast<const ScAreaLink*>( pBase))
            InsertContent( ScContentId::AREALINK, pScAreaLink->GetSource() );

            //  insert in list the names of source areas
    }
}

const ScAreaLink* ScContentTree::GetLink( sal_uLong nIndex )
{
    ScDocument* pDoc = GetSourceDocument();
    if (!pDoc)
        return nullptr;

    sal_uLong nFound = 0;
    sfx2::LinkManager* pLinkManager = pDoc->GetLinkManager();
    assert(pLinkManager && "no LinkManager on document?");
    const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks();
    sal_uInt16 nCount = rLinks.size();
    for (sal_uInt16 i=0; i<nCount; i++)
    {
        ::sfx2::SvBaseLink* pBase = rLinks[i].get();
        if (auto pAreaLink = dynamic_cast<const ScAreaLink*>( pBase))
        {
            if (nFound == nIndex)
                return pAreaLink;
            ++nFound;
        }
    }

    OSL_FAIL("link not found");
    return nullptr;
}

static OUString lcl_NoteString( const ScPostIt& rNote )
{
    return rNote.GetText().replace('\n', ' ');
}

void ScContentTree::GetNoteStrings()
{
    if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::NOTE )        // hidden ?
        return;

    ScDocument* pDoc = GetSourceDocument();
    if (!pDoc)
        return;

    // loop over cell notes
    std::vector<sc::NoteEntry> aEntries;
    pDoc->GetAllNoteEntries(aEntries);
    weld::TreeIter* pParent = m_aRootNodes[ScContentId::NOTE].get();
    for (const auto& rEntry : aEntries)
    {
        OUString aValue = lcl_NoteString(*rEntry.mpNote);
        OUString aId = OUString::number(rEntry.mpNote->GetId());
        m_xTreeView->insert(pParent, -1, &aValue, &aId, nullptr, nullptr, false, m_xScratchIter.get());
        m_xTreeView->set_sensitive(*m_xScratchIter, true);
    }
}

ScAddress ScContentTree::GetNotePos( sal_uLong nIndex )
{
    ScDocument* pDoc = GetSourceDocument();
    if (!pDoc)
        return ScAddress();

    return pDoc->GetNotePosition(nIndex);
}

bool ScContentTree::NoteStringsChanged()
{
    ScDocument* pDoc = GetSourceDocument();
    if (!pDoc)
        return false;

    weld::TreeIter* pParent = m_aRootNodes[ScContentId::NOTE].get();
    if (!pParent)
        return false;

    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator(pParent));
    bool bEntry = m_xTreeView->iter_children(*xEntry);

    std::vector<sc::NoteEntry> aEntries;
    pDoc->GetAllNoteEntries(aEntries);
    for (const auto& rEntry : aEntries)
    {
        const ScPostIt* pNote = rEntry.mpNote;
        if (!bEntry)
            return true;

        if (lcl_NoteString(*pNote) != m_xTreeView->get_text(*xEntry))
            return true;

        bEntry = m_xTreeView->iter_next_sibling(*xEntry);
    }

    return bEntry;
}

bool ScContentTree::DrawNamesChanged( ScContentId nType )
{
    ScDocument* pDoc = GetSourceDocument();
    if (!pDoc)
        return false;

    weld::TreeIter* pParent = m_aRootNodes[nType].get();
    if (!pParent)
        return false;

    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator(pParent));
    bool bEntry = m_xTreeView->iter_children(*xEntry);

    // iterate in flat mode for groups
    SdrIterMode eIter = ( nType == ScContentId::DRAWING ) ? SdrIterMode::Flat : SdrIterMode::DeepNoGroups;

    bool bEqual = true;
    ScDrawLayer* pDrawLayer = pDoc->GetDrawLayer();
    ScDocShell* pShell = pDoc->GetDocumentShell();
    if (pDrawLayer && pShell)
    {
        SCTAB nTabCount = pDoc->GetTableCount();
        for (SCTAB nTab=0; nTab<nTabCount && bEqual; nTab++)
        {
            SdrPage* pPage = pDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
            OSL_ENSURE(pPage,"Page ?");
            if (pPage)
            {
                SdrObjListIter aIter( pPage, eIter );
                SdrObject* pObject = aIter.Next();
                while (pObject && bEqual)
                {
                    if ( IsPartOfType( nType, pObject->GetObjIdentifier() ) )
                    {
                        if ( !bEntry )
                            bEqual = false;
                        else
                        {
                            if (ScDrawLayer::GetVisibleName(pObject) != m_xTreeView->get_text(*xEntry))
                                bEqual = false;

                            bEntry = m_xTreeView->iter_next_sibling(*xEntry);
                        }
                    }
                    pObject = aIter.Next();
                }
            }
        }
    }

    if ( bEntry )
        bEqual = false;             // anything else

    return !bEqual;
}

static bool lcl_GetRange( const ScDocument& rDoc, ScContentId nType, const OUString& rName, ScRange& rRange )
{
    bool bFound = false;

    if ( nType == ScContentId::RANGENAME )
    {
        ScRangeName* pList = rDoc.GetRangeName();
        if (pList)
        {
            const ScRangeData* p = pList->findByUpperName(ScGlobal::getCharClass().uppercase(rName));
            if (p && p->IsValidReference(rRange))
                bFound = true;
        }
    }
    else if ( nType == ScContentId::DBAREA )
    {
        ScDBCollection* pList = rDoc.GetDBCollection();
        if (pList)
        {
            const ScDBData* p = pList->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rName));
            if (p)
            {
                SCTAB nTab;
                SCCOL nCol1, nCol2;
                SCROW nRow1, nRow2;
                p->GetArea(nTab, nCol1, nRow1, nCol2, nRow2);
                rRange = ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab);
                bFound = true;
            }
        }
    }

    return bFound;
}

static bool lcl_DoDragObject( ScDocShell& rSrcShell, std::u16string_view rName, ScContentId nType, weld::TreeView& rTreeView )
{
    bool bDisallow = true;

    ScDocument& rSrcDoc = rSrcShell.GetDocument();
    ScDrawLayer* pModel = rSrcDoc.GetDrawLayer();
    if (pModel)
    {
        bool bOle = ( nType == ScContentId::OLEOBJECT );
        bool bGraf = ( nType == ScContentId::GRAPHIC );
        SdrObjKind nDrawId = bOle ? SdrObjKind::OLE2 : ( bGraf ? SdrObjKind::Graphic : SdrObjKind::Group );
        SCTAB nTab = 0;
        SdrObject* pObject = pModel->GetNamedObject( rName, nDrawId, nTab );
        if (pObject)
        {
            SdrView aEditView(*pModel);
            aEditView.ShowSdrPage(aEditView.GetModel().GetPage(nTab));
            SdrPageView* pPV = aEditView.GetSdrPageView();
            aEditView.MarkObj(pObject, pPV);

            // tdf125520 this is a D&D-start potentially with an OLE object. If
            // so, we need to do similar as e.g. in ScDrawView::BeginDrag so that
            // the temporary SdrModel for transfer does have a GetPersist() so
            // that the EmbeddedObjectContainer gets copied. We need no CheckOle
            // here, test is simpler.
            ScDocShellRef aDragShellRef;
            if(SdrObjKind::OLE2 == pObject->GetObjIdentifier())
            {
                aDragShellRef = new ScDocShell;     // DocShell needs a Ref immediately
                aDragShellRef->DoInitNew();
            }

            ScDrawLayer::SetGlobalDrawPersist(aDragShellRef.get());
            std::unique_ptr<SdrModel> pDragModel(aEditView.CreateMarkedObjModel());
            ScDrawLayer::SetGlobalDrawPersist(nullptr);

            TransferableObjectDescriptor aObjDesc;
            rSrcShell.FillTransferableObjectDescriptor( aObjDesc );
            aObjDesc.maDisplayName = rSrcShell.GetMedium()->GetURLObject().GetURLNoPass();
            // maSize is set in ScDrawTransferObj ctor

            rtl::Reference<ScDrawTransferObj> pTransferObj = new ScDrawTransferObj( std::move(pDragModel), rSrcShell, std::move(aObjDesc) );

            pTransferObj->SetDragSourceObj( *pObject, nTab );
            pTransferObj->SetDragSourceFlags(ScDragSrc::Navigator);

            ScModule::get()->SetDragObject(nullptr, pTransferObj.get());

            rtl::Reference<TransferDataContainer> xHelper(pTransferObj);
            rTreeView.enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK);

            bDisallow = false;
        }
    }

    return bDisallow;
}

static bool lcl_DoDragCells( ScDocShell& rSrcShell, const ScRange& rRange, ScDragSrc nFlags, weld::TreeView& rTreeView )
{
    bool bDisallow = true;

    ScDocument& rSrcDoc = rSrcShell.GetDocument();
    ScMarkData aMark(rSrcDoc.GetSheetLimits());
    aMark.SelectTable( rRange.aStart.Tab(), true );
    aMark.SetMarkArea( rRange );

    if ( !rSrcDoc.HasSelectedBlockMatrixFragment( rRange.aStart.Col(), rRange.aStart.Row(),
                                                   rRange.aEnd.Col(),   rRange.aEnd.Row(),
                                                   aMark ) )
    {
        ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP ));
        ScClipParam aClipParam(rRange, false);
        rSrcDoc.CopyToClip(aClipParam, pClipDoc.get(), &aMark, false, false);
        // pClipDoc->ExtendMerge( rRange, sal_True );

        TransferableObjectDescriptor aObjDesc;
        rSrcShell.FillTransferableObjectDescriptor( aObjDesc );
        aObjDesc.maDisplayName = rSrcShell.GetMedium()->GetURLObject().GetURLNoPass();
        // maSize is set in ScTransferObj ctor

        rtl::Reference<ScTransferObj> pTransferObj = new ScTransferObj( std::move(pClipDoc), std::move(aObjDesc) );

        pTransferObj->SetDragSource( &rSrcShell, aMark );
        pTransferObj->SetDragSourceFlags( nFlags );

        ScModule::get()->SetDragObject(pTransferObj.get(), nullptr); // for internal D&D

        rtl::Reference<TransferDataContainer> xHelper(pTransferObj);
        rTreeView.enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK);

        bDisallow = false;
    }

    return bDisallow;
}

IMPL_LINK(ScContentTree, DragBeginHdl, bool&, rUnsetDragIcon, bool)
{
    rUnsetDragIcon = true;

    StoreNavigatorSettings();

    bool bDisallow = true;

    ScModule* pScMod = ScModule::get();

    ScContentId nType;
    sal_uLong nChild;

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

    GetEntryIndexes(nType, nChild, xEntry.get());

    if( xEntry &&
        (nChild != SC_CONTENT_NOCHILD) &&
        (nType != ScContentId::ROOT) &&
        (nType != ScContentId::NOTE) &&
        (nType != ScContentId::AREALINK) )
    {
        OUString aText(m_xTreeView->get_text(*xEntry));

        ScDocument* pLocalDoc = nullptr;                   // for URL drop
        OUString aDocName;
        ScDocShell* pDocSh = GetManualOrCurrent();
        if (pDocSh)
        {
            if (pDocSh->HasName())
                aDocName = pDocSh->GetMedium()->GetName();
            else
                pLocalDoc = &pDocSh->GetDocument();      // drop only in this document
        }

        bool bDoLinkTrans = false;      // use ScLinkTransferObj
        OUString aLinkURL;                // for ScLinkTransferObj
        OUString aLinkText;

        sal_uInt16 nDropMode = pParentWindow->GetDropMode();
        switch ( nDropMode )
        {
            case SC_DROPMODE_URL:
                {
                    OUString aUrl = aDocName + "#" + aText;

                    pScMod->SetDragJump( pLocalDoc, aUrl, aText );

                    if (!aDocName.isEmpty())
                    {
                        //  provide URL to outside only if the document has a name
                        //  (without name, only internal D&D via SetDragJump)

                        aLinkURL = aUrl;
                        aLinkText = aText;
                    }
                    bDoLinkTrans = true;
                }
                break;
            case SC_DROPMODE_LINK:
                {
                    if ( !aDocName.isEmpty() )           // link only to named documents
                    {
                        // for internal D&D, set flag to insert a link

                        switch ( nType )
                        {
                            case ScContentId::TABLE:
                                pScMod->SetDragLink( aDocName, aText, OUString() );
                                bDoLinkTrans = true;
                                break;
                            case ScContentId::RANGENAME:
                            case ScContentId::DBAREA:
                                pScMod->SetDragLink( aDocName, OUString(), aText );
                                bDoLinkTrans = true;
                                break;

                            // other types cannot be linked
                            default: break;
                        }
                    }
                }
                break;
            case SC_DROPMODE_COPY:
                {
                    ScDocShell* pSrcShell = GetManualOrCurrent();
                    if ( pSrcShell )
                    {
                        ScDocument& rSrcDoc = pSrcShell->GetDocument();
                        if ( nType == ScContentId::RANGENAME || nType == ScContentId::DBAREA )
                        {
                            ScRange aRange;
                            if ( lcl_GetRange( rSrcDoc, nType, aText, aRange ) )
                            {
                                bDisallow = lcl_DoDragCells( *pSrcShell, aRange, ScDragSrc::Navigator, *m_xTreeView );
                            }
                        }
                        else if ( nType == ScContentId::TABLE )
                        {
                            SCTAB nTab;
                            if ( rSrcDoc.GetTable( aText, nTab ) )
                            {
                                ScRange aRange(0, 0, nTab, rSrcDoc.MaxCol(), rSrcDoc.MaxRow(), nTab);
                                bDisallow = lcl_DoDragCells( *pSrcShell, aRange, (ScDragSrc::Navigator | ScDragSrc::Table), *m_xTreeView );
                            }
                        }
                        else if ( nType == ScContentId::GRAPHIC || nType == ScContentId::OLEOBJECT ||
                                    nType == ScContentId::DRAWING )
                        {
                            bDisallow = lcl_DoDragObject( *pSrcShell, aText, nType, *m_xTreeView );

                            //  during ExecuteDrag the navigator can be deleted
                            //  -> don't access member anymore !!!
                        }
                    }
                }
                break;
        }

        if (bDoLinkTrans)
        {
            if (!aLinkURL.isEmpty())
                m_xTransferObj->SetLinkURL(aLinkURL, aLinkText);

            rtl::Reference<TransferDataContainer> xHelper(m_xTransferObj);
            m_xTreeView->enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK);

            bDisallow = false;
        }
    }

    return bDisallow;
}

void ScContentTree::SetRootType( ScContentId nNew )
{
    if ( nNew != nRootType )
    {
        nRootType = nNew;
        Refresh();

        ScNavipiCfg& rCfg = ScModule::get()->GetNavipiCfg();
        rCfg.SetRootType( nRootType );
    }
}

void ScContentTree::ToggleRoot()        // after selection
{
    ScContentId nNew = ScContentId::ROOT;
    if ( nRootType == ScContentId::ROOT )
    {
        std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
        if (m_xTreeView->get_cursor(xEntry.get()))
        {
            std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator(xEntry.get()));
            if (!m_xTreeView->iter_parent(*xParent))
                xParent.reset();

            for (sal_uInt16 i=1; i<=int(ScContentId::LAST); i++)
            {
                if (!m_aRootNodes[static_cast<ScContentId>(i)])
                    continue;
                if ((m_xTreeView->iter_compare(*xEntry, *m_aRootNodes[static_cast<ScContentId>(i)]) == 0) ||
                    (xParent && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[static_cast<ScContentId>(i)]) == 0))
                {
                    nNew = static_cast<ScContentId>(i);
                }
            }
        }
    }

    SetRootType( nNew );
}

void ScContentTree::ResetManualDoc()
{
    aManualDoc.clear();

    ActiveDocChanged();
}

bool ScContentTree::ActiveDocChanged()
{
    bool bRefreshed = false;

    if (aManualDoc.isEmpty())
    {
        Refresh();                                  // content only if automatic
        bRefreshed = true;
    }

        //  if flag active Listbox must be updated

    OUString aCurrent;

    ScDocShell* pSh = GetManualOrCurrent();
    if (pSh)
        aCurrent = pSh->GetTitle();
    else
    {
        //  document is no longer available

        aManualDoc.clear();             // again automatically
        Refresh();
        bRefreshed = true;
        pSh = GetManualOrCurrent();     // should be active now
        if (pSh)
            aCurrent = pSh->GetTitle();
    }

    pParentWindow->GetDocNames( &aCurrent );        // select

    return bRefreshed;
}

void ScContentTree::SetManualDoc(const OUString& rName)
{
    aManualDoc = rName;
    Refresh();
    pParentWindow->GetDocNames( &aManualDoc );      // select
}

void ScContentTree::SelectDoc(const OUString& rName)      // rName like shown in Menu/Listbox
{
    if ( rName == pParentWindow->aStrActiveWin )
    {
        ResetManualDoc();
        return;
    }

    //  omit "active" or "inactive"

    OUString aRealName = rName;
    sal_Int32 nLen = rName.getLength();
    sal_Int32 nActiveStart = nLen - pParentWindow->aStrActive.getLength();
    if ( rName.subView( nActiveStart ) == pParentWindow->aStrActive )
        aRealName = rName.copy( 0, nActiveStart );
    sal_Int32 nNotActiveStart = nLen - pParentWindow->aStrNotActive.getLength();
    if ( rName.subView( nNotActiveStart ) == pParentWindow->aStrNotActive )
        aRealName = rName.copy( 0, nNotActiveStart );

    bool bLoaded = false;

    // Is it a normally loaded document?

    SfxObjectShell* pSh = SfxObjectShell::GetFirst();
    while ( pSh && !bLoaded )
    {
        if ( dynamic_cast<const ScDocShell*>( pSh) !=  nullptr )
            if ( pSh->GetTitle() == aRealName )
                bLoaded = true;
        pSh = SfxObjectShell::GetNext( *pSh );
    }

    if (bLoaded)
    {
        SetManualDoc(aRealName);
    }
    else
    {
        OSL_FAIL("SelectDoc: not found");
    }
}

void ScContentTree::SelectEntryByName(const ScContentId nRoot, std::u16string_view rName)
{
    weld::TreeIter* pParent = m_aRootNodes[nRoot].get();

    if (!pParent || !m_xTreeView->iter_has_child(*pParent))
        return;

    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator(pParent));
    bool bEntry = m_xTreeView->iter_children(*xEntry);

    while (bEntry)
    {
        if (m_xTreeView->get_text(*xEntry) == rName)
        {
            m_xTreeView->select(*xEntry);
            m_xTreeView->set_cursor(*xEntry);

            // Scroll to the selected item
            m_xTreeView->scroll_to_row(*xEntry);

            StoreNavigatorSettings();

            return;
        }
        bEntry = m_xTreeView->iter_next(*xEntry);
    }
}

void ScContentTree::BringCommentToAttention(sal_uInt16 nCommentId)
{
    std::unique_ptr<weld::TreeIter> xIter(m_xTreeView->make_iterator());
    if (!m_xTreeView->get_iter_first(*xIter))
        return;

    do
    {
        ScContentId nType;
        sal_uLong nChild;
        GetEntryIndexes(nType, nChild, xIter.get());

        if (nType == ScContentId::NOTE)
        {
            m_xTreeView->set_cursor(*xIter);
            m_xTreeView->select(*xIter);
            m_xTreeView->expand_row(*xIter);

            OUString aCommentId(OUString::number(nCommentId));
            for (bool bChild = m_xTreeView->iter_children(*xIter); bChild;
                 bChild = m_xTreeView->iter_next_sibling(*xIter))
            {
                if (m_xTreeView->get_id(*xIter) == aCommentId)
                {
                    m_xTreeView->select(*xIter);
                    break;
                }
            }
            break;
        }
        else
            m_xTreeView->collapse_row(*xIter);

    } while (m_xTreeView->iter_next_sibling(*xIter));}

void ScContentTree::ApplyNavigatorSettings()
{
    const ScNavigatorSettings* pSettings = ScNavigatorDlg::GetNavigatorSettings();
    if( !pSettings )
        return;

    ScContentId nRootSel = pSettings->GetRootSelected();
    auto nChildSel = pSettings->GetChildSelected();

    // tdf#133079 ensure Sheet root is selected if nothing
    // else would be
    if (nRootSel == ScContentId::ROOT)
    {
        nRootSel = ScContentId::TABLE;
        nChildSel = SC_CONTENT_NOCHILD;
    }

    for( int i = 1; i <= int(ScContentId::LAST); ++i )
    {
        ScContentId nEntry = static_cast<ScContentId>(i);
        if( m_aRootNodes[ nEntry ] )
        {
            // gray or ungray
            if (!m_xTreeView->iter_has_child(*m_aRootNodes[nEntry]))
                m_xTreeView->set_sensitive(*m_aRootNodes[nEntry], false);
            else
                m_xTreeView->set_sensitive(*m_aRootNodes[nEntry], true);

            // expand
            bool bExp = pSettings->IsExpanded( nEntry );
            if (bExp != m_xTreeView->get_row_expanded(*m_aRootNodes[nEntry]))
            {
                if( bExp )
                    m_xTreeView->expand_row(*m_aRootNodes[nEntry]);
                else
                    m_xTreeView->collapse_row(*m_aRootNodes[nEntry]);
            }

            // select
            if( nRootSel == nEntry )
            {
                std::unique_ptr<weld::TreeIter> xEntry;
                if (bExp && (nChildSel != SC_CONTENT_NOCHILD))
                {
                    xEntry = m_xTreeView->make_iterator(m_aRootNodes[nEntry].get());
                    if (!m_xTreeView->iter_children(*xEntry) || !m_xTreeView->iter_nth_sibling(*xEntry, nChildSel))
                        xEntry.reset();
                }
                m_xTreeView->select(xEntry ? *xEntry : *m_aRootNodes[nEntry]);
                m_xTreeView->set_cursor(xEntry ? *xEntry : *m_aRootNodes[nEntry]);
            }
        }
    }
}

void ScContentTree::StoreNavigatorSettings()
{
    if (m_nAsyncMouseReleaseId)
    {
        Application::RemoveUserEvent(m_nAsyncMouseReleaseId);
        m_nAsyncMouseReleaseId = nullptr;
    }

    ScNavigatorSettings* pSettings = ScNavigatorDlg::GetNavigatorSettings();
    if( !pSettings )
        return;

    for( int i = 1; i <= int(ScContentId::LAST); ++i )
    {
        ScContentId nEntry = static_cast<ScContentId>(i);
        bool bExp = m_aRootNodes[nEntry] && m_xTreeView->get_row_expanded(*m_aRootNodes[nEntry]);
        pSettings->SetExpanded( nEntry, bExp );
    }

    std::unique_ptr<weld::TreeIter> xCurEntry(m_xTreeView->make_iterator());
    if (!m_xTreeView->get_cursor(xCurEntry.get()))
        xCurEntry.reset();

    ScContentId nRoot;
    sal_uLong nChild;
    GetEntryIndexes(nRoot, nChild, xCurEntry.get());

    pSettings->SetRootSelected( nRoot );
    pSettings->SetChildSelected( nChild );
}

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