/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 <scitems.hxx>

#include <editeng/boxitem.hxx>
#include <editeng/editobj.hxx>
#include <o3tl/untaint.hxx>
#include <svx/svditer.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/viewfrm.hxx>
#include <svl/numformat.hxx>
#include <poolcach.hxx>
#include <svl/zforlist.hxx>
#include <unotools/charclass.hxx>
#include <unotools/transliterationwrapper.hxx>
#include <tools/urlobj.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>

#include <com/sun/star/text/WritingMode2.hpp>
#include <com/sun/star/script/vba/XVBACompatibility.hpp>
#include <com/sun/star/sheet/TablePageBreakData.hpp>
#include <com/sun/star/lang/NotInitializedException.hpp>

#include <document.hxx>
#include <docsh.hxx>
#include <docuno.hxx>
#include <table.hxx>
#include <column.hxx>
#include <attrib.hxx>
#include <attarray.hxx>
#include <patattr.hxx>
#include <rangenam.hxx>
#include <poolhelp.hxx>
#include <docpool.hxx>
#include <stlpool.hxx>
#include <stlsheet.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <dbdata.hxx>
#include <chartlis.hxx>
#include <rangelst.hxx>
#include <markdata.hxx>
#include <drwlayer.hxx>
#include <validat.hxx>
#include <prnsave.hxx>
#include <chgtrack.hxx>
#include <hints.hxx>
#include <detdata.hxx>
#include <dpobject.hxx>
#include <scmod.hxx>
#include <dociter.hxx>
#include <progress.hxx>
#include <autonamecache.hxx>
#include <bcaslot.hxx>
#include <postit.hxx>
#include <clipparam.hxx>
#include <defaultsoptions.hxx>
#include <editutil.hxx>
#include <stringutil.hxx>
#include <formulaiter.hxx>
#include <formulacell.hxx>
#include <clipcontext.hxx>
#include <listenercontext.hxx>
#include <scopetools.hxx>
#include <refupdatecontext.hxx>
#include <formulagroup.hxx>
#include <tokenstringcontext.hxx>
#include <compressedarray.hxx>
#include <recursionhelper.hxx>
#include <SparklineGroup.hxx>
#include <SparklineList.hxx>
#include <undomanager.hxx>

#include <formula/vectortoken.hxx>

#include <limits>
#include <memory>
#include <utility>
#include <unordered_map>

#include <comphelper/kit.hxx>

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

#include <mtvelements.hxx>
#include <sfx2/kit/helper.hxx>
#include <SheetViewManager.hxx>

#include <brdcst.hxx>

using ::editeng::SvxBorderLine;
using namespace ::com::sun::star;

namespace WritingMode2 = ::com::sun::star::text::WritingMode2;
using ::cpo::uno::Sequence;
using ::com::sun::star::sheet::TablePageBreakData;

namespace {

std::pair<SCTAB,SCTAB> getMarkedTableRange(const std::vector<ScTableUniquePtr>& rTables, const ScMarkData& rMark)
{
    SCTAB nTabStart = MAXTAB;
    SCTAB nTabEnd = 0;
    SCTAB nMax = static_cast<SCTAB>(rTables.size());
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;

        if (!rTables[rTab])
            continue;

        if (rTab < nTabStart)
            nTabStart = rTab;
        nTabEnd = rTab;
    }

    return std::pair<SCTAB,SCTAB>(nTabStart,nTabEnd);
}

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

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

struct ScDefaultAttr
{
    SCROW                   nFirst { 0 };
    SCSIZE                  nCount { 0 };
};

}

typedef std::unordered_map<const ScPatternAttr*, ScDefaultAttr> ScDefaultAttrMap;

void ScDocument::MakeTable( SCTAB nTab,bool _bNeedsNameCheck )
{
    if (!ValidTab(nTab) || HasTable(nTab))
        return;

    // Get Custom prefix
    const ScDefaultsOptions& rOpt = ScModule::get()->GetDefaultsOptions();
    OUString aString = rOpt.GetInitTabPrefix() + OUString::number(nTab+1);
    if ( _bNeedsNameCheck )
        CreateValidTabName( aString );  // no doubles
    if (nTab < GetTableCount())
    {
        maTabs[nTab].reset( new ScTable(*this, nTab, aString) );
    }
    else
    {
        while (nTab > GetTableCount())
            maTabs.push_back(nullptr);
        maTabs.emplace_back( new ScTable(*this, nTab, aString) );
    }
    maTabs[nTab]->SetLoadingMedium(bLoadingMedium);
}

bool ScDocument::GetHashCode( SCTAB nTab, sal_Int64& rHashCode ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
    {
        rHashCode = pTable->GetHashCode();
        return true;
    }
    return false;
}

bool ScDocument::GetName( SCTAB nTab, OUString& rName ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
    {
        rName = pTable->GetName();
        return true;
    }
    rName.clear();
    return false;
}

const OUString & ScDocument::GetCopyTabName( SCTAB nTab ) const
{
    if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabNames.size()))
        return maTabNames[nTab];
    return EMPTY_OUSTRING;
}

bool ScDocument::SetCodeName( SCTAB nTab, const OUString& rName )
{
    if (ScTable* pTable = FetchTable(nTab))
    {
        pTable->SetCodeName(rName);
        return true;
    }
    SAL_WARN("sc",  "can't set code name " << rName );
    return false;
}

bool ScDocument::GetCodeName( SCTAB nTab, OUString& rName ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
    {
        rName = pTable->GetCodeName();
        return true;
    }
    rName.clear();
    return false;
}

bool ScDocument::SetTotalsRowBelow( SCTAB nTab, bool bVal )
{
    if (ScTable* pTable = FetchTable(nTab))
    {
        pTable->SetTotalsRowBelow(bVal);
        return true;
    }
    return false;
}

bool ScDocument::GetTotalsRowBelow( SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
    {
        return pTable->GetTotalsRowBelow();
    }
    return true;
}

bool ScDocument::GetTable( const OUString& rName, SCTAB& rTab ) const
{
    static OUString aCacheName, aCacheUpperName;

    assert(!IsThreadedGroupCalcInProgress());
    if (aCacheName != rName)
    {
        aCacheName = rName;
        // surprisingly slow ...
        aCacheUpperName = ScGlobal::getCharClass().uppercase(rName);
    }
    const OUString aUpperName = aCacheUpperName;

    for (SCTAB i = 0; i < GetTableCount(); i++)
        if (maTabs[i])
        {
            if (aUpperName == maTabs[i]->GetUpperName())
            {
                rTab = i;
                return true;
            }
        }
    rTab = 0;
    return false;
}

std::vector<OUString> ScDocument::GetAllTableNames() const
{
    std::vector<OUString> aNames;
    aNames.reserve(maTabs.size());
    for (const auto& a : maTabs)
    {
        // Positions need to be preserved for ScCompiler and address convention
        // context, so still push an empty string for NULL tabs.
        OUString aName;
        if (a)
        {
            const ScTable& rTab = *a;
            aName = rTab.GetName();
        }
        aNames.push_back(aName);
    }

    return aNames;
}

ScDBData* ScDocument::GetAnonymousDBData(SCTAB nTab)
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetAnonymousDBData();
    return nullptr;
}

SCTAB ScDocument::GetTableCount() const
{
    return static_cast<SCTAB>(maTabs.size());
}

void ScDocument::SetAnonymousDBData(SCTAB nTab, std::unique_ptr<ScDBData> pDBData)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetAnonymousDBData(std::move(pDBData));
}

void ScDocument::SetAnonymousDBData( std::unique_ptr<ScDBData> pDBData )
{
    mpAnonymousDBData = std::move(pDBData);
}

ScDBData* ScDocument::GetAnonymousDBData()
{
    return mpAnonymousDBData.get();
}

bool ScDocument::ValidTabName( const OUString& rName )
{
    if (rName.isEmpty())
        return false;
    sal_Int32 nLen = rName.getLength();

#if 1
    // Restrict sheet names to what Excel accepts.
    /* TODO: We may want to remove this restriction for full ODFF compliance.
     * Merely loading and calculating ODF documents using these characters in
     * sheet names is not affected by this, but all sheet name editing and
     * copying functionality is, maybe falling back to "Sheet4" or similar. */
    for (sal_Int32 i = 0; i < nLen; ++i)
    {
        const sal_Unicode c = rName[i];
        switch (c)
        {
            case ':':
            case '\\':
            case '/':
            case '?':
            case '*':
            case '[':
            case ']':
                // these characters are not allowed to match XL's convention.
                return false;
            case '\'':
                if (i == 0 || i == nLen - 1)
                    // single quote is not allowed at the first or last
                    // character position.
                    return false;
            break;
        }
    }
#endif

    return true;
}

bool ScDocument::ValidNewTabName( const OUString& rName ) const
{
    bool bValid = ValidTabName(rName);
    if (!bValid)
        return false;
    OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
    for (const auto& a : maTabs)
    {
        if (!a)
            continue;
        const OUString& rOldName = a->GetUpperName();
        bValid = rOldName != aUpperName;
        if (!bValid)
            break;
    }
    return bValid;
}

void ScDocument::CreateValidTabName(OUString& rName) const
{
    if ( !ValidTabName(rName) )
    {
        // Find new one

        // Get Custom prefix
        const ScDefaultsOptions& rOpt = ScModule::get()->GetDefaultsOptions();
        const OUString aStrTable = rOpt.GetInitTabPrefix();

        bool         bOk   = false;

        // First test if the prefix is valid, if so only avoid doubles
        bool bPrefix = ValidTabName( aStrTable );
        OSL_ENSURE(bPrefix, "Invalid Table Name");
        SCTAB nDummy;

        for (SCTAB i = GetTableCount() + 1; !bOk ; i++)
        {
            rName = aStrTable + OUString::number(static_cast<sal_Int32>(i));
            if (bPrefix)
                bOk = ValidNewTabName( rName );
            else
                bOk = !GetTable( rName, nDummy );
        }
    }
    else
    {
        // testing the supplied Name

        if ( !ValidNewTabName(rName) )
        {
            SCTAB i = 1;
            OUString aName;
            do
            {
                i++;
                aName = rName + "_" + OUString::number(static_cast<sal_Int32>(i));
            }
            while (!ValidNewTabName(aName) && (i < MAXTAB+1));
            rName = aName;
        }
    }
}

void ScDocument::CreateValidTabNames(std::vector<OUString>& aNames, SCTAB nCount) const
{
    aNames.clear();//ensure that the vector is empty

    // Get Custom prefix
    const ScDefaultsOptions& rOpt = ScModule::get()->GetDefaultsOptions();
    const OUString aStrTable = rOpt.GetInitTabPrefix();

    OUStringBuffer rName;

    // First test if the prefix is valid, if so only avoid doubles
    bool bPrefix = ValidTabName( aStrTable );
    OSL_ENSURE(bPrefix, "Invalid Table Name");
    SCTAB nDummy;
    SCTAB i = GetTableCount() + 1;

    for (SCTAB j = 0; j < nCount; ++j)
    {
        bool bOk = false;
        while(!bOk)
        {
            rName = aStrTable;
            rName.append(static_cast<sal_Int32>(i));
            if (bPrefix)
                bOk = ValidNewTabName( rName.toString() );
            else
                bOk = !GetTable( rName.toString(), nDummy );
            i++;
        }
        aNames.push_back(rName.makeStringAndClear());
    }
}

void ScDocument::AppendTabOnLoad(const OUString& rName)
{
    SCTAB nTabCount = GetTableCount();
    if (!ValidTab(nTabCount))
        // max table count reached.  No more tables.
        return;

    OUString aName = rName;
    CreateValidTabName(aName);
    maTabs.emplace_back( new ScTable(*this, nTabCount, aName) );
}

void ScDocument::SetTabNameOnLoad(SCTAB nTab, const OUString& rName)
{
    if (!ValidTab(nTab) || GetTableCount() <= nTab)
        return;

    if (!ValidTabName(rName))
        return;

    maTabs[nTab]->SetName(rName);
}

void ScDocument::InvalidateStreamOnSave()
{
    for (const auto& a : maTabs)
    {
        if (a)
            a->SetStreamValid(false);
    }
}

bool ScDocument::InsertTab(
    SCTAB nPos, const OUString& rName, bool bExternalDocument, bool bUndoDeleteTab )
{
    // auto-accept any in-process input to prevent move the cell into next sheet in online.
    if (comphelper::COKit::isActive())
        if (ScModule* mod = ScModule::get(); !mod->IsFormulaMode())
            mod->InputEnterHandler();

    SCTAB nTabCount = GetTableCount();
    bool bValid = ValidTab(nTabCount);
    if ( !bExternalDocument )   // else test rName == "'Doc'!Tab" first
        bValid = (bValid && ValidNewTabName(rName));
    if (bValid)
    {
        if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
        {
            nPos = maTabs.size();
            maTabs.emplace_back( new ScTable(*this, nTabCount, rName) );
            if ( bExternalDocument )
                maTabs[nTabCount]->SetVisible( false );
        }
        else
        {
            if (ValidTab(nPos) && (nPos < nTabCount))
            {
                sc::RefUpdateInsertTabContext aCxt( *this, nPos, 1);

                ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB );
                xColNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,1 );
                xRowNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,1 );
                if (pRangeName)
                    pRangeName->UpdateInsertTab(aCxt);
                pDBCollection->UpdateReference(
                                    URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
                if (pDPCollection)
                    pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,1 );
                if (pDetOpList)
                    pDetOpList->UpdateReference( *this, URM_INSDEL, aRange, 0,0,1 );
                UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
                UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,1 );
                if ( pUnoBroadcaster )
                    pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,1 ) );

                for (const auto& a : maTabs)
                {
                    if (a)
                        a->UpdateInsertTab(aCxt);
                }
                maTabs.emplace(maTabs.begin() + nPos, new ScTable(*this, nPos, rName));

                // UpdateBroadcastAreas must be called between UpdateInsertTab,
                // which ends listening, and StartAllListeners, to not modify
                // areas that are to be inserted by starting listeners.
                UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,1);
                for (const auto& a : maTabs)
                {
                    if (a)
                        a->UpdateCompile();
                }

                StartAllListeners();

                if (pValidationList)
                {
                    ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
                    pValidationList->UpdateInsertTab(aCxt);
                }

                UpdateQueryTables(aCxt);

                bValid = true;
            }
            else
                bValid = false;
        }
    }

    if (bValid)
    {
        sc::SetFormulaDirtyContext aCxt;
        aCxt.mbClearTabDeletedFlag = bUndoDeleteTab;
        aCxt.mnTabDeletedStart = nPos;
        aCxt.mnTabDeletedEnd = nPos;
        SetAllFormulasDirty(aCxt);

        if (comphelper::COKit::isActive() && GetDrawLayer() && GetDocumentShell())
        {
            ScModelObj* pModel = GetDocumentShell()->GetModel();
            KitHelper::notifyDocumentSizeChangedAllViews(pModel);
        }
    }

    return bValid;
}

bool ScDocument::InsertTabs( SCTAB nPos, const std::vector<OUString>& rNames,
            bool bNamesValid )
{
    SCTAB nNewSheets = static_cast<SCTAB>(rNames.size());
    SCTAB nTabCount = GetTableCount();
    bool bValid = bNamesValid || ValidTab(nTabCount+nNewSheets);

    if (bValid)
    {
        if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
        {
            for ( SCTAB i = 0; i < nNewSheets; ++i )
            {
                maTabs.emplace_back( new ScTable(*this, nTabCount + i, rNames.at(i)) );
            }
        }
        else
        {
            if (ValidTab(nPos) && (nPos < nTabCount))
            {
                sc::RefUpdateInsertTabContext aCxt( *this, nPos, nNewSheets);
                ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB );
                xColNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,nNewSheets );
                xRowNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,nNewSheets );
                if (pRangeName)
                    pRangeName->UpdateInsertTab(aCxt);
                pDBCollection->UpdateReference(
                                    URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets );
                if (pDPCollection)
                    pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,nNewSheets );
                if (pDetOpList)
                    pDetOpList->UpdateReference( *this, URM_INSDEL, aRange, 0,0,nNewSheets );
                UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets );
                UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0, nNewSheets );
                if ( pUnoBroadcaster )
                    pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,nNewSheets ) );

                for (const auto& a : maTabs)
                {
                    if (a)
                        a->UpdateInsertTab(aCxt);
                }
                for (SCTAB i = 0; i < nNewSheets; ++i)
                {
                    maTabs.emplace(maTabs.begin() + nPos + i, new ScTable(*this, nPos + i, rNames.at(i)) );
                }

                // UpdateBroadcastAreas must be called between UpdateInsertTab,
                // which ends listening, and StartAllListeners, to not modify
                // areas that are to be inserted by starting listeners.
                UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,nNewSheets);
                for (const auto& a : maTabs)
                {
                    if (a)
                        a->UpdateCompile();
                }

                StartAllListeners();

                if (pValidationList)
                {
                    ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
                    pValidationList->UpdateInsertTab(aCxt);
                }

                UpdateQueryTables(aCxt);

                bValid = true;
            }
            else
                bValid = false;
        }
    }

    if (bValid)
    {
        sc::SetFormulaDirtyContext aCxt;
        SetAllFormulasDirty(aCxt);
    }

    return bValid;
}

bool ScDocument::DeleteTab( SCTAB nTab )
{
    bool bValid = false;
    if (HasTable(nTab))
    {
        SCTAB nTabCount = GetTableCount();
        if (nTabCount > 1)
        {
            sc::AutoCalcSwitch aACSwitch(*this, false);
            sc::RefUpdateDeleteTabContext aCxt( *this, nTab, 1);
            sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);

            ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab );
            DelBroadcastAreasInRange( aRange );

            // #i8180# remove database ranges etc. that are on the deleted tab
            // (restored in undo with ScRefUndoData)

            xColNameRanges->DeleteOnTab( nTab );
            xRowNameRanges->DeleteOnTab( nTab );
            pDBCollection->DeleteOnTab( nTab );
            if (pDPCollection)
                pDPCollection->DeleteOnTab( nTab );
            if (pDetOpList)
                pDetOpList->DeleteOnTab( nTab );
            DeleteAreaLinksOnTab( nTab );

            // normal reference update

            aRange.aEnd.SetTab(GetTableCount() - 1);
            xColNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,-1 );
            xRowNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,-1 );
            if (pRangeName)
                pRangeName->UpdateDeleteTab(aCxt);
            pDBCollection->UpdateReference(
                                URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 );
            if (pDPCollection)
                pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1 );
            if (pDetOpList)
                pDetOpList->UpdateReference( *this, URM_INSDEL, aRange, 0,0,-1 );
            UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 );
            UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1 );
            if (pValidationList)
            {
                ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
                pValidationList->UpdateDeleteTab(aCxt);
            }
            if ( pUnoBroadcaster )
                pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1 ) );

            for (auto & pTab : maTabs)
                if (pTab)
                    pTab->UpdateDeleteTab(aCxt);

            // Delete sheet views holders
            if (ScTable* pTable = FetchTable(nTab))
            {
                if (pTable->IsSheetViewHolder())
                {
                    assert(pTable->GetDefaultViewTable() && "Sheet view holder but the pointer to the table is nullptr");
                    auto pSheetViewManager = pTable->GetDefaultViewTable()->GetSheetViewManager();
                    assert(pSheetViewManager && "Sheet view holder but the table has no SheetViewManager");
                    pSheetViewManager->remove(pTable->GetSheetViewID());
                }
                else
                {
                    auto pManager = pTable->GetSheetViewManager();
                    if (pManager)
                    {
                        for (auto const& rSheetView : pManager->iterateValidSheetViews())
                        {
                            // Set the table pointer to null as the table will be deleted
                            rSheetView.getTablePointer()->RemoveSheetViewTablePointer();
                        }
                    }
                }
            }

            // tdf#149502 make sure ScTable destructor called after the erase is finished, when
            // maTabs[x].nTab==x is true again, as it should be always true.
            // In the end of maTabs.erase, maTabs indexes change, but nTab updated before erase.
            // ~ScTable expect that maTabs[x].nTab==x so it shouldn't be called during erase.
            ScTableUniquePtr pErasedTab = std::move(maTabs[nTab]);
            maTabs.erase(maTabs.begin() + nTab);
            delete pErasedTab.release();

            UpdateQueryTables(aCxt);

            // UpdateBroadcastAreas must be called between UpdateDeleteTab,
            // which ends listening, and StartAllListeners, to not modify
            // areas that are to be inserted by starting listeners.
            UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1);
            for (const auto& a : maTabs)
            {
                if (a)
                    a->UpdateCompile();
            }
            // Excel-Filter deletes some Tables while loading, Listeners will
            // only be triggered after the loading is done.
            if ( !bInsertingFromOtherDoc )
            {
                StartAllListeners();

                sc::SetFormulaDirtyContext aFormulaDirtyCxt;
                SetAllFormulasDirty(aFormulaDirtyCxt);
            }

            if (comphelper::COKit::isActive() && GetDocumentShell())
            {
                ScModelObj* pModel = GetDocumentShell()->GetModel();
                KitHelper::notifyDocumentSizeChangedAllViews(pModel);
            }

            BroadcastRefError(aCxt.maRefErrors);
            bValid = true;
        }
    }
    return bValid;
}

bool ScDocument::DeleteTabs( SCTAB nTab, SCTAB nSheets )
{
    bool bValid = false;
    if (HasTable(nTab) && (nTab + nSheets) <= GetTableCount())
    {
        SCTAB nTabCount = GetTableCount();
        if (nTabCount > nSheets)
        {
            sc::AutoCalcSwitch aACSwitch(*this, false);
            sc::RefUpdateDeleteTabContext aCxt( *this, nTab, nSheets);
            sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);

            for (SCTAB aTab = 0; aTab < nSheets; ++aTab)
            {
                ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab + aTab );
                DelBroadcastAreasInRange( aRange );

                // #i8180# remove database ranges etc. that are on the deleted tab
                // (restored in undo with ScRefUndoData)

                xColNameRanges->DeleteOnTab( nTab + aTab );
                xRowNameRanges->DeleteOnTab( nTab + aTab );
                pDBCollection->DeleteOnTab( nTab + aTab );
                if (pDPCollection)
                    pDPCollection->DeleteOnTab( nTab + aTab );
                if (pDetOpList)
                    pDetOpList->DeleteOnTab( nTab + aTab );
                DeleteAreaLinksOnTab( nTab + aTab );
            }

            if (pRangeName)
                pRangeName->UpdateDeleteTab(aCxt);

            // normal reference update

            ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTabCount - 1 );
            xColNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,-1*nSheets );
            xRowNameRanges->UpdateReference( URM_INSDEL, *this, aRange, 0,0,-1*nSheets );
            pDBCollection->UpdateReference(
                                URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets );
            if (pDPCollection)
                pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1*nSheets );
            if (pDetOpList)
                pDetOpList->UpdateReference( *this, URM_INSDEL, aRange, 0,0,-1*nSheets );
            UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets );
            UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1*nSheets );
            if (pValidationList)
            {
                ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
                pValidationList->UpdateDeleteTab(aCxt);
            }
            if ( pUnoBroadcaster )
                pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1*nSheets ) );

            for (auto & pTab : maTabs)
                if (pTab)
                    pTab->UpdateDeleteTab(aCxt);

            maTabs.erase(maTabs.begin() + nTab, maTabs.begin() + nTab + nSheets);

            UpdateQueryTables(aCxt);

            // UpdateBroadcastAreas must be called between UpdateDeleteTab,
            // which ends listening, and StartAllListeners, to not modify
            // areas that are to be inserted by starting listeners.
            UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1*nSheets);
            for (const auto& a : maTabs)
            {
                if (a)
                    a->UpdateCompile();
            }
            // Excel-Filter deletes some Tables while loading, Listeners will
            // only be triggered after the loading is done.
            if ( !bInsertingFromOtherDoc )
            {
                StartAllListeners();

                sc::SetFormulaDirtyContext aFormulaDirtyCxt;
                SetAllFormulasDirty(aFormulaDirtyCxt);
            }

            if (comphelper::COKit::isActive() && GetDocumentShell())
            {
                ScModelObj* pModel = GetDocumentShell()->GetModel();
                KitHelper::notifyDocumentSizeChangedAllViews(pModel);
            }

            BroadcastRefError(aCxt.maRefErrors);
            bValid = true;
        }
    }
    return bValid;
}

void ScDocument::UpdateQueryTables(const sc::RefUpdateInsertTabContext& rCxt)
{
    if (maSheetQueryTables.empty())
        return;

    std::map<SCTAB, QueryTableModelVector> aNew;
    for (auto& [nTab, rVec] : maSheetQueryTables)
    {
        if (nTab >= rCxt.mnInsertPos)
            aNew[nTab + rCxt.mnSheets] = std::move(rVec);
        else
            aNew[nTab] = std::move(rVec);
    }
    maSheetQueryTables = std::move(aNew);
}

void ScDocument::UpdateQueryTables(const sc::RefUpdateDeleteTabContext& rCxt)
{
    if (maSheetQueryTables.empty())
        return;

    std::map<SCTAB, QueryTableModelVector> aNew;
    for (auto& [nTab, rVec] : maSheetQueryTables)
    {
        if (nTab >= rCxt.mnDeletePos && nTab < rCxt.mnDeletePos + rCxt.mnSheets)
            continue; // deleted sheet
        if (nTab >= rCxt.mnDeletePos + rCxt.mnSheets)
            aNew[nTab - rCxt.mnSheets] = std::move(rVec);
        else
            aNew[nTab] = std::move(rVec);
    }
    maSheetQueryTables = std::move(aNew);
}

void ScDocument::UpdateQueryTables(const sc::RefUpdateMoveTabContext& rCxt)
{
    if (maSheetQueryTables.empty())
        return;

    SCTAB nLower = std::min(rCxt.mnOldPos, rCxt.mnNewPos);
    SCTAB nUpper = std::max(rCxt.mnOldPos, rCxt.mnNewPos);

    std::map<SCTAB, QueryTableModelVector> aNew;
    for (auto& [nTab, rVec] : maSheetQueryTables)
    {
        if (nTab < nLower || nTab > nUpper)
            aNew[nTab] = std::move(rVec);
        else if (nTab == rCxt.mnOldPos)
            aNew[rCxt.mnNewPos] = std::move(rVec);
        else if (rCxt.mnOldPos < rCxt.mnNewPos)
            aNew[nTab - 1] = std::move(rVec);
        else
            aNew[nTab + 1] = std::move(rVec);
    }
    maSheetQueryTables = std::move(aNew);
}

bool ScDocument::RenameTab( SCTAB nTab, const OUString& rName, bool bExternalDocument )
{
    bool bValid = false;
    SCTAB i;
    if (HasTable(nTab))
    {
        if ( bExternalDocument )
            bValid = true;      // composed name
        else
            bValid = ValidTabName(rName);
        for (i = 0; i < GetTableCount() && bValid; i++)
        {
            if (maTabs[i] && (i != nTab))
            {
                OUString aOldName = maTabs[i]->GetName();
                bValid = !ScGlobal::GetTransliteration().isEqual( rName, aOldName );
            }
        }
        if (bValid)
        {
            // #i75258# update charts before renaming, so they can get their live data objects.
            // Once the charts are live, the sheet can be renamed without problems.
            if ( pChartListenerCollection )
                pChartListenerCollection->UpdateChartsContainingTab( nTab );
            maTabs[nTab]->SetName(rName);

            // If formulas refer to the renamed sheet, the TokenArray remains valid,
            // but the XML stream must be re-generated.
            for (const auto& pTable : maTabs)
            {
                if (pTable)
                {
                    pTable->SetStreamValid( false );
                    // tdf#156815 Reset solver settings so next time they're loaded they come with
                    // the updated sheet name
                    pTable->ResetSolverSettings();
                }
            }

            if (comphelper::COKit::isActive() && GetDrawLayer() && GetDocumentShell())
            {
                ScModelObj* pModel = GetDocumentShell()->GetModel();
                KitHelper::notifyDocumentSizeChangedAllViews(pModel);
            }
        }
    }

    collectUIInformation({{"NewName", rName}}, u"Rename_Sheet"_ustr);

    return bValid;
}

void ScDocument::SetVisible( SCTAB nTab, bool bVisible )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetVisible(bVisible);
}

bool ScDocument::IsVisible( SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->IsVisible();
    return false;
}

bool ScDocument::IsStreamValid( SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->IsStreamValid();
    return false;
}

void ScDocument::SetStreamValid( SCTAB nTab, bool bSet, bool bIgnoreLock )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetStreamValid( bSet, bIgnoreLock );
}

void ScDocument::LockStreamValid( bool bLock )
{
    mbStreamValidLocked = bLock;
}

bool ScDocument::IsPendingRowHeights( SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->IsPendingRowHeights();
    return false;
}

void ScDocument::SetPendingRowHeights( SCTAB nTab, bool bSet )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetPendingRowHeights(bSet);
}

sal_uInt16 ScDocument::GetSheetOptimalMinRowHeight(SCTAB nTab) const
{
    const ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return ScGlobal::nStdRowHeight;

    return pTab->GetOptimalMinRowHeight();
}

