/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include <scitems.hxx>
 
#include <editeng/boxitem.hxx>
#include <editeng/editobj.hxx>
#include <svx/svditer.hxx>
#include <sfx2/docfile.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/lok.hxx>
 
#include <vcl/uitest/logger.hxx>
#include <vcl/uitest/eventdescription.hxx>
 
#include <mtvelements.hxx>
#include <sfx2/lokhelper.hxx>
 
using ::editeng::SvxBorderLine;
using namespace ::com::sun::star;
 
namespace WritingMode2 = ::com::sun::star::text::WritingMode2;
using ::com::sun::star::uno::Sequence;
using ::com::sun::star::sheet::TablePageBreakData;
using ::std::set;
 
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 = "grid_window";
    aDescription.aAction = rAction;
    aDescription.aParameters = std::move(aParameters);
    aDescription.aParent = "MainWindow";
    aDescription.aKeyWord = "ScGridWinUIObject";
 
    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 = SC_MOD()->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 = SC_MOD()->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 = SC_MOD()->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::LibreOfficeKit::isActive())
        if (!SC_MOD()->IsFormulaMode())
            SC_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);
                }
 
                bValid = true;
            }
            else
                bValid = false;
        }
    }
 
    if (bValid)
    {
        sc::SetFormulaDirtyContext aCxt;
        aCxt.mbClearTabDeletedFlag = bUndoDeleteTab;
        aCxt.mnTabDeletedStart = nPos;
        aCxt.mnTabDeletedEnd = nPos;
        SetAllFormulasDirty(aCxt);
 
        if (comphelper::LibreOfficeKit::isActive() && GetDrawLayer())
        {
            ScModelObj* pModel = GetDocumentShell()->GetModel();
            SfxLokHelper::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);
                }
 
                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);
 
            // 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();
 
            // 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::LibreOfficeKit::isActive())
            {
                ScModelObj* pModel = GetDocumentShell()->GetModel();
                SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
            }
 
            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);
            // 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::LibreOfficeKit::isActive())
            {
                ScModelObj* pModel = GetDocumentShell()->GetModel();
                SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
            }
 
            bValid = true;
        }
    }
    return bValid;
}
 
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::LibreOfficeKit::isActive() && GetDrawLayer())
            {
                ScModelObj* pModel = GetDocumentShell()->GetModel();
                SfxLokHelper::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 );
}
 
void ScDocument::GetBackColorArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow,
                                   SCCOL& rEndCol, SCROW& rEndRow ) const
{
    if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab])
        maTabs[nTab]->GetBackColorArea( rStartCol, rStartRow, rEndCol, rEndRow );
}
 
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();
}
 
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();
}
 
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,
            /*bGlobalNamesToLocal*/false, /*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);
 
    // 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* pClipRangeName, ScRangeName* pRangeName,
        const sc::UpdatedRangeNames::NameIndicesType& rUsedNames)
{
    pClipRangeName->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 (pClipRangeName->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);
}
 
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;
 
    const auto pSet = std::make_shared<sc::ColumnBlockPositionSet>(*this);
    sc::StartListeningContext aStartCxt(*this, pSet);
    sc::EndListeningContext aEndCxt(*this, pSet, 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 )
{
    TableContainer& 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;
    TableContainer& 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 )
{
    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;
    }
 
    bInsertingFromOtherDoc = true;  // No Broadcast/Listener created at Insert
 
    sc::ColumnSpanSet aBroadcastSpans;
 
    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;
 
    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]->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,
                                           /*bGlobalNamesToLocal*/false, /*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, /*bGlobalNamesToLocal*/false, /*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, /*bGlobalNamesToLocal*/false,
                                           /*bCopyCaptions*/true );
                }
 
                maTabs[i]->DeleteSelection( nDelFlags, rMark );
                pSourceTable->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow,
                                             nFlags, true, maTabs[i].get(), &rMark, bAsLink,
                                             /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*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 )
{
    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
        {
            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
{
    if (const ScTable* pTable = FetchTable(rPos.Tab()))
        return pTable->GetString(rPos.Col(), rPos.Row(), pContext);
    return OUString();
}
 
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
{
    SCTAB nTab = rPos.Tab();
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetValue(rPos.Col(), rPos.Row());
    return 0.0;
}
 
double ScDocument::GetValue( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
{
    ScAddress aAdr(nCol, nRow, nTab);
    return GetValue(aAdr);
}
 
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
{
    SCTAB nTab = rPos.Tab();
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->GetCellType(rPos);
    return CELLTYPE_NONE;
}
 
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);
}
 
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,
                                       const Fraction& rZoomX, const Fraction& rZoomY,
                                       bool bFormula, const ScMarkData* pMarkData,
                                       const ScColWidthParam* pParam )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetOptimalColWidth(nCol, pDev, nPPTX, nPPTY, rZoomX,
                                          rZoomY, 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,
                                    const Fraction& rZoomX, const Fraction& rZoomY,
                                    bool bWidth, bool bTotalSize, bool bInPrintTwips )
{
    if (ScTable* pTable = FetchTable(nTab))
        return pTable->GetNeededSize(nCol, nRow, pDev, nPPTX, nPPTY,
                                     rZoomX, rZoomY, 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(set<SCROW>& rBreaks, SCTAB nTab, bool bPage, bool bManual) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        pTable->GetAllRowBreaks(rBreaks, bPage, bManual);
}
 
void ScDocument::GetAllColBreaks(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->RowFiltered(nRow, pFirstRow, pLastRow);
    return false;
}
 
bool ScDocument::HasFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->HasFilteredRows(nStartRow, nEndRow);
    return false;
}
 