void ScDocument::SetLayoutRTL( SCTAB nTab, bool bRTL, ScObjectHandling eObjectHandling)
{
    ScTable* pTable = FetchTable(nTab);
    if (!pTable)
        return;

    if ( bImportingXML )
    {
        // #i57869# only set the LoadingRTL flag, the real setting (including mirroring)
        // is applied in SetImportingXML(false). This is so the shapes can be loaded in
        // normal LTR mode.

        pTable->SetLoadingRTL( bRTL );
        return;
    }

    pTable->SetLayoutRTL( bRTL );     // only sets the flag
    pTable->SetDrawPageSize(true, true, eObjectHandling);

    //  objects are already repositioned via SetDrawPageSize, only writing mode is missing
    if (!mpDrawLayer)
        return;

    SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
    OSL_ENSURE(pPage,"Page ?");
    if (!pPage)
        return;

    SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
    SdrObject* pObject = aIter.Next();
    while (pObject)
    {
        pObject->SetContextWritingMode( bRTL ? WritingMode2::RL_TB : WritingMode2::LR_TB );
        pObject = aIter.Next();
    }
}

bool ScDocument::IsLayoutRTL( SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->IsLayoutRTL();

    return false;
}

bool ScDocument::IsNegativePage( SCTAB nTab ) const
{
    //  Negative page area is always used for RTL layout.
    //  The separate method is used to find all RTL handling of drawing objects.
    return IsLayoutRTL( nTab );
}

/* ----------------------------------------------------------------------------
    used search area:

    GetCellArea  - Only Data
    GetTableArea - Data / Attributes
    GetPrintArea - intended for character objects,
                    sweeps attributes all the way to bottom / right
---------------------------------------------------------------------------- */

bool ScDocument::GetCellArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow ) const
{
    if (HasTable(nTab))
        return maTabs[nTab]->GetCellArea(rEndCol, rEndRow);

    rEndCol = 0;
    rEndRow = 0;
    return false;
}

bool ScDocument::GetTableArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow, bool bCalcHiddens) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetTableArea(rEndCol, rEndRow, bCalcHiddens);

    rEndCol = 0;
    rEndRow = 0;
    return false;
}

bool ScDocument::ShrinkToDataArea(SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow) const
{
    if (!HasTable(nTab))
        return false;

    SCCOL nCol1, nCol2;
    SCROW nRow1, nRow2;
    maTabs[nTab]->GetFirstDataPos(nCol1, nRow1);
    maTabs[nTab]->GetLastDataPos(nCol2, nRow2);

    if (nCol1 > nCol2 || nRow1 > nRow2)
        // invalid range.
        return false;

    // Make sure the area only shrinks, and doesn't grow.
    if (rStartCol < nCol1)
        rStartCol = nCol1;
    if (nCol2 < rEndCol)
        rEndCol = nCol2;
    if (rStartRow < nRow1)
        rStartRow = nRow1;
    if (nRow2 < rEndRow)
        rEndRow = nRow2;

    if (rStartCol > rEndCol || rStartRow > rEndRow)
        // invalid range.
        return false;

    return true;  // success!
}

bool ScDocument::ShrinkToUsedDataArea( bool& o_bShrunk, SCTAB nTab, SCCOL& rStartCol,
        SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly,
        bool bStickyTopRow, bool bStickyLeftCol, ScDataAreaExtras* pDataAreaExtras ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
    {
        return pTable->ShrinkToUsedDataArea(
            o_bShrunk, rStartCol, rStartRow, rEndCol, rEndRow, bColumnsOnly, bStickyTopRow,
            bStickyLeftCol, pDataAreaExtras);
    }
    o_bShrunk = false;
    return false;
}

SCROW ScDocument::GetLastDataRow( SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nLastRow ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetLastDataRow(nCol1, nCol2, nLastRow);
    return -1;
}

// connected area

void ScDocument::GetDataArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow,
                              SCCOL& rEndCol, SCROW& rEndRow, bool bIncludeOld, bool bOnlyDown ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        pTable->GetDataArea( rStartCol, rStartRow, rEndCol, rEndRow, bIncludeOld, bOnlyDown );
}

bool ScDocument::GetDataAreaSubrange(ScRange& rRange) const
{
    SCTAB nTab = rRange.aStart.Tab();
    if (nTab != rRange.aEnd.Tab())
        return true;

    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetDataAreaSubrange(rRange);

    return true;
}

void ScDocument::LimitChartArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow,
                                    SCCOL& rEndCol, SCROW& rEndRow )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->LimitChartArea( rStartCol, rStartRow, rEndCol, rEndRow);
}

void ScDocument::LimitChartIfAll( ScRangeListRef& rRangeList )
{
    ScRangeListRef aNew = new ScRangeList;
    if (rRangeList.is())
    {
        for ( size_t i = 0, nCount = rRangeList->size(); i < nCount; i++ )
        {
            ScRange aRange( (*rRangeList)[i] );
            if ( ( aRange.aStart.Col() == 0 && aRange.aEnd.Col() == MaxCol() ) ||
                 ( aRange.aStart.Row() == 0 && aRange.aEnd.Row() == MaxRow() ) )
            {
                SCCOL nStartCol = aRange.aStart.Col();
                SCROW nStartRow = aRange.aStart.Row();
                SCCOL nEndCol = aRange.aEnd.Col();
                SCROW nEndRow = aRange.aEnd.Row();
                SCTAB nTab = aRange.aStart.Tab();
                if (ScTable* pTable = FetchTable(nTab))
                    pTable->LimitChartArea(nStartCol, nStartRow, nEndCol, nEndRow);
                aRange.aStart.SetCol( nStartCol );
                aRange.aStart.SetRow( nStartRow );
                aRange.aEnd.SetCol( nEndCol );
                aRange.aEnd.SetRow( nEndRow );
            }
            aNew->push_back(aRange);
        }
    }
    else
    {
        OSL_FAIL("LimitChartIfAll: Ref==0");
    }
    rRangeList = std::move(aNew);
}

static void lcl_GetFirstTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab )
{
    // without ScMarkData, leave start/end unchanged
    if ( !pTabMark )
        return;

    for (SCTAB nTab=0; nTab< aMaxTab; ++nTab)
        if (pTabMark->GetTableSelect(nTab))
        {
            // find first range of consecutive selected sheets
            rTabRangeStart = pTabMark->GetFirstSelected();
            while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) )
                ++nTab;
            rTabRangeEnd = nTab;
            return;
        }
}

static bool lcl_GetNextTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab )
{
    if ( pTabMark )
    {
        // find next range of consecutive selected sheets after rTabRangeEnd
        for (SCTAB nTab=rTabRangeEnd+1; nTab< aMaxTab; ++nTab)
            if (pTabMark->GetTableSelect(nTab))
            {
                rTabRangeStart = nTab;
                while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) )
                    ++nTab;
                rTabRangeEnd = nTab;
                return true;
            }
    }
    return false;
}

bool ScDocument::CanInsertRow( const ScRange& rRange ) const
{
    SCCOL nStartCol = rRange.aStart.Col();
    SCROW nStartRow = rRange.aStart.Row();
    SCTAB nStartTab = rRange.aStart.Tab();
    SCCOL nEndCol = rRange.aEnd.Col();
    SCROW nEndRow = rRange.aEnd.Row();
    SCTAB nEndTab = rRange.aEnd.Tab();
    PutInOrder( nStartCol, nEndCol );
    PutInOrder( nStartRow, nEndRow );
    PutInOrder( nStartTab, nEndTab );
    SCSIZE nSize = static_cast<SCSIZE>(nEndRow - nStartRow + 1);

    bool bTest = true;
    for (SCTAB i = nStartTab; i <= nEndTab && bTest && i < GetTableCount(); i++)
        if (maTabs[i])
            bTest &= maTabs[i]->TestInsertRow( nStartCol, nEndCol, nStartRow, nSize );

    return bTest;
}

namespace {

struct SetDirtyIfPostponedHandler
{
    void operator() (const ScTableUniquePtr & p)
    {
        if (p)
            p->SetDirtyIfPostponed();
    }
};

struct BroadcastRecalcOnRefMoveHandler
{
    void operator() (const ScTableUniquePtr & p)
    {
        if (p)
            p->BroadcastRecalcOnRefMove();
    }
};

struct BroadcastRecalcOnRefMoveGuard
{
    explicit BroadcastRecalcOnRefMoveGuard( ScDocument* pDoc ) :
        aSwitch( *pDoc, false),
        aBulk( pDoc->GetBASM(), SfxHintId::ScDataChanged)
    {
    }

private:
    sc::AutoCalcSwitch aSwitch; // first for ctor/dtor order, destroy second
    ScBulkBroadcast aBulk;      // second for ctor/dtor order, destroy first
};

}

bool ScDocument::InsertRow( SCCOL nStartCol, SCTAB nStartTab,
                            SCCOL nEndCol,   SCTAB nEndTab,
                            SCROW nStartRow, SCSIZE nSize, ScDocument* pRefUndoDoc,
                            const ScMarkData* pTabMark )
{
    SCTAB i;

    PutInOrder( nStartCol, nEndCol );
    PutInOrder( nStartTab, nEndTab );
    if ( pTabMark )
    {
        nStartTab = 0;
        nEndTab = GetTableCount() - 1;
    }

    bool bTest = true;
    bool bRet = false;
    bool bOldAutoCalc = GetAutoCalc();
    SetAutoCalc( false );   // avoid multiple calculations
    bool oldDelayedDeleteBroadcasters = IsDelayedDeletingBroadcasters();
    EnableDelayDeletingBroadcasters( true );
    for ( i = nStartTab; i <= nEndTab && bTest && i < GetTableCount(); i++)
        if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
            bTest &= maTabs[i]->TestInsertRow(nStartCol, nEndCol, nStartRow, nSize);
    if (bTest)
    {
        // UpdateBroadcastAreas have to be called before UpdateReference, so that entries
        // aren't shifted that would be rebuild at UpdateReference

        // handle chunks of consecutive selected sheets together
        SCTAB nTabRangeStart = nStartTab;
        SCTAB nTabRangeEnd = nEndTab;
        lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
        ScRange aShiftedRange(nStartCol, nStartRow, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd);
        sc::EndListeningContext aEndListenCxt(*this);

        std::vector<ScAddress> aGroupPos;
        do
        {
            aShiftedRange.aStart.SetTab(nTabRangeStart);
            aShiftedRange.aEnd.SetTab(nTabRangeEnd);

            // Collect all formula groups that will get split by the shifting,
            // and end all their listening.  Record the position of the top
            // cell of the topmost group, and the position of the bottom cell
            // of the bottommost group.
            EndListeningIntersectedGroups(aEndListenCxt, aShiftedRange, &aGroupPos);

            UpdateBroadcastAreas(URM_INSDEL, aShiftedRange, 0, static_cast<SCROW>(nSize), 0);
        }
        while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));

        lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());

        sc::RefUpdateContext aCxt(*this);
        aCxt.meMode = URM_INSDEL;
        aCxt.maRange = aShiftedRange;
        aCxt.mnRowDelta = nSize;
        do
        {
            aCxt.maRange.aStart.SetTab(nTabRangeStart);
            aCxt.maRange.aEnd.SetTab(nTabRangeEnd);
            UpdateReference(aCxt, pRefUndoDoc, false);        // without drawing objects
        }
        while (lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));

        // UpdateReference should have set "needs listening" flags to those
        // whose references have been modified.  We also need to set this flag
        // to those that were in the groups that got split by shifting.
        SetNeedsListeningGroups(aGroupPos);

        for (i=nStartTab; i<=nEndTab && i < GetTableCount(); i++)
        {
            if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
            {
                maTabs[i]->InsertRow( nStartCol, nEndCol, nStartRow, nSize );
                maTabs[i]->CommentNotifyAddressChange(nStartCol, nStartRow, nEndCol, MaxRow());
            }
        }

        //  UpdateRef for drawing layer must be after inserting,
        //  when the new row heights are known.
        for (i=nStartTab; i<=nEndTab && i < GetTableCount(); i++)
            if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
                maTabs[i]->UpdateDrawRef( URM_INSDEL,
                            nStartCol, nStartRow, nStartTab, nEndCol, MaxRow(), nEndTab,
                            0, static_cast<SCROW>(nSize), 0 );

        if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
        {   // A new Listening is needed when references to deleted ranges are restored,
            // previous Listeners were removed in FormulaCell UpdateReference.
            StartAllListeners();
        }
        else
        {   // Listeners have been removed in UpdateReference
            StartNeededListeners();

            // At least all cells using range names pointing relative to the
            // moved range must be recalculated, and all cells marked postponed
            // dirty.
            for (const auto& a : maTabs)
            {
                if (a)
                    a->SetDirtyIfPostponed();
            }

            {
                BroadcastRecalcOnRefMoveGuard g(this);
                std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
            }
        }
        bRet = true;
    }
    EnableDelayDeletingBroadcasters( oldDelayedDeleteBroadcasters );
    SetAutoCalc( bOldAutoCalc );
    if ( bRet && pChartListenerCollection )
        pChartListenerCollection->UpdateDirtyCharts();
    return bRet;
}

bool ScDocument::InsertRow( const ScRange& rRange )
{
    return InsertRow( rRange.aStart.Col(), rRange.aStart.Tab(),
                      rRange.aEnd.Col(),   rRange.aEnd.Tab(),
                      rRange.aStart.Row(), static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1) );
}

void ScDocument::DeleteRow( SCCOL nStartCol, SCTAB nStartTab,
                            SCCOL nEndCol,   SCTAB nEndTab,
                            SCROW nStartRow, SCSIZE nSize,
                            ScDocument* pRefUndoDoc, bool* pUndoOutline,
                            const ScMarkData* pTabMark )
{
    SCTAB i;

    PutInOrder( nStartCol, nEndCol );
    PutInOrder( nStartTab, nEndTab );
    if ( pTabMark )
    {
        nStartTab = 0;
        nEndTab = GetTableCount() - 1;
    }

    sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations

    // handle chunks of consecutive selected sheets together
    SCTAB nTabRangeStart = nStartTab;
    SCTAB nTabRangeEnd = nEndTab;
    lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
    do
    {
        if ( ValidRow(nStartRow+nSize) )
        {
            DelBroadcastAreasInRange( ScRange(
                ScAddress( nStartCol, nStartRow, nTabRangeStart ),
                ScAddress( nEndCol, nStartRow+nSize-1, nTabRangeEnd ) ) );
            UpdateBroadcastAreas( URM_INSDEL, ScRange(
                ScAddress( nStartCol, nStartRow+nSize, nTabRangeStart ),
                ScAddress( nEndCol, MaxRow(), nTabRangeEnd )), 0, -static_cast<SCROW>(nSize), 0 );
        }
        else
            DelBroadcastAreasInRange( ScRange(
                ScAddress( nStartCol, nStartRow, nTabRangeStart ),
                ScAddress( nEndCol, MaxRow(), nTabRangeEnd ) ) );
    }
    while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));

    sc::RefUpdateContext aCxt(*this);
    const bool bLastRowIncluded = (static_cast<SCROW>(nStartRow + nSize) == GetMaxRowCount() && ValidRow(nStartRow));
    if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
    {
        lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
        aCxt.meMode = URM_INSDEL;
        aCxt.mnRowDelta = -static_cast<SCROW>(nSize);
        if (bLastRowIncluded)
        {
            // Last row is included, shift a virtually non-existent row in.
            aCxt.maRange = ScRange( nStartCol, GetMaxRowCount(), nTabRangeStart, nEndCol, GetMaxRowCount(), nTabRangeEnd);
        }
        else
        {
            aCxt.maRange = ScRange( nStartCol, nStartRow+nSize, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd);
        }
        do
        {
            UpdateReference(aCxt, pRefUndoDoc, true, false);
        }
        while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
    }

    if (pUndoOutline)
        *pUndoOutline = false;

    // Keep track of the positions of all formula groups that have been joined
    // during row deletion.
    std::vector<ScAddress> aGroupPos;

    for ( i = nStartTab; i <= nEndTab && i < GetTableCount(); i++)
    {
        if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
        {
            maTabs[i]->DeleteRow(aCxt.maRegroupCols, nStartCol, nEndCol, nStartRow, nSize, pUndoOutline, &aGroupPos);
            maTabs[i]->CommentNotifyAddressChange(nStartCol, nStartRow, nEndCol, MaxRow());
        }
    }

    // Newly joined groups have some of their members still listening.  We
    // need to make sure none of them are listening.
    EndListeningGroups(aGroupPos);

    // Mark all joined groups for group listening.
    SetNeedsListeningGroups(aGroupPos);

    if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
    {
        // Listeners have been removed in UpdateReference
        StartNeededListeners();

        // At least all cells using range names pointing relative to the moved
        // range must be recalculated, and all cells marked postponed dirty.
        for (const auto& a : maTabs)
        {
            if (a)
                a->SetDirtyIfPostponed();
        }

        {
            BroadcastRecalcOnRefMoveGuard g(this);
            std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
        }
    }

    if (pChartListenerCollection)
        pChartListenerCollection->UpdateDirtyCharts();

    BroadcastRefError(aCxt.maRefErrors);
}

void ScDocument::DeleteRow( const ScRange& rRange )
{
    DeleteRow( rRange.aStart.Col(), rRange.aStart.Tab(),
               rRange.aEnd.Col(),   rRange.aEnd.Tab(),
               rRange.aStart.Row(), static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1) );
}

bool ScDocument::CanInsertCol( const ScRange& rRange ) const
{
    SCCOL nStartCol = rRange.aStart.Col();
    SCROW nStartRow = rRange.aStart.Row();
    SCTAB nStartTab = rRange.aStart.Tab();
    SCCOL nEndCol = rRange.aEnd.Col();
    SCROW nEndRow = rRange.aEnd.Row();
    SCTAB nEndTab = rRange.aEnd.Tab();
    PutInOrder( nStartCol, nEndCol );
    PutInOrder( nStartRow, nEndRow );
    PutInOrder( nStartTab, nEndTab );
    SCSIZE nSize = static_cast<SCSIZE>(nEndCol - nStartCol + 1);

    bool bTest = true;
    for (SCTAB i = nStartTab; i <= nEndTab && bTest && i < GetTableCount(); i++)
        if (maTabs[i])
            bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize );

    return bTest;
}

bool ScDocument::InsertCol( SCROW nStartRow, SCTAB nStartTab,
                            SCROW nEndRow,   SCTAB nEndTab,
                            SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc,
                            const ScMarkData* pTabMark )
{
    SCTAB i;

    PutInOrder( nStartRow, nEndRow );
    PutInOrder( nStartTab, nEndTab );
    if ( pTabMark )
    {
        nStartTab = 0;
        nEndTab = GetTableCount() - 1;
    }

    bool bTest = true;
    bool bRet = false;
    bool bOldAutoCalc = GetAutoCalc();
    SetAutoCalc( false );   // avoid multiple calculations
    bool oldDelayedDeleteBroadcasters = IsDelayedDeletingBroadcasters();
    EnableDelayDeletingBroadcasters( true );
    for ( i = nStartTab; i <= nEndTab && bTest && i < GetTableCount(); i++)
        if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
            bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize );
    if (bTest)
    {
        // handle chunks of consecutive selected sheets together
        SCTAB nTabRangeStart = nStartTab;
        SCTAB nTabRangeEnd = nEndTab;
        lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
        do
        {
            UpdateBroadcastAreas( URM_INSDEL, ScRange(
                ScAddress( nStartCol, nStartRow, nTabRangeStart ),
                ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), static_cast<SCCOL>(nSize), 0, 0 );
        }
        while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));

        lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());

        sc::RefUpdateContext aCxt(*this);
        aCxt.meMode = URM_INSDEL;
        aCxt.maRange = ScRange(nStartCol, nStartRow, nTabRangeStart, MaxCol(), nEndRow, nTabRangeEnd);
        aCxt.mnColDelta = nSize;
        do
        {
            UpdateReference(aCxt, pRefUndoDoc, true, false);
        }
        while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));

        for (i = nStartTab; i <= nEndTab && i < GetTableCount(); i++)
            if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
                maTabs[i]->InsertCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize);

        if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
        {   // A new Listening is needed when references to deleted ranges are restored,
            // previous Listeners were removed in FormulaCell UpdateReference.
            StartAllListeners();
        }
        else
        {
            // Listeners have been removed in UpdateReference
            StartNeededListeners();
            // At least all cells using range names pointing relative to the
            // moved range must be recalculated, and all cells marked postponed
            // dirty.
            {
                ScBulkBroadcast aBulkBroadcast(GetBASM(), SfxHintId::ScDataChanged);
                std::for_each(maTabs.begin(), maTabs.end(), SetDirtyIfPostponedHandler());
            }
            // Cells containing functions such as CELL, COLUMN or ROW may have
            // changed their values on relocation. Broadcast them.
            {
                BroadcastRecalcOnRefMoveGuard g(this);
                std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
            }
        }
        bRet = true;
    }
    EnableDelayDeletingBroadcasters( oldDelayedDeleteBroadcasters );
    SetAutoCalc( bOldAutoCalc );
    if ( bRet && pChartListenerCollection )
        pChartListenerCollection->UpdateDirtyCharts();
    return bRet;
}

bool ScDocument::InsertCol( const ScRange& rRange )
{
    return InsertCol( rRange.aStart.Row(), rRange.aStart.Tab(),
                      rRange.aEnd.Row(),   rRange.aEnd.Tab(),
                      rRange.aStart.Col(), static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1) );
}

void ScDocument::DeleteCol(SCROW nStartRow, SCTAB nStartTab, SCROW nEndRow, SCTAB nEndTab,
                                SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc,
                                bool* pUndoOutline, const ScMarkData* pTabMark )
{
    SCTAB i;

    PutInOrder( nStartRow, nEndRow );
    PutInOrder( nStartTab, nEndTab );
    if ( pTabMark )
    {
        nStartTab = 0;
        nEndTab = GetTableCount() - 1;
    }

    sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations
    ScBulkBroadcast aBulkBroadcast(GetBASM(), SfxHintId::ScDataChanged);

    // handle chunks of consecutive selected sheets together
    SCTAB nTabRangeStart = nStartTab;
    SCTAB nTabRangeEnd = nEndTab;
    lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
    do
    {
        if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) )
        {
            DelBroadcastAreasInRange( ScRange(
                ScAddress( nStartCol, nStartRow, nTabRangeStart ),
                ScAddress( sal::static_int_cast<SCCOL>(nStartCol+nSize-1), nEndRow, nTabRangeEnd ) ) );
            UpdateBroadcastAreas( URM_INSDEL, ScRange(
                ScAddress( sal::static_int_cast<SCCOL>(nStartCol+nSize), nStartRow, nTabRangeStart ),
                ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), -static_cast<SCCOL>(nSize), 0, 0 );
        }
        else
            DelBroadcastAreasInRange( ScRange(
                ScAddress( nStartCol, nStartRow, nTabRangeStart ),
                ScAddress( MaxCol(), nEndRow, nTabRangeEnd ) ) );
    }
    while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));

    sc::RefUpdateContext aCxt(*this);
    const bool bLastColIncluded = (static_cast<SCCOL>(nStartCol + nSize) == GetMaxColCount() && ValidCol(nStartCol));
    if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
    {
        lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
        aCxt.meMode = URM_INSDEL;
        aCxt.mnColDelta = -static_cast<SCCOL>(nSize);
        if (bLastColIncluded)
        {
            // Last column is included, shift a virtually non-existent column in.
            aCxt.maRange = ScRange( GetMaxColCount(), nStartRow, nTabRangeStart, GetMaxColCount(), nEndRow, nTabRangeEnd);
        }
        else
        {
            aCxt.maRange = ScRange( sal::static_int_cast<SCCOL>(nStartCol+nSize), nStartRow, nTabRangeStart,
                    MaxCol(), nEndRow, nTabRangeEnd);
        }
        do
        {
            UpdateReference(aCxt, pRefUndoDoc, true, false);
        }
        while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
    }

    if (pUndoOutline)
        *pUndoOutline = false;

    for (i = nStartTab; i <= nEndTab && i < GetTableCount(); ++i)
    {
        if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
            maTabs[i]->DeleteCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize, pUndoOutline);
    }

    if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
    {
        // Listeners have been removed in UpdateReference
        StartNeededListeners();

        // At least all cells using range names pointing relative to the moved
        // range must be recalculated, and all cells marked postponed dirty.
        for (const auto& a : maTabs)
        {
            if (a)
                a->SetDirtyIfPostponed();
        }

        {
            BroadcastRecalcOnRefMoveGuard g(this);
            std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
        }
    }

    if (pChartListenerCollection)
        pChartListenerCollection->UpdateDirtyCharts();

    BroadcastRefError(aCxt.maRefErrors);
}

void ScDocument::DeleteCol( const ScRange& rRange )
{
    DeleteCol( rRange.aStart.Row(), rRange.aStart.Tab(),
               rRange.aEnd.Row(),   rRange.aEnd.Tab(),
               rRange.aStart.Col(), static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1) );
}

//  for Area-Links: Insert/delete cells, when the range is changed.
//  (without Paint)

static void lcl_GetInsDelRanges( const ScRange& rOld, const ScRange& rNew,
                            ScRange& rColRange, bool& rInsCol, bool& rDelCol,
                            ScRange& rRowRange, bool& rInsRow, bool& rDelRow )
{
    OSL_ENSURE( rOld.aStart == rNew.aStart, "FitBlock: Beginning is different" );

    rInsCol = rDelCol = rInsRow = rDelRow = false;

    SCCOL nStartX = rOld.aStart.Col();
    SCROW nStartY = rOld.aStart.Row();
    SCCOL nOldEndX = rOld.aEnd.Col();
    SCROW nOldEndY = rOld.aEnd.Row();
    SCCOL nNewEndX = rNew.aEnd.Col();
    SCROW nNewEndY = rNew.aEnd.Row();
    SCTAB nTab = rOld.aStart.Tab();

    // if more rows, columns are inserted/deleted at the old height.
    bool bGrowY = ( nNewEndY > nOldEndY );
    SCROW nColEndY = bGrowY ? nOldEndY : nNewEndY;
    SCCOL nRowEndX = bGrowY ? nNewEndX : nOldEndX;

    // Columns

    if ( nNewEndX > nOldEndX )          // Insert columns
    {
        rColRange = ScRange( nOldEndX+1, nStartY, nTab, nNewEndX, nColEndY, nTab );
        rInsCol = true;
    }
    else if ( nNewEndX < nOldEndX )     // Delete columns
    {
        rColRange = ScRange( nNewEndX+1, nStartY, nTab, nOldEndX, nColEndY, nTab );
        rDelCol = true;
    }

    // Rows

    if ( nNewEndY > nOldEndY )          // Insert rows
    {
        rRowRange = ScRange( nStartX, nOldEndY+1, nTab, nRowEndX, nNewEndY, nTab );
        rInsRow = true;
    }
    else if ( nNewEndY < nOldEndY )     // Delete rows
    {
        rRowRange = ScRange( nStartX, nNewEndY+1, nTab, nRowEndX, nOldEndY, nTab );
        rDelRow = true;
    }
}

bool ScDocument::HasPartOfMerged( const ScRange& rRange )
{
    bool bPart = false;
    SCTAB nTab = rRange.aStart.Tab();

    SCCOL nStartX = rRange.aStart.Col();
    SCROW nStartY = rRange.aStart.Row();
    SCCOL nEndX = rRange.aEnd.Col();
    SCROW nEndY = rRange.aEnd.Row();

    if (HasAttrib( nStartX, nStartY, nTab, nEndX, nEndY, nTab,
                        HasAttrFlags::Merged | HasAttrFlags::Overlapped ))
    {
        ExtendMerge( nStartX, nStartY, nEndX, nEndY, nTab );
        ExtendOverlapped( nStartX, nStartY, nEndX, nEndY, nTab );

        bPart = ( nStartX != rRange.aStart.Col() || nEndX != rRange.aEnd.Col() ||
                  nStartY != rRange.aStart.Row() || nEndY != rRange.aEnd.Row() );
    }
    return bPart;
}

formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScAddress& rPos )
{
    SCTAB nTab = rPos.Tab();
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->ResolveStaticReference(rPos.Col(), rPos.Row());
    return formula::FormulaTokenRef();
}

formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScRange& rRange )
{
    SCTAB nTab = rRange.aStart.Tab();
    if (nTab != rRange.aEnd.Tab() || !HasTable(nTab))
        return formula::FormulaTokenRef();

    return maTabs[nTab]->ResolveStaticReference(
        rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
}

formula::VectorRefArray ScDocument::FetchVectorRefArray( const ScAddress& rPos, SCROW nLength )
{
    SCTAB nTab = rPos.Tab();
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->FetchVectorRefArray(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1);
    return formula::VectorRefArray();
}

#ifdef DBG_UTIL
void ScDocument::AssertNoInterpretNeeded( const ScAddress& rPos, SCROW nLength )
{
    SCTAB nTab = rPos.Tab();
    assert(HasTable(nTab));
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->AssertNoInterpretNeeded(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1);
}
#endif

void ScDocument::UnlockAdjustHeight()
{
    assert(nAdjustHeightLock > 0);
    if(nAdjustHeightLock > 0)
        --nAdjustHeightLock;
}

bool ScDocument::HandleRefArrayForParallelism( const ScAddress& rPos, SCROW nLength, const ScFormulaCellGroupRef& mxGroup, ScAddress* pDirtiedAddress)
{
    SCTAB nTab = rPos.Tab();
    if (ScTable* pTable = FetchTable(nTab))
    {
        bool bRet = pTable->HandleRefArrayForParallelism(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1, mxGroup, pDirtiedAddress);
        if (!bRet && pDirtiedAddress && pDirtiedAddress->Row() != -1)
        {
            pDirtiedAddress->SetCol(rPos.Col());
            pDirtiedAddress->SetTab(nTab);
        }
        return bRet;
    }
    return false;
}

bool ScDocument::CanFitBlock( const ScRange& rOld, const ScRange& rNew )
{
    if ( rOld == rNew )
        return true;

    bool bOk = true;
    bool bInsCol,bDelCol,bInsRow,bDelRow;
    ScRange aColRange,aRowRange;
    lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow );

    if ( bInsCol && !CanInsertCol( aColRange ) )            // Cells at the edge ?
        bOk = false;
    if ( bInsRow && !CanInsertRow( aRowRange ) )            // Cells at the edge ?
        bOk = false;

    if ( bInsCol || bDelCol )
    {
        aColRange.aEnd.SetCol(MaxCol());
        if ( HasPartOfMerged(aColRange) )
            bOk = false;
    }
    if ( bInsRow || bDelRow )
    {
        aRowRange.aEnd.SetRow(MaxRow());
        if ( HasPartOfMerged(aRowRange) )
            bOk = false;
    }

    return bOk;
}

void ScDocument::FitBlock( const ScRange& rOld, const ScRange& rNew, bool bClear )
{
    if (bClear)
        DeleteAreaTab( rOld, InsertDeleteFlags::ALL );

    bool bInsCol,bDelCol,bInsRow,bDelRow;
    ScRange aColRange,aRowRange;
    lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow );

    if ( bInsCol )
        InsertCol( aColRange );         // First insert columns
    if ( bInsRow )
        InsertRow( aRowRange );

    if ( bDelRow )
        DeleteRow( aRowRange );         // First delete rows
    if ( bDelCol )
        DeleteCol( aColRange );

    // Expand references to inserted rows

    if ( bInsCol || bInsRow )
    {
        ScRange aGrowSource = rOld;
        aGrowSource.aEnd.SetCol(std::min( rOld.aEnd.Col(), rNew.aEnd.Col() ));
        aGrowSource.aEnd.SetRow(std::min( rOld.aEnd.Row(), rNew.aEnd.Row() ));
        SCCOL nGrowX = bInsCol ? ( rNew.aEnd.Col() - rOld.aEnd.Col() ) : 0;
        SCROW nGrowY = bInsRow ? ( rNew.aEnd.Row() - rOld.aEnd.Row() ) : 0;
        UpdateGrow( aGrowSource, nGrowX, nGrowY );
    }
}

void ScDocument::DeleteArea(
    SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
    InsertDeleteFlags nDelFlag, bool bBroadcast, sc::ColumnSpanSet* pBroadcastSpans )
{
    sc::AutoCalcSwitch aACSwitch(*this, false);

    PutInOrder( nCol1, nCol2 );
    PutInOrder( nRow1, nRow2 );

    std::vector<ScAddress> aGroupPos;
    // Destroy and reconstruct listeners only if content is affected.
    bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
    if (bDelContent)
    {
        // Record the positions of top and/or bottom formula groups that intersect
        // the area borders.
        sc::EndListeningContext aCxt(*this);
        ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0);
        for (SCTAB i = 0; i < GetTableCount(); i++)
        {
            if (rMark.GetTableSelect(i))
            {
                aRange.aStart.SetTab(i);
                aRange.aEnd.SetTab(i);

                EndListeningIntersectedGroups(aCxt, aRange, &aGroupPos);
            }
        }
        aCxt.purgeEmptyBroadcasters();
    }

    for (SCTAB i = 0; i < GetTableCount(); i++)
        if (maTabs[i])
            if ( rMark.GetTableSelect(i) || bIsUndo )
                maTabs[i]->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag, bBroadcast, pBroadcastSpans);

    if (!bDelContent)
        return;

    // Re-start listeners on those top bottom groups that have been split.
    SetNeedsListeningGroups(aGroupPos);
    StartNeededListeners();

    // If formula groups were split their listeners were destroyed and may
    // need to be notified now that they're restored, ScTable::DeleteArea()
    // couldn't do that.
    if (aGroupPos.empty())
        return;

    ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0);
    for (SCTAB i = 0; i < GetTableCount(); i++)
    {
        if (rMark.GetTableSelect(i))
        {
            aRange.aStart.SetTab(i);
            aRange.aEnd.SetTab(i);
            SetDirty( aRange, true);
        }
    }
}

void ScDocument::DeleteAreaTab(SCCOL nCol1, SCROW nRow1,
                                SCCOL nCol2, SCROW nRow2,
                                SCTAB nTab, InsertDeleteFlags nDelFlag)
{
    PutInOrder( nCol1, nCol2 );
    PutInOrder( nRow1, nRow2 );
    if (ScTable* pTable = FetchTable(nTab))
    {
        bool bOldAutoCalc = GetAutoCalc();
        SetAutoCalc( false );   // avoid multiple calculations
        pTable->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag);
        SetAutoCalc( bOldAutoCalc );
    }
}

void ScDocument::DeleteAreaTab( const ScRange& rRange, InsertDeleteFlags nDelFlag )
{
    for ( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); nTab++ )
        DeleteAreaTab( rRange.aStart.Col(), rRange.aStart.Row(),
                       rRange.aEnd.Col(),   rRange.aEnd.Row(),
                       nTab, nDelFlag );
}

void ScDocument::InitUndoSelected(const ScDocument& rSrcDoc, const ScMarkData& rTabSelection,
                                  bool bColInfo, bool bRowInfo )
{
    if (bIsUndo)
    {
        Clear();

        SharePooledResources(&rSrcDoc);

        for (SCTAB nTab = 0; nTab <= rTabSelection.GetLastSelected(); nTab++)
            if ( rTabSelection.GetTableSelect( nTab ) )
            {
                ScTableUniquePtr pTable(new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo));
                if (nTab < GetTableCount())
                    maTabs[nTab] = std::move(pTable);
                else
                    maTabs.push_back(std::move(pTable));
            }
            else
            {
                if (nTab < GetTableCount())
                    maTabs[nTab]=nullptr;
                else
                    maTabs.push_back(nullptr);
            }
    }
    else
    {
        OSL_FAIL("InitUndo");
    }
}

void ScDocument::InitUndo( const ScDocument& rSrcDoc, SCTAB nTab1, SCTAB nTab2,
                                bool bColInfo, bool bRowInfo )
{
    if (!bIsUndo)
    {
        OSL_FAIL("InitUndo");
        return;
    }

    Clear();

    // Undo document shares its pooled resources with the source document.
    SharePooledResources(&rSrcDoc);

    if (rSrcDoc.mpShell->GetMedium())
        maFileURL = rSrcDoc.mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);

    if (nTab2 >= GetTableCount())
        maTabs.resize(nTab2 + 1);
    for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++)
    {
        maTabs[nTab].reset(new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo));
    }
}

void ScDocument::AddUndoTab( SCTAB nTab1, SCTAB nTab2, bool bColInfo, bool bRowInfo )
{
    if (!bIsUndo)
    {
        OSL_FAIL("AddUndoTab");
        return;
    }

    if (nTab2 >= GetTableCount())
    {
        maTabs.resize(nTab2+1);
    }

    for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++)
        if (!maTabs[nTab])
        {
            maTabs[nTab].reset( new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo) );
        }
}

void ScDocument::SetCutMode( bool bVal )
{
    if (bIsClip)
        GetClipParam().mbCutMode = bVal;
    else
    {
        OSL_FAIL("SetCutMode without bIsClip");
    }
}

bool ScDocument::IsCutMode()
{
    if (bIsClip)
        return GetClipParam().mbCutMode;
    else
    {
        OSL_FAIL("IsCutMode without bIsClip");
        return false;
    }
}

void ScDocument::CopyToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
                            SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
                            InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc,
                            const ScMarkData* pMarks, bool bColRowFlags )
{
    if (ValidTab(nTab1) && ValidTab(nTab2))
    {
        ScRange aThisRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
        CopyToDocument(aThisRange, nFlags, bOnlyMarked, rDestDoc, pMarks, bColRowFlags);
    }
}

void ScDocument::UndoToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
                            SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
                            InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc)
{
    PutInOrder( nCol1, nCol2 );
    PutInOrder( nRow1, nRow2 );
    PutInOrder( nTab1, nTab2 );
    if (!(ValidTab(nTab1) && ValidTab(nTab2)))
        return;

    sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations

    if (nTab1 > 0)
        CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc);

    sc::CopyToDocContext aCxt(rDestDoc);
    assert(nTab2 < GetTableCount() && nTab2 < rDestDoc.GetTableCount());
    for (SCTAB i = nTab1; i <= nTab2; i++)
    {
        if (maTabs[i] && rDestDoc.maTabs[i])
            maTabs[i]->UndoToTable(aCxt, nCol1, nRow1, nCol2, nRow2, nFlags,
                                bOnlyMarked, rDestDoc.maTabs[i].get());
    }

    if (nTab2 < MAXTAB)
        CopyToDocument(0, 0, nTab2+1, MaxCol(), MaxRow(), MAXTAB, InsertDeleteFlags::FORMULA, false, rDestDoc);
}

void ScDocument::CopyToDocument(const ScRange& rRange,
                            InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc,
                            const ScMarkData* pMarks, bool bColRowFlags)
{
    ScRange aNewRange = rRange;
    aNewRange.PutInOrder();

    if (rDestDoc.aDocName.isEmpty())
        rDestDoc.aDocName = aDocName;

    sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations
    ScBulkBroadcast aBulkBroadcast(rDestDoc.GetBASM(), SfxHintId::ScDataChanged);
    sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);

    sc::CopyToDocContext aCxt(rDestDoc);
    aCxt.setStartListening(false);

    SCTAB nMinSizeBothTabs = std::min(GetTableCount(), rDestDoc.GetTableCount());
    for (SCTAB i = aNewRange.aStart.Tab(); i <= aNewRange.aEnd.Tab() && i < nMinSizeBothTabs; i++)
    {
        ScTable* pTab = FetchTable(i);
        ScTable* pDestTab = rDestDoc.FetchTable(i);
        if (!pTab || !pDestTab)
            continue;

        pTab->CopyToTable(
            aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(), aNewRange.aEnd.Col(), aNewRange.aEnd.Row(),
            nFlags, bOnlyMarked, pDestTab, pMarks, false, bColRowFlags,
            ScCloneFlags::Default, /*bCopyCaptions*/true);
    }

    rDestDoc.StartAllListeners(aNewRange);
}

void ScDocument::UndoToDocument(const ScRange& rRange,
                            InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc)
{
    sc::AutoCalcSwitch aAutoCalcSwitch(*this, false);

    ScRange aNewRange = rRange;
    aNewRange.PutInOrder();
    SCTAB nTab1 = aNewRange.aStart.Tab();
    SCTAB nTab2 = aNewRange.aEnd.Tab();

    sc::CopyToDocContext aCxt(rDestDoc);
    if (nTab1 > 0)
        CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc);

    SCTAB nMinSizeBothTabs = std::min(GetTableCount(), rDestDoc.GetTableCount());
    for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++)
    {
        if (maTabs[i] && rDestDoc.maTabs[i])
            maTabs[i]->UndoToTable(aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(),
                                    aNewRange.aEnd.Col(), aNewRange.aEnd.Row(),
                                    nFlags, bOnlyMarked, rDestDoc.maTabs[i].get());
    }

    if (nTab2 < GetTableCount())
        CopyToDocument(0, 0 , nTab2+1, MaxCol(), MaxRow(), GetTableCount(), InsertDeleteFlags::FORMULA, false, rDestDoc);
}

void ScDocument::CopyToClip(const ScClipParam& rClipParam,
                            ScDocument* pClipDoc, const ScMarkData* pMarks,
                            bool bKeepScenarioFlags, bool bIncludeObjects )
{
    OSL_ENSURE( pMarks, "CopyToClip: ScMarkData fails" );

    if (bIsClip)
        return;

    if (!pClipDoc)
    {
        SAL_WARN("sc", "CopyToClip: no ClipDoc");
        pClipDoc = ScModule::GetClipDoc();
    }

    if (mpShell->GetMedium())
    {
        pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
        // for unsaved files use the title name and adjust during save of file
        if (pClipDoc->maFileURL.isEmpty())
            pClipDoc->maFileURL = mpShell->GetName();
    }
    else
    {
        pClipDoc->maFileURL = mpShell->GetName();
    }

    //init maTabNames
    for (const auto& rxTab : maTabs)
    {
        if( rxTab )
        {
            OUString aTabName = rxTab->GetName();
            pClipDoc->maTabNames.push_back(aTabName);
        }
        else
            pClipDoc->maTabNames.emplace_back();
    }

    pClipDoc->aDocName = aDocName;
    pClipDoc->SetClipParam(rClipParam);
    ScRange aClipRange = rClipParam.getWholeRange();
    SCTAB nEndTab = GetTableCount();

    pClipDoc->ResetClip(this, pMarks);

    sc::CopyToClipContext aCxt(*pClipDoc, bKeepScenarioFlags);
    CopyRangeNamesToClip(pClipDoc, aClipRange, pMarks);
    CopyDBsToClip(pClipDoc, rClipParam.maRanges, pMarks);

    // 1. Copy selected cells
    for (SCTAB i = 0; i < nEndTab; ++i)
    {
        if (!maTabs[i] || i >= pClipDoc->GetTableCount() || !pClipDoc->maTabs[i])
            continue;

        if ( pMarks && !pMarks->GetTableSelect(i) )
            continue;

        maTabs[i]->CopyToClip(aCxt, rClipParam.maRanges, pClipDoc->maTabs[i].get());
    }

    // 2. Copy drawing objects in the selection. Do in after the first "copy cells" pass, because
    // the embedded objects (charts) could reference cells from tabs not (yet) copied; doing it now
    // allows to know what is already copied, to not overwrite attributes of already copied data.
    if (mpDrawLayer && bIncludeObjects)
    {
        for (SCTAB i = 0; i < nEndTab; ++i)
        {
            tools::Rectangle aObjRect = GetMMRect(aClipRange.aStart.Col(), aClipRange.aStart.Row(),
                                                  aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i);
            mpDrawLayer->CopyToClip(pClipDoc, i, aObjRect);
        }
    }

    // Make sure to mark overlapped cells.
    pClipDoc->ExtendMerge(aClipRange, true);
}

void ScDocument::CopyStaticToDocument(const ScRange& rSrcRange, SCTAB nDestTab, ScDocument& rDestDoc)
{
    ScTable* pSrcTab = rSrcRange.aStart.Tab() < GetTableCount() ? maTabs[rSrcRange.aStart.Tab()].get() : nullptr;
    ScTable* pDestTab = nDestTab < rDestDoc.GetTableCount() ? rDestDoc.maTabs[nDestTab].get() : nullptr;

    if (!pSrcTab || !pDestTab)
        return;

    rDestDoc.GetFormatTable()->MergeFormatter(*GetFormatTable());
    SvNumberFormatterMergeMap aMap = rDestDoc.GetFormatTable()->ConvertMergeTableToMap();

    pSrcTab->CopyStaticToDocument(
        rSrcRange.aStart.Col(), rSrcRange.aStart.Row(), rSrcRange.aEnd.Col(), rSrcRange.aEnd.Row(),
        aMap, pDestTab);
}

void ScDocument::CopyCellToDocument( const ScAddress& rSrcPos, const ScAddress& rDestPos, ScDocument& rDestDoc )
{
    if (!HasTable(rSrcPos.Tab()) || !rDestDoc.HasTable(rDestPos.Tab()))
        return;

    ScTable& rSrcTab = *maTabs[rSrcPos.Tab()];
    ScTable& rDestTab = *rDestDoc.maTabs[rDestPos.Tab()];

    rSrcTab.CopyCellToDocument(rSrcPos.Col(), rSrcPos.Row(), rDestPos.Col(), rDestPos.Row(), rDestTab);
}

void ScDocument::CopyTabToClip(SCCOL nCol1, SCROW nRow1,
                                SCCOL nCol2, SCROW nRow2,
                                SCTAB nTab, ScDocument* pClipDoc)
{
    if (bIsClip)
        return;

    if (!pClipDoc)
    {
        SAL_WARN("sc", "CopyTabToClip: no ClipDoc");
        pClipDoc = ScModule::GetClipDoc();
    }

    if (mpShell->GetMedium())
    {
        pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
        // for unsaved files use the title name and adjust during save of file
        if (pClipDoc->maFileURL.isEmpty())
            pClipDoc->maFileURL = mpShell->GetName();
    }
    else
    {
        pClipDoc->maFileURL = mpShell->GetName();
    }

    //init maTabNames
    for (const auto& rxTab : maTabs)
    {
        if( rxTab )
        {
            OUString aTabName = rxTab->GetName();
            pClipDoc->maTabNames.push_back(aTabName);
        }
        else
            pClipDoc->maTabNames.emplace_back();
    }

    PutInOrder( nCol1, nCol2 );
    PutInOrder( nRow1, nRow2 );

    ScClipParam& rClipParam = pClipDoc->GetClipParam();
    pClipDoc->aDocName = aDocName;
    rClipParam.maRanges.RemoveAll();
    rClipParam.maRanges.push_back(ScRange(nCol1, nRow1, 0, nCol2, nRow2, 0));
    pClipDoc->ResetClip( this, nTab );

    sc::CopyToClipContext aCxt(*pClipDoc, false);
    if (nTab < GetTableCount() && nTab < pClipDoc->GetTableCount())
        if (maTabs[nTab] && pClipDoc->maTabs[nTab])
            maTabs[nTab]->CopyToClip(aCxt, nCol1, nRow1, nCol2, nRow2, pClipDoc->maTabs[nTab].get());

    pClipDoc->GetClipParam().mbCutMode = false;
}

void ScDocument::TransposeClip(ScDocument* pTransClip, InsertDeleteFlags nFlags, bool bAsLink,
                               bool bIncludeFiltered)
{
    OSL_ENSURE( bIsClip && pTransClip && pTransClip->bIsClip,
                    "TransposeClip with wrong Document" );

    // initialize
    // -> pTransClip has to be deleted before the original document!

    pTransClip->ResetClip(this, nullptr);     // all

    // Take over range

    if (pRangeName)
    {
        pTransClip->GetRangeName().clear();
        for (const auto& rEntry : *pRangeName)
        {
            sal_uInt16 nIndex = rEntry.second->GetIndex();
            ScRangeData* pData = new ScRangeData(*rEntry.second);
            if (pTransClip->pRangeName->insert(pData))
                pData->SetIndex(nIndex);
        }
    }

    ScRange aCombinedClipRange = GetClipParam().getWholeRange();

    if (!ValidRow(aCombinedClipRange.aEnd.Row() - aCombinedClipRange.aStart.Row()))
    {
        SAL_WARN("sc", "TransposeClip: Too big");
        return;
    }

    // Transpose of filtered multi range row selection is a special case since filtering
    // and selection are in the same dimension (i.e. row).
    // The filtered row status and the selection ranges are not available at the same time,
    // handle this case specially, do not use GetClipParam().getWholeRange(),
    // instead loop through the ranges, calculate the row offset and handle filtered rows and
    // create in ScClipParam::transpose() a unified range.
    const bool bIsMultiRangeRowFilteredTranspose
        = !bIncludeFiltered && GetClipParam().isMultiRange()
          && HasFilteredRows(aCombinedClipRange.aStart.Row(), aCombinedClipRange.aEnd.Row(),
                             aCombinedClipRange.aStart.Tab())
          && GetClipParam().meDirection == ScClipParam::Row;

    ScRangeList aClipRanges;
    if (bIsMultiRangeRowFilteredTranspose)
        aClipRanges = GetClipParam().maRanges;
    else
        aClipRanges = ScRangeList(aCombinedClipRange);

    // The data
    ScRange aClipRange;
    SCROW nRowCount = 0; // next consecutive row
    for (size_t j = 0, n = aClipRanges.size(); j < n; ++j)
    {
        aClipRange = aClipRanges[j];

        SCROW nRowOffset = 0;
        if (bIsMultiRangeRowFilteredTranspose)
        {
            // adjust for the rows that are filtered
            nRowOffset = nRowCount;

            // calculate filtered rows of current clip range
            SCROW nRowCountNonFiltered = CountNonFilteredRows(
                aClipRange.aStart.Row(), aClipRange.aEnd.Row(), aClipRange.aStart.Tab());
            assert(!bIncludeFiltered && "bIsMultiRangeRowFilteredTranspose can only be true if bIncludeFiltered is false");
            nRowCount += nRowCountNonFiltered; // for next iteration
        }

        for (SCTAB i = 0; i < GetTableCount(); i++)
        {
            if (maTabs[i])
            {
                OSL_ENSURE(pTransClip->maTabs[i], "TransposeClip: Table not there");
                maTabs[i]->TransposeClip(
                    aClipRange.aStart.Col(), aClipRange.aStart.Row(), aClipRange.aEnd.Col(),
                    aClipRange.aEnd.Row(), aCombinedClipRange.aStart.Row(), nRowOffset,
                    pTransClip->maTabs[i].get(), nFlags, bAsLink, bIncludeFiltered);

                if ( mpDrawLayer && ( nFlags & InsertDeleteFlags::OBJECTS ) )
                {
                    //  Drawing objects are copied to the new area without transposing.
                    //  CopyFromClip is used to adjust the objects to the transposed block's
                    //  cell range area.
                    //  (mpDrawLayer in the original clipboard document is set only if there
                    //  are drawing objects to copy)

                    // ToDo: Loop over blocks of non-filtered rows in case of filtered rows exist.
                    pTransClip->InitDrawLayer();
                    ScAddress aTransposedEnd(
                        static_cast<SCCOL>(aClipRange.aEnd.Row() - aClipRange.aStart.Row() + aClipRange.aStart.Col()),
                        static_cast<SCROW>(aClipRange.aEnd.Col() - aClipRange.aStart.Col() + aClipRange.aStart.Row()), i);
                    ScRange aDestRange(aClipRange.aStart, aTransposedEnd);
                    ScAddress aDestStart = aClipRange.aStart;
                    pTransClip->mpDrawLayer->CopyFromClip(mpDrawLayer.get(), i, aClipRange, aDestStart, aDestRange, true);
                }
            }
        }
    }

    pTransClip->SetClipParam(GetClipParam());
    pTransClip->GetClipParam().transpose(*this, bIncludeFiltered,
                                         bIsMultiRangeRowFilteredTranspose);

    // This happens only when inserting...

    GetClipParam().mbCutMode = false;
}

namespace {

void copyUsedNamesToClip(ScRangeName& rClipRangeName, ScRangeName* pRangeName,
        const sc::UpdatedRangeNames::NameIndicesType& rUsedNames)
{
    rClipRangeName.clear();
    for (const auto& rEntry : *pRangeName)        //TODO: also DB and Pivot regions!!!
    {
        sal_uInt16 nIndex = rEntry.second->GetIndex();
        bool bInUse = (rUsedNames.count(nIndex) > 0);
        if (!bInUse)
            continue;

        ScRangeData* pData = new ScRangeData(*rEntry.second);
        if (rClipRangeName.insert(pData))
            pData->SetIndex(nIndex);
    }
}

}

void ScDocument::CopyRangeNamesToClip(ScDocument* pClipDoc, const ScRange& rClipRange, const ScMarkData* pMarks)
{
    if (!pRangeName || pRangeName->empty())
        return;

    sc::UpdatedRangeNames aUsedNames;        // indexes of named ranges that are used in the copied cells
    SCTAB nMinSizeBothTabs = std::min(GetTableCount(), pClipDoc->GetTableCount());
    for (SCTAB i = 0; i < nMinSizeBothTabs; ++i)
        if (maTabs[i] && pClipDoc->maTabs[i])
            if ( !pMarks || pMarks->GetTableSelect(i) )
                maTabs[i]->FindRangeNamesInUse(
                    rClipRange.aStart.Col(), rClipRange.aStart.Row(),
                    rClipRange.aEnd.Col(), rClipRange.aEnd.Row(), aUsedNames);

    /* TODO: handle also sheet-local names */
    sc::UpdatedRangeNames::NameIndicesType aUsedGlobalNames( aUsedNames.getUpdatedNames(-1));
    copyUsedNamesToClip(pClipDoc->GetRangeName(), pRangeName.get(), aUsedGlobalNames);
}

// Carry named database ranges that overlap the copied area into the
// clipboard document. Without this, structured-reference tokens in the
// copied formulas have no ScDBData to resolve against on the clip side,
// so any later stringification of those formulas writes #REF! in place
// of the column name.
void ScDocument::CopyDBsToClip(ScDocument* pClipDoc, const ScRangeList& rClipRanges, const ScMarkData* pMarks)
{
    if (!pDBCollection || pDBCollection->getNamedDBs().empty())
        return;

    ScDBCollection* pClipDBs = pClipDoc->GetDBCollection();
    if (!pClipDBs)
        return;
    ScDBCollection::NamedDBs& rDest = pClipDBs->getNamedDBs();

    for (const auto& rxSrc : pDBCollection->getNamedDBs())
    {
        ScRange aArea;
        rxSrc->GetArea(aArea);
        if (pMarks && !pMarks->GetTableSelect(aArea.aStart.Tab()))
            continue;
        // Carry only a table that overlaps a copied range. A table sitting in
        // the gap of a non-contiguous selection falls inside the bounding box
        // but in none of the copied ranges, and its cells are not copied.
        if (!rClipRanges.Intersects(aArea))
            continue;
        auto pClone = std::make_unique<ScDBData>(*rxSrc);
        rDest.insert(std::move(pClone));
    }
}

// Counterpart of CopyDBsToClip on the paste side: recreate a fully-copied
// named table in the destination, anchored to the paste location rather than
// the source coordinates it was carried with - otherwise the table, with its
// autofilter and style, would sit over the wrong (usually empty) cells.
// A same-document paste keeps the existing table instead (reconstructing an
// independent copy with a fresh unique name is intentionally out of scope).
void ScDocument::CopyDBsFromClip(const ScRange& rDestRange, const ScRange& rClipRange,
                                 const ScDocument* pClipDoc)
{
    if (!pClipDoc)
        return;
    const ScDBCollection* pClipDBs = pClipDoc->GetDBCollection();
    if (!pClipDBs || pClipDBs->getNamedDBs().empty())
        return;

    if (!pDBCollection)
        SetDBCollection(std::unique_ptr<ScDBCollection>(new ScDBCollection(*this)));
    ScDBCollection::NamedDBs& rDest = pDBCollection->getNamedDBs();

    // The whole copied block shifts uniformly from the clip origin to the
    // paste origin; the table area moves by the same delta and onto the
    // destination sheet.
    const SCCOL nDx = rDestRange.aStart.Col() - rClipRange.aStart.Col();
    const SCROW nDy = rDestRange.aStart.Row() - rClipRange.aStart.Row();
    const SCTAB nDestTab = rDestRange.aStart.Tab();

    for (const auto& rxClip : pClipDBs->getNamedDBs())
    {
        // Only recreate a table the copy fully covered; a partial copy must
        // not register a clipped or misplaced table.
        ScRange aSrcArea;
        rxClip->GetArea(aSrcArea);
        if (!rClipRange.Contains(aSrcArea))
            continue;

        // Destination already owns a table of this name (same-document paste):
        // leave it, and any references resolving to it, alone.
        if (rDest.findByUpperName(rxClip->GetUpperName()))
            continue;

        const SCCOL nNewCol1 = aSrcArea.aStart.Col() + nDx;
        const SCROW nNewRow1 = aSrcArea.aStart.Row() + nDy;
        const SCCOL nNewCol2 = aSrcArea.aEnd.Col() + nDx;
        const SCROW nNewRow2 = aSrcArea.aEnd.Row() + nDy;
        if (!ValidColRowTab(nNewCol1, nNewRow1, nDestTab) || !ValidColRowTab(nNewCol2, nNewRow2, nDestTab))
            continue;

        auto pClone = std::make_unique<ScDBData>(*rxClip);
        pClone->SetIndex(0); // let the destination assign a fresh, collision-free index

        // Changing the area resets the table column names, because the header
        // range moves. Keep the names the clip carried so a structured
        // reference still resolves a column by name. The block is a pure
        // translation, so the names map to the same columns as before.
        std::vector<OUString> aColumnNames = pClone->GetTableColumnNames();
        pClone->MoveTo(nDestTab, nNewCol1, nNewRow1, nNewCol2, nNewRow2);
        if (!aColumnNames.empty())
            pClone->SetTableColumnNames(std::move(aColumnNames));

        rDest.insert(std::move(pClone));
    }
}

ScDocument::NumFmtMergeHandler::NumFmtMergeHandler(ScDocument& rDoc, const ScDocument& rSrcDoc)
    : mrDoc(rDoc)
{
    mrDoc.MergeNumberFormatter(rSrcDoc);
}

ScDocument::NumFmtMergeHandler::~NumFmtMergeHandler()
{
    ScMutationGuard aGuard(mrDoc, ScMutationGuardFlags::CORE);
    mrDoc.pFormatExchangeList = nullptr;
}

void ScDocument::PrepareFormulaCalc()
{
    ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
    mpFormulaGroupCxt.reset();
}

SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos )
{
    ScTable* pTab = FetchTable(rPos.Tab());
    if (!pTab)
        return nullptr;

    return pTab->GetBroadcaster(rPos.Col(), rPos.Row());
}

const SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos ) const
{
    const ScTable* pTab = FetchTable(rPos.Tab());
    if (!pTab)
        return nullptr;

    return pTab->GetBroadcaster(rPos.Col(), rPos.Row());
}

void ScDocument::DeleteBroadcasters( sc::ColumnBlockPosition& rBlockPos, const ScAddress& rTopPos, SCROW nLength )
{
    ScTable* pTab = FetchTable(rTopPos.Tab());
    if (!pTab || nLength <= 0)
        return;

    pTab->DeleteBroadcasters(rBlockPos, rTopPos.Col(), rTopPos.Row(), rTopPos.Row()+nLength-1);
}

#if DUMP_COLUMN_STORAGE
void ScDocument::DumpColumnStorage( SCTAB nTab, SCCOL nCol ) const
{
    const ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return;

    pTab->DumpColumnStorage(nCol);
}
#endif

bool ScDocument::HasTable(SCTAB nTab) const
{
    return ValidTab(nTab)
        && nTab < GetTableCount()
        && maTabs[nTab];
}

ScTable* ScDocument::FetchTable( SCTAB nTab )
{
    if (!HasTable(nTab))
        return nullptr;

    return maTabs[nTab].get();
}

const ScTable* ScDocument::FetchTable( SCTAB nTab ) const
{
    if (!HasTable(nTab))
        return nullptr;

    return maTabs[nTab].get();
}

ScColumnsRange ScDocument::GetWritableColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd)
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetWritableColumnsRange(nColBegin, nColEnd);

    SAL_WARN("sc",  "GetWritableColumnsRange() called for non-existent table");
    return ScColumnsRange(-1, -1);
}

ScColumnsRange ScDocument::GetAllocatedColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetAllocatedColumnsRange(nColBegin, nColEnd);
    return ScColumnsRange(-1, -1);
}

ScColumnsRange ScDocument::GetColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetColumnsRange(nColBegin, nColEnd);
    return ScColumnsRange(-1, -1);
}

void ScDocument::MergeNumberFormatter(const ScDocument& rSrcDoc)
{
    SvNumberFormatter* pThisFormatter = mxPoolHelper->GetFormTable();
    SvNumberFormatter* pOtherFormatter = rSrcDoc.mxPoolHelper->GetFormTable();
    if (pOtherFormatter && pOtherFormatter != pThisFormatter)
    {
        SvNumberFormatterIndexTable* pExchangeList =
                 pThisFormatter->MergeFormatter(*pOtherFormatter);
        if (!pExchangeList->empty())
        {
            ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
            pFormatExchangeList = pExchangeList;
        }
    }
}

ScClipParam& ScDocument::GetClipParam()
{
    if (!mpClipParam)
        mpClipParam.reset(new ScClipParam);

    return *mpClipParam;
}

void ScDocument::SetClipParam(const ScClipParam& rParam)
{
    mpClipParam.reset(new ScClipParam(rParam));
}

bool ScDocument::IsClipboardSource() const
{
    if (bIsClip || mpShell == nullptr || mpShell->IsLoading())
        return false;

    ScDocument* pClipDoc = ScModule::GetClipDoc();
    return pClipDoc && pClipDoc->bIsClip && pClipDoc->mxPoolHelper.is() && mxPoolHelper.is() &&
            mxPoolHelper->GetDocPool() == pClipDoc->mxPoolHelper->GetDocPool();
}

void ScDocument::StartListeningFromClip(
    sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt,
    SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->StartListeningFormulaCells(rStartCxt, rEndCxt, nCol1, nRow1, nCol2, nRow2);
}

void ScDocument::StartListeningFromClip( SCCOL nCol1, SCROW nRow1,
                                        SCCOL nCol2, SCROW nRow2,
                                        const ScMarkData& rMark, InsertDeleteFlags nInsFlag )
{
    if (!(nInsFlag & InsertDeleteFlags::CONTENTS))
        return;

    auto xSet = std::make_shared<sc::ColumnBlockPositionSet>(*this);
    sc::StartListeningContext aStartCxt(*this, xSet);
    sc::EndListeningContext aEndCxt(*this, std::move(xSet), nullptr);

    for (SCTAB nTab : rMark)
        StartListeningFromClip(aStartCxt, aEndCxt, nTab, nCol1, nRow1, nCol2, nRow2);
}

void ScDocument::SetDirtyFromClip(
    SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
    InsertDeleteFlags nInsFlag, sc::ColumnSpanSet& rBroadcastSpans )
{
    if (nInsFlag & InsertDeleteFlags::CONTENTS)
    {
        SCTAB nMax = GetTableCount();
        for (const auto& rTab : rMark)
        {
            if (rTab >= nMax)
                break;
            if (maTabs[rTab])
                maTabs[rTab]->SetDirtyFromClip(nCol1, nRow1, nCol2, nRow2, rBroadcastSpans);
        }
    }
}

bool ScDocument::InitColumnBlockPosition( sc::ColumnBlockPosition& rBlockPos, SCTAB nTab, SCCOL nCol )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->InitColumnBlockPosition(rBlockPos, nCol);
    return false;
}

void ScDocument::CopyBlockFromClip(
    sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
    const ScMarkData& rMark, SCCOL nDx, SCROW nDy )
{
    std::vector<ScTableUniquePtr>& rClipTabs = rCxt.getClipDoc()->maTabs;
    SCTAB nTabEnd = rCxt.getTabEnd();
    SCTAB nClipTab = 0;
    for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < GetTableCount(); i++)
    {
        if (maTabs[i] && rMark.GetTableSelect(i) )
        {
            while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());

            maTabs[i]->CopyFromClip(
                rCxt, nCol1, nRow1, nCol2, nRow2, nDx, nDy, rClipTabs[nClipTab].get());

            if (rCxt.getClipDoc()->mpDrawLayer && (rCxt.getInsertFlag() & InsertDeleteFlags::OBJECTS))
            {
                //  also copy drawing objects

                // drawing layer must be created before calling CopyFromClip
                // (ScDocShell::MakeDrawLayer also does InitItems etc.)
                OSL_ENSURE( mpDrawLayer, "CopyBlockFromClip: No drawing layer" );
                if ( mpDrawLayer )
                {
                    //  For GetMMRect, the row heights in the target document must already be valid
                    //  (copied in an extra step before pasting, or updated after pasting cells, but
                    //  before pasting objects).
                    ScRange aSourceRange(nCol1 - nDx, nRow1 - nDy, nClipTab, nCol2 - nDx, nRow2 - nDy, nClipTab);
                    ScRange aDestRange(nCol1, nRow1, i, nCol2, nRow2, i);
                    mpDrawLayer->CopyFromClip(rCxt.getClipDoc()->mpDrawLayer.get(), nClipTab, aSourceRange,
                                                ScAddress( nCol1, nRow1, i ), aDestRange);
                }
            }

            nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
        }
    }
    if (!(rCxt.getInsertFlag() & InsertDeleteFlags::CONTENTS))
        return;

    nClipTab = 0;
    for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < GetTableCount(); i++)
    {
        if (maTabs[i] && rMark.GetTableSelect(i) )
        {
            while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
            SCTAB nDz = i - nClipTab;

            //  ranges of consecutive selected tables (in clipboard and dest. doc)
            //  must be handled in one UpdateReference call
            SCTAB nFollow = 0;
            while ( i + nFollow < nTabEnd
                    && rMark.GetTableSelect( i + nFollow + 1 )
                    && nClipTab + nFollow < MAXTAB
                    && rClipTabs[(nClipTab + nFollow + 1) % static_cast<SCTAB>(rClipTabs.size())] )
                ++nFollow;

            sc::RefUpdateContext aRefCxt(*this, rCxt.getClipDoc());
            aRefCxt.maRange = ScRange(nCol1, nRow1, i, nCol2, nRow2, i+nFollow);
            aRefCxt.mnColDelta = nDx;
            aRefCxt.mnRowDelta = nDy;
            aRefCxt.mnTabDelta = nDz;
            aRefCxt.setBlockPositionReference(rCxt.getBlockPositionSet()); // share mdds position caching
            if (rCxt.getClipDoc()->GetClipParam().mbCutMode)
            {
                // Update references only if cut originates from the same
                // document we are pasting into.
                if (rCxt.getClipDoc()->GetPool() == GetPool())
                {
                    bool bOldInserting = IsInsertingFromOtherDoc();
                    SetInsertingFromOtherDoc( true);
                    aRefCxt.meMode = URM_MOVE;
                    UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);

                    // For URM_MOVE group listeners may have been removed,
                    // re-establish them.
                    if (!aRefCxt.maRegroupCols.empty())
                    {
                        /* TODO: holding the ColumnSet in a shared_ptr at
                         * RefUpdateContext would eliminate the need of
                         * copying it here. */
                        auto pColSet = std::make_shared<sc::ColumnSet>( aRefCxt.maRegroupCols);
                        StartNeededListeners( pColSet);
                    }

                    SetInsertingFromOtherDoc( bOldInserting);
                }
            }
            else
            {
                aRefCxt.meMode = URM_COPY;
                UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);
            }

            nClipTab = (nClipTab+nFollow+1) % static_cast<SCTAB>(rClipTabs.size());
            i = sal::static_int_cast<SCTAB>( i + nFollow );
        }
    }
}

SCROW ScDocument::CopyNonFilteredFromClip(sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1,
                                          SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
                                          SCCOL nDx, SCROW& rClipStartRow, SCROW nClipEndRow)
{
    //  call CopyBlockFromClip for ranges of consecutive non-filtered rows
    //  nCol1/nRow1 etc. is in target doc

    //  filtered state is taken from first used table in clipboard (as in GetClipArea)
    SCTAB nFlagTab = 0;
    std::vector<ScTableUniquePtr>& rClipTabs = rCxt.getClipDoc()->maTabs;
    while ( nFlagTab < static_cast<SCTAB>(rClipTabs.size()) && !rClipTabs[nFlagTab] )
        ++nFlagTab;

    SCROW nSourceRow = rClipStartRow;
    SCROW nSourceEnd = nClipEndRow;
    SCROW nDestRow = nRow1;
    SCROW nFilteredRows = 0;

    while ( nSourceRow <= nSourceEnd && nDestRow <= nRow2 )
    {
        // skip filtered rows
        SCROW nSourceRowOriginal = nSourceRow;
        nSourceRow = rCxt.getClipDoc()->FirstNonFilteredRow(nSourceRow, nSourceEnd, nFlagTab);
        nFilteredRows += nSourceRow - nSourceRowOriginal;

        if ( nSourceRow <= nSourceEnd )
        {
            // look for more non-filtered rows following
            SCROW nLastRow = nSourceRow;
            (void)rCxt.getClipDoc()->RowFiltered(nSourceRow, nFlagTab, nullptr, &nLastRow);
            SCROW nFollow = nLastRow - nSourceRow;

            if (nFollow > nSourceEnd - nSourceRow)
                nFollow = nSourceEnd - nSourceRow;
            if (nFollow > nRow2 - nDestRow)
                nFollow = nRow2 - nDestRow;

            SCROW nNewDy = nDestRow - nSourceRow;
            CopyBlockFromClip(
                rCxt, nCol1, nDestRow, nCol2, nDestRow + nFollow, rMark, nDx, nNewDy);

            nSourceRow += nFollow + 1;
            nDestRow += nFollow + 1;
        }
    }
    rClipStartRow = nSourceRow;
    return nFilteredRows;
}

namespace {

class BroadcastAction : public sc::ColumnSpanSet::ColumnAction
{
    ScDocument& mrDoc;
    ScColumn* mpCol;

public:
    explicit BroadcastAction( ScDocument& rDoc ) : mrDoc(rDoc), mpCol(nullptr) {}

    virtual void startColumn( ScColumn* pCol ) override
    {
        mpCol = pCol;
    }

    virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
    {
        if (!bVal)
            return;

        assert(mpCol);
        ScRange aRange(mpCol->GetCol(), nRow1, mpCol->GetTab());
        aRange.aEnd.SetRow(nRow2);
        mrDoc.BroadcastCells(aRange, SfxHintId::ScDataChanged);
    };
};

}

void ScDocument::CopyFromClip(
    const ScRange& rDestRange, const ScMarkData& rMark, InsertDeleteFlags nInsFlag,
    ScDocument* pRefUndoDoc, ScDocument* pClipDoc, bool bResetCut,
    bool bAsLink, bool bIncludeFiltered, bool bSkipEmptyCells,
    const ScRangeList * pDestRanges, bool bPreserveDestProtection )
{
    if (bIsClip)
        return;

    if (!pClipDoc)
    {
        OSL_FAIL("CopyFromClip: no ClipDoc");
        pClipDoc = ScModule::GetClipDoc();
    }

    if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount())
        return;

    sc::AutoCalcSwitch aACSwitch(*this, false); // temporarily turn off auto calc.

    NumFmtMergeHandler aNumFmtMergeHdl(*this, *pClipDoc);

    SCCOL nAllCol1 = rDestRange.aStart.Col();
    SCROW nAllRow1 = rDestRange.aStart.Row();
    SCCOL nAllCol2 = rDestRange.aEnd.Col();
    SCROW nAllRow2 = rDestRange.aEnd.Row();

    SCCOL nXw = 0;
    SCROW nYw = 0;
    ScRange aClipRange = pClipDoc->GetClipParam().getWholeRange();
    for (SCTAB nTab = 0; nTab < pClipDoc->GetTableCount(); nTab++)    // find largest merge overlap
        if (pClipDoc->maTabs[nTab])                   // all sheets of the clipboard content
        {
            SCCOL nThisEndX = aClipRange.aEnd.Col();
            SCROW nThisEndY = aClipRange.aEnd.Row();
            pClipDoc->ExtendMerge( aClipRange.aStart.Col(),
                                    aClipRange.aStart.Row(),
                                    nThisEndX, nThisEndY, nTab );
            // only extra value from ExtendMerge
            nThisEndX = sal::static_int_cast<SCCOL>( nThisEndX - aClipRange.aEnd.Col() );
            nThisEndY = sal::static_int_cast<SCROW>( nThisEndY - aClipRange.aEnd.Row() );
            if ( nThisEndX > nXw )
                nXw = nThisEndX;
            if ( nThisEndY > nYw )
                nYw = nThisEndY;
        }

    SCCOL nDestAddX;
    SCROW nDestAddY;
    pClipDoc->GetClipArea( nDestAddX, nDestAddY, bIncludeFiltered );
    nXw = sal::static_int_cast<SCCOL>( nXw + nDestAddX );
    nYw = sal::static_int_cast<SCROW>( nYw + nDestAddY );   // ClipArea, plus ExtendMerge value

    /*  Decide which contents to delete before copying. Delete all
        contents if nInsFlag contains any real content flag.
        #i102056# Notes are pasted from clipboard in a second pass,
        together with the special flag InsertDeleteFlags::ADDNOTES that states to not
        overwrite/delete existing cells but to insert the notes into
        these cells. In this case, just delete old notes from the
        destination area. */
    InsertDeleteFlags nDelFlag = nInsFlag;
    // tdf#163019 - remove formula of the cell to update formula listeners
    if (nInsFlag & InsertDeleteFlags::CONTENTS)
        nDelFlag |= InsertDeleteFlags::FORMULA;

    // tdf#161189 - remove the note deletion flag if no notes are included
    if ((nInsFlag & (InsertDeleteFlags::CONTENTS | InsertDeleteFlags::ADDNOTES))
        == (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES))
        nDelFlag &= ~InsertDeleteFlags::NOTE;

    if (nInsFlag & InsertDeleteFlags::ATTRIB)
        nDelFlag |= InsertDeleteFlags::ATTRIB;

    sc::CopyFromClipContext aCxt(*this, pRefUndoDoc, pClipDoc, nInsFlag, bAsLink, bSkipEmptyCells);
    std::pair<SCTAB,SCTAB> aTabRanges = getMarkedTableRange(maTabs, rMark);
    aCxt.setTabRange(aTabRanges.first, aTabRanges.second);
    aCxt.setDeleteFlag(nDelFlag);

    ScRangeList aLocalRangeList;
    if (!pDestRanges)
    {
        aLocalRangeList.push_back( rDestRange);
        pDestRanges = &aLocalRangeList;
    }

    // Recreate named tables the copy fully covered at the paste location, so the
    // structured-reference tokens resolved further down bind to a table placed
    // at the paste position. Only a formula paste carries such tokens, so a
    // values-only paste needs no table. Must run before the cells, and their
    // formulas, are copied in.
    if (nInsFlag & InsertDeleteFlags::FORMULA)
        CopyDBsFromClip(rDestRange, aClipRange, pClipDoc);

    bInsertingFromOtherDoc = true;  // No Broadcast/Listener created at Insert

    sc::ColumnSpanSet aBroadcastSpans;

    // An interactive paste treats a cell's own directly applied protection as
    // a property of the cell, not of the pasted data: copying a protected cell
    // onto a cell the user directly unprotected must leave it editable (and
    // vice versa). Note the directly set protection over the destination now,
    // and put it back once the pasted attributes have overwritten it below.
    // Only direct formatting is handled here; protection that instead comes
    // from a cell style (including a style that conditional formatting applies)
    // is left to follow the usual paste. A moved cell (cut and paste) keeps its
    // own protection, so it is excluded.
    struct KeptProtection
    {
        SCTAB nTab;
        SCCOL nCol;
        SCROW nRow1;
        SCROW nRow2;
        ScProtectionAttr aProtection;
    };
    std::vector<KeptProtection> aKeptProtection;
    const bool bKeepDestProtection = bPreserveDestProtection
        && (nInsFlag & InsertDeleteFlags::ATTRIB)
        && !pClipDoc->GetClipParam().mbCutMode;
    if (bKeepDestProtection)
    {
        for (const SCTAB nTab : rMark)
        {
            for (const ScRange& rRange : *pDestRanges)
            {
                ScDocAttrIterator aIter(*this, nTab, rRange.aStart.Col(), rRange.aStart.Row(),
                                        rRange.aEnd.Col(), rRange.aEnd.Row());
                SCCOL nAttrCol;
                SCROW nAttrRow1, nAttrRow2;
                while (const ScPatternAttr* pPattern = aIter.GetNext(nAttrCol, nAttrRow1, nAttrRow2))
                {
                    if (const ScProtectionAttr* pProtect
                            = pPattern->GetItemSet().GetItemIfSet(ATTR_PROTECTION, false))
                        aKeptProtection.push_back(
                            { nTab, nAttrCol, nAttrRow1, nAttrRow2, *pProtect });
                }
            }
        }
    }

    SCCOL nClipStartCol = aClipRange.aStart.Col();
    SCROW nClipStartRow = aClipRange.aStart.Row();
    SCROW nClipEndRow = aClipRange.aEnd.Row();
    for ( size_t nRange = 0; nRange < pDestRanges->size(); ++nRange )
    {
        const ScRange & rRange = (*pDestRanges)[nRange];
        SCCOL nCol1 = rRange.aStart.Col();
        SCROW nRow1 = rRange.aStart.Row();
        SCCOL nCol2 = rRange.aEnd.Col();
        SCROW nRow2 = rRange.aEnd.Row();

        aCxt.setDestRange(nCol1, nRow1, nCol2, nRow2);
        DeleteBeforeCopyFromClip(aCxt, rMark, aBroadcastSpans); // <- this removes existing formula listeners

        if (CopyOneCellFromClip(aCxt, nCol1, nRow1, nCol2, nRow2))
            continue;

        SCCOL nC1 = nCol1;
        SCROW nR1 = nRow1;
        SCCOL nC2 = nC1 + nXw;
        if (nC2 > nCol2)
            nC2 = nCol2;
        SCROW nR2 = nR1 + nYw;
        if (nR2 > nRow2)
            nR2 = nRow2;

        const SCCOLROW nThreshold = 8192;
        bool bPreallocatePattern = ((nInsFlag & InsertDeleteFlags::ATTRIB) && (nRow2 - nRow1 > nThreshold));
        std::vector< SCTAB > vTables;

        if (bPreallocatePattern)
        {
            for (SCTAB i = aCxt.getTabStart(); i <= aCxt.getTabEnd(); ++i)
                if (maTabs[i] && rMark.GetTableSelect( i ) )
                    vTables.push_back( i );
        }

        do
        {
            // Pasting is done column-wise, when pasting to a filtered
            // area this results in partitioning and we have to
            // remember and reset the start row for each column until
            // it can be advanced for the next chunk of unfiltered
            // rows.
            SCROW nSaveClipStartRow = nClipStartRow;
            do
            {
                nClipStartRow = nSaveClipStartRow;
                SCCOL nDx = nC1 - nClipStartCol;
                SCROW nDy = nR1 - nClipStartRow;
                if ( bIncludeFiltered )
                {
                    CopyBlockFromClip(
                        aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nDy);
                    nClipStartRow += nR2 - nR1 + 1;
                }
                else
                {
                    CopyNonFilteredFromClip(aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nClipStartRow,
                                            nClipEndRow);
                }
                nC1 = nC2 + 1;
                nC2 = std::min(static_cast<SCCOL>(nC1 + nXw), nCol2);
            } while (nC1 <= nCol2);
            if (nClipStartRow > nClipEndRow)
                nClipStartRow = aClipRange.aStart.Row();
            nC1 = nCol1;
            nC2 = nC1 + nXw;
            if (nC2 > nCol2)
                nC2 = nCol2;

            // Preallocate pattern memory once if further chunks are to be pasted.
            if (bPreallocatePattern && (nR2+1) <= nRow2)
            {
                SCROW nR3 = nR2 + 1;
                for (SCTAB nTab : vTables)
                {
                    for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
                    {
                        // Pattern count of the first chunk pasted.
                        SCSIZE nChunk = GetPatternCount( nTab, nCol, nR1, nR2);
                        // If it is only one pattern per chunk and chunks are
                        // pasted consecutively then it will get its range
                        // enlarged for each chunk and no further allocation
                        // happens. For non-consecutive chunks we're out of
                        // luck in this case.
                        if (nChunk > 1)
                        {
                            SCSIZE nNeeded = nChunk * (nRow2 - nR3 + 1) / (nYw + 1);
                            SCSIZE nRemain = GetPatternCount( nTab, nCol, nR3, nRow2);
                            if (nNeeded > nRemain)
                            {
                                SCSIZE nCurr = GetPatternCount( nTab, nCol);
                                ReservePatternCount( nTab, nCol, nCurr + nNeeded);
                            }
                        }
                    }
                }
                bPreallocatePattern = false;
            }

            nR1 = nR2 + 1;
            nR2 = std::min(static_cast<SCROW>(nR1 + nYw), nRow2);
        } while (nR1 <= nRow2);
    }

    bInsertingFromOtherDoc = false;

    // Put back the destination's own direct protection that the paste above
    // overwrote (see the note where aKeptProtection is filled).
    for (const KeptProtection& rKept : aKeptProtection)
    {
        ScPatternAttr aPattern(getCellAttributeHelper());
        aPattern.ItemSetPut(rKept.aProtection);
        ApplyPatternAreaTab(rKept.nCol, rKept.nRow1, rKept.nCol, rKept.nRow2, rKept.nTab, aPattern);
    }

    if (nInsFlag & InsertDeleteFlags::CONTENTS)
    {
        for (SCTAB nTab : rMark)
            aCxt.setListeningFormulaSpans(nTab, nAllCol1, nAllRow1, nAllCol2, nAllRow2);
    }

    // Create Listener after everything has been inserted
    aCxt.startListeningFormulas();

    {
        ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);

        // Set all formula cells dirty, and collect non-empty non-formula cell
        // positions so that we can broadcast on them below.
        SetDirtyFromClip(nAllCol1, nAllRow1, nAllCol2, nAllRow2, rMark, nInsFlag, aBroadcastSpans);

        BroadcastAction aAction(*this);
        aBroadcastSpans.executeColumnAction(*this, aAction);
    }

    if (bResetCut)
        pClipDoc->GetClipParam().mbCutMode = false;
}

void ScDocument::CopyMultiRangeFromClip(const ScAddress& rDestPos, const ScMarkData& rMark,
                                        InsertDeleteFlags nInsFlag, ScDocument* pClipDoc,
                                        bool bResetCut, bool bAsLink, bool bIncludeFiltered,
                                        bool bSkipAttrForEmpty)
{
    if (bIsClip)
        return;

    if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount())
        // There is nothing in the clip doc to copy.
        return;

    // Right now, we don't allow pasting into filtered rows, so we don't even handle it here.

    sc::AutoCalcSwitch aACSwitch(*this, false); // turn of auto calc temporarily.
    NumFmtMergeHandler aNumFmtMergeHdl(*this, *pClipDoc);

    const ScRange& aDestRange = rMark.GetMarkArea();

    bInsertingFromOtherDoc = true;  // No Broadcast/Listener created at Insert

    SCCOL nCol1 = rDestPos.Col();
    SCROW nRow1 = rDestPos.Row();
    ScClipParam& rClipParam = pClipDoc->GetClipParam();

    sc::ColumnSpanSet aBroadcastSpans;

    if (!bSkipAttrForEmpty)
    {
        // Do the deletion first.
        SCCOL nColSize = rClipParam.getPasteColSize();
        SCROW nRowSize = rClipParam.getPasteRowSize(*pClipDoc, bIncludeFiltered);

        DeleteArea(nCol1, nRow1, nCol1+nColSize-1, nRow1+nRowSize-1, rMark, InsertDeleteFlags::CONTENTS, false, &aBroadcastSpans);
    }

    sc::CopyFromClipContext aCxt(*this, nullptr, pClipDoc, nInsFlag, bAsLink, bSkipAttrForEmpty);
    std::pair<SCTAB,SCTAB> aTabRanges = getMarkedTableRange(maTabs, rMark);
    aCxt.setTabRange(aTabRanges.first, aTabRanges.second);

    for (size_t i = 0, n = rClipParam.maRanges.size(); i < n; ++i)
    {
        const ScRange & rRange = rClipParam.maRanges[i];

        SCROW nRowCount = rRange.aEnd.Row() - rRange.aStart.Row() + 1;
        SCCOL nDx = static_cast<SCCOL>(nCol1 - rRange.aStart.Col());
        SCROW nDy = static_cast<SCROW>(nRow1 - rRange.aStart.Row());
        SCCOL nCol2 = nCol1 + rRange.aEnd.Col() - rRange.aStart.Col();
        SCROW nEndRow = nRow1 + nRowCount - 1;
        SCROW nFilteredRows = 0;

        if (bIncludeFiltered)
        {
            CopyBlockFromClip(aCxt, nCol1, nRow1, nCol2, nEndRow, rMark, nDx, nDy);
        }
        else
        {
            SCROW nClipStartRow = rRange.aStart.Row();
            SCROW nClipEndRow = rRange.aEnd.Row();
            nFilteredRows += CopyNonFilteredFromClip(aCxt, nCol1, nRow1, nCol2, nEndRow, rMark, nDx,
                                                     nClipStartRow, nClipEndRow);
            nRowCount -= nFilteredRows;
        }

        switch (rClipParam.meDirection)
        {
            case ScClipParam::Row:
                // Begin row for the next range being pasted.
                nRow1 += nRowCount;
                break;
            case ScClipParam::Column:
                nCol1 += rRange.aEnd.Col() - rRange.aStart.Col() + 1;
            break;
            default:
                ;
        }
    }

    bInsertingFromOtherDoc = false;

    // Create Listener after everything has been inserted
    StartListeningFromClip(aDestRange.aStart.Col(), aDestRange.aStart.Row(),
                           aDestRange.aEnd.Col(), aDestRange.aEnd.Row(), rMark, nInsFlag );

    {
        ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);

        // Set formula cells dirty and collect non-formula cells.
        SetDirtyFromClip(
                aDestRange.aStart.Col(), aDestRange.aStart.Row(), aDestRange.aEnd.Col(), aDestRange.aEnd.Row(),
                rMark, nInsFlag, aBroadcastSpans);

        BroadcastAction aAction(*this);
        aBroadcastSpans.executeColumnAction(*this, aAction);
    }

    if (bResetCut)
        pClipDoc->GetClipParam().mbCutMode = false;
}

void ScDocument::SetClipArea( const ScRange& rArea, bool bCut )
{
    if (bIsClip)
    {
        ScClipParam& rClipParam = GetClipParam();
        rClipParam.maRanges.RemoveAll();
        rClipParam.maRanges.push_back(rArea);
        rClipParam.mbCutMode = bCut;
    }
    else
    {
        OSL_FAIL("SetClipArea: No Clip");
    }
}