bool ScDocument::ColFiltered(SCCOL nCol, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->ColFiltered(nCol);
    return false;
}
 
void ScDocument::SetRowFiltered(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bFiltered)
{
    if (ScTable* pTable = FetchTable(nTab))
        pTable->SetRowFiltered(nStartRow, nEndRow, bFiltered);
}
 
SCROW ScDocument::FirstNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
{
    if (const ScTable* pTable = FetchTable(nTab))
        return pTable->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->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->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->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
{
    if (const ScTable* pTable = FetchTable(rPos.Tab()))
        return pTable->GetPattern(rPos.Col(), rPos.Row());
 
    return nullptr;
}
 
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,
                                    const Fraction& rZoomX, const Fraction& rZoomY )
{
    for (const auto& rTab : maTabs)
    {
        if (rTab)
        {
            rTab->StyleSheetChanged(pStyleSheet, bRemoved, pDev, nPPTX, nPPTY, rZoomX, rZoomY);
        }
    }
}
 
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 ) );
}
 
static HasAttrFlags OptimizeHasAttrib( HasAttrFlags nMask, const ScDocumentPool* pPool )
{
    if ( nMask & HasAttrFlags::Rotate )
    {
        //  Is attribute used in document?
        //  (as in fillinfo)
 
        bool bAnyItem = false;
        ItemSurrogates aSurrogates;
        pPool->GetItemSurrogates(aSurrogates, ATTR_ROTATE_VALUE);
        for (const SfxPoolItem* pItem : aSurrogates)
        {
            // 90 or 270 degrees is former SvxOrientationItem - only look for other values
            // (see ScPatternAttr::GetCellOrientation)
            Degree100 nAngle = static_cast<const ScRotateValueItem*>(pItem)->GetValue();
            if ( nAngle && nAngle != 9000_deg100 && nAngle != 27000_deg100 )
            {
                bAnyItem = true;
                break;
            }
        }
        if (!bAnyItem)
            nMask &= ~HasAttrFlags::Rotate;
    }
    return nMask;
}
 
bool ScDocument::HasAttrib( SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
                            SCCOL nCol2, SCROW nRow2, SCTAB nTab2, HasAttrFlags nMask ) const
{
    nMask = OptimizeHasAttrib( nMask, mxPoolHelper->GetDocPool());
 
    if (nMask == HasAttrFlags::NONE)
        return false;
 
    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
{
    nMask = OptimizeHasAttrib( nMask, mxPoolHelper->GetDocPool());
 
    if (nMask == HasAttrFlags::NONE || 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
{
    const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG );
    if (pAttr)
        return pAttr->IsHorOverlapped();
    else
    {
        OSL_FAIL("Overlapped: Attr==0");
        return false;
    }
}
 
bool ScDocument::IsVerOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab, SCROW* nStartRow, SCROW* nEndRow ) const
{
    SCROW dummy;
    const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG,
                                            nStartRow ? *nStartRow : dummy, nEndRow ? *nEndRow : dummy );
    if (pAttr)
        return pAttr->IsVerOverlapped();
    else
    {
        OSL_FAIL("Overlapped: Attr==0");
        return false;
    }
}
 
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.
    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::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());
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1028 Possible overflow. Consider casting operands, not the result.

V1023 A pointer without owner is added to the 'maTabs' container by the 'emplace_back' method. A memory leak will occur in case of an exception.

V1023 A pointer without owner is added to the 'maTabs' container by the 'emplace_back' method. A memory leak will occur in case of an exception.

V1023 A pointer without owner is added to the 'maTabs' container by the 'emplace_back' method. A memory leak will occur in case of an exception.

V1023 A pointer without owner is added to the 'maTabs' container by the 'emplace_back' method. A memory leak will occur in case of an exception.

V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.

V1048 The 'bValid' variable was assigned the same value.

V1048 The 'bValid' variable was assigned the same value.