void ScDocument::GetClipArea(SCCOL& nClipX, SCROW& nClipY, bool bIncludeFiltered)
{
    if (!bIsClip)
    {
        OSL_FAIL("GetClipArea: No Clip");
        return;
    }

    ScRangeList& rClipRanges = GetClipParam().maRanges;
    if (rClipRanges.empty())
        // No clip range.  Bail out.
        return;

    ScRange const & rRange = rClipRanges.front();
    SCCOL nStartCol = rRange.aStart.Col();
    SCCOL nEndCol   = rRange.aEnd.Col();
    SCROW nStartRow = rRange.aStart.Row();
    SCROW nEndRow   = rRange.aEnd.Row();
    for ( size_t i = 1, n = rClipRanges.size(); i < n; ++i )
    {
        ScRange const & rRange2 = rClipRanges[ i ];
        if (rRange2.aStart.Col() < nStartCol)
            nStartCol = rRange2.aStart.Col();
        if (rRange2.aStart.Row() < nStartRow)
            nStartRow = rRange2.aStart.Row();
        if (rRange2.aEnd.Col() > nEndCol)
            nEndCol = rRange2.aEnd.Col();
        if (rRange2.aEnd.Row() > nEndRow)
            nEndRow = rRange2.aEnd.Row();
    }

    nClipX = nEndCol - nStartCol;

    if ( bIncludeFiltered )
        nClipY = nEndRow - nStartRow;
    else
    {
        //  count non-filtered rows
        //  count on first used table in clipboard
        SCTAB nCountTab = 0;
        while (nCountTab < GetTableCount() && !maTabs[nCountTab])
            ++nCountTab;

        SCROW nResult = CountNonFilteredRows(nStartRow, nEndRow, nCountTab);

        if ( nResult > 0 )
            nClipY = nResult - 1;
        else
            nClipY = 0;                 // always return at least 1 row
    }
}

void ScDocument::GetClipStart(SCCOL& nClipX, SCROW& nClipY)
{
    if (bIsClip)
    {
        ScRangeList& rClipRanges = GetClipParam().maRanges;
        if ( !rClipRanges.empty() )
        {
            nClipX = rClipRanges.front().aStart.Col();
            nClipY = rClipRanges.front().aStart.Row();
        }
    }
    else
    {
        OSL_FAIL("GetClipStart: No Clip");
    }
}

bool ScDocument::HasClipFilteredRows()
{
    //  count on first used table in clipboard
    SCTAB nCountTab = 0;
    while (nCountTab < GetTableCount() && !maTabs[nCountTab])
        ++nCountTab;

    ScRangeList& rClipRanges = GetClipParam().maRanges;
    if ( rClipRanges.empty() )
        return false;

    if (maTabs.size() > 0)
    {
        for (size_t i = 0, n = rClipRanges.size(); i < n; ++i)
        {
            ScRange& rRange = rClipRanges[i];
            bool bAnswer
                = maTabs[nCountTab]->getFilterData().hasFilteredRows(rRange.aStart.Row(), rRange.aEnd.Row());
            if (bAnswer)
                return true;
        }
    }
    return false;
}

void ScDocument::MixDocument( const ScRange& rRange, ScPasteFunc nFunction, bool bSkipEmpty,
                              ScDocument& rSrcDoc )
{
    SCTAB nTab1 = rRange.aStart.Tab();
    SCTAB nTab2 = rRange.aEnd.Tab();
    sc::MixDocContext aCxt(*this);
    SCTAB nMinSizeBothTabs = std::min(GetTableCount(), rSrcDoc.GetTableCount());
    for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++)
    {
        ScTable* pTab = FetchTable(i);
        const ScTable* pSrcTab = rSrcDoc.FetchTable(i);
        if (!pTab || !pSrcTab)
            continue;

        pTab->MixData(
            aCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(),
            nFunction, bSkipEmpty, pSrcTab);
    }
}

void ScDocument::FillTab( const ScRange& rSrcArea, const ScMarkData& rMark,
                                InsertDeleteFlags nFlags, ScPasteFunc nFunction,
                                bool bSkipEmpty, bool bAsLink )
{
    InsertDeleteFlags nDelFlags = nFlags;
    if (nDelFlags & InsertDeleteFlags::CONTENTS)
        nDelFlags |= InsertDeleteFlags::CONTENTS;          // Either all contents or delete nothing!

    SCTAB nSrcTab = rSrcArea.aStart.Tab();

    if (ScTable* pSourceTable = FetchTable(nSrcTab))
    {
        SCCOL nStartCol = rSrcArea.aStart.Col();
        SCROW nStartRow = rSrcArea.aStart.Row();
        SCCOL nEndCol = rSrcArea.aEnd.Col();
        SCROW nEndRow = rSrcArea.aEnd.Row();
        ScDocumentUniquePtr pMixDoc;
        bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS );

        bool bOldAutoCalc = GetAutoCalc();
        SetAutoCalc( false );                   // avoid multiple calculations

        sc::CopyToDocContext aCxt(*this);
        sc::MixDocContext aMixDocCxt(*this);

        SCTAB nCount = GetTableCount();
        for (const SCTAB& i : rMark)
        {
            if (i >= nCount)
                break;
            if (i != nSrcTab && maTabs[i])
            {
                if (bDoMix)
                {
                    if (!pMixDoc)
                    {
                        pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
                        pMixDoc->InitUndo( *this, i, i );
                    }
                    else
                        pMixDoc->AddUndoTab( i, i );

                    // context used for copying content to the temporary mix document.
                    sc::CopyToDocContext aMixCxt(*pMixDoc);
                    maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow,
                                           InsertDeleteFlags::CONTENTS, false, pMixDoc->maTabs[i].get(),
                                           /*pMarkData*/nullptr, /*bAsLink*/false, /*bColRowFlags*/true,
                                           ScCloneFlags::Default, /*bCopyCaptions*/true );
                }
                maTabs[i]->DeleteArea( nStartCol,nStartRow, nEndCol,nEndRow, nDelFlags);
                pSourceTable->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow,
                                             nFlags, false, maTabs[i].get(), nullptr, bAsLink,
                                             /*bColRowFlags*/true, ScCloneFlags::Default, /*bCopyCaptions*/true );

                if (bDoMix)
                    maTabs[i]->MixData(aMixDocCxt, nStartCol,nStartRow, nEndCol,nEndRow,
                                       nFunction, bSkipEmpty, pMixDoc->maTabs[i].get() );
            }
        }

        SetAutoCalc( bOldAutoCalc );
    }
    else
    {
        OSL_FAIL("wrong table");
    }
}

void ScDocument::FillTabMarked( SCTAB nSrcTab, const ScMarkData& rMark,
                                InsertDeleteFlags nFlags, ScPasteFunc nFunction,
                                bool bSkipEmpty, bool bAsLink )
{
    InsertDeleteFlags nDelFlags = nFlags;
    if (nDelFlags & InsertDeleteFlags::CONTENTS)
        nDelFlags |= InsertDeleteFlags::CONTENTS;          // Either all contents or delete nothing!

    if (ScTable* pSourceTable = FetchTable(nSrcTab))
    {
        ScDocumentUniquePtr pMixDoc;
        bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS );

        bool bOldAutoCalc = GetAutoCalc();
        SetAutoCalc( false );                   // avoid multiple calculations

        const ScRange& aArea = rMark.GetMultiMarkArea();
        SCCOL nStartCol = aArea.aStart.Col();
        SCROW nStartRow = aArea.aStart.Row();
        SCCOL nEndCol = aArea.aEnd.Col();
        SCROW nEndRow = aArea.aEnd.Row();

        sc::CopyToDocContext aCxt(*this);
        sc::MixDocContext aMixDocCxt(*this);
        SCTAB nCount = GetTableCount();
        for (const SCTAB& i : rMark)
        {
            if (i >= nCount)
                break;
            if ( i != nSrcTab && maTabs[i] )
            {
                if (bDoMix)
                {
                    if (!pMixDoc)
                    {
                        pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
                        pMixDoc->InitUndo( *this, i, i );
                    }
                    else
                        pMixDoc->AddUndoTab( i, i );

                    sc::CopyToDocContext aMixCxt(*pMixDoc);
                    maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow,
                                           InsertDeleteFlags::CONTENTS, true, pMixDoc->maTabs[i].get(), &rMark,
                                           /*bAsLink*/false, /*bColRowFlags*/true, ScCloneFlags::Default,
                                           /*bCopyCaptions*/true );
                }

                maTabs[i]->DeleteSelection( nDelFlags, rMark );
                pSourceTable->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow,
                                             nFlags, true, maTabs[i].get(), &rMark, bAsLink,
                                             /*bColRowFlags*/true, ScCloneFlags::Default, /*bCopyCaptions*/true );

                if (bDoMix)
                    maTabs[i]->MixMarked(aMixDocCxt, rMark, nFunction, bSkipEmpty, pMixDoc->maTabs[i].get());
            }
        }

        SetAutoCalc( bOldAutoCalc );
    }
    else
    {
        OSL_FAIL("wrong table");
    }
}

bool ScDocument::SetString( SCCOL nCol, SCROW nRow, SCTAB nTab, const OUString& rString,
                            const ScSetStringParam* pParam )
{
    ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return false;

    const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(nCol, nRow);
    if (pCurCellFormula && pCurCellFormula->IsShared())
    {
        // In case setting this string affects an existing formula group, end
        // its listening to purge then empty cell broadcasters. Affected
        // remaining split group listeners will be set up again via
        // ScColumn::DetachFormulaCell() and
        // ScColumn::StartListeningUnshared().

        sc::EndListeningContext aCxt(*this);
        ScAddress aPos(nCol, nRow, nTab);
        EndListeningIntersectedGroup(aCxt, aPos, nullptr);
        aCxt.purgeEmptyBroadcasters();
    }

    return pTab->SetString(nCol, nRow, nTab, rString, pParam);
}

bool ScDocument::SetString(
    const ScAddress& rPos, const OUString& rString, const ScSetStringParam* pParam )
{
    return SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rString, pParam);
}

bool ScDocument::SetEditText( const ScAddress& rPos, std::unique_ptr<EditTextObject> pEditText )
{
    if (ScTable* pTable = FetchTable(rPos.Tab()))
        return pTable->SetEditText(rPos.Col(), rPos.Row(), std::move(pEditText));
    return false;
}

void ScDocument::SetEditText( const ScAddress& rPos, const EditTextObject& rEditText, const SfxItemPool* pEditPool )
{
    if (ScTable* pTable = FetchTable(rPos.Tab()))
        pTable->SetEditText(rPos.Col(), rPos.Row(), rEditText, pEditPool);
}

void ScDocument::SetEditText( const ScAddress& rPos, const OUString& rStr )
{
    if (ScTable* pTable = FetchTable(rPos.Tab()))
    {
        ScFieldEditEngine& rEngine = GetEditEngine();
        rEngine.SetTextCurrentDefaults(rStr);
        pTable->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject());
    }
}

SCROW ScDocument::GetFirstEditTextRow( const ScRange& rRange ) const
{
    if (const ScTable* pTable = FetchTable(rRange.aStart.Tab()))
        return pTable->GetFirstEditTextRow(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
    return -1;
}


void ScDocument::SetTextCell(const ScAddress& rPos, const OUString& rStr,
                             const ScSetStringParam* pParam)
{
    if (ScTable* pTable = FetchTable(rPos.Tab()))
    {
        if (ScStringUtil::isMultiline(rStr))
        {
            ScFieldEditEngine& rEngine = GetEditEngine();
            rEngine.SetTextCurrentDefaults(rStr);
            pTable->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject());
        }
        else
        {
            if (pParam)
                pTable->SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rStr, pParam);
            else
            {
                ScSetStringParam aParam;
                aParam.setTextInput();
                pTable->SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rStr, &aParam);
            }
        }
    }
}

void ScDocument::SetEmptyCell( const ScAddress& rPos )
{
    if (ScTable* pTable = FetchTable(rPos.Tab()))
        pTable->SetEmptyCell(rPos.Col(), rPos.Row());
}

void ScDocument::SetValue( SCCOL nCol, SCROW nRow, SCTAB nTab, const double& rVal )
{
    SetValue(ScAddress(nCol, nRow, nTab), rVal);
}

void ScDocument::SetValue( const ScAddress& rPos, double fVal )
{
    ScTable* pTab = FetchTable(rPos.Tab());
    if (!pTab)
        return;

    const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(rPos.Col(), rPos.Row());
    if (pCurCellFormula && pCurCellFormula->IsShared())
    {
        // In case setting this value affects an existing formula group, end
        // its listening to purge then empty cell broadcasters. Affected
        // remaining split group listeners will be set up again via
        // ScColumn::DetachFormulaCell() and
        // ScColumn::StartListeningUnshared().

        sc::EndListeningContext aCxt(*this);
        EndListeningIntersectedGroup(aCxt, rPos, nullptr);
        aCxt.purgeEmptyBroadcasters();
    }

    pTab->SetValue(rPos.Col(), rPos.Row(), fVal);
}

OUString ScDocument::GetString( SCCOL nCol, SCROW nRow, SCTAB nTab, ScInterpreterContext* pContext ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetString(nCol, nRow, pContext);
    return OUString();
}

OUString ScDocument::GetString( const ScAddress& rPos, ScInterpreterContext* pContext ) const
{
    return GetString(rPos.Col(), rPos.Row(), rPos.Tab(), pContext);
}

double* ScDocument::GetValueCell( const ScAddress& rPos )
{
    if (ScTable* pTable = FetchTable(rPos.Tab()))
        return pTable->GetValueCell(rPos.Col(), rPos.Row());
    return nullptr;
}

svl::SharedString ScDocument::GetSharedString( const ScAddress& rPos ) const
{
    if (const ScTable* pTable = FetchTable(rPos.Tab()))
        return pTable->GetSharedString(rPos.Col(), rPos.Row());
    return svl::SharedString();
}

std::shared_ptr<sc::FormulaGroupContext>& ScDocument::GetFormulaGroupContext()
{
    ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
    if (!mpFormulaGroupCxt)
        mpFormulaGroupCxt = std::make_shared<sc::FormulaGroupContext>();

    return mpFormulaGroupCxt;
}

void ScDocument::DiscardFormulaGroupContext()
{
    assert(!IsThreadedGroupCalcInProgress());
    if( !mbFormulaGroupCxtBlockDiscard )
        mpFormulaGroupCxt.reset();
}

OUString ScDocument::GetInputString(SCCOL nCol, SCROW nRow, SCTAB nTab, bool bForceSystemLocale ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetInputString(nCol, nRow, bForceSystemLocale);
    else
        return OUString();
}

FormulaError ScDocument::GetStringForFormula( const ScAddress& rPos, OUString& rString )
{
    // Used in formulas (add-in parameters etc), so it must use the same semantics as
    // ScInterpreter::GetCellString: always format values as numbers.
    // The return value is the error code.

    ScRefCellValue aCell(*this, rPos);
    if (aCell.isEmpty())
    {
        rString.clear();
        return FormulaError::NONE;
    }

    FormulaError nErr = FormulaError::NONE;
    OUString aStr;
    SvNumberFormatter* pFormatter = GetFormatTable();
    switch (aCell.getType())
    {
        case CELLTYPE_STRING:
        case CELLTYPE_EDIT:
            aStr = aCell.getString(*this);
        break;
        case CELLTYPE_FORMULA:
        {
            ScFormulaCell* pFCell = aCell.getFormula();
            nErr = pFCell->GetErrCode();
            if (pFCell->IsValue())
            {
                double fVal = pFCell->GetValue();
                sal_uInt32 nIndex = pFormatter->GetStandardFormat(
                                    SvNumFormatType::NUMBER,
                                    ScGlobal::eLnge);
                aStr = pFormatter->GetInputLineString(fVal, nIndex);
            }
            else
                aStr = pFCell->GetString().getString();
        }
        break;
        case CELLTYPE_VALUE:
        {
            double fVal = aCell.getDouble();
            sal_uInt32 nIndex = pFormatter->GetStandardFormat(
                                    SvNumFormatType::NUMBER,
                                    ScGlobal::eLnge);
            aStr = pFormatter->GetInputLineString(fVal, nIndex);
        }
        break;
        default:
            ;
    }

    rString = aStr;
    return nErr;
}

const EditTextObject* ScDocument::GetEditText( const ScAddress& rPos ) const
{
    SCTAB nTab = rPos.Tab();
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetEditText(rPos.Col(), rPos.Row());
    return nullptr;
}

void ScDocument::RemoveEditTextCharAttribs( const ScAddress& rPos, const ScPatternAttr& rAttr )
{
    if (ScTable* pTable = FetchTable(rPos.Tab()))
        return pTable->RemoveEditTextCharAttribs(rPos.Col(), rPos.Row(), rAttr);
}

double ScDocument::GetValue( const ScAddress& rPos ) const
{
    return GetValue(rPos.Col(), rPos.Row(), rPos.Tab());
}

double ScDocument::GetValue( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetValue(nCol, nRow);
    return 0.0;
}

sal_uInt32 ScDocument::GetNumberFormat( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
            return pTable->GetNumberFormat(nCol, nRow);
    return 0;
}

sal_uInt32 ScDocument::GetNumberFormat( const ScRange& rRange ) const
{
    SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = rRange.aEnd.Tab();
    SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col();
    SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();

    if (!HasTable(nTab1) || !HasTable(nTab2))
        return 0;

    sal_uInt32 nFormat = 0;
    bool bFirstItem = true;
    for (SCTAB nTab = nTab1; nTab <= nTab2 && nTab < GetTableCount() ; ++nTab)
        for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
        {
            sal_uInt32 nThisFormat = maTabs[nTab]->GetNumberFormat(nCol, nRow1, nRow2);
            if (bFirstItem)
            {
                nFormat = nThisFormat;
                bFirstItem = false;
            }
            else if (nThisFormat != nFormat)
                return 0;
        }

    return nFormat;
}

sal_uInt32 ScDocument::GetNumberFormat( const ScInterpreterContext& rContext, const ScAddress& rPos ) const
{
    SCTAB nTab = rPos.Tab();
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetNumberFormat( rContext, rPos );
    return 0;
}

void ScDocument::SetNumberFormat( const ScAddress& rPos, sal_uInt32 nNumberFormat )
{
    assert(!IsThreadedGroupCalcInProgress());
    SCTAB nTab = rPos.Tab();
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetNumberFormat(rPos.Col(), rPos.Row(), nNumberFormat);
}

void ScDocument::GetNumberFormatInfo( const ScInterpreterContext& rContext, SvNumFormatType& nType, sal_uInt32& nIndex,
            const ScAddress& rPos ) const
{
    SCTAB nTab = rPos.Tab();
    if (nTab < GetTableCount() && maTabs[nTab])
    {
        nIndex = maTabs[nTab]->GetNumberFormat( rContext, rPos );
        nType = rContext.NFGetType(nIndex);
    }
    else
    {
        nType = SvNumFormatType::UNDEFINED;
        nIndex = 0;
    }
}

OUString ScDocument::GetFormula( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetFormula(nCol, nRow);

    return OUString();
}

const ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos ) const
{
    if (const ScTable* pTable = FetchTable(rPos.Tab()))
        return pTable->GetFormulaCell(rPos.Col(), rPos.Row());
    return nullptr;
}

ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos )
{
    if (ScTable* pTable = FetchTable(rPos.Tab()))
        return pTable->GetFormulaCell(rPos.Col(), rPos.Row());
    return nullptr;
}

CellType ScDocument::GetCellType( const ScAddress& rPos ) const
{
    return GetCellType(rPos.Col(), rPos.Row(), rPos.Tab());
}

CellType ScDocument::GetCellType( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetCellType( nCol, nRow );
    return CELLTYPE_NONE;
}

bool ScDocument::HasStringData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab) ; pTable && nCol < pTable->GetAllocatedColumnsCount())
        return pTable->HasStringData(nCol, nRow);
    return false;
}

bool ScDocument::HasValueData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab) ; pTable && nCol < pTable->GetAllocatedColumnsCount())
        return pTable->HasValueData( nCol, nRow );
    return false;
}

bool ScDocument::HasValueData( const ScAddress& rPos ) const
{
    return HasValueData(rPos.Col(), rPos.Row(), rPos.Tab());
}

bool ScDocument::HasStringCells( const ScRange& rRange ) const
{
    //  true, if String- or Edit cells in range

    SCCOL nStartCol = rRange.aStart.Col();
    SCROW nStartRow = rRange.aStart.Row();
    SCTAB nStartTab = rRange.aStart.Tab();
    SCCOL nEndCol = rRange.aEnd.Col();
    SCROW nEndRow = rRange.aEnd.Row();
    SCTAB nEndTab = rRange.aEnd.Tab();

    for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < GetTableCount(); nTab++)
    {
        if ( maTabs[nTab] && maTabs[nTab]->HasStringCells( nStartCol, nStartRow, nEndCol, nEndRow ) )
            return true;
    }
    return false;
}

bool ScDocument::HasSelectionData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    sal_uInt32 nValidation = GetAttr(nCol, nRow, nTab, ATTR_VALIDDATA).GetValue();
    if( nValidation )
    {
        const ScValidationData* pData = GetValidationEntry( nValidation );
        if( pData && pData->HasSelectionList() )
            return true;
    }
    return HasStringCells( ScRange( nCol, 0, nTab, nCol, MaxRow(), nTab ) );
}

bool ScDocument::HasValidationData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    sal_uInt32 nValidation = GetAttr(nCol, nRow, nTab, ATTR_VALIDDATA).GetValue();
    if( nValidation )
    {
        const ScValidationData* pData = GetValidationEntry( nValidation );
        if( pData && pData->GetDataMode() != ScValidationMode::SC_VALID_ANY )
            return true;
    }
    return false;
}

void ScDocument::CheckVectorizationState()
{
    bool bOldAutoCalc = GetAutoCalc();
    bAutoCalc = false;      // no multiple calculations

    for (const auto& a : maTabs)
    {
        if (a)
            a->CheckVectorizationState();
    }

    SetAutoCalc(bOldAutoCalc);
}

void ScDocument::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt )
{
    bool bOldAutoCalc = GetAutoCalc();
    bAutoCalc = false;      // no multiple calculations
    {   // scope for bulk broadcast
        ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
        for (const auto& a : maTabs)
        {
            if (a)
                a->SetAllFormulasDirty(rCxt);
        }
    }

    // Although Charts are also set to dirty in Tracking without AutoCalc
    // if all formulas are dirty, the charts can no longer be caught
    //  (#45205#) - that is why all Charts have to be explicitly handled again
    if (pChartListenerCollection)
        pChartListenerCollection->SetDirty();

    SetAutoCalc( bOldAutoCalc );
}

void ScDocument::SetDirty( const ScRange& rRange, bool bIncludeEmptyCells )
{
    bool bOldAutoCalc = GetAutoCalc();
    bAutoCalc = false;      // no multiple calculations
    {   // scope for bulk broadcast
        ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
        SCTAB nTab2 = rRange.aEnd.Tab();
        for (SCTAB i = rRange.aStart.Tab(); i <= nTab2 && i < GetTableCount(); i++)
            if (maTabs[i]) maTabs[i]->SetDirty( rRange,
                    (bIncludeEmptyCells ? ScColumn::BROADCAST_BROADCASTERS : ScColumn::BROADCAST_DATA_POSITIONS));

        /* TODO: this now also notifies conditional formatting and does a UNO
         * broadcast, which wasn't done here before. Is that an actually
         * desired side effect, or should we come up with a method that
         * doesn't? */
        if (bIncludeEmptyCells)
            BroadcastCells( rRange, SfxHintId::ScDataChanged, false);
    }
    SetAutoCalc( bOldAutoCalc );
}

void ScDocument::SetTableOpDirty( const ScRange& rRange )
{
    bool bOldAutoCalc = GetAutoCalc();
    bAutoCalc = false;      // no multiple recalculation
    SCTAB nTab2 = rRange.aEnd.Tab();
    for (SCTAB i = rRange.aStart.Tab(); i <= nTab2 && i < GetTableCount(); i++)
        if (maTabs[i]) maTabs[i]->SetTableOpDirty( rRange );
    SetAutoCalc( bOldAutoCalc );
}

void ScDocument::InterpretDirtyCells( const ScRangeList& rRanges )
{
    if (!GetAutoCalc())
        return;

    PrepareFormulaCalc();

    for (size_t nPos=0, nRangeCount = rRanges.size(); nPos < nRangeCount; nPos++)
    {
        const ScRange& rRange = rRanges[nPos];
        for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
        {
            ScTable* pTab = FetchTable(nTab);
            if (!pTab)
                return;

            pTab->InterpretDirtyCells(
                rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
        }
    }

    ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
    mpFormulaGroupCxt.reset();
}

bool ScDocument::InterpretCellsIfNeeded( const ScRangeList& rRanges )
{
    bool allInterpreted = true;
    for (size_t nPos=0, nRangeCount = rRanges.size(); nPos < nRangeCount; nPos++)
    {
        const ScRange& rRange = rRanges[nPos];
        for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
        {
            ScTable* pTab = FetchTable(nTab);
            if (!pTab)
                break;

            if( !pTab->InterpretCellsIfNeeded(
                rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()))
            {
                allInterpreted = false;
            }
        }
    }
    return allInterpreted;
}

void ScDocument::AddTableOpFormulaCell( ScFormulaCell* pCell )
{
    if (m_TableOpList.empty())
        return;

    ScInterpreterTableOpParams *const p = m_TableOpList.back();
    if ( p->bCollectNotifications )
    {
        if ( p->bRefresh )
        {   // refresh pointers only
            p->aNotifiedFormulaCells.push_back( pCell );
        }
        else
        {   // init both, address and pointer
            p->aNotifiedFormulaCells.push_back( pCell );
            p->aNotifiedFormulaPos.push_back( pCell->aPos );
        }
    }
}

void ScDocument::CalcAll()
{
    PrepareFormulaCalc();
    ClearLookupCaches();    // Ensure we don't deliver zombie data.
    sc::AutoCalcSwitch aSwitch(*this, true);
    for (const auto& a : maTabs)
    {
        if (a)
            a->SetDirtyVar();
    }
    for (const auto& a : maTabs)
    {
        if (a)
            a->CalcAll();
    }
    ClearFormulaTree();

    // In eternal hard recalc state caches were not added as listeners,
    // invalidate them so the next non-CalcAll() normal lookup will not be
    // presented with outdated data.
    if (GetHardRecalcState() == HardRecalcState::ETERNAL)
        ClearLookupCaches();
}

void ScDocument::CompileAll()
{
    sc::CompileFormulaContext aCxt(*this);
    for (const auto& a : maTabs)
    {
        if (a)
            a->CompileAll(aCxt);
    }

    sc::SetFormulaDirtyContext aFormulaDirtyCxt;
    SetAllFormulasDirty(aFormulaDirtyCxt);
}

void ScDocument::CompileXML()
{
    bool bOldAutoCalc = GetAutoCalc();
    SetAutoCalc( false );
    ScProgress aProgress( GetDocumentShell(), ScResId(
                STR_PROGRESS_CALCULATING ), GetXMLImportedFormulaCount(), true );

    sc::CompileFormulaContext aCxt(*this);

    // set AutoNameCache to speed up automatic name lookup
    OSL_ENSURE( !pAutoNameCache, "AutoNameCache already set" );
    pAutoNameCache.reset( new ScAutoNameCache( *this ) );

    if (pRangeName)
        pRangeName->CompileUnresolvedXML(aCxt);

    std::for_each(maTabs.begin(), maTabs.end(),
        [&](ScTableUniquePtr & pTab)
        {
            if (pTab)
                pTab->CompileXML(aCxt, aProgress);
        }
    );
    StartAllListeners();

    pAutoNameCache.reset();  // valid only during CompileXML, where cell contents don't change

    if ( pValidationList )
    {
        ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
        pValidationList->CompileXML();
    }

    // Track all formula cells that were appended to the FormulaTrack during
    // import or CompileXML().
    TrackFormulas();

    SetAutoCalc( bOldAutoCalc );
}

bool ScDocument::CompileErrorCells(FormulaError nErrCode)
{
    bool bCompiled = false;
    sc::CompileFormulaContext aCxt(*this);
    for (const auto& pTab : maTabs)
    {
        if (pTab && pTab->CompileErrorCells(aCxt, nErrCode))
            bCompiled = true;
    }

    return bCompiled;
}

void ScDocument::CalcAfterLoad( bool bStartListening )
{
    if (bIsClip)    // Excel data is loaded from the Clipboard to a Clip-Doc
        return;     // the calculation is then only performed when inserting into the real document

    bCalcingAfterLoad = true;
    sc::CompileFormulaContext aCxt(*this);
    {
        for (const auto& pTable : maTabs)
        {
            if (pTable)
                pTable->CalcAfterLoad(aCxt, bStartListening);
        }
        for (const auto& pTable : maTabs)
        {
            if (pTable)
                pTable->SetDirtyAfterLoad();
        }
    }
    bCalcingAfterLoad = false;

    SetDetectiveDirty(false);   // No real changes yet

    // #i112436# If formula cells are already dirty, they don't broadcast further changes.
    // So the source ranges of charts must be interpreted even if they are not visible,
    // similar to ScMyShapeResizer::CreateChartListener for loading own files (i104899).
    if (pChartListenerCollection)
    {
        const ScChartListenerCollection::ListenersType& rListeners = pChartListenerCollection->getListeners();
        for (auto const& it : rListeners)
        {
            const ScChartListener *const p = it.second.get();
            InterpretDirtyCells(*p->GetRangeList());
        }
    }
}

FormulaError ScDocument::GetErrCode( const ScAddress& rPos ) const
{
    SCTAB nTab = rPos.Tab();
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetErrCode( rPos );
    return FormulaError::NONE;
}

void ScDocument::ResetChanged( const ScRange& rRange )
{
    SCTAB nTabSize = GetTableCount();
    SCTAB nTab1 = rRange.aStart.Tab();
    SCTAB nTab2 = rRange.aEnd.Tab();
    for (SCTAB nTab = nTab1; nTab1 <= nTab2 && nTab < nTabSize; ++nTab)
        if (maTabs[nTab])
            maTabs[nTab]->ResetChanged(rRange);
}

// Column widths / Row heights   --------------------------------------

void ScDocument::SetColWidth( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetColWidth(nCol, nNewWidth);
}

void ScDocument::SetColWidthOnly( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetColWidthOnly(nCol, nNewWidth);
}

void ScDocument::SetRowHeight( SCROW nRow, SCTAB nTab, sal_uInt16 nNewHeight )
{
   if (ScTable* pTable = FetchTable(nTab))
        pTable->SetRowHeight(nRow, nNewHeight);
}

void ScDocument::SetRowHeightRange( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetRowHeightRange(nStartRow, nEndRow, nNewHeight, 1.0, true);
}

void ScDocument::SetRowHeightOnly( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetRowHeightOnly( nStartRow, nEndRow, nNewHeight );
}

void ScDocument::SetManualHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bManual )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetManualHeight( nStartRow, nEndRow, bManual );
}

sal_uInt16 ScDocument::GetColWidth( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetColWidth( nCol, bHiddenAsZero );
    OSL_FAIL("wrong table number");
    return 0;
}

tools::Long ScDocument::GetColWidth( SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetColWidth(nStartCol, nEndCol);
    return 0;
}

sal_uInt16 ScDocument::GetOriginalWidth( SCCOL nCol, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetOriginalWidth( nCol );
    OSL_FAIL("wrong table number");
    return 0;
}

sal_uInt16 ScDocument::GetCommonWidth( SCCOL nEndCol, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetCommonWidth( nEndCol );
    OSL_FAIL("Wrong table number");
    return 0;
}

sal_uInt16 ScDocument::GetOriginalHeight( SCROW nRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetOriginalHeight( nRow );
    OSL_FAIL("Wrong table number");
    return 0;
}

sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetRowHeight( nRow, nullptr, nullptr, bHiddenAsZero );
    OSL_FAIL("Wrong sheet number");
    return 0;
}

sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, SCROW* pStartRow, SCROW* pEndRow, bool bHiddenAsZero ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetRowHeight( nRow, pStartRow, pEndRow, bHiddenAsZero );
    OSL_FAIL("Wrong sheet number");
    return 0;
}

tools::Long ScDocument::GetRowHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHiddenAsZero ) const
{
    if (nStartRow == nEndRow)
        return GetRowHeight( nStartRow, nTab, bHiddenAsZero );  // faster for a single row

    // check bounds because this method replaces former for(i=start;i<=end;++i) loops
    if (nStartRow > nEndRow)
        return 0;

    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetRowHeight( nStartRow, nEndRow, bHiddenAsZero );

    OSL_FAIL("wrong sheet number");
    return 0;
}

SCROW ScDocument::GetRowForHeight( SCTAB nTab, tools::Long nHeight ) const
{
    return maTabs[nTab]->GetRowForHeight(nHeight);
}

SCROW ScDocument::GetRowForHeightPixels( SCTAB nTab, SCROW nStartRow, tools::Long& rStartRowHeightPx, tools::Long nHeightPx, double fPPTY ) const
{
    return maTabs[nTab]->GetRowForHeightPixels(nStartRow, rStartRowHeightPx, nHeightPx, fPPTY);
}

tools::Long ScDocument::GetScaledRowHeight( SCROW nStartRow, SCROW nEndRow,
        SCTAB nTab, double fScale ) const
{
    // faster for a single row
    if (nStartRow == nEndRow)
        return static_cast<tools::Long>(GetRowHeight( nStartRow, nTab) * fScale);

    // check bounds because this method replaces former for(i=start;i<=end;++i) loops
    if (nStartRow > nEndRow)
        return 0;

    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetScaledRowHeight( nStartRow, nEndRow, fScale);

    OSL_FAIL("wrong sheet number");
    return 0;
}

SCROW ScDocument::GetHiddenRowCount( SCROW nRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetHiddenRowCount( nRow );
    OSL_FAIL("wrong table number");
    return 0;
}

tools::Long ScDocument::GetColOffset( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetColOffset( nCol, bHiddenAsZero );
    OSL_FAIL("wrong table number");
    return 0;
}

tools::Long ScDocument::GetRowOffset( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetRowOffset( nRow, bHiddenAsZero );
    OSL_FAIL("wrong table number");
    return 0;
}

sal_uInt16 ScDocument::GetOptimalColWidth( SCCOL nCol, SCTAB nTab, OutputDevice* pDev,
                                       double nPPTX, double nPPTY,
                                       double fZoomX, double fZoomY,
                                       bool bFormula, const ScMarkData* pMarkData,
                                       const ScColWidthParam* pParam )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetOptimalColWidth(nCol, pDev, nPPTX, nPPTY, fZoomX,
                                          fZoomY, bFormula, pMarkData, pParam);
    OSL_FAIL("wrong table number");
    return 0;
}

tools::Long ScDocument::GetNeededSize( SCCOL nCol, SCROW nRow, SCTAB nTab,
                                    OutputDevice* pDev,
                                    double nPPTX, double nPPTY,
                                    double fZoomX, double fZoomY,
                                    bool bWidth, bool bTotalSize, bool bInPrintTwips )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetNeededSize(nCol, nRow, pDev, nPPTX, nPPTY,
                                     fZoomX, fZoomY, bWidth, bTotalSize,
                                     bInPrintTwips);
    OSL_FAIL("wrong table number");
    return 0;
}

bool ScDocument::SetOptimalHeight( sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bApi )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->SetOptimalHeight(rCxt, nStartRow, nEndRow, bApi);
    return false;
}

void ScDocument::UpdateAllRowHeights( sc::RowHeightContext& rCxt, const ScMarkData* pTabMark )
{
    // one progress across all (selected) sheets

    sal_uInt64 nCellCount = 0;
    for (SCTAB nTab = 0; nTab < GetTableCount(); nTab++)
        if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) )
            nCellCount += maTabs[nTab]->GetWeightedCount();

    ScProgress aProgress( GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true );

    sal_uInt64 nProgressStart = 0;
    for (SCTAB nTab = 0; nTab < GetTableCount(); nTab++)
        if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) )
        {
            maTabs[nTab]->SetOptimalHeightOnly(rCxt, 0, MaxRow(), &aProgress, nProgressStart);
            maTabs[nTab]->SetDrawPageSize();
            nProgressStart += maTabs[nTab]->GetWeightedCount();
        }
}

// Column/Row - Flags   ----------------------------------------------

void ScDocument::ShowCol(SCCOL nCol, SCTAB nTab, bool bShow)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->ShowCol(nCol, bShow);
}

void ScDocument::ShowRow(SCROW nRow, SCTAB nTab, bool bShow)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->ShowRow(nRow, bShow);
}

void ScDocument::ShowRows(SCROW nRow1, SCROW nRow2, SCTAB nTab, bool bShow)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->ShowRows( nRow1, nRow2, bShow );
}

void ScDocument::SetRowFlags( SCROW nRow, SCTAB nTab, CRFlags nNewFlags )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetRowFlags( nRow, nNewFlags );
}

void ScDocument::SetRowFlags( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, CRFlags nNewFlags )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetRowFlags( nStartRow, nEndRow, nNewFlags );
}

CRFlags ScDocument::GetColFlags( SCCOL nCol, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetColFlags( nCol );
    OSL_FAIL("wrong table number");
    return CRFlags::NONE;
}

CRFlags ScDocument::GetRowFlags( SCROW nRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetRowFlags( nRow );
    OSL_FAIL("wrong table number");
    return CRFlags::NONE;
}

void ScDocument::GetAllRowBreaks(std::set<SCROW>& rBreaks, SCTAB nTab, bool bPage, bool bManual) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        pTable->GetAllRowBreaks(rBreaks, bPage, bManual);
}

void ScDocument::GetAllColBreaks(std::set<SCCOL>& rBreaks, SCTAB nTab, bool bPage, bool bManual) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        pTable->GetAllColBreaks(rBreaks, bPage, bManual);
}

ScBreakType ScDocument::HasRowBreak(SCROW nRow, SCTAB nTab) const
{
    ScBreakType nType = ScBreakType::NONE;
    if (const ScTable* pTable = FetchTable(nTab); pTable && ValidRow(nRow))
    {
        if (pTable->HasRowPageBreak(nRow))
            nType |= ScBreakType::Page;

        if (pTable->HasRowManualBreak(nRow))
            nType |= ScBreakType::Manual;
    }
    return nType;
}

ScBreakType ScDocument::HasColBreak(SCCOL nCol, SCTAB nTab) const
{
    ScBreakType nType = ScBreakType::NONE;

    if (const ScTable* pTable = FetchTable(nTab); pTable && ValidCol(nCol))
    {
        if (pTable->HasColPageBreak(nCol))
            nType |= ScBreakType::Page;

        if (pTable->HasColManualBreak(nCol))
            nType |= ScBreakType::Manual;
    }
    return nType;
}

void ScDocument::SetRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual)
{
    if (ScTable* pTable = FetchTable(nTab); pTable && ValidRow(nRow))
        pTable->SetRowBreak(nRow, bPage, bManual);
}

void ScDocument::SetColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual)
{
    if (ScTable* pTable = FetchTable(nTab); pTable && ValidCol(nCol))
        pTable->SetColBreak(nCol, bPage, bManual);
}

void ScDocument::RemoveRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual)
{
    if (ScTable* pTable = FetchTable(nTab); pTable && ValidRow(nRow))
        pTable->RemoveRowBreak(nRow, bPage, bManual);
}

void ScDocument::RemoveColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual)
{
    if (ScTable* pTable = FetchTable(nTab); pTable && ValidCol(nCol))
        pTable->RemoveColBreak(nCol, bPage, bManual);
}

Sequence<TablePageBreakData> ScDocument::GetRowBreakData(SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetRowBreakData();

    return Sequence<TablePageBreakData>();
}

bool ScDocument::RowHidden(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->RowHidden(nRow, pFirstRow, pLastRow);
    return false;
}

bool ScDocument::HasHiddenRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->HasHiddenRows(nStartRow, nEndRow);
    return false;
}

bool ScDocument::ColHidden(SCCOL nCol, SCTAB nTab, SCCOL* pFirstCol, SCCOL* pLastCol) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->ColHidden(nCol, pFirstCol, pLastCol);

    if (pFirstCol)
        *pFirstCol = nCol;
    if (pLastCol)
        *pLastCol = nCol;
    return false;
}

void ScDocument::SetRowHidden(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHidden)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetRowHidden(nStartRow, nEndRow, bHidden);
}

void ScDocument::SetColHidden(SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab, bool bHidden)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetColHidden(nStartCol, nEndCol, bHidden);
}

SCROW ScDocument::FirstVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->FirstVisibleRow(nStartRow, nEndRow);
    return 0;
}

SCROW ScDocument::LastVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->LastVisibleRow(nStartRow, nEndRow);
    return ::std::numeric_limits<SCROW>::max();
}

SCROW ScDocument::CountVisibleRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->CountVisibleRows(nStartRow, nEndRow);
    return 0;
}

bool ScDocument::RowFiltered(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->getFilterData().rowFiltered(nRow, pFirstRow, pLastRow);
    return false;
}

bool ScDocument::HasFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->getFilterData().hasFilteredRows(nStartRow, nEndRow);
    return false;
}

bool ScDocument::ColFiltered(SCCOL nCol, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->getFilterData().columnFiltered(nCol);
    return false;
}

void ScDocument::SetRowFiltered(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bFiltered)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->getFilterData().setRowFiltered(nStartRow, nEndRow, bFiltered);
}

SCROW ScDocument::FirstNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->getFilterData().firstNonFilteredRow(nStartRow, nEndRow);
    return std::numeric_limits<SCROW>::max();
}

SCROW ScDocument::LastNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->getFilterData().lastNonFilteredRow(nStartRow, nEndRow);
    return std::numeric_limits<SCROW>::max();
}

SCROW ScDocument::CountNonFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->getFilterData().countNonFilteredRows(nStartRow, nEndRow);
    return 0;
}

bool ScDocument::IsManualRowHeight(SCROW nRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->IsManualRowHeight(nRow);
    return false;
}

void ScDocument::SyncColRowFlags()
{
    for (const auto& pTable : maTabs)
    {
        if (pTable)
            pTable->SyncColRowFlags();
    }
}

SCROW ScDocument::GetLastFlaggedRow( SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetLastFlaggedRow();
    return 0;
}

SCCOL ScDocument::GetLastChangedColFlagsWidth( SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetLastChangedColFlagsWidth();
    return 0;
}

SCROW ScDocument::GetLastChangedRowFlagsWidth( SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetLastChangedRowFlagsWidth();
    return 0;
}

SCCOL ScDocument::GetNextDifferentChangedColFlagsWidth( SCTAB nTab, SCCOL nStart) const
{
    if (const ScTable* pTable = FetchTable(nTab))
    {
        CRFlags nStartFlags = pTable->GetColFlags(nStart);
        sal_uInt16 nStartWidth = pTable->GetOriginalWidth(nStart);
        for (SCCOL nCol : pTable->GetColumnsRange( nStart + 1, MaxCol()))
        {
            if (((nStartFlags & CRFlags::ManualBreak) != (pTable->GetColFlags(nCol) & CRFlags::ManualBreak)) ||
                (nStartWidth != pTable->GetOriginalWidth(nCol)) ||
                ((nStartFlags & CRFlags::Hidden) != (pTable->GetColFlags(nCol) & CRFlags::Hidden)) )
            {
                return nCol;
            }
        }
        return MaxCol()+1;
    }
    return 0;
}

SCROW ScDocument::GetNextDifferentChangedRowFlagsWidth( SCTAB nTab, SCROW nStart) const
{
    const ScTable* pTable = FetchTable(nTab);
    if (!pTable)
        return 0;

    const ScBitMaskCompressedArray<SCROW, CRFlags>* pRowFlagsArray = pTable->GetRowFlagsArray();
    if (!pRowFlagsArray)
        return 0;

    if (!pTable->mpRowHeights || !pTable->maFilterData.mpHiddenRows)
        return 0;

    size_t nIndex;          // ignored
    SCROW nFlagsEndRow;
    SCROW nHiddenEndRow;
    SCROW nHeightEndRow;
    CRFlags nFlags;
    bool bHidden;
    sal_uInt16 nHeight;
    CRFlags nStartFlags = nFlags = pRowFlagsArray->GetValue( nStart, nIndex, nFlagsEndRow);
    bool bStartHidden = bHidden = pTable->RowHidden( nStart, nullptr, &nHiddenEndRow);
    sal_uInt16 nStartHeight = nHeight = pTable->GetRowHeight( nStart, nullptr, &nHeightEndRow, false);
    SCROW nRow;
    while ((nRow = std::min( nHiddenEndRow, std::min( nFlagsEndRow, nHeightEndRow)) + 1) <= MaxRow())
    {
        if (nFlagsEndRow < nRow)
            nFlags = pRowFlagsArray->GetValue( nRow, nIndex, nFlagsEndRow);
        if (nHiddenEndRow < nRow)
            bHidden = pTable->RowHidden( nRow, nullptr, &nHiddenEndRow);
        if (nHeightEndRow < nRow)
            nHeight = pTable->GetRowHeight( nRow, nullptr, &nHeightEndRow, false);

        if (((nStartFlags & CRFlags::ManualBreak) != (nFlags & CRFlags::ManualBreak)) ||
            ((nStartFlags & CRFlags::ManualSize) != (nFlags & CRFlags::ManualSize)) ||
            (bStartHidden != bHidden) ||
            (nStartHeight != nHeight))
            return nRow;
    }

    return MaxRow()+1;
}

void ScDocument::GetColDefault( SCTAB nTab, SCCOL nCol, SCROW nLastRow, SCROW& nDefault)
{
    nDefault = 0;
    ScDocAttrIterator aDocAttrItr(*this, nTab, nCol, 0, nCol, nLastRow);
    SCCOL nColumn;
    SCROW nStartRow;
    SCROW nEndRow;
    const ScPatternAttr* pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow);
    if (nEndRow >= nLastRow)
        return;

    ScDefaultAttrMap aMap;
    while (pAttr)
    {
        auto aItr = aMap.find(pAttr);
        if (aItr == aMap.end())
        {
            ScDefaultAttr aAttr;
            aAttr.nCount = static_cast<SCSIZE>(nEndRow - nStartRow + 1);
            aAttr.nFirst = nStartRow;
            aMap.insert({ pAttr, aAttr});
        }
        else
        {
            aItr->second.nCount += static_cast<SCSIZE>(nEndRow - nStartRow + 1);
        }
        pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow);
    }
    auto aDefaultItr = aMap.begin();
    auto aItr = aDefaultItr;
    ++aItr;
    while (aItr != aMap.end())
    {
        // for entries with equal count, use the one with the lowest start row,
        // don't use the random order of pointer comparisons
        if ( aItr->second.nCount > aDefaultItr->second.nCount ||
             ( aItr->second.nCount == aDefaultItr->second.nCount && aItr->second.nFirst < aDefaultItr->second.nFirst ) )
            aDefaultItr = aItr;
        ++aItr;
    }
    nDefault = aDefaultItr->second.nFirst;
}

void ScDocument::StripHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->StripHidden( rX1, rY1, rX2, rY2 );
}

void ScDocument::ExtendHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->ExtendHidden( rX1, rY1, rX2, rY2 );
}

//  Attribute   ----------------------------------------------------------

const SfxPoolItem& ScDocument::GetAttr(SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich) const
{
    if (const ScTable* pTable = FetchTable(nTab))
    {
        const SfxPoolItem* pTemp = pTable->GetAttr( nCol, nRow, nWhich );
        if (pTemp)
            return *pTemp;
        else
        {
            OSL_FAIL( "Attribute Null" );
        }
    }
    return mxPoolHelper->GetDocPool()->GetUserOrPoolDefaultItem(nWhich);
}

const SfxPoolItem& ScDocument::GetAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich, SCROW& nStartRow, SCROW& nEndRow ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
    {
        const SfxPoolItem* pTemp = pTable->GetAttr( nCol, nRow, nWhich, nStartRow, nEndRow );
        if (pTemp)
            return *pTemp;
        else
        {
            OSL_FAIL( "Attribute Null" );
        }
    }
    return mxPoolHelper->GetDocPool()->GetUserOrPoolDefaultItem(nWhich);
}

const SfxPoolItem& ScDocument::GetAttr(const ScAddress& rPos, sal_uInt16 nWhich) const
{
    return GetAttr(rPos.Col(), rPos.Row(), rPos.Tab(), nWhich);
}

const ScPatternAttr* ScDocument::GetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetPattern( nCol, nRow );
    return nullptr;
}

const ScPatternAttr* ScDocument::GetPattern( const ScAddress& rPos ) const
{
    return GetPattern(rPos.Col(), rPos.Row(), rPos.Tab());
}

const ScPatternAttr* ScDocument::GetMostUsedPattern( SCCOL nCol, SCROW nStartRow, SCROW nEndRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetMostUsedPattern(nCol, nStartRow, nEndRow);
    return nullptr;
}

void ScDocument::ApplyAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, const SfxPoolItem& rAttr )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->ApplyAttr( nCol, nRow, rAttr );
}

void ScDocument::ApplyPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScPatternAttr& rAttr )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->ApplyPattern(nCol, nRow, rAttr);
}

void ScDocument::ApplyPatternArea( SCCOL nStartCol, SCROW nStartRow,
                        SCCOL nEndCol, SCROW nEndRow,
                        const ScMarkData& rMark,
                        const ScPatternAttr& rAttr,
                        ScEditDataArray* pDataArray,
                        bool* const pIsChanged )
{
    SCTAB nMax = GetTableCount();
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;
        if (maTabs[rTab])
            maTabs[rTab]->ApplyPatternArea( nStartCol, nStartRow, nEndCol, nEndRow, rAttr, pDataArray, pIsChanged );
    }
}

void ScDocument::ApplyPatternAreaTab( SCCOL nStartCol, SCROW nStartRow,
                        SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScPatternAttr& rAttr )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->ApplyPatternArea(nStartCol, nStartRow, nEndCol, nEndRow, rAttr);
}

void ScDocument::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange,
        const ScMarkData& rMark, const ScPatternAttr& rPattern, SvNumFormatType nNewType )
{
    SCTAB nMax = GetTableCount();
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;
        if (maTabs[rTab])
            maTabs[rTab]->ApplyPatternIfNumberformatIncompatible( rRange, rPattern, nNewType );
    }
}

void ScDocument::AddCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->AddCondFormatData(rRange, nIndex);
}

void ScDocument::RemoveCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->RemoveCondFormatData(rRange, nIndex);
}

void ScDocument::ApplyStyle( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScStyleSheet& rStyle)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->ApplyStyle(nCol, nRow, &rStyle);
}

void ScDocument::ApplyStyleArea( SCCOL nStartCol, SCROW nStartRow,
                        SCCOL nEndCol, SCROW nEndRow,
                        const ScMarkData& rMark,
                        const ScStyleSheet& rStyle)
{
    SCTAB nMax = GetTableCount();
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;
        if (maTabs[rTab])
            maTabs[rTab]->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle );
    }
}

void ScDocument::ApplyStyleAreaTab( SCCOL nStartCol, SCROW nStartRow,
                        SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScStyleSheet& rStyle)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle );
}

void ScDocument::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark)
{
    // ApplySelectionStyle needs multi mark
    if ( rMark.IsMarked() && !rMark.IsMultiMarked() )
    {
        const ScRange& aRange = rMark.GetMarkArea();
        ApplyStyleArea( aRange.aStart.Col(), aRange.aStart.Row(),
                          aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rStyle );
    }
    else
    {
        SCTAB nMax = GetTableCount();
        for (const auto& rTab : rMark)
        {
            if (rTab >= nMax)
                break;
            if ( maTabs[rTab] )
                maTabs[rTab]->ApplySelectionStyle( rStyle, rMark );
        }
    }
}

void ScDocument::ApplySelectionLineStyle( const ScMarkData& rMark,
                    const SvxBorderLine* pLine, bool bColorOnly )
{
    if ( bColorOnly && !pLine )
        return;

    SCTAB nMax = GetTableCount();
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;
        if (maTabs[rTab])
            maTabs[rTab]->ApplySelectionLineStyle( rMark, pLine, bColorOnly );
    }
}

const ScStyleSheet* ScDocument::GetStyle( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetStyle(nCol, nRow);
    return nullptr;
}

const ScStyleSheet* ScDocument::GetSelectionStyle( const ScMarkData& rMark ) const
{
    bool    bEqual = true;
    bool    bFound;

    const ScStyleSheet* pStyle = nullptr;
    const ScStyleSheet* pNewStyle;

    if ( rMark.IsMultiMarked() )
    {
        SCTAB nMax = GetTableCount();
        for (const auto& rTab : rMark)
        {
            if (rTab >= nMax)
                break;

            if (maTabs[rTab])
            {
                pNewStyle = maTabs[rTab]->GetSelectionStyle( rMark, bFound );
                if (bFound)
                {
                    if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
                        bEqual = false;                            // different
                    pStyle = pNewStyle;
                }
            }
        }
    }
    if ( rMark.IsMarked() )
    {
        const ScRange& aRange = rMark.GetMarkArea();
        for (SCTAB i = aRange.aStart.Tab(); i <= aRange.aEnd.Tab() && bEqual && i < GetTableCount(); i++)
            if (maTabs[i] && rMark.GetTableSelect(i))
            {
                pNewStyle = maTabs[i]->GetAreaStyle( bFound,
                                        aRange.aStart.Col(), aRange.aStart.Row(),
                                        aRange.aEnd.Col(),   aRange.aEnd.Row()   );
                if (bFound)
                {
                    if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
                        bEqual = false;                                // different
                    pStyle = pNewStyle;
                }
            }
    }

    return bEqual ? pStyle : nullptr;
}

void ScDocument::StyleSheetChanged( const SfxStyleSheetBase* pStyleSheet, bool bRemoved,
                                    OutputDevice* pDev,
                                    double nPPTX, double nPPTY,
                                    double fZoomX, double fZoomY )
{
    for (const auto& rTab : maTabs)
    {
        if (rTab)
        {
            rTab->StyleSheetChanged(pStyleSheet, bRemoved, pDev, nPPTX, nPPTY, fZoomX, fZoomY);
        }
    }
}

bool ScDocument::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const
{
    if ( bStyleSheetUsageInvalid || rStyle.GetUsage() == ScStyleSheet::Usage::UNKNOWN )
    {
        SfxStyleSheetIterator aIter( mxPoolHelper->GetStylePool(),
                    SfxStyleFamily::Para );
        for ( const SfxStyleSheetBase* pStyle = aIter.First(); pStyle;
                                       pStyle = aIter.Next() )
        {
            if (pStyle->isScStyleSheet())
            {
                const ScStyleSheet* pScStyle = static_cast<const ScStyleSheet*>( pStyle  );
                pScStyle->SetUsage( ScStyleSheet::Usage::NOTUSED );
            }
        }

        bool bIsUsed = false;

        for (const auto& pTable : maTabs)
        {
            if (pTable && pTable->IsStyleSheetUsed(rStyle))
                bIsUsed = true;
        }

        bStyleSheetUsageInvalid = false;

        return bIsUsed;
    }

    return rStyle.GetUsage() == ScStyleSheet::Usage::USED;
}

bool ScDocument::ApplyFlagsTab( SCCOL nStartCol, SCROW nStartRow,
                        SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->ApplyFlags(nStartCol, nStartRow, nEndCol, nEndRow, nFlags);

    OSL_FAIL("ApplyFlags: wrong table");
    return false;
}

bool ScDocument::RemoveFlagsTab( SCCOL nStartCol, SCROW nStartRow,
                        SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->RemoveFlags(nStartCol, nStartRow, nEndCol, nEndRow, nFlags);

    OSL_FAIL("RemoveFlags: wrong table");
    return false;
}

void ScDocument::SetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const CellAttributeHolder& rHolder )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetPattern(nCol, nRow, rHolder);
}

void ScDocument::SetPattern( const ScAddress& rPos, const CellAttributeHolder& rHolder )
{
    SetPattern(rPos.Col(), rPos.Row(), rPos.Tab(), rHolder);
}

void ScDocument::SetPattern( const ScAddress& rPos, const ScPatternAttr& rAttr )
{
    SCTAB nTab = rPos.Tab();
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetPattern(rPos, rAttr);
}

std::unique_ptr<ScPatternAttr> ScDocument::CreateSelectionPattern( const ScMarkData& rMark, bool bDeep )
{
    ScMergePatternState aState;

    if ( rMark.IsMultiMarked() )                                // multi selection
    {
        SCTAB nMax = GetTableCount();
        for (const auto& rTab : rMark)
        {
            if (rTab >= nMax)
                break;
            if (maTabs[rTab])
                maTabs[rTab]->MergeSelectionPattern( aState, rMark, bDeep );
        }
    }
    if ( rMark.IsMarked() )                                     // single selection
    {
        const ScRange& aRange = rMark.GetMarkArea();
        SCTAB nMax = GetTableCount();
        for (const auto& rTab : rMark)
        {
            if (rTab >= nMax)
                break;
            if (maTabs[rTab])
                maTabs[rTab]->MergePatternArea( aState,
                                aRange.aStart.Col(), aRange.aStart.Row(),
                                aRange.aEnd.Col(), aRange.aEnd.Row(), bDeep );
        }
    }

    OSL_ENSURE( aState.pItemSet, "SelectionPattern Null" );
    if (aState.pItemSet)
    {
        std::unique_ptr<ScPatternAttr> pPattern(new ScPatternAttr(getCellAttributeHelper(), &aState.pItemSet.value()));
        if (aState.mbValidPatternId)
            pPattern->SetPAKey(aState.mnPatternId);

        return pPattern;
    }
    else
        return std::unique_ptr<ScPatternAttr>(new ScPatternAttr(getCellAttributeHelper())); // empty
}

const ScPatternAttr* ScDocument::GetSelectionPattern( const ScMarkData& rMark )
{
    pSelectionAttr = CreateSelectionPattern( rMark );
    return pSelectionAttr.get();
}

void ScDocument::GetSelectionFrame( const ScMarkData& rMark,
                                    SvxBoxItem&     rLineOuter,
                                    SvxBoxInfoItem& rLineInner )
{
    rLineOuter.SetLine(nullptr, SvxBoxItemLine::TOP);
    rLineOuter.SetLine(nullptr, SvxBoxItemLine::BOTTOM);
    rLineOuter.SetLine(nullptr, SvxBoxItemLine::LEFT);
    rLineOuter.SetLine(nullptr, SvxBoxItemLine::RIGHT);
    rLineOuter.SetAllDistances(0);

    rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::HORI);
    rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::VERT);
    rLineInner.SetTable(true);
    rLineInner.SetDist(true);
    rLineInner.SetMinDist(false);

    ScLineFlags aFlags;

    if( rMark.IsMultiMarked() )
    {
        ScRangeList aRangeList;
        rMark.FillRangeListWithMarks( &aRangeList, false );
        size_t nRangeCount = aRangeList.size();
        bool bMultipleRows = false, bMultipleCols = false;
        for( size_t nRangeIdx = 0; nRangeIdx < nRangeCount; ++nRangeIdx )
        {
            const ScRange & rRange = aRangeList[ nRangeIdx ];
            bMultipleRows = ( bMultipleRows || ( rRange.aStart.Row() != rRange.aEnd.Row() ) );
            bMultipleCols = ( bMultipleCols || ( rRange.aStart.Col() != rRange.aEnd.Col() ) );
            SCTAB nMax = GetTableCount();
            for (const auto& rTab : rMark)
            {
                if (rTab >= nMax)
                    break;

                if (maTabs[rTab])
                    maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags,
                                          rRange.aStart.Col(), rRange.aStart.Row(),
                                          rRange.aEnd.Col(),   rRange.aEnd.Row() );
            }
        }
        rLineInner.EnableHor( bMultipleRows );
        rLineInner.EnableVer( bMultipleCols );
    }
    else if( rMark.IsMarked() )
    {
        const ScRange& aRange = rMark.GetMarkArea();
        rLineInner.EnableHor( aRange.aStart.Row() != aRange.aEnd.Row() );
        rLineInner.EnableVer( aRange.aStart.Col() != aRange.aEnd.Col() );
        SCTAB nMax = GetTableCount();
        for (const auto& rTab : rMark)
        {
            if (rTab >= nMax)
                break;

            if (maTabs[rTab])
                maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags,
                                          aRange.aStart.Col(), aRange.aStart.Row(),
                                          aRange.aEnd.Col(),   aRange.aEnd.Row() );
        }
    }

        // Evaluate don't care Status

    rLineInner.SetValid( SvxBoxInfoItemValidFlags::LEFT,   ( aFlags.nLeft != SC_LINE_DONTCARE ) );
    rLineInner.SetValid( SvxBoxInfoItemValidFlags::RIGHT,  ( aFlags.nRight != SC_LINE_DONTCARE ) );
    rLineInner.SetValid( SvxBoxInfoItemValidFlags::TOP,    ( aFlags.nTop != SC_LINE_DONTCARE ) );
    rLineInner.SetValid( SvxBoxInfoItemValidFlags::BOTTOM, ( aFlags.nBottom != SC_LINE_DONTCARE ) );
    rLineInner.SetValid( SvxBoxInfoItemValidFlags::HORI,   ( aFlags.nHori != SC_LINE_DONTCARE ) );
    rLineInner.SetValid( SvxBoxInfoItemValidFlags::VERT,   ( aFlags.nVert != SC_LINE_DONTCARE ) );
}

bool ScDocument::HasAttrib( SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
                            SCCOL nCol2, SCROW nRow2, SCTAB nTab2, HasAttrFlags nMask ) const
{
    for (SCTAB i = nTab1; i <= nTab2 && i < GetTableCount(); i++)
        if (maTabs[i])
        {
            if ( nMask & HasAttrFlags::RightOrCenter )
            {
                //  On a RTL sheet, don't start to look for the default left value
                //  (which is then logically right), instead always assume true.
                //  That way, ScAttrArray::HasAttrib doesn't have to handle RTL sheets.

                if ( IsLayoutRTL(i) )
                    return true;
            }

            if( maTabs[i]->HasAttrib( nCol1, nRow1, nCol2, nRow2, nMask ))
                return true;
        }

    return false;
}

bool ScDocument::HasAttrib( SCCOL nCol, SCROW nRow, SCTAB nTab, HasAttrFlags nMask, SCROW* nStartRow, SCROW* nEndRow ) const
{
    if (nTab >= GetTableCount())
    {
        if( nStartRow )
            *nStartRow = 0;
        if( nEndRow )
            *nEndRow = MaxRow();
        return false;
    }

    if ( nMask & HasAttrFlags::RightOrCenter )
    {
        //  On a RTL sheet, don't start to look for the default left value
        //  (which is then logically right), instead always assume true.
        //  That way, ScAttrArray::HasAttrib doesn't have to handle RTL sheets.

        if ( IsLayoutRTL(nTab) )
        {
            if( nStartRow )
                *nStartRow = 0;
            if( nEndRow )
                *nEndRow = MaxRow();
            return true;
        }
    }

    return maTabs[nTab]->HasAttrib( nCol, nRow, nMask, nStartRow, nEndRow );
}

bool ScDocument::HasAttrib( const ScRange& rRange, HasAttrFlags nMask ) const
{
    return HasAttrib( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(),
                      rRange.aEnd.Col(),   rRange.aEnd.Row(),   rRange.aEnd.Tab(),
                      nMask );
}

void ScDocument::FindMaxRotCol( SCTAB nTab, RowInfo* pRowInfo, SCSIZE nArrCount,
                                SCCOL nX1, SCCOL nX2 ) const
{
    if (HasTable(nTab))
    {
        maTabs[nTab]->FindMaxRotCol(pRowInfo, nArrCount, nX1, nX2);
        return;
    }
    OSL_FAIL("FindMaxRotCol: wrong table");
}

void ScDocument::GetBorderLines( SCCOL nCol, SCROW nRow, SCTAB nTab,
                        const SvxBorderLine** ppLeft, const SvxBorderLine** ppTop,
                        const SvxBorderLine** ppRight, const SvxBorderLine** ppBottom ) const
{
    //TODO: consider page limits for printing !!!!!

    const SvxBoxItem* pThisAttr = GetEffItem( nCol, nRow, nTab, ATTR_BORDER );
    OSL_ENSURE(pThisAttr,"where is the attribute?");

    const SvxBorderLine* pLeftLine   = pThisAttr->GetLeft();
    const SvxBorderLine* pTopLine    = pThisAttr->GetTop();
    const SvxBorderLine* pRightLine  = pThisAttr->GetRight();
    const SvxBorderLine* pBottomLine = pThisAttr->GetBottom();

    if ( nCol > 0 )
    {
        const SvxBorderLine* pOther = GetEffItem( o3tl::sanitizing_dec(nCol), nRow, nTab, ATTR_BORDER )->GetRight();
        if ( ScHasPriority( pOther, pLeftLine ) )
            pLeftLine = pOther;
    }
    if ( nRow > 0 )
    {
        const SvxBorderLine* pOther = GetEffItem( nCol, nRow-1, nTab, ATTR_BORDER )->GetBottom();
        if ( ScHasPriority( pOther, pTopLine ) )
            pTopLine = pOther;
    }
    if ( nCol < MaxCol() )
    {
        const SvxBorderLine* pOther = GetEffItem( o3tl::sanitizing_inc(nCol), nRow, nTab, ATTR_BORDER )->GetLeft();
        if ( ScHasPriority( pOther, pRightLine ) )
            pRightLine = pOther;
    }
    if ( nRow < MaxRow() )
    {
        const SvxBorderLine* pOther = GetEffItem( nCol, nRow+1, nTab, ATTR_BORDER )->GetTop();
        if ( ScHasPriority( pOther, pBottomLine ) )
            pBottomLine = pOther;
    }

    if (ppLeft)
        *ppLeft = pLeftLine;
    if (ppTop)
        *ppTop = pTopLine;
    if (ppRight)
        *ppRight = pRightLine;
    if (ppBottom)
        *ppBottom = pBottomLine;
}

bool ScDocument::IsNotesBlockEmpty(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
                                   SCTAB nTab) const
{
    if (HasTable(nTab))
        return maTabs[nTab]->IsNotesBlockEmpty(nStartCol, nStartRow, nEndCol, nEndRow);
    OSL_FAIL("wrong table number");
    return false;
}

bool ScDocument::IsBlockEmpty(SCCOL nStartCol, SCROW nStartRow,
                              SCCOL nEndCol, SCROW nEndRow, SCTAB nTab) const
{
    if (HasTable(nTab))
        return maTabs[nTab]->IsBlockEmpty(nStartCol, nStartRow, nEndCol, nEndRow);
    OSL_FAIL("wrong table number");
    return false;
}

void ScDocument::LockTable(SCTAB nTab)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->LockTable();
    else
    {
        OSL_FAIL("wrong table number");
    }
}

void ScDocument::UnlockTable(SCTAB nTab)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->UnlockTable();
    else
    {
        OSL_FAIL("wrong table number");
    }
}

bool ScDocument::IsBlockEditable( SCTAB nTab, SCCOL nStartCol, SCROW nStartRow,
                                        SCCOL nEndCol, SCROW nEndRow,
                                        bool* pOnlyNotBecauseOfMatrix /* = NULL */,
                                        bool bNoMatrixAtAll ) const
{
    // import into read-only document is possible
    if (!bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly())
    {
        if ( pOnlyNotBecauseOfMatrix )
            *pOnlyNotBecauseOfMatrix = false;
        return false;
    }

    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->IsBlockEditable(nStartCol, nStartRow, nEndCol, nEndRow,
                                       pOnlyNotBecauseOfMatrix, bNoMatrixAtAll);

    OSL_FAIL("wrong table number");
    if ( pOnlyNotBecauseOfMatrix )
        *pOnlyNotBecauseOfMatrix = false;
    return false;
}

bool ScDocument::IsSelectionEditable( const ScMarkData& rMark,
            bool* pOnlyNotBecauseOfMatrix /* = NULL */ ) const
{
    // import into read-only document is possible
    if ( !bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly() )
    {
        if ( pOnlyNotBecauseOfMatrix )
            *pOnlyNotBecauseOfMatrix = false;
        return false;
    }

    const ScRange& aRange = rMark.GetMarkArea();

    bool bOk = true;
    bool bMatrix = ( pOnlyNotBecauseOfMatrix != nullptr );
    SCTAB nMax = GetTableCount();
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;

        if ( maTabs[rTab] )
        {
            if (rMark.IsMarked())
            {
                if ( !maTabs[rTab]->IsBlockEditable( aRange.aStart.Col(),
                        aRange.aStart.Row(), aRange.aEnd.Col(),
                        aRange.aEnd.Row(), pOnlyNotBecauseOfMatrix ) )
                {
                    bOk = false;
                    if ( pOnlyNotBecauseOfMatrix )
                        bMatrix = *pOnlyNotBecauseOfMatrix;
                }
            }
            if (rMark.IsMultiMarked())
            {
                if ( !maTabs[rTab]->IsSelectionEditable( rMark, pOnlyNotBecauseOfMatrix ) )
                {
                    bOk = false;
                    if ( pOnlyNotBecauseOfMatrix )
                        bMatrix = *pOnlyNotBecauseOfMatrix;
                }
            }
        }

        if (!bOk && !bMatrix)
            break;
    }

    if ( pOnlyNotBecauseOfMatrix )
        *pOnlyNotBecauseOfMatrix = ( !bOk && bMatrix );

    return bOk;
}

bool ScDocument::HasSelectedBlockMatrixFragment( SCCOL nStartCol, SCROW nStartRow,
                                SCCOL nEndCol, SCROW nEndRow,
                                const ScMarkData& rMark ) const
{
    bool bOk = true;
    SCTAB nMax = GetTableCount();
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;

        if (maTabs[rTab] && maTabs[rTab]->HasBlockMatrixFragment( nStartCol, nStartRow, nEndCol, nEndRow ))
            bOk = false;

        if (!bOk)
            break;
    }

    return !bOk;
}

bool ScDocument::GetMatrixFormulaRange( const ScAddress& rCellPos, ScRange& rMatrix )
{
    //  if rCell is part of a matrix formula, return its complete range

    ScFormulaCell* pFCell = GetFormulaCell(rCellPos);
    if (!pFCell)
        // not a formula cell.  Bail out.
        return false;

    ScAddress aOrigin = rCellPos;
    if (!pFCell->GetMatrixOrigin(*this, aOrigin))
        // Failed to get the address of the matrix origin.
        return false;

    if (aOrigin != rCellPos)
    {
        pFCell = GetFormulaCell(aOrigin);
        if (!pFCell)
            // The matrix origin cell is not a formula cell !?  Something is up...
            return false;
    }

    SCCOL nSizeX;
    SCROW nSizeY;
    pFCell->GetMatColsRows(nSizeX, nSizeY);
    if (nSizeX <= 0 || nSizeY <= 0)
    {
        // GetMatrixEdge computes also dimensions of the matrix
        // if not already done (may occur if document is loaded
        // from old file format).
        // Needs an "invalid" initialized address.
        aOrigin.SetInvalid();
        pFCell->GetMatrixEdge(*this, aOrigin);
        pFCell->GetMatColsRows(nSizeX, nSizeY);
    }

    if (nSizeX <= 0 || nSizeY <= 0)
        // Matrix size is still invalid. Give up.
        return false;

    ScAddress aEnd( aOrigin.Col() + nSizeX - 1,
                    aOrigin.Row() + nSizeY - 1,
                    aOrigin.Tab() );

    rMatrix.aStart = aOrigin;
    rMatrix.aEnd = aEnd;

    return true;
}

void ScDocument::ExtendOverlapped( SCCOL& rStartCol, SCROW& rStartRow,
                                SCCOL nEndCol, SCROW nEndRow, SCTAB nTab ) const
{
    if ( ValidColRow(rStartCol,rStartRow) && ValidColRow(nEndCol,nEndRow) && ValidTab(nTab) )
    {
        if (const ScTable* pTable = FetchTable(nTab))
        {
            SCCOL nCol;
            SCCOL nOldCol = rStartCol;
            SCROW nOldRow = rStartRow;
            for (nCol=nOldCol; nCol<=nEndCol; nCol++)
                while (GetAttr(nCol, rStartRow, nTab, ATTR_MERGE_FLAG).IsVerOverlapped())
                    --rStartRow;

            //TODO: pass on ?

            const ScAttrArray& pAttrArray = pTable->GetColumnData(nOldCol).AttrArray();
            SCSIZE nIndex;
            if ( pAttrArray.Count() )
                pAttrArray.Search( nOldRow, nIndex );
            else
                nIndex = 0;
            SCROW nAttrPos = nOldRow;
            while (nAttrPos<=nEndRow)
            {
                OSL_ENSURE( nIndex < pAttrArray.Count(), "Wrong index in AttrArray" );

                bool bHorOverlapped;
                if ( pAttrArray.Count() )
                    bHorOverlapped = pAttrArray.mvData[nIndex].getScPatternAttr()->GetItem(ATTR_MERGE_FLAG).IsHorOverlapped();
                else
                    bHorOverlapped = getCellAttributeHelper().getDefaultCellAttribute().GetItem(ATTR_MERGE_FLAG).IsHorOverlapped();

                if ( bHorOverlapped )
                {
                    SCROW nEndRowSeg = (pAttrArray.Count()) ? pAttrArray.mvData[nIndex].nEndRow : MaxRow();
                    SCROW nLoopEndRow = std::min( nEndRow, nEndRowSeg );
                    for (SCROW nAttrRow = nAttrPos; nAttrRow <= nLoopEndRow; nAttrRow++)
                    {
                        SCCOL nTempCol = nOldCol;
                        do
                            --nTempCol;
                        while (GetAttr(nTempCol,nAttrRow,nTab,ATTR_MERGE_FLAG).IsHorOverlapped());
                        if (nTempCol < rStartCol)
                            rStartCol = nTempCol;
                    }
                }
                if ( pAttrArray.Count() )
                {
                    nAttrPos = pAttrArray.mvData[nIndex].nEndRow + 1;
                    ++nIndex;
                }
                else
                    nAttrPos = MaxRow() + 1;
            }
        }
    }
    else
    {
        OSL_FAIL("ExtendOverlapped: invalid range");
    }
}

void ScDocument::ExtendMergeSel( SCCOL nStartCol, SCROW nStartRow,
                              SCCOL& rEndCol, SCROW& rEndRow,
                              const ScMarkData& rMark, bool bRefresh )
{
    // use all selected sheets from rMark

    SCCOL nOldEndCol = rEndCol;
    SCROW nOldEndRow = rEndRow;

    SCTAB nMax = GetTableCount();
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;

        if ( maTabs[rTab] )
        {
            SCCOL nThisEndCol = nOldEndCol;
            SCROW nThisEndRow = nOldEndRow;
            ExtendMerge( nStartCol, nStartRow, nThisEndCol, nThisEndRow, rTab, bRefresh );
            if ( nThisEndCol > rEndCol )
                rEndCol = nThisEndCol;
            if ( nThisEndRow > rEndRow )
                rEndRow = nThisEndRow;
        }
    }
}

bool ScDocument::ExtendMerge( SCCOL nStartCol, SCROW nStartRow,
                              SCCOL& rEndCol,  SCROW& rEndRow,
                              SCTAB nTab, bool bRefresh )
{
    bool bFound = false;
    if ( ValidColRow(nStartCol,nStartRow) && ValidColRow(rEndCol,rEndRow) && ValidTab(nTab) )
    {
        if (ScTable* pTable = FetchTable(nTab))
            bFound = pTable->ExtendMerge( nStartCol, nStartRow, rEndCol, rEndRow, bRefresh );

        if (bRefresh)
            RefreshAutoFilter( nStartCol, nStartRow, rEndCol, rEndRow, nTab );
    }
    else
    {
        OSL_FAIL("ExtendMerge: invalid range");
    }

    return bFound;
}

bool ScDocument::ExtendMerge( ScRange& rRange, bool bRefresh )
{
    bool bFound = false;
    SCTAB nStartTab = rRange.aStart.Tab();
    SCTAB nEndTab   = rRange.aEnd.Tab();
    SCCOL nEndCol   = rRange.aEnd.Col();
    SCROW nEndRow   = rRange.aEnd.Row();

    PutInOrder( nStartTab, nEndTab );
    for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < GetTableCount(); nTab++ )
    {
        SCCOL nExtendCol = rRange.aEnd.Col();
        SCROW nExtendRow = rRange.aEnd.Row();
        if (ExtendMerge( rRange.aStart.Col(), rRange.aStart.Row(),
                         nExtendCol,          nExtendRow,
                         nTab, bRefresh ) )
        {
            bFound = true;
            if (nExtendCol > nEndCol) nEndCol = nExtendCol;
            if (nExtendRow > nEndRow) nEndRow = nExtendRow;
        }
    }

    rRange.aEnd.SetCol(nEndCol);
    rRange.aEnd.SetRow(nEndRow);

    return bFound;
}

void ScDocument::ExtendTotalMerge( ScRange& rRange ) const
{
    // Extend range to merged cells without including any new non-overlapped cells
    ScRange aExt = rRange;
    // ExtendMerge() is non-const, but called without refresh.
    if (!const_cast<ScDocument*>(this)->ExtendMerge( aExt ))
        return;

    if ( aExt.aEnd.Row() > rRange.aEnd.Row() )
    {
        ScRange aTest = aExt;
        aTest.aStart.SetRow( rRange.aEnd.Row() + 1 );
        if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) )
            aExt.aEnd.SetRow(rRange.aEnd.Row());
    }
    if ( aExt.aEnd.Col() > rRange.aEnd.Col() )
    {
        ScRange aTest = aExt;
        aTest.aStart.SetCol( rRange.aEnd.Col() + 1 );
        if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) )
            aExt.aEnd.SetCol(rRange.aEnd.Col());
    }

    rRange = aExt;
}

void ScDocument::ExtendOverlapped( ScRange& rRange ) const
{
    SCTAB nStartTab = rRange.aStart.Tab();
    SCTAB nEndTab   = rRange.aEnd.Tab();
    SCCOL nStartCol = rRange.aStart.Col();
    SCROW nStartRow = rRange.aStart.Row();

    PutInOrder( nStartTab, nEndTab );
    for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < GetTableCount(); nTab++ )
    {
        SCCOL nExtendCol = rRange.aStart.Col();
        SCROW nExtendRow = rRange.aStart.Row();
        ExtendOverlapped( nExtendCol, nExtendRow,
                                rRange.aEnd.Col(), rRange.aEnd.Row(), nTab );
        if (nExtendCol < nStartCol)
        {
            nStartCol = nExtendCol;
        }
        if (nExtendRow < nStartRow)
        {
            nStartRow = nExtendRow;
        }
    }

    rRange.aStart.SetCol(nStartCol);
    rRange.aStart.SetRow(nStartRow);
}

bool ScDocument::RefreshAutoFilter( SCCOL nStartCol, SCROW nStartRow,
                                    SCCOL nEndCol, SCROW nEndRow, SCTAB nTab )
{
    SCTAB nDBTab;
    SCCOL nDBStartCol;
    SCROW nDBStartRow;
    SCCOL nDBEndCol;
    SCROW nDBEndRow;

    //      Delete Autofilter

    bool bChange = RemoveFlagsTab( nStartCol,nStartRow, nEndCol,nEndRow, nTab, ScMF::Auto );

    //      Set Autofilter

    const ScDBData* pData = nullptr;
    ScDBCollection::NamedDBs& rDBs = pDBCollection->getNamedDBs();
    for (const auto& rxDB : rDBs)
    {
        if (rxDB->HasAutoFilter())
        {
            rxDB->GetArea(nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow);
            if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow &&
                                    nDBStartCol<=nEndCol && nDBEndCol>=nStartCol )
            {
                if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow,
                                    nDBTab, ScMF::Auto ))
                    bChange = true;
            }
        }
    }
    if (ScTable* pTable = FetchTable(nTab))
        pData = pTable->GetAnonymousDBData();
    else
        pData = nullptr;
    if (pData && pData->HasAutoFilter())
    {
        pData->GetArea( nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow );
        if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow &&
                                nDBStartCol<=nEndCol && nDBEndCol>=nStartCol )
        {
            if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow,
                                nDBTab, ScMF::Auto ))
                bChange = true;
        }
    }
    return bChange;
}

void ScDocument::SkipOverlapped( SCCOL& rCol, SCROW& rRow, SCTAB nTab ) const
{
    while (IsHorOverlapped(rCol, rRow, nTab))
        --rCol;
    while (IsVerOverlapped(rCol, rRow, nTab))
        --rRow;
}

bool ScDocument::IsHorOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    return GetAttr(nCol, nRow, nTab, ATTR_MERGE_FLAG).IsHorOverlapped();
}

bool ScDocument::IsVerOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab, SCROW* nStartRow, SCROW* nEndRow ) const
{
    SCROW dummy;
    const ScMergeFlagAttr& rAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG,
                                            nStartRow ? *nStartRow : dummy, nEndRow ? *nEndRow : dummy );
    return rAttr.IsVerOverlapped();
}

void ScDocument::ApplySelectionFrame( const ScMarkData& rMark,
                                      const SvxBoxItem& rLineOuter,
                                      const SvxBoxInfoItem* pLineInner )
{
    ScRangeList aRangeList;
    rMark.FillRangeListWithMarks( &aRangeList, false );
    size_t nRangeCount = aRangeList.size();
    SCTAB nMax = GetTableCount();
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;

        if (maTabs[rTab])
        {
            for ( size_t j=0; j < nRangeCount; j++ )
            {
                const ScRange & rRange = aRangeList[ j ];
                maTabs[rTab]->ApplyBlockFrame( rLineOuter, pLineInner,
                    rRange.aStart.Col(), rRange.aStart.Row(),
                    rRange.aEnd.Col(),   rRange.aEnd.Row() );
            }
        }
    }
    if (!rLineOuter.IsRemoveAdjacentCellBorder())
        return;

    SvxBoxItem aTmp0(rLineOuter);
    aTmp0.SetLine( nullptr, SvxBoxItemLine::TOP );
    aTmp0.SetLine( nullptr, SvxBoxItemLine::BOTTOM );
    aTmp0.SetLine( nullptr, SvxBoxItemLine::LEFT );
    aTmp0.SetLine( nullptr, SvxBoxItemLine::RIGHT );
    SvxBoxItem aLeft( aTmp0 );
    SvxBoxItem aRight( aTmp0 );
    SvxBoxItem aTop( aTmp0 );
    SvxBoxItem aBottom( aTmp0 );

    SvxBoxInfoItem aTmp1( *pLineInner );
    aTmp1.SetTable( false );
    aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::HORI );
    aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::VERT );
    aTmp1.SetValid( SvxBoxInfoItemValidFlags::ALL, false );
    aTmp1.SetValid( SvxBoxInfoItemValidFlags::DISTANCE );
    SvxBoxInfoItem aLeftInfo( aTmp1 );
    SvxBoxInfoItem aRightInfo( aTmp1 );
    SvxBoxInfoItem aTopInfo( aTmp1 );
    SvxBoxInfoItem aBottomInfo( aTmp1 );

    if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::TOP ) && !rLineOuter.GetTop())
        aTopInfo.SetValid( SvxBoxInfoItemValidFlags::BOTTOM );

    if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::BOTTOM ) && !rLineOuter.GetBottom())
        aBottomInfo.SetValid( SvxBoxInfoItemValidFlags::TOP );

    if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::LEFT ) && !rLineOuter.GetLeft())
        aLeftInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT );

    if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::RIGHT ) && !rLineOuter.GetRight())
        aRightInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT );

    const ScRangeList& rRangeListTopEnvelope = rMark.GetTopEnvelope();
    const ScRangeList& rRangeListBottomEnvelope = rMark.GetBottomEnvelope();
    const ScRangeList& rRangeListLeftEnvelope = rMark.GetLeftEnvelope();
    const ScRangeList& rRangeListRightEnvelope = rMark.GetRightEnvelope();

    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;

        if ( maTabs[rTab] )
        {
            size_t nEnvelopeRangeCount = rRangeListTopEnvelope.size();
            for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
            {
                const ScRange & rRange = rRangeListTopEnvelope[ j ];
                maTabs[rTab]->ApplyBlockFrame( aTop, &aTopInfo,
                                                rRange.aStart.Col(), rRange.aStart.Row(),
                                                rRange.aEnd.Col(),   rRange.aEnd.Row() );
            }
            nEnvelopeRangeCount = rRangeListBottomEnvelope.size();
            for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
            {
                const ScRange & rRange = rRangeListBottomEnvelope[ j ];
                maTabs[rTab]->ApplyBlockFrame( aBottom, &aBottomInfo,
                                                rRange.aStart.Col(), rRange.aStart.Row(),
                                                rRange.aEnd.Col(),   rRange.aEnd.Row() );
            }
            nEnvelopeRangeCount = rRangeListLeftEnvelope.size();
            for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
            {
                const ScRange & rRange = rRangeListLeftEnvelope[ j ];
                maTabs[rTab]->ApplyBlockFrame( aLeft, &aLeftInfo,
                                                rRange.aStart.Col(), rRange.aStart.Row(),
                                                rRange.aEnd.Col(),   rRange.aEnd.Row() );
            }
            nEnvelopeRangeCount = rRangeListRightEnvelope.size();
            for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
            {
                const ScRange & rRange = rRangeListRightEnvelope[ j ];
                maTabs[rTab]->ApplyBlockFrame( aRight, &aRightInfo,
                                                rRange.aStart.Col(), rRange.aStart.Row(),
                                                rRange.aEnd.Col(),   rRange.aEnd.Row() );
            }
        }
    }
}

void ScDocument::ApplyFrameAreaTab(const ScRange& rRange,
                                   const SvxBoxItem& rLineOuter,
                                   const SvxBoxInfoItem& rLineInner)
{
    SCTAB nStartTab = rRange.aStart.Tab();
    SCTAB nEndTab = rRange.aStart.Tab();
    for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < GetTableCount(); nTab++)
        if (maTabs[nTab])
            maTabs[nTab]->ApplyBlockFrame(rLineOuter, &rLineInner,
                                          rRange.aStart.Col(), rRange.aStart.Row(),
                                          rRange.aEnd.Col(),   rRange.aEnd.Row());
}

void ScDocument::ApplySelectionPattern( const ScPatternAttr& rAttr, const ScMarkData& rMark, ScEditDataArray* pDataArray, bool* const pIsChanged )
{
    const SfxItemSet* pSet = &rAttr.GetItemSet();
    bool bSet = false;
    sal_uInt16 i;
    for (i=ATTR_PATTERN_START; i<=ATTR_PATTERN_END && !bSet; i++)
        if (pSet->GetItemState(i) == SfxItemState::SET)
            bSet = true;

    if (!bSet)
        return;

    // ApplySelectionCache needs multi mark
    if ( rMark.IsMarked() && !rMark.IsMultiMarked() )
    {
        const ScRange& aRange = rMark.GetMarkArea();
        ApplyPatternArea( aRange.aStart.Col(), aRange.aStart.Row(),
                          aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rAttr, pDataArray, pIsChanged );
    }
    else
    {
        ScItemPoolCache aCache( getCellAttributeHelper(), *pSet );
        SCTAB nMax = GetTableCount();
        for (const auto& rTab : rMark)
        {
            if (rTab >= nMax)
                break;
            if (maTabs[rTab])
                maTabs[rTab]->ApplySelectionCache( aCache, rMark, pDataArray, pIsChanged );
        }
    }
}

void ScDocument::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark )
{
    SCTAB nMax = GetTableCount();
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;
        if (maTabs[rTab])
            maTabs[rTab]->ChangeSelectionIndent( bIncrement, rMark );
    }
}

void ScDocument::ClearSelectionItems( const sal_uInt16* pWhich, const ScMarkData& rMark )
{
    SCTAB nMax = GetTableCount();
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;
        if (maTabs[rTab])
            maTabs[rTab]->ClearSelectionItems( pWhich, rMark );
    }
}

void ScDocument::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast )
{
    sc::AutoCalcSwitch aACSwitch(*this, false);

    std::vector<ScAddress> aGroupPos;
    // Destroy and reconstruct listeners only if content is affected.
    bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
    if (bDelContent)
    {
        // Record the positions of top and/or bottom formula groups that
        // intersect the area borders.
        sc::EndListeningContext aCxt(*this);
        ScRangeList aRangeList;
        rMark.FillRangeListWithMarks( &aRangeList, false);
        for (size_t i = 0; i < aRangeList.size(); ++i)
        {
            const ScRange & rRange = aRangeList[i];
            EndListeningIntersectedGroups( aCxt, rRange, &aGroupPos);
        }
        aCxt.purgeEmptyBroadcasters();
    }

    SCTAB nMax = GetTableCount();
    for (const auto& rTab : rMark)
    {
        if (rTab >= nMax)
            break;
        if (maTabs[rTab])
            maTabs[rTab]->DeleteSelection(nDelFlag, rMark, bBroadcast);
    }

    if (!bDelContent)
        return;

    // Re-start listeners on those top bottom groups that have been split.
    SetNeedsListeningGroups(aGroupPos);
    StartNeededListeners();

    // If formula groups were split their listeners were destroyed and may
    // need to be notified now that they're restored,
    // ScTable::DeleteSelection() couldn't do that.
    if (aGroupPos.empty())
        return;

    ScRangeList aRangeList;
    rMark.FillRangeListWithMarks( &aRangeList, false);
    for (size_t i = 0; i < aRangeList.size(); ++i)
    {
        SetDirty( aRangeList[i], true);
    }
    //Notify listeners on top and bottom of the group that has been split
    for (size_t i = 0; i < aGroupPos.size(); ++i) {
        ScFormulaCell *pFormulaCell = GetFormulaCell(aGroupPos[i]);
        if (pFormulaCell)
            pFormulaCell->SetDirty(true);
    }
}

void ScDocument::DeleteSelectionTab(
    SCTAB nTab, InsertDeleteFlags nDelFlag, const ScMarkData& rMark )
{
    if (ScTable* pTable = FetchTable(nTab))
    {
        sc::AutoCalcSwitch aACSwitch(*this, false);

        std::vector<ScAddress> aGroupPos;
        // Destroy and reconstruct listeners only if content is affected.
        bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
        if (bDelContent)
        {
            // Record the positions of top and/or bottom formula groups that
            // intersect the area borders.
            sc::EndListeningContext aCxt(*this);
            ScRangeList aRangeList;
            rMark.FillRangeListWithMarks( &aRangeList, false);
            for (size_t i = 0; i < aRangeList.size(); ++i)
            {
                const ScRange & rRange = aRangeList[i];
                if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab())
                {
                    ScRange aRange( rRange);
                    aRange.aStart.SetTab( nTab);
                    aRange.aEnd.SetTab( nTab);
                    EndListeningIntersectedGroups( aCxt, aRange, &aGroupPos);
                }
            }
            aCxt.purgeEmptyBroadcasters();
        }

        pTable->DeleteSelection(nDelFlag, rMark);

        if (bDelContent)
        {
            // Re-start listeners on those top bottom groups that have been split.
            SetNeedsListeningGroups(aGroupPos);
            StartNeededListeners();

            // If formula groups were split their listeners were destroyed and may
            // need to be notified now that they're restored,
            // ScTable::DeleteSelection() couldn't do that.
            if (!aGroupPos.empty())
            {
                ScRangeList aRangeList;
                rMark.FillRangeListWithMarks( &aRangeList, false);
                for (size_t i = 0; i < aRangeList.size(); ++i)
                {
                    const ScRange & rRange = aRangeList[i];
                    if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab())
                    {
                        ScRange aRange( rRange);
                        aRange.aStart.SetTab( nTab);
                        aRange.aEnd.SetTab( nTab);
                        SetDirty( aRange, true);
                    }
                }
            }
        }
    }
    else
    {
        OSL_FAIL("wrong table");
    }
}

ScDocumentPool* ScDocument::GetPool()
{
    return mxPoolHelper ? mxPoolHelper->GetDocPool() : nullptr;
}

ScStyleSheetPool* ScDocument::GetStyleSheetPool() const
{
    return mxPoolHelper ? mxPoolHelper->GetStylePool() : nullptr;
}

bool ScDocument::IsEmptyData(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
       return pTable->IsEmptyData(nStartCol, nStartRow, nEndCol, nEndRow);
    return true;
}

SCSIZE ScDocument::GetEmptyLinesInBlock( SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab,
                            SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab, ScDirection eDir )
{
    PutInOrder(nStartCol, nEndCol);
    PutInOrder(nStartRow, nEndRow);
    PutInOrder(nStartTab, nEndTab);
    if (ScTable* pTable = FetchTable(nStartTab))
        return pTable->GetEmptyLinesInBlock(nStartCol, nStartRow, nEndCol, nEndRow, eDir);
    return 0;
}

void ScDocument::FindAreaPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, ScMoveDirection eDirection ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        pTable->FindAreaPos(rCol, rRow, eDirection);
}

void ScDocument::GetNextPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, SCCOL nMovX, SCROW nMovY,
        bool bMarked, bool bUnprotected, const ScMarkData& rMark, SCCOL nTabStartCol ) const
{
    OSL_ENSURE( !nMovX || !nMovY, "GetNextPos: only X or Y" );

    ScMarkData aCopyMark = rMark;
    aCopyMark.SetMarking(false);
    aCopyMark.MarkToMulti();

    if (const ScTable* pTable = FetchTable(nTab))
        pTable->GetNextPos(rCol, rRow, nMovX, nMovY, bMarked, bUnprotected, aCopyMark, nTabStartCol);
}

//  Data operations

sal_uInt64 ScDocument::GetCellCount() const
{
    sal_uInt64 nCellCount = 0;

    for (const auto& a : maTabs)
    {
        if (a)
            nCellCount += a->GetCellCount();
    }

    return nCellCount;
}

sal_uInt64 ScDocument::GetFormulaGroupCount() const
{
    sal_uInt64 nFormulaGroupCount = 0;

    ScFormulaGroupIterator aIter( *const_cast<ScDocument*>(this) );
    for ( sc::FormulaGroupEntry* ptr = aIter.first(); ptr; ptr = aIter.next())
    {
         nFormulaGroupCount++;
    }

    return nFormulaGroupCount;
}

sal_uInt64 ScDocument::GetCodeCount() const
{
    sal_uInt64 nCodeCount = 0;

    for (const auto& a : maTabs)
    {
        if (a)
            nCodeCount += a->GetCodeCount();
    }

    return nCodeCount;
}

void ScDocument::PageStyleModified( SCTAB nTab, const OUString& rNewName )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->PageStyleModified( rNewName );
}

void ScDocument::SetPageStyle( SCTAB nTab, const OUString& rName )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetPageStyle( rName );
}

OUString ScDocument::GetPageStyle( SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetPageStyle();
    return OUString();
}

void ScDocument::SetPageSize( SCTAB nTab, const Size& rSize )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetPageSize( rSize );
}

Size ScDocument::GetPageSize( SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetPageSize();

    OSL_FAIL("invalid tab");
    return Size();
}

void ScDocument::SetRepeatArea( SCTAB nTab, SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCROW nEndRow )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetRepeatArea( nStartCol, nEndCol, nStartRow, nEndRow );
}

void ScDocument::InvalidatePageBreaks(SCTAB nTab)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->InvalidatePageBreaks();
}

void ScDocument::UpdatePageBreaks( SCTAB nTab, const ScRange* pUserArea )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->UpdatePageBreaks( pUserArea );
}

void ScDocument::RemoveManualBreaks( SCTAB nTab )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->RemoveManualBreaks();
}

bool ScDocument::HasManualBreaks( SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->HasManualBreaks();

    OSL_FAIL("invalid tab");
    return false;
}

void ScDocument::GetDocStat( ScDocStat& rDocStat )
{
    rDocStat.nTableCount = GetTableCount();
    rDocStat.aDocName    = aDocName;
    rDocStat.nFormulaCount = GetFormulaGroupCount();
    rDocStat.nCellCount  = GetCellCount();
}

bool ScDocument::HasPrintRange()
{
    bool bResult = false;

    for (const auto& a : maTabs)
    {
        if (!a)
            continue;
        bResult = a->IsPrintEntireSheet() || (a->GetPrintRangeCount() > 0);
        if (bResult)
            break;
    }

    return bResult;
}

bool ScDocument::IsPrintEntireSheet( SCTAB nTab ) const
{
    const ScTable* pTable = FetchTable(nTab);
    return (pTable && pTable->IsPrintEntireSheet());
}

sal_uInt16 ScDocument::GetPrintRangeCount( SCTAB nTab )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetPrintRangeCount();
    return 0;
}

const ScRange* ScDocument::GetPrintRange( SCTAB nTab, sal_uInt16 nPos )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetPrintRange(nPos);

    return nullptr;
}

std::optional<ScRange> ScDocument::GetRepeatColRange( SCTAB nTab )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetRepeatColRange();

    return std::nullopt;
}

std::optional<ScRange> ScDocument::GetRepeatRowRange( SCTAB nTab )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetRepeatRowRange();
    return std::nullopt;
}

void ScDocument::ClearPrintRanges( SCTAB nTab )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->ClearPrintRanges();
}

void ScDocument::ClearPrintNamedRanges( SCTAB nTab )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->ClearPrintNamedRanges();
}

void ScDocument::AddPrintRange( SCTAB nTab, const ScRange& rNew )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->AddPrintRange(rNew);
}

void ScDocument::SetPrintEntireSheet( SCTAB nTab )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetPrintEntireSheet();
}

void ScDocument::SetRepeatColRange( SCTAB nTab, std::optional<ScRange> oNew )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetRepeatColRange(std::move(oNew));
}

void ScDocument::SetRepeatRowRange( SCTAB nTab, std::optional<ScRange> oNew )
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetRepeatRowRange(std::move(oNew));
}

std::unique_ptr<ScPrintRangeSaver> ScDocument::CreatePrintRangeSaver() const
{
    const SCTAB nCount = GetTableCount();
    std::unique_ptr<ScPrintRangeSaver> pNew(new ScPrintRangeSaver( nCount ));
    for (SCTAB i=0; i<nCount; i++)
        if (maTabs[i])
            maTabs[i]->FillPrintSaver( pNew->GetTabData(i) );
    return pNew;
}

void ScDocument::RestorePrintRanges( const ScPrintRangeSaver& rSaver )
{
    const SCTAB nCount = rSaver.GetTabCount();
    const SCTAB maxIndex = std::min(nCount, GetTableCount());
    for (SCTAB i=0; i<maxIndex; i++)
        if (maTabs[i])
            maTabs[i]->RestorePrintRanges( rSaver.GetTabData(i) );
}

bool ScDocument::NeedPageResetAfterTab( SCTAB nTab ) const
{
    // The page number count restarts at a sheet, if another template is set at
    // the preceding one (only compare names) and if a pagenumber is specified (not 0)

    if (nTab + 1 < GetTableCount() && maTabs[nTab] && maTabs[nTab+1])
    {
        const OUString & rNew = maTabs[nTab+1]->GetPageStyle();
        if ( rNew != maTabs[nTab]->GetPageStyle() )
        {
            SfxStyleSheetBase* pStyle = mxPoolHelper->GetStylePool()->Find( rNew, SfxStyleFamily::Page );
            if ( pStyle )
            {
                const SfxItemSet& rSet = pStyle->GetItemSet();
                sal_uInt16 nFirst = rSet.Get(ATTR_PAGE_FIRSTPAGENO).GetValue();
                if ( nFirst != 0 )
                    return true;        // Specify page number in new template
            }
        }
    }

    return false;       // otherwise not
}

ScUndoManager* ScDocument::GetUndoManager()
{
    if (!mpUndoManager)
    {
        // to support enhanced text edit for draw objects, use an SdrUndoManager
        ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);

        ScUndoManager* pUndoManager = new ScUndoManager;
        pUndoManager->SetDocShell(GetDocumentShell());
        mpUndoManager = pUndoManager;
    }

    return mpUndoManager;
}

ScRowBreakIterator* ScDocument::GetRowBreakIterator(SCTAB nTab) const
{
    if (HasTable(nTab))
        return new ScRowBreakIterator(maTabs[nTab]->maRowPageBreaks);
    return nullptr;
}

void ScDocument::AddSubTotalCell(ScFormulaCell* pCell)
{
    maSubTotalCells.insert(pCell);
}

void ScDocument::RemoveSubTotalCell(ScFormulaCell* pCell)
{
    maSubTotalCells.erase(pCell);
}

namespace {

bool lcl_hasDirtyRange(const ScDocument& rDoc, ScFormulaCell* pCell, const ScRange& rDirtyRange)
{
    ScDetectiveRefIter aRefIter(rDoc, pCell);
    ScRange aRange;
    while (aRefIter.GetNextRef(aRange))
    {
        if (aRange.Intersects(rDirtyRange))
            return true;
    }
    return false;
}

}

void ScDocument::SetSubTotalCellsDirty(const ScRange& rDirtyRange)
{
    // to update the list by skipping cells that no longer contain subtotal function.
    std::set<ScFormulaCell*> aNewSet;

    bool bOldRecalc = GetAutoCalc();
    SetAutoCalc(false);
    for (ScFormulaCell* pCell : maSubTotalCells)
    {
        if (pCell->IsSubTotal())
        {
            aNewSet.insert(pCell);
            if (lcl_hasDirtyRange(*this, pCell, rDirtyRange))
                pCell->SetDirty();
        }
    }

    SetAutoCalc(bOldRecalc);
    maSubTotalCells.swap(aNewSet); // update the list.
}

sal_uInt16 ScDocument::GetTextWidth( const ScAddress& rPos ) const
{
    SCTAB nTab = rPos.Tab();
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetTextWidth(rPos.Col(), rPos.Row());
    return 0;
}

SvtScriptType ScDocument::GetScriptType( const ScAddress& rPos ) const
{
    SCTAB nTab = rPos.Tab();
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetScriptType(rPos.Col(), rPos.Row());
    return SvtScriptType::NONE;
}

void ScDocument::SetScriptType( const ScAddress& rPos, SvtScriptType nType )
{
    SCTAB nTab = rPos.Tab();
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetScriptType(rPos.Col(), rPos.Row(), nType);
}

void ScDocument::EnableUndo( bool bVal )
{
    // The undo manager increases lock count every time undo is disabled.
    // Because of this, we shouldn't disable undo unless it's currently
    // enabled, or else re-enabling it may not actually re-enable undo unless
    // the lock count becomes zero.

    if (bVal != GetUndoManager()->IsUndoEnabled())
    {
        GetUndoManager()->EnableUndo(bVal);
        if( mpDrawLayer ) mpDrawLayer->EnableUndo(bVal);
    }

    mbUndoEnabled = bVal;
}

void ScDocument::EnableUserInteraction( bool bVal )
{
    mbUserInteractionEnabled = bVal;
}

bool ScDocument::IsInVBAMode() const
{
    if (!mpShell)
        return false;

    try
    {
        uno::Reference<script::vba::XVBACompatibility> xVBA(
            mpShell->GetBasicContainer(), uno::UNO_QUERY);

        return xVBA.is() && xVBA->getVBACompatibilityMode();
    }
    catch (const lang::NotInitializedException&) {}

    return false;
}

// Sparklines
std::shared_ptr<sc::Sparkline> ScDocument::GetSparkline(ScAddress const& rPosition)
{
    SCTAB nTab = rPosition.Tab();
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetSparkline(rPosition.Col(), rPosition.Row());
    return std::shared_ptr<sc::Sparkline>();
}

bool ScDocument::HasSparkline(ScAddress const & rPosition)
{
    return bool(GetSparkline(rPosition));
}

sc::Sparkline* ScDocument::CreateSparkline(ScAddress const& rPosition, std::shared_ptr<sc::SparklineGroup> const& pSparklineGroup)
{
    SCTAB nTab = rPosition.Tab();
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->CreateSparkline(rPosition.Col(), rPosition.Row(), pSparklineGroup);
    return nullptr;
}

bool ScDocument::DeleteSparkline(ScAddress const & rPosition)
{
    SCTAB nTab = rPosition.Tab();
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->DeleteSparkline(rPosition.Col(), rPosition.Row());
    return false;
}

sc::SparklineList* ScDocument::GetSparklineList(SCTAB nTab)
{
    if (ScTable* pTable = FetchTable(nTab))
        return &pTable->GetSparklineList();
    return nullptr;
}

bool ScDocument::HasOneSparklineGroup(ScRange const& rRange)
{
    std::shared_ptr<sc::SparklineGroup> pSparklineGroup;
    return GetSparklineGroupInRange(rRange, pSparklineGroup);
}

bool ScDocument::GetSparklineGroupInRange(ScRange const& rRange, std::shared_ptr<sc::SparklineGroup>& rGroup)
{
    std::shared_ptr<sc::SparklineGroup> pFoundGroup;
    SCTAB nTab = rRange.aStart.Tab();

    for (SCCOL nX = rRange.aStart.Col(); nX <= rRange.aEnd.Col(); nX++)
    {
        for (SCROW nY = rRange.aStart.Row(); nY <= rRange.aEnd.Row(); nY++)
        {
            auto pSparkline = GetSparkline(ScAddress(nX, nY, nTab));
            if (!pSparkline)
            {
                return false;
            }
            else if (!pFoundGroup)
            {
               pFoundGroup = pSparkline->getSparklineGroup();
            }
            else if (pFoundGroup != pSparkline->getSparklineGroup())
            {
                return false;
            }
        }
    }

    rGroup = std::move(pFoundGroup);
    return true;
}

std::shared_ptr<sc::SparklineGroup> ScDocument::SearchSparklineGroup(tools::Guid const& rGuid)
{
    for (auto const& rTable : maTabs)
    {
        if (!rTable)
            continue;

        auto& rSparklineList = rTable->GetSparklineList();

        for (auto const& pSparklineGroup : rSparklineList.getSparklineGroups())
        {
            if (pSparklineGroup->getID() == rGuid)
                return pSparklineGroup;
        }
    }

    return std::shared_ptr<sc::SparklineGroup>();
}

// Notes

ScPostIt* ScDocument::GetNote(const ScAddress& rPos)
{
    return GetNote(rPos.Col(), rPos.Row(), rPos.Tab());
}

ScPostIt* ScDocument::GetNote(SCCOL nCol, SCROW nRow, SCTAB nTab)
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetNote(nCol, nRow);
    return nullptr;
}

void ScDocument::SetNote(const ScAddress& rPos, std::unique_ptr<ScPostIt> pNote)
{
    return SetNote(rPos.Col(), rPos.Row(), rPos.Tab(), std::move(pNote));
}

void ScDocument::SetNote(SCCOL nCol, SCROW nRow, SCTAB nTab, std::unique_ptr<ScPostIt> pNote)
{
    if (ScTable* pTable = FetchTable(nTab))
    {
        pTable->SetNote(nCol, nRow, std::move(pNote));

        if (ScDocShell* pDocSh = GetDocumentShell())
        {
            HelperNotifyChanges::NotifyIfChangesListeners(
                *pDocSh, ScRange(nCol, nRow, nTab), u"note"_ustr);
        }
    }
}

bool ScDocument::HasNote(const ScAddress& rPos) const
{
    return HasNote(rPos.Col(), rPos.Row(), rPos.Tab());
}

bool ScDocument::HasNote(SCCOL nCol, SCROW nRow, SCTAB nTab) const
{
    if (!ValidColRow(nCol, nRow))
        return false;

    const ScTable* pTab = FetchTable(nTab);
    if (!pTab)
        return false;

    if (nCol >= pTab->GetAllocatedColumnsCount())
        return false;

    const ScPostIt* pNote = pTab->aCol[nCol].GetCellNote(nRow);
    return pNote != nullptr;
}

bool ScDocument::HasNote(SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow) const
{
    if (const ScTable* pTable = FetchTable(nTab))
    {
        nStartCol = pTable->ClampToAllocatedColumns(nStartCol);
        nEndCol = pTable->ClampToAllocatedColumns(nEndCol);
        for (SCCOL nCol = nStartCol; nCol < nEndCol; ++nCol)
            if (pTable->aCol[nCol].HasCellNote(nStartRow, nEndRow))
                return true;
    }
    return false;
}

bool ScDocument::HasColNotes(SCCOL nCol, SCTAB nTab) const
{
    if (!ValidCol(nCol))
        return false;

    if (const ScTable* pTable = FetchTable(nTab))
    {
        if (nCol >= pTable->GetAllocatedColumnsCount())
            return false;

        return pTable->aCol[nCol].HasCellNotes();
    }

    return false;
}

bool ScDocument::HasTabNotes(SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
    {
        for (SCCOL nCol=0, nColSize = pTable->aCol.size(); nCol < nColSize; ++nCol)
            if ( HasColNotes(nCol, nTab) )
                return true;
    }
    return false;
}

bool ScDocument::HasNotes() const
{
    for (SCTAB i = 0; i <= MAXTAB; ++i)
    {
        if (HasTabNotes(i))
            return true;
    }
    return false;
}

std::unique_ptr<ScPostIt> ScDocument::ReleaseNote(const ScAddress& rPos)
{
    if (ScTable* pTable = FetchTable(rPos.Tab()))
        return pTable->ReleaseNote(rPos.Col(), rPos.Row());
    return nullptr;
}

ScPostIt* ScDocument::GetOrCreateNote(const ScAddress& rPos)
{
    if (HasNote(rPos))
        return GetNote(rPos);
    else
        return CreateNote(rPos);
}

ScPostIt* ScDocument::CreateNote(const ScAddress& rPos)
{
    ScPostIt* pPostIt = new ScPostIt(*this, rPos);
    SetNote(rPos, std::unique_ptr<ScPostIt>(pPostIt));
    return pPostIt;
}

size_t ScDocument::GetNoteCount( SCTAB nTab, SCCOL nCol ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetNoteCount(nCol);
    return 0;
}

void ScDocument::CreateAllNoteCaptions()
{
    for (const auto& pTable : maTabs)
    {
        if (pTable)
            pTable->CreateAllNoteCaptions();
    }
}

void ScDocument::ForgetNoteCaptions( const ScRangeList& rRanges, bool bPreserveData )
{
    for (size_t i = 0, n = rRanges.size(); i < n; ++i)
    {
        const ScRange & rRange = rRanges[i];
        const ScAddress& s = rRange.aStart;
        const ScAddress& e = rRange.aEnd;
        for (SCTAB nTab = s.Tab(); nTab <= e.Tab(); ++nTab)
        {
            if (ScTable* pTable = FetchTable(nTab))
                pTable->ForgetNoteCaptions(s.Col(), s.Row(), e.Col(), e.Row(), bPreserveData);
        }
    }
}

CommentCaptionState ScDocument::GetAllNoteCaptionsState( const ScRangeList& rRanges )
{
    CommentCaptionState aTmpState = CommentCaptionState::ALLHIDDEN;
    CommentCaptionState aState = CommentCaptionState::ALLHIDDEN;
    bool bFirstControl = true;
    std::vector<sc::NoteEntry> aNotes;

    for (size_t i = 0, n = rRanges.size(); i < n; ++i)
    {
        const ScRange & rRange = rRanges[i];

        for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
        {
            aState = maTabs[nTab]->GetAllNoteCaptionsState( rRange, aNotes );

            if (aState == CommentCaptionState::MIXED)
                return aState;

            if (bFirstControl)                      // it is possible that a range is ALLSHOWN, another range is ALLHIDDEN,
            {                                       // we have to detect that situation as mixed.
                aTmpState = aState;
                bFirstControl = false;
            }
            else if(aTmpState != aState)
            {
                aState = CommentCaptionState::MIXED;
                return aState;
            }
        }
    }
    return aState;
}

ScAddress ScDocument::GetNotePosition( size_t nIndex ) const
{
    for (size_t nTab = 0; nTab < maTabs.size(); ++nTab)
    {
        for (SCCOL nCol : GetAllocatedColumnsRange(nTab, 0, MaxCol()))
        {
            size_t nColNoteCount = GetNoteCount(nTab, nCol);
            if (!nColNoteCount)
                continue;

            if (nIndex >= nColNoteCount)
            {
                nIndex -= nColNoteCount;
                continue;
            }

            SCROW nRow = GetNotePosition(nTab, nCol, nIndex);
            if (nRow >= 0)
                return ScAddress(nCol, nRow, nTab);

            OSL_FAIL("note not found");
            return ScAddress::INITIALIZE_INVALID;
        }
    }

    OSL_FAIL("note not found");
    return ScAddress::INITIALIZE_INVALID;
}

ScAddress ScDocument::GetNotePosition( size_t nIndex, SCTAB nTab ) const
{
    for (SCCOL nCol : GetAllocatedColumnsRange(nTab, 0, MaxCol()))
    {
        size_t nColNoteCount = GetNoteCount(nTab, nCol);
        if (!nColNoteCount)
            continue;

        if (nIndex >= nColNoteCount)
        {
            nIndex -= nColNoteCount;
            continue;
        }

        SCROW nRow = GetNotePosition(nTab, nCol, nIndex);
        if (nRow >= 0)
            return ScAddress(nCol, nRow, nTab);

        OSL_FAIL("note not found");
        return ScAddress::INITIALIZE_INVALID;
    }

    OSL_FAIL("note not found");
    return ScAddress::INITIALIZE_INVALID;
}

SCROW ScDocument::GetNotePosition( SCTAB nTab, SCCOL nCol, size_t nIndex ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetNotePosition(nCol, nIndex);
    return -1;
}

void ScDocument::GetAllNoteEntries( std::vector<sc::NoteEntry>& rNotes ) const
{
    for (const auto & pTable : maTabs)
    {
        if (pTable)
            pTable->GetAllNoteEntries(rNotes);
    }
}

void ScDocument::GetAllNoteEntries( SCTAB nTab, std::vector<sc::NoteEntry>& rNotes ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        pTable->GetAllNoteEntries(rNotes);
}

void ScDocument::GetNotesInRange( const ScRangeList& rRangeList, std::vector<sc::NoteEntry>& rNotes ) const
{
    for( size_t i = 0; i < rRangeList.size(); ++i)
    {
        const ScRange & rRange = rRangeList[i];
        for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
        {
            if (!maTabs[nTab])
                continue;
            maTabs[nTab]->GetNotesInRange( rRange, rNotes );
        }
    }
}

void ScDocument::GetUnprotectedCells( ScRangeList& rRangeList, SCTAB nTab ) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        pTable->GetUnprotectedCells(rRangeList);
}

bool ScDocument::ContainsNotesInRange( const ScRangeList& rRangeList ) const
{
    for( size_t i = 0; i < rRangeList.size(); ++i)
    {
        const ScRange & rRange = rRangeList[i];
        for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
        {
            if (!maTabs[nTab])
                continue;
            bool bContainsNote = maTabs[nTab]->ContainsNotesInRange( rRange );
            if(bContainsNote)
                return true;
        }
    }

    return false;
}

void ScDocument::AddPerson(const ScPersonData& rPerson)
{
    if (std::none_of(maPersonList.begin(), maPersonList.end(),
                     [&rPerson](const auto& r) { return r.maId == rPerson.maId; }))
        maPersonList.push_back(rPerson);
}

const ScPersonData* ScDocument::GetPersonById(const OUString& rId) const
{
    auto it = std::find_if(maPersonList.begin(), maPersonList.end(),
                           [&rId](const auto& r) { return r.maId == rId; });
    return it != maPersonList.end() ? &*it : nullptr;
}

void ScDocument::SetAutoNameCache( std::unique_ptr<ScAutoNameCache> pCache )
{
    pAutoNameCache = std::move(pCache);
}

thread_local ScDocumentThreadSpecific ScDocument::maThreadSpecific;

ScRecursionHelper& ScDocument::GetRecursionHelper()
{
    if (!IsThreadedGroupCalcInProgress())
    {
        if (!maNonThreaded.xRecursionHelper)
            maNonThreaded.xRecursionHelper = std::make_unique<ScRecursionHelper>();
        return *maNonThreaded.xRecursionHelper;
    }
    else
    {
        if (!maThreadSpecific.xRecursionHelper)
            maThreadSpecific.xRecursionHelper = std::make_unique<ScRecursionHelper>();
        return *maThreadSpecific.xRecursionHelper;
    }
}

void ScDocument::SetupContextFromNonThreadedContext(ScInterpreterContext& /*threadedContext*/, int /*threadNumber*/)
{
    (void)this;
    // lookup cache is now only in pooled ScInterpreterContext's
}

void ScDocument::MergeContextBackIntoNonThreadedContext(ScInterpreterContext& threadedContext, int /*threadNumber*/)
{
    // Move data from a context used by a calculation thread to the main thread's context.
    // Called from the main thread after the calculation thread has already finished.
    assert(!IsThreadedGroupCalcInProgress());
    maInterpreterContext.maDelayedSetNumberFormat.insert(
        maInterpreterContext.maDelayedSetNumberFormat.end(),
        std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.begin()),
        std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.end()));
    // lookup cache is now only in pooled ScInterpreterContext's
    threadedContext.MergeDefaultFormatKeys(*GetFormatTable());
}

void ScDocument::BroadcastRefError(const sc::RefErrorContext& rCtx)
{
    if (!rCtx.mbErrorCreated)
        return;

    ScHint aHint(SfxHintId::ScRefErrorCreated, rCtx.maFormulaCell);
    if (ScDocShell* pDocSh = GetDocumentShell())
        pDocSh->Broadcast(aHint);
}

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