/* -*- 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 <comphelper/lok.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <sfx2/app.hxx>
#include <editeng/editobj.hxx>
#include <editeng/justifyitem.hxx>
#include <sfx2/linkmgr.hxx>
#include <sfx2/bindings.hxx>
#include <utility>
#include <vcl/weld.hxx>
#include <vcl/stdtext.hxx>
#include <vcl/svapp.hxx>
#include <svx/svdocapt.hxx>
#include <sal/log.hxx>
#include <unotools/charclass.hxx>
#include <osl/diagnose.h>

#include <com/sun/star/container/XNameContainer.hpp>
#include <com/sun/star/script/ModuleType.hpp>
#include <com/sun/star/script/XLibraryContainer.hpp>
#include <com/sun/star/script/vba/XVBAModuleInfo.hpp>

#include <docfunc.hxx>

#include <sc.hrc>
#include <strings.hrc>

#include <arealink.hxx>
#include <attrib.hxx>
#include <dociter.hxx>
#include <autoform.hxx>
#include <formulacell.hxx>
#include <cellmergeoption.hxx>
#include <detdata.hxx>
#include <detfunc.hxx>
#include <docpool.hxx>
#include <docsh.hxx>
#include <drwlayer.hxx>
#include <editutil.hxx>
#include <globstr.hrc>
#include <olinetab.hxx>
#include <patattr.hxx>
#include <rangenam.hxx>
#include <refundo.hxx>
#include <scresid.hxx>
#include <stlpool.hxx>
#include <stlsheet.hxx>
#include <tablink.hxx>
#include <tabvwsh.hxx>
#include <uiitems.hxx>
#include <undoblk.hxx>
#include <undocell.hxx>
#include <undodraw.hxx>
#include <undotab.hxx>
#include <sizedev.hxx>
#include <scmod.hxx>
#include <inputhdl.hxx>
#include <editable.hxx>
#include <compiler.hxx>
#include <scui_def.hxx>
#include <tabprotection.hxx>
#include <clipparam.hxx>
#include <externalrefmgr.hxx>
#include <undorangename.hxx>
#include <progress.hxx>
#include <dpobject.hxx>
#include <stringutil.hxx>
#include <cellvalue.hxx>
#include <tokenarray.hxx>
#include <rowheightcontext.hxx>
#include <cellvalues.hxx>
#include <undoconvert.hxx>
#include <docfuncutil.hxx>
#include <sheetevents.hxx>
#include <conditio.hxx>
#include <columnspanset.hxx>
#include <validat.hxx>
#include <SparklineGroup.hxx>
#include <SparklineAttributes.hxx>
#include <SparklineData.hxx>
#include <undo/UndoInsertSparkline.hxx>
#include <undo/UndoDeleteSparkline.hxx>
#include <undo/UndoDeleteSparklineGroup.hxx>
#include <undo/UndoEditSparklineGroup.hxx>
#include <undo/UndoUngroupSparklines.hxx>
#include <undo/UndoGroupSparklines.hxx>
#include <undo/UndoEditSparkline.hxx>
#include <SheetViewOperationsTester.hxx>
#include <config_features.h>

#include <memory>
#include <basic/basmgr.hxx>
#include <set>
#include <vector>
#include <sfx2/viewfrm.hxx>

using namespace com::sun::star;

#define AUTOFORMAT_WARN_SIZE 0x10ffffUL

bool ScDocFunc::CheckSheetViewProtection(sc::Operation eOperation)
{
    sc::SheetViewOperationsTester aSheetViewTester(ScDocShell::GetViewData());
    return aSheetViewTester.check(eOperation);
}

void ScDocFunc::NotifyDrawUndo( std::unique_ptr<SdrUndoAction> pUndoAction)
{
    // #i101118# if drawing layer collects the undo actions, add it there
    ScDrawLayer* pDrawLayer = rDocShell.GetDocument().GetDrawLayer();
    if( pDrawLayer && pDrawLayer->IsRecording() )
        pDrawLayer->AddCalcUndo( std::move(pUndoAction) );
    else
        rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoDraw>( std::move(pUndoAction), rDocShell ) );
    rDocShell.SetDrawModified();

    // the affected sheet isn't known, so all stream positions are invalidated
    ScDocument& rDoc = rDocShell.GetDocument();
    SCTAB nTabCount = rDoc.GetTableCount();
    for (SCTAB nTab=0; nTab<nTabCount; nTab++)
        rDoc.SetStreamValid(nTab, false);
}

//  paint row above the range (because of lines after AdjustRowHeight)

static void lcl_PaintAbove( ScDocShell& rDocShell, const ScRange& rRange )
{
    SCROW nRow = rRange.aStart.Row();
    if ( nRow > 0 )
    {
        SCTAB nTab = rRange.aStart.Tab();   //! all of them?
        --nRow;
        ScDocument& rDoc = rDocShell.GetDocument();
        rDocShell.PostPaint( ScRange(0,nRow,nTab,rDoc.MaxCol(),nRow,nTab), PaintPartFlags::Grid );
    }
}

bool ScDocFunc::AdjustRowHeight( const ScRange& rRange, bool bPaint, bool bApi )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    SfxViewShell* pSomeViewForThisDoc = rDocShell.GetBestViewShell(false);
    if ( rDoc.IsImportingXML() )
    {
        //  for XML import, all row heights are updated together after importing
        return false;
    }
    if ( rDoc.IsAdjustHeightLocked() )
    {
        return false;
    }

    SCTAB nTab      = rRange.aStart.Tab();
    SCROW nStartRow = rRange.aStart.Row();
    SCROW nEndRow   = rRange.aEnd.Row();

    ScSizeDeviceProvider aProv( rDocShell );
    Fraction aOne(1,1);

    sc::RowHeightContext aCxt(rDoc.MaxRow(), aProv.GetPPTX(), aProv.GetPPTY(), aOne, aOne, aProv.GetDevice());
    bool bChanged = rDoc.SetOptimalHeight(aCxt, nStartRow, nEndRow, nTab, bApi);
    // tdf#76183: recalculate objects' positions
    if (bChanged)
    {
        if (comphelper::LibreOfficeKit::isActive())
        {
            SfxViewShell* pViewShell = SfxViewShell::GetFirst();
            while (pViewShell)
            {
                ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell);
                if (pTabViewShell && pSomeViewForThisDoc && pTabViewShell->GetDocId() == pSomeViewForThisDoc->GetDocId())
                {
                    if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKHeightHelper(nTab))
                        pPosHelper->invalidateByIndex(nStartRow);
                }
                pViewShell = SfxViewShell::GetNext(*pViewShell);
            }
        }
        rDoc.SetDrawPageSize(nTab);
    }

    if ( bPaint && bChanged )
        rDocShell.PostPaint(ScRange(0, nStartRow, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab),
                            PaintPartFlags::Grid | PaintPartFlags::Left);

    if (comphelper::LibreOfficeKit::isActive())
    {
        ScTabViewShell::notifyAllViewsHeaderInvalidation(pSomeViewForThisDoc, ROW_HEADER, nTab);
        ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
            pSomeViewForThisDoc, false /* bColumns */, true /* bRows */, true /* bSizes*/,
            false /* bHidden */, false /* bFiltered */, false /* bGroups */, nTab);
    }

    return bChanged;
}

bool ScDocFunc::DetectiveAddPred(const ScAddress& rPos)
{
    ScDocShellModificator aModificator( rDocShell );

    rDocShell.MakeDrawLayer();
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bUndo (rDoc.IsUndoEnabled());
    ScDrawLayer* pModel = rDoc.GetDrawLayer();
    SCCOL nCol = rPos.Col();
    SCROW nRow = rPos.Row();
    SCTAB nTab = rPos.Tab();

    if (bUndo)
        pModel->BeginCalcUndo(false);
    bool bDone = ScDetectiveFunc(rDoc, nTab).ShowPred( nCol, nRow );
    std::unique_ptr<SdrUndoGroup> pUndo;
    if (bUndo)
        pUndo = pModel->GetCalcUndo();
    if (bDone)
    {
        ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_ADDPRED );
        rDoc.AddDetectiveOperation( aOperation );
        if (bUndo)
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                        std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), &aOperation ) );
        }
        aModificator.SetDocumentModified();
        SfxBindings* pBindings = rDocShell.GetViewBindings();
        if (pBindings)
            pBindings->Invalidate( SID_DETECTIVE_REFRESH );
    }

    return bDone;
}

bool ScDocFunc::DetectiveDelPred(const ScAddress& rPos)
{
    ScDocument& rDoc = rDocShell.GetDocument();

    bool bUndo(rDoc.IsUndoEnabled());
    ScDrawLayer* pModel = rDoc.GetDrawLayer();
    if (!pModel)
        return false;

    ScDocShellModificator aModificator( rDocShell );

    SCCOL nCol = rPos.Col();
    SCROW nRow = rPos.Row();
    SCTAB nTab = rPos.Tab();

    if (bUndo)
        pModel->BeginCalcUndo(false);
    bool bDone = ScDetectiveFunc(rDoc, nTab).DeletePred( nCol, nRow );
    std::unique_ptr<SdrUndoGroup> pUndo;
    if (bUndo)
        pUndo = pModel->GetCalcUndo();
    if (bDone)
    {
        ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_DELPRED );
        rDoc.AddDetectiveOperation( aOperation );
        if (bUndo)
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                        std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), &aOperation ) );
        }
        aModificator.SetDocumentModified();
        SfxBindings* pBindings = rDocShell.GetViewBindings();
        if (pBindings)
            pBindings->Invalidate( SID_DETECTIVE_REFRESH );
    }

    return bDone;
}

bool ScDocFunc::DetectiveAddSucc(const ScAddress& rPos)
{
    ScDocShellModificator aModificator( rDocShell );

    rDocShell.MakeDrawLayer();
    ScDocument& rDoc = rDocShell.GetDocument();

    bool bUndo(rDoc.IsUndoEnabled());
    ScDrawLayer* pModel = rDoc.GetDrawLayer();
    SCCOL nCol = rPos.Col();
    SCROW nRow = rPos.Row();
    SCTAB nTab = rPos.Tab();

    if (bUndo)
        pModel->BeginCalcUndo(false);
    bool bDone = ScDetectiveFunc(rDoc, nTab).ShowSucc( nCol, nRow );
    std::unique_ptr<SdrUndoGroup> pUndo;
    if (bUndo)
        pUndo = pModel->GetCalcUndo();
    if (bDone)
    {
        ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_ADDSUCC );
        rDoc.AddDetectiveOperation( aOperation );
        if (bUndo)
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                        std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), &aOperation ) );
        }
        aModificator.SetDocumentModified();
        SfxBindings* pBindings = rDocShell.GetViewBindings();
        if (pBindings)
            pBindings->Invalidate( SID_DETECTIVE_REFRESH );
    }

    return bDone;
}

bool ScDocFunc::DetectiveDelSucc(const ScAddress& rPos)
{
    ScDocument& rDoc = rDocShell.GetDocument();

    bool bUndo (rDoc.IsUndoEnabled());
    ScDrawLayer* pModel = rDoc.GetDrawLayer();
    if (!pModel)
        return false;

    ScDocShellModificator aModificator( rDocShell );

    SCCOL nCol = rPos.Col();
    SCROW nRow = rPos.Row();
    SCTAB nTab = rPos.Tab();

    if (bUndo)
        pModel->BeginCalcUndo(false);
    bool bDone = ScDetectiveFunc(rDoc, nTab).DeleteSucc( nCol, nRow );
    std::unique_ptr<SdrUndoGroup> pUndo;
    if (bUndo)
        pUndo = pModel->GetCalcUndo();
    if (bDone)
    {
        ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_DELSUCC );
        rDoc.AddDetectiveOperation( aOperation );
        if (bUndo)
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                        std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), &aOperation ) );
        }
        aModificator.SetDocumentModified();
        SfxBindings* pBindings = rDocShell.GetViewBindings();
        if (pBindings)
            pBindings->Invalidate( SID_DETECTIVE_REFRESH );
    }

    return bDone;
}

bool ScDocFunc::DetectiveAddError(const ScAddress& rPos)
{
    ScDocShellModificator aModificator( rDocShell );

    rDocShell.MakeDrawLayer();
    ScDocument& rDoc = rDocShell.GetDocument();

    bool bUndo (rDoc.IsUndoEnabled());
    ScDrawLayer* pModel = rDoc.GetDrawLayer();
    SCCOL nCol = rPos.Col();
    SCROW nRow = rPos.Row();
    SCTAB nTab = rPos.Tab();

    if (bUndo)
        pModel->BeginCalcUndo(false);
    bool bDone = ScDetectiveFunc(rDoc, nTab).ShowError( nCol, nRow );
    std::unique_ptr<SdrUndoGroup> pUndo;
    if (bUndo)
        pUndo = pModel->GetCalcUndo();
    if (bDone)
    {
        ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_ADDERROR );
        rDoc.AddDetectiveOperation( aOperation );
        if (bUndo)
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                        std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), &aOperation ) );
        }
        aModificator.SetDocumentModified();
        SfxBindings* pBindings = rDocShell.GetViewBindings();
        if (pBindings)
            pBindings->Invalidate( SID_DETECTIVE_REFRESH );
    }

    return bDone;
}

bool ScDocFunc::DetectiveMarkInvalid(SCTAB nTab)
{
    ScDocShellModificator aModificator( rDocShell );

    rDocShell.MakeDrawLayer();
    ScDocument& rDoc = rDocShell.GetDocument();

    bool bUndo (rDoc.IsUndoEnabled());
    ScDrawLayer* pModel = rDoc.GetDrawLayer();

    std::unique_ptr<weld::WaitObject> xWaitWin(new weld::WaitObject(ScDocShell::GetActiveDialogParent()));
    if (bUndo)
        pModel->BeginCalcUndo(false);
    bool bOverflow;
    bool bDone = ScDetectiveFunc(rDoc, nTab).MarkInvalid( bOverflow );
    std::unique_ptr<SdrUndoGroup> pUndo;
    if (bUndo)
        pUndo = pModel->GetCalcUndo();
    xWaitWin.reset();
    if (bDone)
    {
        if (pUndo && bUndo)
        {
            pUndo->SetComment( ScResId( STR_UNDO_DETINVALID ) );
            rDocShell.GetUndoManager()->AddUndoAction( std::move(pUndo) );
        }
        aModificator.SetDocumentModified();
        if ( bOverflow )
        {
            std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr,
                                                          VclMessageType::Info, VclButtonsType::Ok,
                                                          ScResId(STR_DETINVALID_OVERFLOW)));
            xInfoBox->run();
        }
    }

    return bDone;
}

bool ScDocFunc::DetectiveDelAll(SCTAB nTab)
{
    ScDocument& rDoc = rDocShell.GetDocument();

    bool bUndo (rDoc.IsUndoEnabled());
    ScDrawLayer* pModel = rDoc.GetDrawLayer();
    if (!pModel)
        return false;

    ScDocShellModificator aModificator( rDocShell );

    if (bUndo)
        pModel->BeginCalcUndo(false);
    bool bDone = ScDetectiveFunc(rDoc, nTab).DeleteAll( ScDetectiveDelete::Detective );
    std::unique_ptr<SdrUndoGroup> pUndo;
    if (bUndo)
        pUndo = pModel->GetCalcUndo();
    if (bDone)
    {
        ScDetOpList* pOldList = rDoc.GetDetOpList();
        std::unique_ptr<ScDetOpList> pUndoList;
        if (bUndo && pOldList)
            pUndoList.reset(new ScDetOpList(*pOldList));

        rDoc.ClearDetectiveOperations();

        if (bUndo)
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                        std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), nullptr, std::move(pUndoList) ) );
        }
        aModificator.SetDocumentModified();
        SfxBindings* pBindings = rDocShell.GetViewBindings();
        if (pBindings)
            pBindings->Invalidate( SID_DETECTIVE_REFRESH );
    }

    return bDone;
}

bool ScDocFunc::DetectiveRefresh( bool bAutomatic )
{
    bool bDone = false;
    ScDocument& rDoc = rDocShell.GetDocument();

    ScDetOpList* pList = rDoc.GetDetOpList();
    if ( pList && pList->Count() )
    {
        rDocShell.MakeDrawLayer();
        ScDrawLayer* pModel = rDoc.GetDrawLayer();
        const bool bUndo (rDoc.IsUndoEnabled());
        if (bUndo)
            pModel->BeginCalcUndo(false);

        //  Delete in all sheets

        SCTAB nTabCount = rDoc.GetTableCount();
        for (SCTAB nTab=0; nTab<nTabCount; nTab++)
            ScDetectiveFunc( rDoc,nTab ).DeleteAll( ScDetectiveDelete::Arrows );    // don't remove circles

        //  repeat

        size_t nCount = pList->Count();
        for (size_t i=0; i < nCount; ++i)
        {
            const ScDetOpData& rData = pList->GetObject(i);
            const ScAddress& aPos = rData.GetPos();
            ScDetectiveFunc aFunc( rDoc, aPos.Tab() );
            SCCOL nCol = aPos.Col();
            SCROW nRow = aPos.Row();
            switch (rData.GetOperation())
            {
                case SCDETOP_ADDSUCC:
                    aFunc.ShowSucc( nCol, nRow );
                    break;
                case SCDETOP_DELSUCC:
                    aFunc.DeleteSucc( nCol, nRow );
                    break;
                case SCDETOP_ADDPRED:
                     aFunc.ShowPred( nCol, nRow );
                     break;
                case SCDETOP_DELPRED:
                    aFunc.DeletePred( nCol, nRow );
                    break;
                case SCDETOP_ADDERROR:
                    aFunc.ShowError( nCol, nRow );
                    break;
                default:
                    OSL_FAIL("wrong operation in DetectiveRefresh");
            }
        }

        if (bUndo)
        {
            std::unique_ptr<SdrUndoGroup> pUndo = pModel->GetCalcUndo();
            if (pUndo)
            {
                pUndo->SetComment( ScResId( STR_UNDO_DETREFRESH ) );
                // associate with the last action
                rDocShell.GetUndoManager()->AddUndoAction(
                                                std::make_unique<ScUndoDraw>( std::move(pUndo), rDocShell ),
                                                    bAutomatic );
            }
        }
        rDocShell.SetDrawModified();
        bDone = true;
    }
    return bDone;
}

static void lcl_collectAllPredOrSuccRanges(
    const ScRangeList& rSrcRanges, std::vector<ScTokenRef>& rRefTokens, ScDocShell& rDocShell,
    bool bPred)
{
    ScDocument& rDoc = rDocShell.GetDocument();
    std::vector<ScTokenRef> aRefTokens;
    if (rSrcRanges.empty())
        return;
    ScRange const & rFrontRange = rSrcRanges.front();
    ScDetectiveFunc aDetFunc(rDoc, rFrontRange.aStart.Tab());
    for (ScRange const & r : rSrcRanges)
    {
        if (bPred)
        {
            aDetFunc.GetAllPreds(
                r.aStart.Col(), r.aStart.Row(), r.aEnd.Col(), r.aEnd.Row(), aRefTokens);
        }
        else
        {
            aDetFunc.GetAllSuccs(
                r.aStart.Col(), r.aStart.Row(), r.aEnd.Col(), r.aEnd.Row(), aRefTokens);
        }
    }
    rRefTokens.swap(aRefTokens);
}

void ScDocFunc::DetectiveCollectAllPreds(const ScRangeList& rSrcRanges, std::vector<ScTokenRef>& rRefTokens)
{
    lcl_collectAllPredOrSuccRanges(rSrcRanges, rRefTokens, rDocShell, true);
}

void ScDocFunc::DetectiveCollectAllSuccs(const ScRangeList& rSrcRanges, std::vector<ScTokenRef>& rRefTokens)
{
    lcl_collectAllPredOrSuccRanges(rSrcRanges, rRefTokens, rDocShell, false);
}

bool ScDocFunc::DeleteContents(
    const ScMarkData& rMark, InsertDeleteFlags nFlags, bool bRecord, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    if ( !rMark.IsMarked() && !rMark.IsMultiMarked() )
    {
        OSL_FAIL("ScDocFunc::DeleteContents without markings");
        return false;
    }

    ScDocument& rDoc = rDocShell.GetDocument();

    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;

    if (!CheckSheetViewProtection(sc::Operation::DeleteContent))
        return false;

    ScEditableTester aTester = ScEditableTester::CreateAndTestSelection(rDoc, rMark);
    if (!aTester.IsEditable())
    {
        if (!bApi)
            rDocShell.ErrorMessage(aTester.GetMessageId());
        return false;
    }

    ScMarkData aMultiMark = rMark;
    aMultiMark.SetMarking(false);       // for MarkToMulti

    ScDocumentUniquePtr pUndoDoc;
    bool bMulti = aMultiMark.IsMultiMarked();
    aMultiMark.MarkToMulti();
    const ScRange& aMarkRange = aMultiMark.GetMultiMarkArea();
    ScRange aExtendedRange(aMarkRange);
    if ( rDoc.ExtendMerge( aExtendedRange, true ) )
        bMulti = false;

    // no objects on protected tabs
    bool bObjects = (nFlags & InsertDeleteFlags::OBJECTS) && !sc::DocFuncUtil::hasProtectedTab(rDoc, rMark);

    sal_uInt16 nExtFlags = 0;       // extra flags are needed only if attributes are deleted
    if ( nFlags & InsertDeleteFlags::ATTRIB )
        rDocShell.UpdatePaintExt( nExtFlags, aMarkRange );

    //  order of operations:
    //  1) BeginDrawUndo
    //  2) Delete objects (DrawUndo will be filled)
    //  3) Copy content for undo and set up undo actions
    //  4) Delete content

    bool bDrawUndo = bObjects || (nFlags & InsertDeleteFlags::NOTE);
    if (bRecord && bDrawUndo)
        rDoc.BeginDrawUndo();

    if (bObjects)
    {
        if (bMulti)
            rDoc.DeleteObjectsInSelection( aMultiMark );
        else
            rDoc.DeleteObjectsInArea( aMarkRange.aStart.Col(), aMarkRange.aStart.Row(),
                                       aMarkRange.aEnd.Col(),   aMarkRange.aEnd.Row(),
                                       aMultiMark );
    }

    // To keep track of all non-empty cells within the deleted area.
    std::shared_ptr<ScSimpleUndo::DataSpansType> pDataSpans;

    if ( bRecord )
    {
        pUndoDoc = sc::DocFuncUtil::createDeleteContentsUndoDoc(rDoc, aMultiMark, aMarkRange, nFlags, bMulti);
        pDataSpans = sc::DocFuncUtil::getNonEmptyCellSpans(rDoc, aMultiMark, aMarkRange);
    }

    rDoc.DeleteSelection( nFlags, aMultiMark );

    // add undo action after drawing undo is complete (objects and note captions)
    if( bRecord )
    {
        sc::DocFuncUtil::addDeleteContentsUndo(
            rDocShell.GetUndoManager(), rDocShell, aMultiMark, aExtendedRange,
            std::move(pUndoDoc), nFlags, pDataSpans, bMulti, bDrawUndo);
    }

    if (!AdjustRowHeight( aExtendedRange, true, bApi ))
        rDocShell.PostPaint( aExtendedRange, PaintPartFlags::Grid, nExtFlags );
    else if (nExtFlags & SC_PF_LINES)
        lcl_PaintAbove( rDocShell, aExtendedRange );    // for lines above the range

    aModificator.SetDocumentModified();

    return true;
}

tools::Long ScDocShell::GetTwipWidthHint(const ScAddress& rPos)
{
    ScViewData* pViewData = GetViewData();
    if (!pViewData)
        return -1;

    ScSizeDeviceProvider aProv(*this);
    Fraction aZoomX, aZoomY;
    double nPPTX, nPPTY;
    pViewData->setupSizeDeviceProviderForColWidth(aProv, aZoomX, aZoomY, nPPTX, nPPTY);

    ScDocument& rDoc = GetDocument();
    tools::Long nWidth = rDoc.GetNeededSize(rPos.Col(), rPos.Row(), rPos.Tab(), aProv.GetDevice(),
                                            nPPTX, nPPTY, aZoomX, aZoomY, true /*bWidth*/);

    return (nWidth + 2) / nPPTX; // same as ScColumn::GetOptimalColWidth
}

bool ScDocFunc::DeleteCell(
    const ScAddress& rPos, const ScMarkData& rMark, InsertDeleteFlags nFlags, bool bRecord, bool bApi )
{
    ScDocShellModificator aModificator(rDocShell);

    ScDocument& rDoc = rDocShell.GetDocument();

    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;

    if (!CheckSheetViewProtection(sc::Operation::DeleteCell))
        return false;

    ScEditableTester aTester = ScEditableTester::CreateAndTestSelectedBlock(rDoc, rPos.Col(), rPos.Row(), rPos.Col(), rPos.Row(), rMark);
    if (!aTester.IsEditable())
    {
        rDocShell.ErrorMessage(aTester.GetMessageId());
        return false;
    }

    // no objects on protected tabs
    bool bObjects = (nFlags & InsertDeleteFlags::OBJECTS) && !sc::DocFuncUtil::hasProtectedTab(rDoc, rMark);

    sal_uInt16 nExtFlags = 0;       // extra flags are needed only if attributes are deleted
    if (nFlags & InsertDeleteFlags::ATTRIB)
        rDocShell.UpdatePaintExt(nExtFlags, ScRange(rPos));

    //  order of operations:
    //  1) BeginDrawUndo
    //  2) delete objects (DrawUndo is filled)
    //  3) copy contents for undo
    //  4) delete contents
    //  5) add undo-action

    bool bDrawUndo = bObjects || (nFlags & InsertDeleteFlags::NOTE);     // needed for shown notes
    if (bDrawUndo && bRecord)
        rDoc.BeginDrawUndo();

    if (bObjects)
        rDoc.DeleteObjectsInArea(rPos.Col(), rPos.Row(), rPos.Col(), rPos.Row(), rMark);

    // To keep track of all non-empty cells within the deleted area.
    std::shared_ptr<ScSimpleUndo::DataSpansType> pDataSpans;

    ScDocumentUniquePtr pUndoDoc;
    if (bRecord)
    {
        pUndoDoc = sc::DocFuncUtil::createDeleteContentsUndoDoc(rDoc, rMark, ScRange(rPos), nFlags, false);
        pDataSpans = sc::DocFuncUtil::getNonEmptyCellSpans(rDoc, rMark, ScRange(rPos));
    }

    tools::Long nBefore(rDocShell.GetTwipWidthHint(rPos));
    rDoc.DeleteArea(rPos.Col(), rPos.Row(), rPos.Col(), rPos.Row(), rMark, nFlags);

    if (bRecord)
    {
        sc::DocFuncUtil::addDeleteContentsUndo(
            rDocShell.GetUndoManager(), rDocShell, rMark, ScRange(rPos), std::move(pUndoDoc),
            nFlags, pDataSpans, false, bDrawUndo);
    }

    if (!AdjustRowHeight(ScRange(rPos), true, bApi))
        rDocShell.PostPaint(
            rPos.Col(), rPos.Row(), rPos.Tab(), rPos.Col(), rPos.Row(), rPos.Tab(),
            PaintPartFlags::Grid, nExtFlags, nBefore);

    aModificator.SetDocumentModified();

    return true;
}

bool ScDocFunc::TransliterateText( const ScMarkData& rMark, TransliterationFlags nType,
                                    bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    ScDocument& rDoc = rDocShell.GetDocument();
    bool bRecord = true;
    if (!rDoc.IsUndoEnabled())
        bRecord = false;

    if (!CheckSheetViewProtection(sc::Operation::TransliterateText))
        return false;

    ScEditableTester aTester = ScEditableTester::CreateAndTestSelection(rDoc, rMark);
    if (!aTester.IsEditable())
    {
        if (!bApi)
            rDocShell.ErrorMessage(aTester.GetMessageId());
        return false;
    }

    ScMarkData aMultiMark = rMark;
    aMultiMark.SetMarking(false);       // for MarkToMulti
    aMultiMark.MarkToMulti();
    const ScRange& aMarkRange = aMultiMark.GetMultiMarkArea();

    if (bRecord)
    {
        SCTAB nStartTab = aMarkRange.aStart.Tab();
        SCTAB nTabCount = rDoc.GetTableCount();

        ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
        pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab );
        for (const auto& rTab : rMark)
        {
            if (rTab >= nTabCount)
                break;

            if (rTab != nStartTab)
                pUndoDoc->AddUndoTab( rTab, rTab );
        }

        ScRange aCopyRange = aMarkRange;
        aCopyRange.aStart.SetTab(0);
        aCopyRange.aEnd.SetTab(nTabCount-1);
        rDoc.CopyToDocument(aCopyRange, InsertDeleteFlags::CONTENTS, true, *pUndoDoc, &aMultiMark);

        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoTransliterate>( rDocShell, aMultiMark, std::move(pUndoDoc), nType ) );
    }

    rDoc.TransliterateText( aMultiMark, nType );

    if (!AdjustRowHeight( aMarkRange, true, true ))
        rDocShell.PostPaint( aMarkRange, PaintPartFlags::Grid );

    aModificator.SetDocumentModified();

    return true;
}

bool ScDocFunc::SetNormalString( bool& o_rbNumFmtSet, const ScAddress& rPos, const OUString& rText, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );
    ScDocument& rDoc = rDocShell.GetDocument();

    bool bUndo(rDoc.IsUndoEnabled());

    if (!CheckSheetViewProtection(sc::Operation::SetNormalString))
        return false;

    ScEditableTester aTester = ScEditableTester::CreateAndTestBlock(rDoc, rPos.Tab(), rPos.Col(), rPos.Row(), rPos.Col(), rPos.Row());
    if (!aTester.IsEditable())
    {
        if (!bApi)
            rDocShell.ErrorMessage(aTester.GetMessageId());
        return false;
    }

    bool bEditDeleted = (rDoc.GetCellType(rPos) == CELLTYPE_EDIT);
    ScUndoEnterData::ValuesType aOldValues;

    if (bUndo)
    {
        ScUndoEnterData::Value aOldValue;

        aOldValue.mnTab = rPos.Tab();
        aOldValue.maCell.assign(rDoc, rPos);

        const ScPatternAttr* pPattern = rDoc.GetPattern( rPos.Col(),rPos.Row(),rPos.Tab() );
        if ( const SfxUInt32Item* pItem = pPattern->GetItemSet().GetItemIfSet(
                                ATTR_VALUE_FORMAT,false) )
        {
            aOldValue.mbHasFormat = true;
            aOldValue.mnFormat = pItem->GetValue();
        }
        else
            aOldValue.mbHasFormat = false;

        aOldValues.push_back(aOldValue);
    }

    tools::Long nBefore(rDocShell.GetTwipWidthHint(rPos));
    o_rbNumFmtSet = rDoc.SetString( rPos.Col(), rPos.Row(), rPos.Tab(), rText );
    tools::Long nAfter(rDocShell.GetTwipWidthHint(rPos));

    if (bUndo)
    {
        //  because of ChangeTracking, UndoAction can be created only after SetString was called
        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoEnterData>(rDocShell, rPos, aOldValues, rText, nullptr));
    }

    if ( bEditDeleted || rDoc.HasAttrib( ScRange(rPos), HasAttrFlags::NeedHeight ) )
        AdjustRowHeight( ScRange(rPos), true, bApi );

    rDocShell.PostPaintCell( rPos, std::max(nBefore, nAfter) );
    aModificator.SetDocumentModified();

    // notify input handler here the same way as in PutCell
    if (bApi)
        NotifyInputHandler( rPos );

    const SfxUInt32Item& rItem = rDoc.GetAttr(rPos, ATTR_VALIDDATA);
    const ScValidationData* pData = rDoc.GetValidationEntry(rItem.GetValue());
    if (pData)
    {
        ScRefCellValue aCell(rDoc, rPos);
        if (pData->IsDataValid(aCell, rPos))
            ScDetectiveFunc(rDoc, rPos.Tab()).DeleteCirclesAt(rPos.Col(), rPos.Row());
    }

    return true;
}

bool ScDocFunc::SetValueCell( const ScAddress& rPos, double fVal, bool bInteraction )
{
    ScDocShellModificator aModificator( rDocShell );
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bUndo = rDoc.IsUndoEnabled();

    bool bHeight = rDoc.HasAttrib(ScRange(rPos), HasAttrFlags::NeedHeight);

    ScCellValue aOldVal;
    if (bUndo)
        aOldVal.assign(rDoc, rPos);

    rDoc.SetValue(rPos, fVal);

    if (bUndo)
    {
        SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
        ScCellValue aNewVal;
        aNewVal.assign(rDoc, rPos);
        pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(rDocShell, rPos, aOldVal, aNewVal));
    }

    if (bHeight)
        AdjustRowHeight(ScRange(rPos), true, !bInteraction);

    rDocShell.PostPaintCell( rPos );
    aModificator.SetDocumentModified();

    // #103934#; notify editline and cell in edit mode
    if (!bInteraction)
        NotifyInputHandler( rPos );

    return true;
}

void ScDocFunc::SetValueCells( const ScAddress& rPos, const std::vector<double>& aVals, bool bInteraction )
{
    ScDocument& rDoc = rDocShell.GetDocument();

    // Check for invalid range.
    SCROW nLastRow = rPos.Row() + aVals.size() - 1;
    if (nLastRow > rDoc.MaxRow())
        // out of bound.
        return;

    ScRange aRange(rPos);
    aRange.aEnd.SetRow(nLastRow);

    ScDocShellModificator aModificator(rDocShell);

    if (rDoc.IsUndoEnabled())
    {
        std::unique_ptr<sc::UndoSetCells> pUndoObj(new sc::UndoSetCells(rDocShell, rPos));
        rDoc.TransferCellValuesTo(rPos, aVals.size(), pUndoObj->GetOldValues());
        pUndoObj->SetNewValues(aVals);
        SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
        pUndoMgr->AddUndoAction(std::move(pUndoObj));
    }

    rDoc.SetValues(rPos, aVals);

    rDocShell.PostPaint(aRange, PaintPartFlags::Grid);
    aModificator.SetDocumentModified();

    // #103934#; notify editline and cell in edit mode
    if (!bInteraction)
        NotifyInputHandler(rPos);
}

bool ScDocFunc::SetStringCell( const ScAddress& rPos, const OUString& rStr, bool bInteraction )
{
    ScDocShellModificator aModificator( rDocShell );
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bUndo = rDoc.IsUndoEnabled();

    bool bHeight = rDoc.HasAttrib(ScRange(rPos), HasAttrFlags::NeedHeight);

    ScCellValue aOldVal;
    if (bUndo)
        aOldVal.assign(rDoc, rPos);

    ScSetStringParam aParam;
    aParam.setTextInput();
    rDoc.SetString(rPos, rStr, &aParam);

    if (bUndo)
    {
        SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
        ScCellValue aNewVal;
        aNewVal.assign(rDoc, rPos);
        pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(rDocShell, rPos, aOldVal, aNewVal));
    }

    if (bHeight)
        AdjustRowHeight(ScRange(rPos), true, !bInteraction);

    rDocShell.PostPaintCell( rPos );
    aModificator.SetDocumentModified();

    // #103934#; notify editline and cell in edit mode
    if (!bInteraction)
        NotifyInputHandler( rPos );

    return true;
}

bool ScDocFunc::SetEditCell( const ScAddress& rPos, const EditTextObject& rStr, bool bInteraction )
{
    ScDocShellModificator aModificator( rDocShell );
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bUndo = rDoc.IsUndoEnabled();

    bool bHeight = rDoc.HasAttrib(ScRange(rPos), HasAttrFlags::NeedHeight);

    ScCellValue aOldVal;
    if (bUndo)
        aOldVal.assign(rDoc, rPos);

    rDoc.SetEditText(rPos, rStr.Clone());

    if (bUndo)
    {
        SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
        ScCellValue aNewVal;
        aNewVal.assign(rDoc, rPos);
        pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(rDocShell, rPos, aOldVal, aNewVal));
    }

    if (bHeight)
        AdjustRowHeight(ScRange(rPos), true, !bInteraction);

    rDocShell.PostPaintCell( rPos );
    aModificator.SetDocumentModified();

    // #103934#; notify editline and cell in edit mode
    if (!bInteraction)
        NotifyInputHandler( rPos );

    return true;
}

bool ScDocFunc::SetStringOrEditCell( const ScAddress& rPos, const OUString& rStr, bool bInteraction )
{
    ScDocument& rDoc = rDocShell.GetDocument();

    if (ScStringUtil::isMultiline(rStr))
    {
        ScFieldEditEngine& rEngine = rDoc.GetEditEngine();
        rEngine.SetTextCurrentDefaults(rStr);
        std::unique_ptr<EditTextObject> pEditText(rEngine.CreateTextObject());
        return SetEditCell(rPos, *pEditText, bInteraction);
    }
    else
        return SetStringCell(rPos, rStr, bInteraction);
}

bool ScDocFunc::SetFormulaCell( const ScAddress& rPos, ScFormulaCell* pCell, bool bInteraction )
{
    std::unique_ptr<ScFormulaCell> xCell(pCell);

    ScDocShellModificator aModificator( rDocShell );
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bUndo = rDoc.IsUndoEnabled();

    bool bHeight = rDoc.HasAttrib(ScRange(rPos), HasAttrFlags::NeedHeight);

    ScCellValue aOldVal;
    if (bUndo)
        aOldVal.assign(rDoc, rPos);

    pCell = rDoc.SetFormulaCell(rPos, xCell.release());

    // For performance reasons API calls may disable calculation while
    // operating and recalculate once when done. If through user interaction
    // and AutoCalc is disabled, calculate the formula (without its
    // dependencies) once so the result matches the current document's content.
    if (bInteraction && !rDoc.GetAutoCalc() && pCell)
    {
        // calculate just the cell once and set Dirty again
        pCell->Interpret();
        pCell->SetDirtyVar();
        rDoc.PutInFormulaTree( pCell);
    }

    if (bUndo)
    {
        SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
        ScCellValue aNewVal;
        aNewVal.assign(rDoc, rPos);
        pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(rDocShell, rPos, aOldVal, aNewVal));
    }

    if (bHeight)
        AdjustRowHeight(ScRange(rPos), true, !bInteraction);

    rDocShell.PostPaintCell( rPos );
    aModificator.SetDocumentModified();

    // #103934#; notify editline and cell in edit mode
    if (!bInteraction)
        NotifyInputHandler( rPos );

    return true;
}

bool ScDocFunc::SetFormulaCells( const ScAddress& rPos, std::vector<ScFormulaCell*>& rCells, bool bInteraction )
{
    ScDocument& rDoc = rDocShell.GetDocument();

    const size_t nLength = rCells.size();
    if (rPos.Row() + nLength - 1 > o3tl::make_unsigned(rDoc.MaxRow()))
        // out of bound
        return false;

    ScRange aRange(rPos);
    aRange.aEnd.IncRow(nLength - 1);

    ScDocShellModificator aModificator( rDocShell );
    bool bUndo = rDoc.IsUndoEnabled();

    std::unique_ptr<sc::UndoSetCells> pUndoObj;
    if (bUndo)
    {
        pUndoObj.reset(new sc::UndoSetCells(rDocShell, rPos));
        rDoc.TransferCellValuesTo(rPos, nLength, pUndoObj->GetOldValues());
    }

    rDoc.SetFormulaCells(rPos, rCells);

    // For performance reasons API calls may disable calculation while
    // operating and recalculate once when done. If through user interaction
    // and AutoCalc is disabled, calculate the formula (without its
    // dependencies) once so the result matches the current document's content.
    if (bInteraction && !rDoc.GetAutoCalc())
    {
        for (auto* pCell : rCells)
        {
            // calculate just the cell once and set Dirty again
            pCell->Interpret();
            pCell->SetDirtyVar();
            rDoc.PutInFormulaTree( pCell);
        }
    }

    if (bUndo)
    {
        pUndoObj->SetNewValues(rCells);
        SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
        pUndoMgr->AddUndoAction(std::move(pUndoObj));
    }

    rDocShell.PostPaint(aRange, PaintPartFlags::Grid);
    aModificator.SetDocumentModified();

    // #103934#; notify editline and cell in edit mode
    if (!bInteraction)
        NotifyInputHandler( rPos );

    return true;
}

void ScDocFunc::NotifyInputHandler( const ScAddress& rPos )
{
    ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell();
    if ( !(pViewSh && pViewSh->GetViewData().GetDocShell() == &rDocShell) )
        return;

    ScInputHandler* pInputHdl = ScModule::get()->GetInputHdl();
    if ( pInputHdl && pInputHdl->GetCursorPos() == rPos )
    {
        bool bIsEditMode(pInputHdl->IsEditMode());

        // set modified if in editmode, because so the string is not set in the InputWindow like in the cell
        // (the cell shows the same like the InputWindow)
        if (bIsEditMode)
            pInputHdl->SetModified();
        pViewSh->UpdateInputHandler(false, !bIsEditMode);
    }
}

namespace {

    struct ScMyRememberItem
    {
        sal_Int32   nIndex;
        SfxItemSet  aItemSet;

        ScMyRememberItem(SfxItemSet _aItemSet, sal_Int32 nTempIndex) :
            nIndex(nTempIndex), aItemSet(std::move(_aItemSet)) {}
    };

}

void ScDocFunc::PutData( const ScAddress& rPos, ScEditEngineDefaulter& rEngine, bool bApi )
{
    //  PutData calls PutCell or SetNormalString

    bool bRet = false;
    ScDocument& rDoc = rDocShell.GetDocument();
    ScEditAttrTester aTester( &rEngine );
    bool bEditCell = aTester.NeedsObject();
    if ( bEditCell )
    {
        // #i61702# With bLoseContent set, the content of rEngine isn't restored
        // (used in loading XML, where after the removeActionLock call the API object's
        // EditEngine isn't accessed again.
        bool bLoseContent = rDoc.IsImportingXML();

        const bool bUpdateMode = rEngine.SetUpdateLayout(false);

        std::vector<std::unique_ptr<ScMyRememberItem>> aRememberItems;

        //  All paragraph attributes must be removed before calling CreateTextObject,
        //  not only alignment, so the object doesn't contain the cell attributes as
        //  paragraph attributes. Before removing the attributes store them in a vector to
        //  set them back to the EditEngine.
        sal_Int32 nCount = rEngine.GetParagraphCount();
        for (sal_Int32 i=0; i<nCount; i++)
        {
            const SfxItemSet& rOld = rEngine.GetParaAttribs( i );
            if ( rOld.Count() )
            {
                if ( !bLoseContent )
                {
                    aRememberItems.push_back(std::make_unique<ScMyRememberItem>(rEngine.GetParaAttribs(i), i));
                }
                rEngine.SetParaAttribs( i, SfxItemSet( *rOld.GetPool(), rOld.GetRanges() ) );
            }
        }

        // A copy of pNewData will be stored in the cell.
        std::unique_ptr<EditTextObject> pNewData(rEngine.CreateTextObject());
        bRet = SetEditCell(rPos, *pNewData, !bApi);

        // Set the paragraph attributes back to the EditEngine.
        for (const auto& rxItem : aRememberItems)
        {
            rEngine.SetParaAttribs(rxItem->nIndex, rxItem->aItemSet);
        }

        // #i61702# if the content isn't accessed, there's no need to set the UpdateMode again
        if ( bUpdateMode && !bLoseContent )
            rEngine.SetUpdateLayout(true);
    }
    else
    {
        OUString aText = rEngine.GetText();
        if (aText.isEmpty())
        {
            bool bNumFmtSet = false;
            bRet = SetNormalString( bNumFmtSet, rPos, aText, bApi );
        }
        else
            bRet = SetStringCell(rPos, aText, !bApi);
    }

    if ( !(bRet && aTester.NeedsCellAttr()) )
        return;

    const SfxItemSet& rEditAttr = aTester.GetAttribs();
    ScPatternAttr aPattern(rDoc.getCellAttributeHelper());
    aPattern.GetFromEditItemSet( &rEditAttr );
    aPattern.DeleteUnchanged( rDoc.GetPattern( rPos.Col(), rPos.Row(), rPos.Tab() ) );
    aPattern.ItemSetClearItem(ATTR_HOR_JUSTIFY);    // wasn't removed above if no edit object
    if ( aPattern.GetItemSet().Count() > 0 )
    {
        ScMarkData aMark(rDoc.GetSheetLimits());
        aMark.SelectTable( rPos.Tab(), true );
        aMark.SetMarkArea( ScRange( rPos ) );
        ApplyAttributes( aMark, aPattern, bApi );
    }
}

bool ScDocFunc::SetCellText(
    const ScAddress& rPos, const OUString& rText, bool bInterpret, bool bEnglish, bool bApi,
    const formula::FormulaGrammar::Grammar eGrammar )
{
    bool bSet = false;
    if ( bInterpret )
    {
        if ( bEnglish )
        {
            ScDocument& rDoc = rDocShell.GetDocument();

            ::std::optional<ScExternalRefManager::ApiGuard> pExtRefGuard;
            if (bApi)
                pExtRefGuard.emplace(rDoc);

            ScInputStringType aRes =
                ScStringUtil::parseInputString(rDoc.GetNonThreadedContext(), rText, LANGUAGE_ENGLISH_US);

            switch (aRes.meType)
            {
                case ScInputStringType::Formula:
                    bSet = SetFormulaCell(rPos, new ScFormulaCell(rDoc, rPos, aRes.maText, eGrammar), !bApi);
                break;
                case ScInputStringType::Number:
                    bSet = SetValueCell(rPos, aRes.mfValue, !bApi);
                break;
                case ScInputStringType::Text:
                    bSet = SetStringOrEditCell(rPos, aRes.maText, !bApi);
                break;
                default:
                    ;
            }
        }
        // otherwise keep Null -> SetString with local formulas/number formats
    }
    else if (!rText.isEmpty())
    {
        bSet = SetStringOrEditCell(rPos, rText, !bApi);
    }

    if (!bSet)
    {
        bool bNumFmtSet = false;
        bSet = SetNormalString( bNumFmtSet, rPos, rText, bApi );
    }
    return bSet;
}

bool ScDocFunc::ShowNote( const ScAddress& rPos, bool bShow )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    ScPostIt* pNote = rDoc.GetNote( rPos );
    if( !pNote || (bShow == pNote->IsCaptionShown()) ||
        (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations()) )
        return false;

    // move the caption to internal or hidden layer and create undo action
    pNote->ShowCaption( rPos, bShow );
    if( rDoc.IsUndoEnabled() )
        rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoShowHideNote>( rDocShell, rPos, bShow ) );

    rDoc.SetStreamValid(rPos.Tab(), false);

    ScTabView::OnLOKNoteStateChanged(pNote);

    if (ScViewData* pViewData = ScDocShell::GetViewData())
    {
        if (ScDrawView* pDrawView = pViewData->GetScDrawView())
            pDrawView->SyncForGrid( pNote->GetCaption());
    }

    rDocShell.SetDocumentModified();

    return true;
}

void ScDocFunc::SetNoteText( const ScAddress& rPos, const OUString& rText, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    ScDocument& rDoc = rDocShell.GetDocument();

    if (!CheckSheetViewProtection(sc::Operation::SetNoteText))
        return;

    ScEditableTester aTester = ScEditableTester::CreateAndTestBlock(rDoc, rPos.Tab(), rPos.Col(), rPos.Row(), rPos.Col(), rPos.Row());
    if (!aTester.IsEditable())
    {
        if (!bApi)
            rDocShell.ErrorMessage(aTester.GetMessageId());
        return;
    }

    OUString aNewText = convertLineEnd(rText, GetSystemLineEnd()); //! is this necessary ???

    if( ScPostIt* pNote = (!aNewText.isEmpty()) ? rDoc.GetOrCreateNote( rPos ) : rDoc.GetNote(rPos) )
        pNote->SetText( rPos, aNewText );

    //! Undo !!!

    rDoc.SetStreamValid(rPos.Tab(), false);

    rDocShell.PostPaintCell( rPos );
    aModificator.SetDocumentModified();
}

void ScDocFunc::ReplaceNote( const ScAddress& rPos, const OUString& rNoteText, const OUString* pAuthor, const OUString* pDate, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );
    ScDocument& rDoc = rDocShell.GetDocument();

    if (!CheckSheetViewProtection(sc::Operation::ReplaceNoteText))
        return;

    ScEditableTester aTester = ScEditableTester::CreateAndTestBlock(rDoc, rPos.Tab(), rPos.Col(), rPos.Row(), rPos.Col(), rPos.Row());
    if (aTester.IsEditable())
    {
        ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer();
        SfxUndoManager* pUndoMgr = (pDrawLayer && rDoc.IsUndoEnabled()) ? rDocShell.GetUndoManager() : nullptr;

        ScNoteData aOldData;
        std::unique_ptr<ScPostIt> pOldNote = rDoc.ReleaseNote( rPos );
        sal_uInt32 nNoteId = 0;
        if( pOldNote )
        {
            nNoteId = pOldNote->GetId();
            // ensure existing caption object before draw undo tracking starts
            pOldNote->GetOrCreateCaption( rPos );
            // rescue note data for undo
            aOldData = pOldNote->GetNoteData();
        }

        // collect drawing undo actions for deleting/inserting caption objects
        if( pUndoMgr )
            pDrawLayer->BeginCalcUndo(false);

        // delete the note (creates drawing undo action for the caption object)
        bool hadOldNote(pOldNote);
        pOldNote.reset();

        // create new note (creates drawing undo action for the new caption object)
        ScNoteData aNewData;
        ScPostIt* pNewNote = nullptr;
        if( (pNewNote = ScNoteUtil::CreateNoteFromString( rDoc, rPos, rNoteText, false, true, nNoteId )) )
        {
            if( pAuthor ) pNewNote->SetAuthor( *pAuthor );
            if( pDate ) pNewNote->SetDate( *pDate );

            // rescue note data for undo
            aNewData = pNewNote->GetNoteData();
        }

        // create the undo action
        if( pUndoMgr && (aOldData.mxCaption || aNewData.mxCaption) )
            pUndoMgr->AddUndoAction( std::make_unique<ScUndoReplaceNote>( rDocShell, rPos, aOldData, aNewData, pDrawLayer->GetCalcUndo() ) );

        // repaint cell (to make note marker visible)
        rDocShell.PostPaintCell( rPos );

        rDoc.SetStreamValid(rPos.Tab(), false);

        aModificator.SetDocumentModified();

        // Let our LOK clients know about the new/modified note
        if (pNewNote)
        {
            ScDocShell::LOKCommentNotify(hadOldNote ? LOKCommentNotificationType::Modify : LOKCommentNotificationType::Add,
                                         rDoc, rPos, pNewNote);
        }
    }
    else if (!bApi)
    {
        rDocShell.ErrorMessage(aTester.GetMessageId());
    }
}

void ScDocFunc::ImportNote( const ScAddress& rPos,
                            std::unique_ptr<GenerateNoteCaption> xGenerator,
                            const tools::Rectangle& rCaptionRect, bool bShown )
{
    ScDocShellModificator aModificator( rDocShell );
    ScDocument& rDoc = rDocShell.GetDocument();

    std::unique_ptr<ScPostIt> pOldNote = rDoc.ReleaseNote( rPos );
    SAL_WARN_IF(pOldNote, "sc.ui", "imported data has >1 notes on same cell? at pos " << rPos);

    // create new note
    ScNoteUtil::CreateNoteFromGenerator(rDoc, rPos, std::move(xGenerator),
                                        rCaptionRect, bShown);

    rDoc.SetStreamValid(rPos.Tab(), false);

    aModificator.SetDocumentModified();
}

bool ScDocFunc::ApplyAttributes( const ScMarkData& rMark, const ScPatternAttr& rPattern,
                                    bool bApi )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bRecord = true;
    if ( !rDoc.IsUndoEnabled() )
        bRecord = false;

    bool bImportingXML = rDoc.IsImportingXML();
    bool bImportingXLSX = rDoc.IsImportingXLSX();
    // Cell formats can still be set if the range isn't editable only because of matrix formulas.
    // #i62483# When loading XML, the check can be skipped altogether.
    bool bOnlyNotBecauseOfMatrix;
    if ( !bImportingXML && !rDoc.IsSelectionEditable( rMark, &bOnlyNotBecauseOfMatrix )
            && !bOnlyNotBecauseOfMatrix )
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_PROTECTIONERR);
        return false;
    }

    ScDocShellModificator aModificator( rDocShell );

    //! Border

    ScRange aMultiRange;
    bool bMulti = rMark.IsMultiMarked();
    if ( bMulti )
        aMultiRange = rMark.GetMultiMarkArea();
    else
        aMultiRange = rMark.GetMarkArea();

    if ( bRecord )
    {
        ScDocumentUniquePtr pUndoDoc( new ScDocument( SCDOCMODE_UNDO ));
        pUndoDoc->InitUndo( rDoc, aMultiRange.aStart.Tab(), aMultiRange.aEnd.Tab() );
        rDoc.CopyToDocument(aMultiRange, InsertDeleteFlags::ATTRIB, bMulti, *pUndoDoc, &rMark);

        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoSelectionAttr>(
                    rDocShell, rMark,
                    aMultiRange.aStart.Col(), aMultiRange.aStart.Row(), aMultiRange.aStart.Tab(),
                    aMultiRange.aEnd.Col(), aMultiRange.aEnd.Row(), aMultiRange.aEnd.Tab(),
                    std::move(pUndoDoc), bMulti, &rPattern ) );
    }

    // While loading XML it is not necessary to ask HasAttrib. It needs too much time.
    sal_uInt16 nExtFlags = 0;
    if ( !bImportingXML && !bImportingXLSX )
        rDocShell.UpdatePaintExt( nExtFlags, aMultiRange );     // content before the change

    bool bChanged = false;
    rDoc.ApplySelectionPattern( rPattern, rMark, nullptr, &bChanged );

    if(bChanged)
    {
        if ( !bImportingXML && !bImportingXLSX )
            rDocShell.UpdatePaintExt( nExtFlags, aMultiRange );     // content after the change

        if (!AdjustRowHeight( aMultiRange, true, bApi ))
            rDocShell.PostPaint( aMultiRange, PaintPartFlags::Grid, nExtFlags );
        else if (nExtFlags & SC_PF_LINES)
            lcl_PaintAbove( rDocShell, aMultiRange );   // because of lines above the range

        aModificator.SetDocumentModified();
    }

    return true;
}

bool ScDocFunc::ApplyStyle( const ScMarkData& rMark, const OUString& rStyleName,
                                bool bApi )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bRecord = true;
    if ( !rDoc.IsUndoEnabled() )
        bRecord = false;

    bool bImportingXML = rDoc.IsImportingXML();
    // Cell formats can still be set if the range isn't editable only because of matrix formulas.
    // #i62483# When loading XML, the check can be skipped altogether.
    bool bOnlyNotBecauseOfMatrix;
    if ( !bImportingXML && !rDoc.IsSelectionEditable( rMark, &bOnlyNotBecauseOfMatrix )
            && !bOnlyNotBecauseOfMatrix )
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_PROTECTIONERR);
        return false;
    }

    ScStyleSheet* pStyleSheet = static_cast<ScStyleSheet*>( rDoc.GetStyleSheetPool()->Find(
                                                rStyleName, SfxStyleFamily::Para ));
    if (!pStyleSheet)
        return false;

    ScDocShellModificator aModificator( rDocShell );

    ScRange aMultiRange;
    bool bMulti = rMark.IsMultiMarked();
    if ( bMulti )
        aMultiRange = rMark.GetMultiMarkArea();
    else
        aMultiRange = rMark.GetMarkArea();

    if ( bRecord )
    {
        ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
        SCTAB nStartTab = aMultiRange.aStart.Tab();
        SCTAB nTabCount = rDoc.GetTableCount();
        pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab );
        for (const auto& rTab : rMark)
        {
            if (rTab >= nTabCount)
                break;

            if (rTab != nStartTab)
                pUndoDoc->AddUndoTab( rTab, rTab );
        }

        ScRange aCopyRange = aMultiRange;
        aCopyRange.aStart.SetTab(0);
        aCopyRange.aEnd.SetTab(nTabCount-1);
        rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, bMulti, *pUndoDoc, &rMark );

        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoSelectionStyle>(
                    rDocShell, rMark, aMultiRange, rStyleName, std::move(pUndoDoc) ) );

    }

    rDoc.ApplySelectionStyle( *pStyleSheet, rMark );

    if (!AdjustRowHeight( aMultiRange, true, bApi ))
        rDocShell.PostPaint( aMultiRange, PaintPartFlags::Grid );

    aModificator.SetDocumentModified();

    return true;
}

namespace {

/**
 * Check if this insertion attempt would end up cutting one or more pivot
 * tables in half, which is not desirable.
 *
 * @return true if this insertion can be done safely without shearing any
 *         existing pivot tables, false otherwise.
 */
bool canInsertCellsByPivot(const ScRange& rRange, const ScMarkData& rMarkData, InsCellCmd eCmd, const ScDocument& rDoc)
{
    if (!rDoc.HasPivotTable())
        // This document has no pivot tables.
        return true;

    const ScDPCollection* pDPs = rDoc.GetDPCollection();

    ScRange aRange(rRange); // local copy
    switch (eCmd)
    {
        case INS_INSROWS_BEFORE:
        {
            aRange.aStart.SetCol(0);
            aRange.aEnd.SetCol(rDoc.MaxCol());
            [[fallthrough]];
        }
        case INS_CELLSDOWN:
        {
            auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) {
                return pDPs->IntersectsTableByColumns(aRange.aStart.Col(), aRange.aEnd.Col(), aRange.aStart.Row(), rTab); });
            if (bIntersects)
                // This column range cuts through at least one pivot table.  Not good.
                return false;

            // Start row must be either at the top or above any pivot tables.
            if (aRange.aStart.Row() < 0)
                // I don't know how to handle this case.
                return false;

            if (aRange.aStart.Row() == 0)
                // First row is always allowed.
                return true;

            ScRange aTest(aRange);
            aTest.aStart.IncRow(-1); // Test one row up.
            aTest.aEnd.SetRow(aTest.aStart.Row());
            for (const auto& rTab : rMarkData)
            {
                aTest.aStart.SetTab(rTab);
                aTest.aEnd.SetTab(rTab);
                if (pDPs->HasTable(aTest))
                    return false;
            }
        }
        break;
        case INS_INSCOLS_BEFORE:
        {
            aRange.aStart.SetRow(0);
            aRange.aEnd.SetRow(rDoc.MaxRow());
            [[fallthrough]];
        }
        case INS_CELLSRIGHT:
        {
            auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) {
                return pDPs->IntersectsTableByRows(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Row(), rTab); });
            if (bIntersects)
                // This column range cuts through at least one pivot table.  Not good.
                return false;

            // Start row must be either at the top or above any pivot tables.
            if (aRange.aStart.Col() < 0)
                // I don't know how to handle this case.
                return false;

            if (aRange.aStart.Col() == 0)
                // First row is always allowed.
                return true;

            ScRange aTest(aRange);
            aTest.aStart.IncCol(-1); // Test one column to the left.
            aTest.aEnd.SetCol(aTest.aStart.Col());
            for (const auto& rTab : rMarkData)
            {
                aTest.aStart.SetTab(rTab);
                aTest.aEnd.SetTab(rTab);
                if (pDPs->HasTable(aTest))
                    return false;
            }
        }
        break;
        default:
            ;
    }
    return true;
}

/**
 * Check if this deletion attempt would end up cutting one or more pivot
 * tables in half, which is not desirable.
 *
 * @return true if this deletion can be done safely without shearing any
 *         existing pivot tables, false otherwise.
 */
bool canDeleteCellsByPivot(const ScRange& rRange, const ScMarkData& rMarkData, DelCellCmd eCmd, const ScDocument& rDoc)
{
    if (!rDoc.HasPivotTable())
        // This document has no pivot tables.
        return true;

    const ScDPCollection* pDPs = rDoc.GetDPCollection();

    ScRange aRange(rRange); // local copy

    switch (eCmd)
    {
        case DelCellCmd::Rows:
        {
            aRange.aStart.SetCol(0);
            aRange.aEnd.SetCol(rDoc.MaxCol());
            [[fallthrough]];
        }
        case DelCellCmd::CellsUp:
        {
            auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) {
                return pDPs->IntersectsTableByColumns(aRange.aStart.Col(), aRange.aEnd.Col(), aRange.aStart.Row(), rTab); });
            if (bIntersects)
                // This column range cuts through at least one pivot table.  Not good.
                return false;

            ScRange aTest(aRange);
            for (const auto& rTab : rMarkData)
            {
                aTest.aStart.SetTab(rTab);
                aTest.aEnd.SetTab(rTab);
                if (pDPs->HasTable(aTest))
                    return false;
            }
        }
        break;
        case DelCellCmd::Cols:
        {
            aRange.aStart.SetRow(0);
            aRange.aEnd.SetRow(rDoc.MaxRow());
            [[fallthrough]];
        }
        case DelCellCmd::CellsLeft:
        {
            auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) {
                return pDPs->IntersectsTableByRows(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Row(), rTab); });
            if (bIntersects)
                // This column range cuts through at least one pivot table.  Not good.
                return false;

            ScRange aTest(aRange);
            for (const auto& rTab : rMarkData)
            {
                aTest.aStart.SetTab(rTab);
                aTest.aEnd.SetTab(rTab);
                if (pDPs->HasTable(aTest))
                    return false;
            }
        }
        break;
        default:
            ;
    }
    return true;
}

}

bool ScDocFunc::InsertCells( const ScRange& rRange, const ScMarkData* pTabMark, InsCellCmd eCmd,
                             bool bRecord, bool bApi, bool bPartOfPaste, size_t nInsertCount )
{
    ScDocShellModificator aModificator( rDocShell );
    ScDocument& rDoc = rDocShell.GetDocument();

    if (rDocShell.GetDocument().GetChangeTrack() &&
        ((eCmd == INS_CELLSDOWN  && (rRange.aStart.Col() != 0 || rRange.aEnd.Col() != rDoc.MaxCol())) ||
         (eCmd == INS_CELLSRIGHT && (rRange.aStart.Row() != 0 || rRange.aEnd.Row() != rDoc.MaxRow()))))
    {
        // We should not reach this via UI disabled slots.
        assert(bApi);
        SAL_WARN("sc.ui","ScDocFunc::InsertCells - no change-tracking of partial cell shift");
        return false;
    }

    ScRange aTargetRange( rRange );

    // If insertion is for full cols/rows and after the current
    // selection, then shift the range accordingly
    if ( eCmd == INS_INSROWS_AFTER )
    {
        ScRange aErrorRange( ScAddress::UNINITIALIZED );
        if (!aTargetRange.Move(0, rRange.aEnd.Row() - rRange.aStart.Row() + 1, 0, aErrorRange, rDoc))
        {
            return false;
        }
    }
    if ( eCmd == INS_INSCOLS_AFTER )
    {
        ScRange aErrorRange( ScAddress::UNINITIALIZED );
        if (!aTargetRange.Move(rRange.aEnd.Col() - rRange.aStart.Col() + 1, 0, 0, aErrorRange, rDoc))
        {
            return false;
        }
    }

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

    if ( !rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow) )
    {
        OSL_FAIL("invalid row in InsertCells");
        return false;
    }

    SCTAB nTabCount = rDoc.GetTableCount();
    SCCOL nPaintStartCol = nStartCol;
    SCROW nPaintStartRow = nStartRow;
    SCCOL nPaintEndCol = nEndCol;
    SCROW nPaintEndRow = nEndRow;
    PaintPartFlags nPaintFlags = PaintPartFlags::Grid;
    bool bSuccess;

    ScTabViewShell* pViewSh = rDocShell.GetBestViewShell();  //preserve current cursor position
    SCCOL nCursorCol = 0;
    SCROW nCursorRow = 0;
    if( pViewSh )
    {
        nCursorCol = pViewSh->GetViewData().GetCurX();
        nCursorRow = pViewSh->GetViewData().GetCurY();
    }

    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;

    ScMarkData aMark(rDoc.GetSheetLimits());
    if (pTabMark)
        aMark = *pTabMark;
    else
    {
        SCTAB nCount = 0;
        for( SCTAB i=0; i<nTabCount; i++ )
        {
            if( !rDoc.IsScenario(i) )
            {
                nCount++;
                if( nCount == nEndTab+1 )
                {
                    aMark.SelectTable( i, true );
                    break;
                }
            }
        }
    }

    ScMarkData aFullMark( aMark );          // including scenario sheets
    for (const auto& rTab : aMark)
    {
        if (rTab >= nTabCount)
            break;

        for( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ )
            aFullMark.SelectTable( j, true );
    }

    SCTAB nSelCount = aMark.GetSelectCount();

    // Adjust also related scenarios

    SCCOL nMergeTestStartCol = nStartCol;
    SCROW nMergeTestStartRow = nStartRow;
    SCCOL nMergeTestEndCol = nEndCol;
    SCROW nMergeTestEndRow = nEndRow;

    ScRange aExtendMergeRange( aTargetRange );

    if( aTargetRange.aStart == aTargetRange.aEnd && rDoc.HasAttrib(aTargetRange, HasAttrFlags::Merged) )
    {
        rDoc.ExtendMerge( aExtendMergeRange );
        rDoc.ExtendOverlapped( aExtendMergeRange );
        nMergeTestEndCol = aExtendMergeRange.aEnd.Col();
        nMergeTestEndRow = aExtendMergeRange.aEnd.Row();
        nPaintEndCol = nMergeTestEndCol;
        nPaintEndRow = nMergeTestEndRow;
    }

    if ( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER )
    {
        nMergeTestStartCol = 0;
        nMergeTestEndCol = rDoc.MaxCol();
    }
    if ( eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER )
    {
        nMergeTestStartRow = 0;
        nMergeTestEndRow = rDoc.MaxRow();
    }
    if ( eCmd == INS_CELLSDOWN )
        nMergeTestEndRow = rDoc.MaxRow();
    if ( eCmd == INS_CELLSRIGHT )
        nMergeTestEndCol = rDoc.MaxCol();

    bool bNeedRefresh = false;

    SCCOL nEditTestEndCol = (eCmd==INS_INSCOLS_BEFORE || eCmd==INS_INSCOLS_AFTER) ? rDoc.MaxCol() : nMergeTestEndCol;
    SCROW nEditTestEndRow = (eCmd==INS_INSROWS_BEFORE || eCmd==INS_INSROWS_AFTER) ? rDoc.MaxRow() : nMergeTestEndRow;

    ScEditableTester aTester;
    sc::Operation eOperation = sc::Operation::Unknown;
    switch (eCmd)
    {
        case INS_INSCOLS_BEFORE:
            eOperation = sc::Operation::InsertColumnsBefore;
            aTester = ScEditableTester::CreateAndTestBlockForAction(
                rDoc, sc::EditAction::InsertColumnsBefore, nMergeTestStartCol, 0, nMergeTestEndCol, rDoc.MaxRow(), aMark);
            break;
        case INS_INSCOLS_AFTER:
            eOperation = sc::Operation::InsertColumnsBefore;
            aTester = ScEditableTester::CreateAndTestBlockForAction(
                rDoc, sc::EditAction::InsertColumnsAfter, nMergeTestStartCol, 0, nMergeTestEndCol, rDoc.MaxRow(), aMark);
            break;
        case INS_INSROWS_BEFORE:
            eOperation = sc::Operation::InsertRowsBefore;
            aTester = ScEditableTester::CreateAndTestBlockForAction(
                rDoc, sc::EditAction::InsertRowsBefore, 0, nMergeTestStartRow, rDoc.MaxCol(), nMergeTestEndRow, aMark);
            break;
        case INS_INSROWS_AFTER:
            eOperation = sc::Operation::InsertRowsAfter;
            aTester = ScEditableTester::CreateAndTestBlockForAction(
                rDoc, sc::EditAction::InsertRowsAfter, 0, nMergeTestStartRow, rDoc.MaxCol(), nMergeTestEndRow, aMark);
            break;
        default:
            if (eCmd == INS_CELLSDOWN)
                eOperation = sc::Operation::InsertCellsDown;
            else if (eCmd == INS_CELLSRIGHT)
                eOperation = sc::Operation::InsertCellsRight;

            aTester = ScEditableTester::CreateAndTestSelectedBlock(
                rDoc, nMergeTestStartCol, nMergeTestStartRow, nEditTestEndCol, nEditTestEndRow, aMark);
            break;
    }

    if (!CheckSheetViewProtection(eOperation))
        return false;

    if (!aTester.IsEditable())
    {
        if (!bApi)
            rDocShell.ErrorMessage(aTester.GetMessageId());
        return false;
    }

    // Check if this insertion is allowed with respect to pivot table.
    if (!canInsertCellsByPivot(aTargetRange, aMark, eCmd, rDoc))
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_NO_INSERT_DELETE_OVER_PIVOT_TABLE);
        return false;
    }

    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );      // important due to TrackFormulas at UpdateReference

    ScDocumentUniquePtr pRefUndoDoc;
    std::unique_ptr<ScRefUndoData> pUndoData;
    if ( bRecord )
    {
        pRefUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
        pRefUndoDoc->InitUndo( rDoc, 0, nTabCount-1 );

        // pRefUndoDoc is filled in InsertCol / InsertRow

        pUndoData.reset(new ScRefUndoData( rDoc ));

        rDoc.BeginDrawUndo();
    }

    // #i8302 : we unmerge overwhelming ranges, before insertion all the actions are put in the same ListAction
    // the patch comes from mloiseleur and maoyg
    bool bInsertMerge = false;
    std::vector<ScRange> qIncreaseRange;
    OUString aUndo = ScResId( STR_UNDO_INSERTCELLS );
    if (bRecord)
    {
        ViewShellId nViewShellId(-1);
        if (pViewSh)
            nViewShellId = pViewSh->GetViewShellId();
        rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, nViewShellId );
    }
    std::unique_ptr<ScUndoRemoveMerge> pUndoRemoveMerge;

    for (const SCTAB i : aMark)
    {
        if (i >= nTabCount)
            break;

        if( rDoc.HasAttrib( nMergeTestStartCol, nMergeTestStartRow, i, nMergeTestEndCol, nMergeTestEndRow, i, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
        {
            if (eCmd==INS_CELLSRIGHT)
                bNeedRefresh = true;

            SCCOL nMergeStartCol = nMergeTestStartCol;
            SCROW nMergeStartRow = nMergeTestStartRow;
            SCCOL nMergeEndCol   = nMergeTestEndCol;
            SCROW nMergeEndRow   = nMergeTestEndRow;

            rDoc.ExtendMerge( nMergeStartCol, nMergeStartRow, nMergeEndCol, nMergeEndRow, i );
            rDoc.ExtendOverlapped( nMergeStartCol, nMergeStartRow, nMergeEndCol, nMergeEndRow, i );

            if(( eCmd == INS_CELLSDOWN && ( nMergeStartCol != nMergeTestStartCol || nMergeEndCol != nMergeTestEndCol )) ||
                (eCmd == INS_CELLSRIGHT && ( nMergeStartRow != nMergeTestStartRow || nMergeEndRow != nMergeTestEndRow )) )
            {
                if (!bApi)
                    rDocShell.ErrorMessage(STR_MSSG_INSERTCELLS_0);
                rDocShell.GetUndoManager()->LeaveListAction();
                return false;
            }

            SCCOL nTestCol = -1;
            SCROW nTestRow1 = -1;
            SCROW nTestRow2 = -1;

            ScDocAttrIterator aTestIter( rDoc, i, nMergeTestStartCol, nMergeTestStartRow, nMergeTestEndCol, nMergeTestEndRow );
            ScRange aExtendRange( nMergeTestStartCol, nMergeTestStartRow, i, nMergeTestEndCol, nMergeTestEndRow, i );
            const ScPatternAttr* pPattern = nullptr;
            while ( ( pPattern = aTestIter.GetNext( nTestCol, nTestRow1, nTestRow2 ) ) != nullptr )
            {
                const ScMergeAttr& rMergeFlag = pPattern->GetItem(ATTR_MERGE);
                const ScMergeFlagAttr& rMergeFlagAttr = pPattern->GetItem(ATTR_MERGE_FLAG);
                ScMF nNewFlags = rMergeFlagAttr.GetValue() & (ScMF::Hor | ScMF::Ver);
                if (rMergeFlag.IsMerged() || nNewFlags == ScMF::Hor || nNewFlags == ScMF::Ver)
                {
                    ScRange aRange( nTestCol, nTestRow1, i );
                    rDoc.ExtendOverlapped(aRange);
                    rDoc.ExtendMerge(aRange, true);

                    if( nTestRow1 < nTestRow2 && nNewFlags == ScMF::Hor )
                    {
                        for( SCROW nTestRow = nTestRow1; nTestRow <= nTestRow2; nTestRow++ )
                        {
                            ScRange aTestRange( nTestCol, nTestRow, i );
                            rDoc.ExtendOverlapped( aTestRange );
                            rDoc.ExtendMerge( aTestRange, true);
                            ScRange aMergeRange( aTestRange.aStart.Col(),aTestRange.aStart.Row(), i );
                            if( !aExtendRange.Contains( aMergeRange ) )
                            {
                                qIncreaseRange.push_back( aTestRange );
                                bInsertMerge = true;
                            }
                        }
                    }
                    else
                    {
                        ScRange aMergeRange( aRange.aStart.Col(),aRange.aStart.Row(), i );
                        if( !aExtendRange.Contains( aMergeRange ) )
                        {
                            qIncreaseRange.push_back( aRange );
                        }
                        bInsertMerge = true;
                    }
                }
            }

            if( bInsertMerge )
            {
                if( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER || eCmd == INS_CELLSDOWN )
                {
                    nStartRow = aExtendMergeRange.aStart.Row();
                    nEndRow = aExtendMergeRange.aEnd.Row();

                    if( eCmd == INS_CELLSDOWN )
                        nEndCol = nMergeTestEndCol;
                    else
                    {
                        nStartCol = 0;
                        nEndCol = rDoc.MaxCol();
                    }
                }
                else if( eCmd == INS_CELLSRIGHT || eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER )
                {

                    nStartCol = aExtendMergeRange.aStart.Col();
                    nEndCol = aExtendMergeRange.aEnd.Col();
                    if( eCmd == INS_CELLSRIGHT )
                    {
                        nEndRow = nMergeTestEndRow;
                    }
                    else
                    {
                        nStartRow = 0;
                        nEndRow = rDoc.MaxRow();
                    }
                }

                if( !qIncreaseRange.empty() )
                {
                    if (bRecord && !pUndoRemoveMerge)
                    {
                        ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
                        pUndoDoc->InitUndo( rDoc, *aMark.begin(), *aMark.rbegin());
                        pUndoRemoveMerge.reset( new ScUndoRemoveMerge( rDocShell, rRange, std::move(pUndoDoc) ));
                    }

                    for( const ScRange& aRange : qIncreaseRange )
                    {
                        if( rDoc.HasAttrib( aRange, HasAttrFlags::Overlapped | HasAttrFlags::Merged ) )
                        {
                            UnmergeCells( aRange, bRecord, pUndoRemoveMerge.get() );
                        }
                    }
                }
            }
            else
            {
                if (!bApi)
                    rDocShell.ErrorMessage(STR_MSSG_INSERTCELLS_0);
                rDocShell.GetUndoManager()->LeaveListAction();
                return false;
            }
        }
    }

    if (bRecord && pUndoRemoveMerge)
    {
        rDocShell.GetUndoManager()->AddUndoAction( std::move(pUndoRemoveMerge));
    }

    switch (eCmd)
    {
        case INS_CELLSDOWN:
            bSuccess = rDoc.InsertRow( nStartCol, 0, nEndCol, MAXTAB, nStartRow, static_cast<SCSIZE>(nEndRow-nStartRow+1), pRefUndoDoc.get(), &aFullMark );
            nPaintEndRow = rDoc.MaxRow();
            break;
        case INS_INSROWS_BEFORE:
        case INS_INSROWS_AFTER:
            bSuccess = rDoc.InsertRow( 0, 0, rDoc.MaxCol(), MAXTAB, nStartRow, static_cast<SCSIZE>(nEndRow-nStartRow+1), pRefUndoDoc.get(), &aFullMark );
            nPaintStartCol = 0;
            nPaintEndCol = rDoc.MaxCol();
            nPaintEndRow = rDoc.MaxRow();
            nPaintFlags |= PaintPartFlags::Left;
            break;
        case INS_CELLSRIGHT:
            bSuccess = rDoc.InsertCol( nStartRow, 0, nEndRow, MAXTAB, nStartCol, static_cast<SCSIZE>(nEndCol-nStartCol+1), pRefUndoDoc.get(), &aFullMark );
            nPaintEndCol = rDoc.MaxCol();
            break;
        case INS_INSCOLS_BEFORE:
        case INS_INSCOLS_AFTER:
            bSuccess = rDoc.InsertCol( 0, 0, rDoc.MaxRow(), MAXTAB, nStartCol, static_cast<SCSIZE>(nEndCol-nStartCol+1), pRefUndoDoc.get(), &aFullMark );
            nPaintStartRow = 0;
            nPaintEndRow = rDoc.MaxRow();
            nPaintEndCol = rDoc.MaxCol();
            nPaintFlags |= PaintPartFlags::Top;
            break;
        default:
            OSL_FAIL("Wrong code at inserting");
            bSuccess = false;
            break;
    }

    if ( bSuccess )
    {
        SCTAB  nUndoPos  = 0;

        if ( bRecord )
        {
            std::unique_ptr<SCTAB[]> pTabs(new SCTAB[nSelCount]);
            std::unique_ptr<SCTAB[]> pScenarios(new SCTAB[nSelCount]);
            nUndoPos    = 0;
            for (const auto& rTab : aMark)
            {
                if (rTab >= nTabCount)
                    break;

                SCTAB nCount = 0;
                for( SCTAB j=rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ )
                    nCount ++;

                pScenarios[nUndoPos] = nCount;
                pTabs[nUndoPos] = rTab;
                nUndoPos ++;
            }

            if( !bInsertMerge )
            {
                rDocShell.GetUndoManager()->LeaveListAction();
            }

            rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoInsertCells>(
                rDocShell, ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab ),
                nUndoPos, std::move(pTabs), std::move(pScenarios), eCmd, std::move(pRefUndoDoc), std::move(pUndoData), bPartOfPaste ) );
        }

        // #i8302 : we remerge growing ranges, with the new part inserted

        while( !qIncreaseRange.empty() )
        {
            ScRange aRange = qIncreaseRange.back();
            if( !rDoc.HasAttrib( aRange, HasAttrFlags::Overlapped | HasAttrFlags::Merged ) )
            {
                switch (eCmd)
                {
                    case INS_CELLSDOWN:
                    case INS_INSROWS_BEFORE:
                    case INS_INSROWS_AFTER:
                        aRange.aEnd.IncRow(static_cast<SCCOL>(nEndRow-nStartRow+1));
                        break;
                    case INS_CELLSRIGHT:
                    case INS_INSCOLS_BEFORE:
                    case INS_INSCOLS_AFTER:
                        aRange.aEnd.IncCol(static_cast<SCCOL>(nEndCol-nStartCol+1));
                        break;
                    default:
                        break;
                }
                ScCellMergeOption aMergeOption(
                    aRange.aStart.Col(), aRange.aStart.Row(),
                    aRange.aEnd.Col(), aRange.aEnd.Row() );
                aMergeOption.maTabs.insert(aRange.aStart.Tab());
                MergeCells(aMergeOption, false, true, true);
            }
            qIncreaseRange.pop_back();
        }

        if( bInsertMerge )
            rDocShell.GetUndoManager()->LeaveListAction();

        for (const SCTAB i : aMark)
        {
            if (i >= nTabCount)
                break;

            rDoc.SetDrawPageSize(i);

            if (bNeedRefresh)
                rDoc.ExtendMerge( nMergeTestStartCol, nMergeTestStartRow, nMergeTestEndCol, nMergeTestEndRow, i, true );
            else
                rDoc.RefreshAutoFilter( nMergeTestStartCol, nMergeTestStartRow, nMergeTestEndCol, nMergeTestEndRow, i );

            if ( eCmd == INS_INSROWS_BEFORE ||eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSROWS_AFTER ||eCmd == INS_INSCOLS_AFTER )
                rDoc.UpdatePageBreaks( i );

            sal_uInt16 nExtFlags = 0;
            rDocShell.UpdatePaintExt( nExtFlags, nPaintStartCol, nPaintStartRow, i, nPaintEndCol, nPaintEndRow, i );

            SCTAB nScenarioCount = 0;

            for( SCTAB j = i+1; j<nTabCount && rDoc.IsScenario(j); j++ )
                nScenarioCount ++;

            bool bAdjusted = ( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER ) ?
                        AdjustRowHeight(ScRange(0, nStartRow, i, rDoc.MaxCol(), nEndRow, i+nScenarioCount ), true, bApi) :
                        AdjustRowHeight(ScRange(0, nPaintStartRow, i, rDoc.MaxCol(), nPaintEndRow, i+nScenarioCount ), true, bApi);
            if (bAdjusted)
            {
                //  paint only what is not done by AdjustRowHeight
                if (nPaintFlags & PaintPartFlags::Top)
                    rDocShell.PostPaint( nPaintStartCol, nPaintStartRow, i, nPaintEndCol, nPaintEndRow, i+nScenarioCount, PaintPartFlags::Top );
            }
            else
                rDocShell.PostPaint( nPaintStartCol, nPaintStartRow, i, nPaintEndCol, nPaintEndRow, i+nScenarioCount, nPaintFlags, nExtFlags );
        }
    }
    else
    {
        if( bInsertMerge )
        {
            while( !qIncreaseRange.empty() )
            {
                ScRange aRange = qIncreaseRange.back();
                ScCellMergeOption aMergeOption(
                    aRange.aStart.Col(), aRange.aStart.Row(),
                    aRange.aEnd.Col(), aRange.aEnd.Row() );
                MergeCells(aMergeOption, false, true, true);
                qIncreaseRange.pop_back();
            }

            if( pViewSh )
            {
                pViewSh->MarkRange( aTargetRange, false );
                pViewSh->SetCursor( nCursorCol, nCursorRow );
            }
        }

        rDocShell.GetUndoManager()->LeaveListAction();
        rDocShell.GetUndoManager()->RemoveLastUndoAction();

        pRefUndoDoc.reset();
        if (!bApi)
            rDocShell.ErrorMessage(STR_INSERT_FULL);        // column/row full
    }

    // The cursor position needs to be modified earlier than updating
    // any enabled edit view which is triggered by SetDocumentModified below.
    if (bSuccess)
    {
        bool bInsertCols = ( eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER);
        bool bInsertRows = ( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER );

        if (bInsertCols)
        {
            pViewSh->OnLOKInsertDeleteColumn(rRange.aStart.Col() - (eCmd == INS_INSCOLS_BEFORE ? 1: 0), 1);
        }

        if (bInsertRows)
        {
            pViewSh->OnLOKInsertDeleteRow(rRange.aStart.Row() - (eCmd == INS_INSROWS_BEFORE ? 1: 0), 1);
        }
    }

    aModificator.SetDocumentModified();

    SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) );
    return bSuccess;
}

bool ScDocFunc::DeleteCells( const ScRange& rRange, const ScMarkData* pTabMark, DelCellCmd eCmd,
                             bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );
    ScDocument& rDoc = rDocShell.GetDocument();

    if (rDocShell.GetDocument().GetChangeTrack() &&
        ((eCmd == DelCellCmd::CellsUp   && (rRange.aStart.Col() != 0 || rRange.aEnd.Col() != rDoc.MaxCol())) ||
         (eCmd == DelCellCmd::CellsLeft && (rRange.aStart.Row() != 0 || rRange.aEnd.Row() != rDoc.MaxRow()))))
    {
        // We should not reach this via UI disabled slots.
        assert(bApi);
        SAL_WARN("sc.ui","ScDocFunc::DeleteCells - no change-tracking of partial cell shift");
        return false;
    }

    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();

    if ( !rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow) )
    {
        OSL_FAIL("invalid row in DeleteCells");
        return false;
    }

    SCTAB nTabCount = rDoc.GetTableCount();
    SCCOL nPaintStartCol = nStartCol;
    SCROW nPaintStartRow = nStartRow;
    SCCOL nPaintEndCol = nEndCol;
    SCROW nPaintEndRow = nEndRow;
    PaintPartFlags nPaintFlags = PaintPartFlags::Grid;

    bool bRecord = true;
    if (!rDoc.IsUndoEnabled())
        bRecord = false;

    ScMarkData aMark(rDoc.GetSheetLimits());
    if (pTabMark)
        aMark = *pTabMark;
    else
    {
        SCTAB nCount = 0;
        for(SCTAB i=0; i<nTabCount; i++ )
        {
            if( !rDoc.IsScenario(i) )
            {
                nCount++;
                if( nCount == nEndTab+1 )
                {
                    aMark.SelectTable(i, true);
                    break;
                }
            }
        }
    }

    ScMarkData aFullMark( aMark );          // including scenario sheets
    for (const auto& rTab : aMark)
    {
        if (rTab >= nTabCount)
            break;

        for( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ )
            aFullMark.SelectTable( j, true );
    }

    SCTAB nSelCount = aMark.GetSelectCount();

    SCCOL nUndoStartCol = nStartCol;
    SCROW nUndoStartRow = nStartRow;
    SCCOL nUndoEndCol = nEndCol;
    SCROW nUndoEndRow = nEndRow;

    ScRange aExtendMergeRange( rRange );

    if( rRange.aStart == rRange.aEnd && rDoc.HasAttrib(rRange, HasAttrFlags::Merged) )
    {
        rDoc.ExtendMerge( aExtendMergeRange );
        rDoc.ExtendOverlapped( aExtendMergeRange );
        nUndoEndCol = aExtendMergeRange.aEnd.Col();
        nUndoEndRow = aExtendMergeRange.aEnd.Row();
        nPaintEndCol = nUndoEndCol;
        nPaintEndRow = nUndoEndRow;
    }

    if (eCmd==DelCellCmd::Rows)
    {
        nUndoStartCol = 0;
        nUndoEndCol = rDoc.MaxCol();
    }
    if (eCmd==DelCellCmd::Cols)
    {
        nUndoStartRow = 0;
        nUndoEndRow = rDoc.MaxRow();
    }
                    // Test for cell protection

    SCCOL nEditTestEndX = nUndoEndCol;
    if ( eCmd==DelCellCmd::Cols || eCmd==DelCellCmd::CellsLeft )
        nEditTestEndX = rDoc.MaxCol();
    SCROW nEditTestEndY = nUndoEndRow;
    if ( eCmd==DelCellCmd::Rows || eCmd==DelCellCmd::CellsUp )
        nEditTestEndY = rDoc.MaxRow();

    ScEditableTester aTester;
    sc::Operation eOperation = sc::Operation::Unknown;
    switch (eCmd)
    {
        case DelCellCmd::Cols:
            eOperation = sc::Operation::DeleteColumns;
            aTester = ScEditableTester::CreateAndTestBlockForAction(
                rDoc, sc::EditAction::DeleteColumns, nUndoStartCol, 0, nUndoEndCol, rDoc.MaxRow(), aMark);
            break;
        case DelCellCmd::Rows:
            eOperation = sc::Operation::DeleteRows;
            aTester = ScEditableTester::CreateAndTestBlockForAction(
                rDoc, sc::EditAction::DeleteRows, 0, nUndoStartRow, rDoc.MaxCol(), nUndoEndRow, aMark);
            break;
        default:
            if (eCmd == DelCellCmd::CellsLeft)
                eOperation = sc::Operation::DeleteCellsLeft;
            else if (eCmd == DelCellCmd::CellsUp)
                eOperation = sc::Operation::DeleteCellsUp;

            aTester = ScEditableTester::CreateAndTestSelectedBlock(
                rDoc, nUndoStartCol, nUndoStartRow, nEditTestEndX, nEditTestEndY, aMark);
    }

    if (!CheckSheetViewProtection(eOperation))
        return false;

    if (!aTester.IsEditable())
    {
        if (!bApi)
            rDocShell.ErrorMessage(aTester.GetMessageId());
        return false;
    }

    if (!canDeleteCellsByPivot(rRange, aMark, eCmd, rDoc))
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_NO_INSERT_DELETE_OVER_PIVOT_TABLE);
        return false;
    }
                    // Test for merged cells

    SCCOL nMergeTestEndCol = (eCmd==DelCellCmd::CellsLeft) ? rDoc.MaxCol() : nUndoEndCol;
    SCROW nMergeTestEndRow = (eCmd==DelCellCmd::CellsUp)   ? rDoc.MaxRow() : nUndoEndRow;
    SCCOL nExtendStartCol = nUndoStartCol;
    SCROW nExtendStartRow = nUndoStartRow;
    bool bNeedRefresh = false;

    //Issue 8302 want to be able to insert into the middle of merged cells
    //the patch comes from maoyg
    std::vector<ScRange> qDecreaseRange;
    bool bDeletingMerge = false;
    OUString aUndo = ScResId( STR_UNDO_DELETECELLS );
    if (bRecord)
    {
        ViewShellId nViewShellId(-1);
        if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell())
            nViewShellId = pViewSh->GetViewShellId();
        rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, nViewShellId );
    }
    std::unique_ptr<ScUndoRemoveMerge> pUndoRemoveMerge;

    for (const SCTAB i : aMark)
    {
        if (i >= nTabCount)
            break;

        if ( rDoc.HasAttrib( nUndoStartCol, nUndoStartRow, i, nMergeTestEndCol, nMergeTestEndRow, i, HasAttrFlags::Merged | HasAttrFlags::Overlapped ))
        {
            SCCOL nMergeStartCol = nUndoStartCol;
            SCROW nMergeStartRow = nUndoStartRow;
            SCCOL nMergeEndCol   = nMergeTestEndCol;
            SCROW nMergeEndRow   = nMergeTestEndRow;

            rDoc.ExtendMerge( nMergeStartCol, nMergeStartRow, nMergeEndCol, nMergeEndRow, i );
            rDoc.ExtendOverlapped( nMergeStartCol, nMergeStartRow, nMergeEndCol, nMergeEndRow, i );
            if( ( eCmd == DelCellCmd::CellsUp && ( nMergeStartCol != nUndoStartCol || nMergeEndCol != nMergeTestEndCol))||
                ( eCmd == DelCellCmd::CellsLeft && ( nMergeStartRow != nUndoStartRow || nMergeEndRow != nMergeTestEndRow)))
            {
                if (!bApi)
                    rDocShell.ErrorMessage(STR_MSSG_DELETECELLS_0);
                rDocShell.GetUndoManager()->LeaveListAction();
                return false;
            }

            nExtendStartCol = nMergeStartCol;
            nExtendStartRow = nMergeStartRow;
            SCCOL nTestCol = -1;
            SCROW nTestRow1 = -1;
            SCROW nTestRow2 = -1;

            ScDocAttrIterator aTestIter( rDoc, i, nUndoStartCol, nUndoStartRow, nMergeTestEndCol, nMergeTestEndRow );
            ScRange aExtendRange( nUndoStartCol, nUndoStartRow, i, nMergeTestEndCol, nMergeTestEndRow, i );
            const ScPatternAttr* pPattern = nullptr;
            while ( ( pPattern = aTestIter.GetNext( nTestCol, nTestRow1, nTestRow2 ) ) != nullptr )
            {
                const ScMergeAttr& rMergeFlag = pPattern->GetItem(ATTR_MERGE);
                const ScMergeFlagAttr& rMergeFlagAttr = pPattern->GetItem(ATTR_MERGE_FLAG);
                ScMF nNewFlags = rMergeFlagAttr.GetValue() & (ScMF::Hor | ScMF::Ver);
                if (rMergeFlag.IsMerged() || nNewFlags == ScMF::Hor || nNewFlags == ScMF::Ver)
                {
                    ScRange aRange( nTestCol, nTestRow1, i );
                    rDoc.ExtendOverlapped( aRange );
                    rDoc.ExtendMerge( aRange, true );

                    if( nTestRow1 < nTestRow2 && nNewFlags == ScMF::Hor )
                    {
                        for( SCROW nTestRow = nTestRow1; nTestRow <= nTestRow2; nTestRow++ )
                        {
                            ScRange aTestRange( nTestCol, nTestRow, i );
                            rDoc.ExtendOverlapped( aTestRange );
                            rDoc.ExtendMerge( aTestRange, true );
                            ScRange aMergeRange( aTestRange.aStart.Col(),aTestRange.aStart.Row(), i );
                            if( !aExtendRange.Contains( aMergeRange ) )
                            {
                                qDecreaseRange.push_back( aTestRange );
                                bDeletingMerge = true;
                            }
                        }
                    }
                    else
                    {
                        ScRange aMergeRange( aRange.aStart.Col(),aRange.aStart.Row(), i );
                        if( !aExtendRange.Contains( aMergeRange ) )
                        {
                            qDecreaseRange.push_back( aRange );
                        }
                        bDeletingMerge = true;
                    }
                }
            }

            if( bDeletingMerge )
            {

                if( eCmd == DelCellCmd::Rows || eCmd == DelCellCmd::CellsUp )
                {
                    nStartRow = aExtendMergeRange.aStart.Row();
                    nEndRow = aExtendMergeRange.aEnd.Row();
                    bNeedRefresh = true;

                    if( eCmd == DelCellCmd::CellsUp )
                    {
                        nEndCol = aExtendMergeRange.aEnd.Col();
                    }
                    else
                    {
                        nStartCol = 0;
                        nEndCol = rDoc.MaxCol();
                    }
                }
                else if( eCmd == DelCellCmd::CellsLeft || eCmd == DelCellCmd::Cols )
                {

                    nStartCol = aExtendMergeRange.aStart.Col();
                    nEndCol = aExtendMergeRange.aEnd.Col();
                    if( eCmd == DelCellCmd::CellsLeft )
                    {
                        nEndRow = aExtendMergeRange.aEnd.Row();
                        bNeedRefresh = true;
                    }
                    else
                    {
                        nStartRow = 0;
                        nEndRow = rDoc.MaxRow();
                    }
                }

                if( !qDecreaseRange.empty() )
                {
                    if (bRecord && !pUndoRemoveMerge)
                    {
                        ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
                        pUndoDoc->InitUndo( rDoc, *aMark.begin(), *aMark.rbegin());
                        pUndoRemoveMerge.reset( new ScUndoRemoveMerge( rDocShell, rRange, std::move(pUndoDoc) ));
                    }

                    for( const ScRange& aRange : qDecreaseRange )
                    {
                        if( rDoc.HasAttrib( aRange, HasAttrFlags::Overlapped | HasAttrFlags::Merged ) )
                        {
                            UnmergeCells( aRange, bRecord, pUndoRemoveMerge.get() );
                        }
                    }
                }
            }
            else
            {
                if (!bApi)
                    rDocShell.ErrorMessage(STR_MSSG_DELETECELLS_0);
                rDocShell.GetUndoManager()->LeaveListAction();
                return false;
            }
        }
    }

    if (bRecord && pUndoRemoveMerge)
    {
        rDocShell.GetUndoManager()->AddUndoAction( std::move(pUndoRemoveMerge));
    }

    //     do it

    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );      // important because of TrackFormulas in UpdateReference

    ScDocumentUniquePtr pUndoDoc;
    std::unique_ptr<ScDocument> pRefUndoDoc;
    std::unique_ptr<ScRefUndoData> pUndoData;
    if ( bRecord )
    {
        // With the fix for #101329#, UpdateRef always puts cells into pRefUndoDoc at their old position,
        // so it's no longer necessary to copy more than the deleted range into pUndoDoc.

        pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
        pUndoDoc->InitUndo( rDoc, 0, nTabCount-1, (eCmd==DelCellCmd::Cols), (eCmd==DelCellCmd::Rows) );
        for (const auto& rTab : aMark)
        {
            if (rTab >= nTabCount)
                break;

            SCTAB nScenarioCount = 0;

            for( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ )
                nScenarioCount ++;

            rDoc.CopyToDocument( nUndoStartCol, nUndoStartRow, rTab, nUndoEndCol, nUndoEndRow, rTab+nScenarioCount,
                InsertDeleteFlags::ALL | InsertDeleteFlags::NOCAPTIONS, false, *pUndoDoc );
        }

        pRefUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
        pRefUndoDoc->InitUndo( rDoc, 0, nTabCount-1 );

        pUndoData.reset(new ScRefUndoData( rDoc ));

        rDoc.BeginDrawUndo();
    }

    sal_uInt16 nExtFlags = 0;
    for (const auto& rTab : aMark)
    {
        if (rTab >= nTabCount)
            break;

        rDocShell.UpdatePaintExt( nExtFlags, nStartCol, nStartRow, rTab, nEndCol, nEndRow, rTab );
    }

    switch (eCmd)
    {
    case DelCellCmd::CellsUp:
    case DelCellCmd::CellsLeft:
        rDoc.DeleteObjectsInArea(nStartCol, nStartRow, nEndCol, nEndRow, aMark, true);
        break;
    case DelCellCmd::Rows:
        rDoc.DeleteObjectsInArea(0, nStartRow, rDoc.MaxCol(), nEndRow, aMark, true);
        break;
    case DelCellCmd::Cols:
        rDoc.DeleteObjectsInArea(nStartCol, 0, nEndCol, rDoc.MaxRow(), aMark, true);
        break;
    default:
        break;
    }


    bool bUndoOutline = false;
    switch (eCmd)
    {
        case DelCellCmd::CellsUp:
            rDoc.DeleteRow( nStartCol, 0, nEndCol, MAXTAB, nStartRow, static_cast<SCSIZE>(nEndRow-nStartRow+1), pRefUndoDoc.get(), nullptr, &aFullMark );
            nPaintEndRow = rDoc.MaxRow();
            break;
        case DelCellCmd::Rows:
            rDoc.DeleteRow( 0, 0, rDoc.MaxCol(), MAXTAB, nStartRow, static_cast<SCSIZE>(nEndRow-nStartRow+1), pRefUndoDoc.get(), &bUndoOutline, &aFullMark );
            nPaintStartCol = 0;
            nPaintEndCol = rDoc.MaxCol();
            nPaintEndRow = rDoc.MaxRow();
            nPaintFlags |= PaintPartFlags::Left;
            break;
        case DelCellCmd::CellsLeft:
            rDoc.DeleteCol( nStartRow, 0, nEndRow, MAXTAB, nStartCol, static_cast<SCSIZE>(nEndCol-nStartCol+1), pRefUndoDoc.get(), nullptr, &aFullMark );
            nPaintEndCol = rDoc.MaxCol();
            break;
        case DelCellCmd::Cols:
            rDoc.DeleteCol( 0, 0, rDoc.MaxRow(), MAXTAB, nStartCol, static_cast<SCSIZE>(nEndCol-nStartCol+1), pRefUndoDoc.get(), &bUndoOutline, &aFullMark );
            nPaintStartRow = 0;
            nPaintEndRow = rDoc.MaxRow();
            nPaintEndCol = rDoc.MaxCol();
            nPaintFlags |= PaintPartFlags::Top;
            break;
        default:
            OSL_FAIL("Wrong code at deleting");
            break;
    }

    //! Test if the size of outline has changed

    if ( bRecord )
    {
        for (const auto& rTab : aFullMark)
        {
            if (rTab >= nTabCount)
                break;

            pRefUndoDoc->DeleteAreaTab(nUndoStartCol,nUndoStartRow,nUndoEndCol,nUndoEndRow, rTab, InsertDeleteFlags::ALL);
        }

            //  for all sheets, so that formulas can be copied
        pUndoDoc->AddUndoTab( 0, nTabCount-1 );

            //  copy with bColRowFlags=false (#54194#)
        pRefUndoDoc->CopyToDocument(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB,InsertDeleteFlags::FORMULA,false,*pUndoDoc,nullptr,false);
        pRefUndoDoc.reset();

        std::unique_ptr<SCTAB[]> pTabs(      new SCTAB[nSelCount]);
        std::unique_ptr<SCTAB[]> pScenarios( new SCTAB[nSelCount]);
        SCTAB   nUndoPos  = 0;

        for (const auto& rTab : aMark)
        {
            if (rTab >= nTabCount)
                break;

            SCTAB nCount = 0;
            for( SCTAB j=rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ )
                nCount ++;

            pScenarios[nUndoPos] = nCount;
            pTabs[nUndoPos] = rTab;
            nUndoPos ++;
        }

        if( !bDeletingMerge )
        {
            rDocShell.GetUndoManager()->LeaveListAction();
        }

        rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoDeleteCells>(
            rDocShell, ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab ),
            nUndoPos, std::move(pTabs), std::move(pScenarios),
            eCmd, std::move(pUndoDoc), std::move(pUndoData) ) );
    }

    // #i8302 want to be able to insert into the middle of merged cells
    // the patch comes from maoyg

    while( !qDecreaseRange.empty() )
    {
        ScRange aRange = qDecreaseRange.back();

        sal_Int32 nDecreaseRowCount = 0;
        sal_Int32 nDecreaseColCount = 0;
        if( eCmd == DelCellCmd::CellsUp || eCmd == DelCellCmd::Rows )
        {
            if( nStartRow >= aRange.aStart.Row() && nStartRow <= aRange.aEnd.Row() && nEndRow>= aRange.aStart.Row() && nEndRow <= aRange.aEnd.Row() )
                nDecreaseRowCount = nEndRow-nStartRow+1;
            else if( nStartRow >= aRange.aStart.Row() && nStartRow <= aRange.aEnd.Row() && nEndRow >= aRange.aStart.Row() && nEndRow >= aRange.aEnd.Row() )
                nDecreaseRowCount = aRange.aEnd.Row()-nStartRow+1;
            else if( nStartRow >= aRange.aStart.Row() && nStartRow >= aRange.aEnd.Row() && nEndRow>= aRange.aStart.Row() && nEndRow <= aRange.aEnd.Row() )
                nDecreaseRowCount = aRange.aEnd.Row()-nEndRow+1;
        }
        else if( eCmd == DelCellCmd::CellsLeft || eCmd == DelCellCmd::Cols )
        {
            if( nStartCol >= aRange.aStart.Col() && nStartCol <= aRange.aEnd.Col() && nEndCol>= aRange.aStart.Col() && nEndCol <= aRange.aEnd.Col() )
                nDecreaseColCount = nEndCol-nStartCol+1;
            else if( nStartCol >= aRange.aStart.Col() && nStartCol <= aRange.aEnd.Col() && nEndCol >= aRange.aStart.Col() && nEndCol >= aRange.aEnd.Col() )
                nDecreaseColCount = aRange.aEnd.Col()-nStartCol+1;
            else if( nStartCol >= aRange.aStart.Col() && nStartCol >= aRange.aEnd.Col() && nEndCol>= aRange.aStart.Col() && nEndCol <= aRange.aEnd.Col() )
                nDecreaseColCount = aRange.aEnd.Col()-nEndCol+1;
        }

        switch (eCmd)
        {
            case DelCellCmd::CellsUp:
            case DelCellCmd::Rows:
                aRange.aEnd.SetRow(static_cast<SCCOL>( aRange.aEnd.Row()-nDecreaseRowCount));
                break;
            case DelCellCmd::CellsLeft:
            case DelCellCmd::Cols:
                aRange.aEnd.SetCol(static_cast<SCCOL>( aRange.aEnd.Col()-nDecreaseColCount));
                break;
            default:
                break;
        }

        if( !rDoc.HasAttrib( aRange, HasAttrFlags::Overlapped | HasAttrFlags::Merged ) )
        {
            ScCellMergeOption aMergeOption(aRange);
            MergeCells( aMergeOption, false, true, true );
        }
        qDecreaseRange.pop_back();
    }

    if( bDeletingMerge )
        rDocShell.GetUndoManager()->LeaveListAction();

    if ( eCmd==DelCellCmd::Cols || eCmd==DelCellCmd::CellsLeft )
        nMergeTestEndCol = rDoc.MaxCol();
    if ( eCmd==DelCellCmd::Rows || eCmd==DelCellCmd::CellsUp )
        nMergeTestEndRow = rDoc.MaxRow();
    if ( bNeedRefresh )
    {
        // #i51445# old merge flag attributes must be deleted also for single cells,
        // not only for whole columns/rows

        ScPatternAttr aPattern(rDoc.getCellAttributeHelper());
        aPattern.ItemSetPut(ScMergeFlagAttr());

        rDoc.ApplyPatternArea( nExtendStartCol, nExtendStartRow, nMergeTestEndCol, nMergeTestEndRow, aMark, aPattern );

        for (const auto& rTab : aMark)
        {
            if (rTab >= nTabCount)
                break;

            SCTAB nScenarioCount = 0;

            for( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ )
                nScenarioCount ++;

            ScRange aMergedRange( nExtendStartCol, nExtendStartRow, rTab, nMergeTestEndCol, nMergeTestEndRow, rTab+nScenarioCount );
            rDoc.ExtendMerge( aMergedRange, true );
        }
    }

    for (const auto& rTab : aMark)
    {
        if (rTab >= nTabCount)
            break;

        rDoc.RefreshAutoFilter( nExtendStartCol, nExtendStartRow, nMergeTestEndCol, nMergeTestEndRow, rTab );
    }

    for (const auto& rTab : aMark)
    {
        if (rTab >= nTabCount)
            break;

        rDoc.SetDrawPageSize(rTab);

        if ( eCmd == DelCellCmd::Cols || eCmd == DelCellCmd::Rows )
            rDoc.UpdatePageBreaks( rTab );

        rDocShell.UpdatePaintExt( nExtFlags, nPaintStartCol, nPaintStartRow, rTab, nPaintEndCol, nPaintEndRow, rTab );

        SCTAB nScenarioCount = 0;

        for( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ )
            nScenarioCount ++;

        //  delete entire rows: do not adjust
        if ( eCmd == DelCellCmd::Rows || !AdjustRowHeight(ScRange( 0, nPaintStartRow, rTab, rDoc.MaxCol(), nPaintEndRow, rTab+nScenarioCount ), true, bApi) )
            rDocShell.PostPaint( nPaintStartCol, nPaintStartRow, rTab, nPaintEndCol, nPaintEndRow, rTab+nScenarioCount, nPaintFlags,  nExtFlags );
        else
        {
            //  paint only what is not done by AdjustRowHeight
            if (nExtFlags & SC_PF_LINES)
                lcl_PaintAbove( rDocShell, ScRange( nPaintStartCol, nPaintStartRow, rTab, nPaintEndCol, nPaintEndRow, rTab+nScenarioCount) );
            if (nPaintFlags & PaintPartFlags::Top)
                rDocShell.PostPaint( nPaintStartCol, nPaintStartRow, rTab, nPaintEndCol, nPaintEndRow, rTab+nScenarioCount, PaintPartFlags::Top );
        }
    }

    // The cursor position needs to be modified earlier than updating
    // any enabled edit view which is triggered by SetDocumentModified below.
    ScTabViewShell* pViewSh = rDocShell.GetBestViewShell();
    if (pViewSh)
    {
        if (eCmd == DelCellCmd::Cols)
        {
            pViewSh->OnLOKInsertDeleteColumn(rRange.aStart.Col(), -1 * (rRange.aEnd.Col() - rRange.aStart.Col() + 1));
        }
        if (eCmd == DelCellCmd::Rows)
        {
            pViewSh->OnLOKInsertDeleteRow(rRange.aStart.Row(), -1 * (rRange.aEnd.Row() - rRange.aStart.Row() + 1));
        }
    }

    aModificator.SetDocumentModified();

    SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) );

    return true;
}

bool ScDocFunc::MoveBlock( const ScRange& rSource, const ScAddress& rDestPos,
                                bool bCut, bool bRecord, bool bPaint, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    SCCOL nStartCol = rSource.aStart.Col();
    SCROW nStartRow = rSource.aStart.Row();
    SCTAB nStartTab = rSource.aStart.Tab();
    SCCOL nEndCol = rSource.aEnd.Col();
    SCROW nEndRow = rSource.aEnd.Row();
    SCTAB nEndTab = rSource.aEnd.Tab();
    SCCOL nDestCol = rDestPos.Col();
    SCROW nDestRow = rDestPos.Row();
    SCTAB nDestTab = rDestPos.Tab();

    ScDocument& rDoc = rDocShell.GetDocument();
    if ( !rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow) || !rDoc.ValidRow(nDestRow) )
    {
        OSL_FAIL("invalid row in MoveBlock");
        return false;
    }

    //  adjust related scenarios too - but only when moved within one sheet
    bool bScenariosAdded = false;
    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;

    SCTAB nTabCount = rDoc.GetTableCount();
    if ( nDestTab == nStartTab && !rDoc.IsScenario(nEndTab) )
        while ( nEndTab+1 < nTabCount && rDoc.IsScenario(nEndTab+1) )
        {
            ++nEndTab;
            bScenariosAdded = true;
        }

    SCTAB nSrcTabCount = nEndTab-nStartTab+1;
    SCTAB nDestEndTab = nDestTab+nSrcTabCount-1;
    SCTAB nTab;

    ScDocumentUniquePtr pClipDoc(new ScDocument(SCDOCMODE_CLIP));

    ScMarkData aSourceMark(rDoc.GetSheetLimits());
    for (nTab=nStartTab; nTab<=nEndTab; nTab++)
        aSourceMark.SelectTable( nTab, true );      // select source
    aSourceMark.SetMarkArea( rSource );

    ScDocShellRef aDragShellRef;
    if ( rDoc.HasOLEObjectsInArea( rSource ) )
    {
        aDragShellRef = new ScDocShell;     // DocShell needs a Ref immediately
        aDragShellRef->DoInitNew();
    }
    ScDrawLayer::SetGlobalDrawPersist( aDragShellRef.get() );

    ScClipParam aClipParam(ScRange(nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nStartTab), bCut);
    rDoc.CopyToClip(aClipParam, pClipDoc.get(), &aSourceMark, bScenariosAdded, true);

    ScDrawLayer::SetGlobalDrawPersist(nullptr);

    SCCOL nOldEndCol = nEndCol;
    SCROW nOldEndRow = nEndRow;
    bool bClipOver = false;
    for (nTab=nStartTab; nTab<=nEndTab; nTab++)
    {
        SCCOL nTmpEndCol = nOldEndCol;
        SCROW nTmpEndRow = nOldEndRow;
        if (rDoc.ExtendMerge( nStartCol, nStartRow, nTmpEndCol, nTmpEndRow, nTab ))
            bClipOver = true;
        if ( nTmpEndCol > nEndCol ) nEndCol = nTmpEndCol;
        if ( nTmpEndRow > nEndRow ) nEndRow = nTmpEndRow;
    }

    SCCOL nDestEndCol = nDestCol + ( nOldEndCol-nStartCol );
    SCROW nDestEndRow = nDestRow + ( nOldEndRow-nStartRow );

    SCCOL nUndoEndCol = nDestCol + ( nEndCol-nStartCol );       // extended in destination block
    SCROW nUndoEndRow = nDestRow + ( nEndRow-nStartRow );

    bool bIncludeFiltered = bCut;
    if ( !bIncludeFiltered )
    {
        //  adjust sizes to include only non-filtered rows

        SCCOL nClipX;
        SCROW nClipY;
        pClipDoc->GetClipArea( nClipX, nClipY, false );
        SCROW nUndoAdd = nUndoEndRow - nDestEndRow;
        nDestEndRow = nDestRow + nClipY;
        nUndoEndRow = nDestEndRow + nUndoAdd;
    }

    if (!rDoc.ValidCol(nUndoEndCol) || !rDoc.ValidRow(nUndoEndRow))
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_PASTE_FULL);
        return false;
    }

    if (!CheckSheetViewProtection(sc::Operation::MoveBlock))
        return false;

    //  Test for cell protection

    ScEditableTester aTester;
    for (nTab=nDestTab; nTab<=nDestEndTab; nTab++)
        aTester.TestBlock( rDoc, nTab, nDestCol,nDestRow, nUndoEndCol,nUndoEndRow );
    if (bCut)
        for (nTab=nStartTab; nTab<=nEndTab; nTab++)
            aTester.TestBlock( rDoc, nTab, nStartCol,nStartRow, nEndCol,nEndRow );

    if (!aTester.IsEditable())
    {
        if (!bApi)
            rDocShell.ErrorMessage(aTester.GetMessageId());
        return false;
    }

    //  Test for merged cells- when moving after delete

    if (bClipOver && !bCut)
        if (rDoc.HasAttrib( nDestCol,nDestRow,nDestTab, nUndoEndCol,nUndoEndRow,nDestEndTab,
                                HasAttrFlags::Merged | HasAttrFlags::Overlapped ))
        {       // "Merge of already merged cells not possible"
            if (!bApi)
                rDocShell.ErrorMessage(STR_MSSG_MOVEBLOCKTO_0);
            return false;
        }

    //  Are there borders in the cells? (for painting)

    sal_uInt16 nSourceExt = 0;
    rDocShell.UpdatePaintExt( nSourceExt, nStartCol,nStartRow,nStartTab, nEndCol,nEndRow,nEndTab );
    sal_uInt16 nDestExt = 0;
    rDocShell.UpdatePaintExt( nDestExt, nDestCol,nDestRow,nDestTab, nDestEndCol,nDestEndRow,nDestEndTab );

    //  do it

    ScDocumentUniquePtr pUndoDoc;

    if (bRecord)
    {
        bool bWholeCols = ( nStartRow == 0 && nEndRow == rDoc.MaxRow() );
        bool bWholeRows = ( nStartCol == 0 && nEndCol == rDoc.MaxCol() );
        InsertDeleteFlags nUndoFlags = (InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS) | InsertDeleteFlags::NOCAPTIONS;

        pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
        pUndoDoc->InitUndo( rDoc, nStartTab, nEndTab, bWholeCols, bWholeRows );

        if (bCut)
        {
            rDoc.CopyToDocument( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab,
                                    nUndoFlags, false, *pUndoDoc );
        }

        if ( nDestTab != nStartTab )
            pUndoDoc->AddUndoTab( nDestTab, nDestEndTab, bWholeCols, bWholeRows );
        rDoc.CopyToDocument( nDestCol, nDestRow, nDestTab,
                                    nDestEndCol, nDestEndRow, nDestEndTab,
                                    nUndoFlags, false, *pUndoDoc );
        rDoc.BeginDrawUndo();
    }

    bool bSourceHeight = false;     // adjust heights?
    if (bCut)
    {
        ScMarkData aDelMark(rDoc.GetSheetLimits());    // only for tables
        for (nTab=nStartTab; nTab<=nEndTab; nTab++)
        {
            rDoc.DeleteAreaTab( nStartCol,nStartRow, nOldEndCol,nOldEndRow, nTab, InsertDeleteFlags::ALL );
            aDelMark.SelectTable( nTab, true );
        }
        rDoc.DeleteObjectsInArea( nStartCol,nStartRow, nOldEndCol,nOldEndRow, aDelMark );

        //  Test for merged cells

        if (bClipOver)
            if (rDoc.HasAttrib( nDestCol,nDestRow,nDestTab,
                                    nUndoEndCol,nUndoEndRow,nDestEndTab,
                                    HasAttrFlags::Merged | HasAttrFlags::Overlapped ))
            {
                rDoc.CopyFromClip( rSource, aSourceMark, InsertDeleteFlags::ALL, nullptr, pClipDoc.get() );
                for (nTab=nStartTab; nTab<=nEndTab; nTab++)
                {
                    SCCOL nTmpEndCol = nEndCol;
                    SCROW nTmpEndRow = nEndRow;
                    rDoc.ExtendMerge( nStartCol, nStartRow, nTmpEndCol, nTmpEndRow, nTab, true );
                }

                // Report error only after restoring content
                if (!bApi)      // "Merge of already merged cells not possible"
                    rDocShell.ErrorMessage(STR_MSSG_MOVEBLOCKTO_0);

                return false;
            }

        bSourceHeight = AdjustRowHeight( rSource, false, bApi );
    }

    ScRange aPasteDest( nDestCol, nDestRow, nDestTab, nDestEndCol, nDestEndRow, nDestEndTab );

    ScMarkData aDestMark(rDoc.GetSheetLimits());
    for (nTab=nDestTab; nTab<=nDestEndTab; nTab++)
        aDestMark.SelectTable( nTab, true );        // select destination
    aDestMark.SetMarkArea( aPasteDest );

    /*  Do not copy drawing objects here. While pasting, the
        function ScDocument::UpdateReference() is called which calls
        ScDrawLayer::MoveCells() which may move away inserted objects to wrong
        positions (e.g. if source and destination range overlaps).*/

    rDoc.CopyFromClip(
        aPasteDest, aDestMark, InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS,
        pUndoDoc.get(), pClipDoc.get(), true, false, bIncludeFiltered);

    // skipped rows and merged cells don't mix
    if ( !bIncludeFiltered && pClipDoc->HasClipFilteredRows() )
        UnmergeCells( aPasteDest, false, nullptr );

    bool bDestHeight = AdjustRowHeight(
                            ScRange( 0,nDestRow,nDestTab, rDoc.MaxCol(),nDestEndRow,nDestEndTab ),
                            false, bApi );

    /*  Paste drawing objects after adjusting formula references
        and row heights. There are no cell notes or drawing objects, if the
        clipdoc does not contain a drawing layer.*/
    if ( pClipDoc->GetDrawLayer() )
        rDoc.CopyFromClip( aPasteDest, aDestMark, InsertDeleteFlags::OBJECTS,
                           nullptr, pClipDoc.get(), true, false, bIncludeFiltered );

    if (bRecord)
    {
        ScRange aUndoRange(nStartCol, nStartRow, nStartTab, nOldEndCol, nOldEndRow, nEndTab);
        ScAddress aDestPos(nDestCol, nDestRow, nDestTab);

        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoDragDrop>(
                rDocShell, aUndoRange, aDestPos, bCut, std::move(pUndoDoc), bScenariosAdded));
    }

    SCCOL nDestPaintEndCol = nDestEndCol;
    SCROW nDestPaintEndRow = nDestEndRow;
    for (nTab=nDestTab; nTab<=nDestEndTab; nTab++)
    {
        SCCOL nTmpEndCol = nDestEndCol;
        SCROW nTmpEndRow = nDestEndRow;
        rDoc.ExtendMerge( nDestCol, nDestRow, nTmpEndCol, nTmpEndRow, nTab, true );
        if (nTmpEndCol > nDestPaintEndCol) nDestPaintEndCol = nTmpEndCol;
        if (nTmpEndRow > nDestPaintEndRow) nDestPaintEndRow = nTmpEndRow;
    }

    if (bCut)
        for (nTab=nStartTab; nTab<=nEndTab; nTab++)
            rDoc.RefreshAutoFilter( nStartCol, nStartRow, nEndCol, nEndRow, nTab );

    if (bPaint)
    {
            //  destination range:

        SCCOL nPaintStartX = nDestCol;
        SCROW nPaintStartY = nDestRow;
        SCCOL nPaintEndX = nDestPaintEndCol;
        SCROW nPaintEndY = nDestPaintEndRow;
        PaintPartFlags nFlags = PaintPartFlags::Grid;

        if ( nStartRow==0 && nEndRow==rDoc.MaxRow() )      // copy widths too?
        {
            nPaintEndX = rDoc.MaxCol();
            nPaintStartY = 0;
            nPaintEndY = rDoc.MaxRow();
            nFlags |= PaintPartFlags::Top;
        }
        if ( bDestHeight || ( nStartCol == 0 && nEndCol == rDoc.MaxCol() ) )
        {
            nPaintEndY = rDoc.MaxRow();
            nPaintStartX = 0;
            nPaintEndX = rDoc.MaxCol();
            nFlags |= PaintPartFlags::Left;
        }
        if ( bScenariosAdded )
        {
            nPaintStartX = 0;
            nPaintStartY = 0;
            nPaintEndX = rDoc.MaxCol();
            nPaintEndY = rDoc.MaxRow();
        }

        rDocShell.PostPaint( nPaintStartX,nPaintStartY,nDestTab,
                            nPaintEndX,nPaintEndY,nDestEndTab, nFlags, nSourceExt | nDestExt );

        if ( bCut )
        {
                //  source range:

            nPaintStartX = nStartCol;
            nPaintStartY = nStartRow;
            nPaintEndX = nEndCol;
            nPaintEndY = nEndRow;
            nFlags = PaintPartFlags::Grid;

            if ( bSourceHeight )
            {
                nPaintEndY = rDoc.MaxRow();
                nPaintStartX = 0;
                nPaintEndX = rDoc.MaxCol();
                nFlags |= PaintPartFlags::Left;
            }
            if ( bScenariosAdded )
            {
                nPaintStartX = 0;
                nPaintStartY = 0;
                nPaintEndX = rDoc.MaxCol();
                nPaintEndY = rDoc.MaxRow();
            }

            rDocShell.PostPaint( nPaintStartX,nPaintStartY,nStartTab,
                                nPaintEndX,nPaintEndY,nEndTab, nFlags, nSourceExt );
        }
    }

    aModificator.SetDocumentModified();

    SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) );

    return true;
}

static uno::Reference< uno::XInterface > GetDocModuleObject( const SfxObjectShell& rDocSh, const OUString& sCodeName )
{
    uno::Reference< lang::XMultiServiceFactory> xSF(rDocSh.GetModel(), uno::UNO_QUERY);
    uno::Reference< container::XNameAccess > xVBACodeNamedObjectAccess;
    uno::Reference< uno::XInterface > xDocModuleApiObject;
    if ( xSF.is() )
    {
        xVBACodeNamedObjectAccess.set( xSF->createInstance(u"ooo.vba.VBAObjectModuleObjectProvider"_ustr), uno::UNO_QUERY );
        xDocModuleApiObject.set( xVBACodeNamedObjectAccess->getByName( sCodeName ), uno::UNO_QUERY );
    }
    return xDocModuleApiObject;

}

static script::ModuleInfo lcl_InitModuleInfo( const SfxObjectShell& rDocSh, const OUString& sModule )
{
    script::ModuleInfo sModuleInfo;
    sModuleInfo.ModuleType = script::ModuleType::DOCUMENT;
    sModuleInfo.ModuleObject = GetDocModuleObject( rDocSh, sModule );
    return sModuleInfo;
}

void VBA_InsertModule( ScDocument& rDoc, SCTAB nTab, const OUString& sSource )
{
    ScDocShell& rDocSh = *rDoc.GetDocumentShell();
    uno::Reference< script::XLibraryContainer > xLibContainer = rDocSh.GetBasicContainer();
    OSL_ENSURE( xLibContainer.is(), "No BasicContainer!" );

    uno::Reference< container::XNameContainer > xLib;
    if( xLibContainer.is() )
    {
        OUString aLibName( u"Standard"_ustr );
#if HAVE_FEATURE_SCRIPTING
        if ( rDocSh.GetBasicManager() && !rDocSh.GetBasicManager()->GetName().isEmpty() )
        {
            aLibName = rDocSh.GetBasicManager()->GetName();
        }
#endif
        uno::Any aLibAny = xLibContainer->getByName( aLibName );
        aLibAny >>= xLib;
    }
    if( !xLib.is() )
        return;

    // if the Module with codename exists then find a new name
    sal_Int32 nNum = 1;
    OUString genModuleName = u"Sheet1"_ustr;
    while( xLib->hasByName( genModuleName ) )
        genModuleName = "Sheet" + OUString::number( ++nNum );

    uno::Any aSourceAny;
    OUString sTmpSource = sSource;
    if ( sTmpSource.isEmpty() )
        sTmpSource = "Rem Attribute VBA_ModuleType=VBADocumentModule\nOption VBASupport 1\n";
    aSourceAny <<= sTmpSource;
    uno::Reference< script::vba::XVBAModuleInfo > xVBAModuleInfo( xLib, uno::UNO_QUERY );
    if ( xVBAModuleInfo.is() )
    {
        rDoc.SetCodeName( nTab, genModuleName );
        script::ModuleInfo sModuleInfo = lcl_InitModuleInfo(  rDocSh, genModuleName );
        xVBAModuleInfo->insertModuleInfo( genModuleName, sModuleInfo );
        xLib->insertByName( genModuleName, aSourceAny );
    }
}

void VBA_DeleteModule( ScDocShell& rDocSh, const OUString& sModuleName )
{
    uno::Reference< script::XLibraryContainer > xLibContainer = rDocSh.GetBasicContainer();
    OSL_ENSURE( xLibContainer.is(), "No BasicContainer!" );

    uno::Reference< container::XNameContainer > xLib;
    if( xLibContainer.is() )
    {
        OUString aLibName( u"Standard"_ustr );
#if HAVE_FEATURE_SCRIPTING
        if ( rDocSh.GetBasicManager() && !rDocSh.GetBasicManager()->GetName().isEmpty() )
        {
            aLibName = rDocSh.GetBasicManager()->GetName();
        }
#endif
        uno::Any aLibAny = xLibContainer->getByName( aLibName );
        aLibAny >>= xLib;
    }
    if( xLib.is() )
    {
        uno::Reference< script::vba::XVBAModuleInfo > xVBAModuleInfo( xLib, uno::UNO_QUERY );
        if( xLib->hasByName( sModuleName ) )
            xLib->removeByName( sModuleName );
        if ( xVBAModuleInfo.is() && xVBAModuleInfo->hasModuleInfo(sModuleName) )
            xVBAModuleInfo->removeModuleInfo( sModuleName );

    }
}

bool ScDocFunc::InsertTable( SCTAB nTab, const OUString& rName, bool bRecord, bool bApi )
{
    bool bSuccess = false;
    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );

    ScDocShellModificator aModificator( rDocShell );

    ScDocument& rDoc = rDocShell.GetDocument();

    // Strange loop, also basic is loaded too early ( InsertTable )
    // is called via the xml import for sheets in described in ODF
    bool bInsertDocModule = false;

    if(  !rDocShell.GetDocument().IsImportingXML() )
    {
        bInsertDocModule = rDoc.IsInVBAMode();
    }
    if ( bInsertDocModule || ( bRecord && !rDoc.IsUndoEnabled() ) )
        bRecord = false;

    if (bRecord)
        rDoc.BeginDrawUndo();                          //  InsertTab generates SdrUndoNewPage

    SCTAB nTabCount = rDoc.GetTableCount();
    bool bAppend = ( nTab >= nTabCount );
    if ( bAppend )
        nTab = nTabCount;       // important for Undo

    if (rDoc.InsertTab( nTab, rName ))
    {
        if (bRecord)
            rDocShell.GetUndoManager()->AddUndoAction(
                        std::make_unique<ScUndoInsertTab>( rDocShell, nTab, bAppend, rName));
        //  Update views:
        // Only insert vba modules if vba mode ( and not currently importing XML )
        if( bInsertDocModule )
        {
            VBA_InsertModule( rDoc, nTab, OUString() );
        }
        rDocShell.Broadcast( ScTablesHint( SC_TAB_INSERTED, nTab ) );

        rDocShell.PostPaintExtras();
        aModificator.SetDocumentModified();
        SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) );
        bSuccess = true;
    }
    else if (!bApi)
        rDocShell.ErrorMessage(STR_TABINSERT_ERROR);

    return bSuccess;
}

bool ScDocFunc::DeleteTable( SCTAB nTab, bool bRecord )
{
    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );

    ScDocShellModificator aModificator( rDocShell );

    bool bSuccess = false;
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bVbaEnabled = rDoc.IsInVBAMode();
    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;
    if ( bVbaEnabled )
        bRecord = false;
    bool bWasLinked = rDoc.IsLinked(nTab);
    ScDocumentUniquePtr pUndoDoc;
    std::unique_ptr<ScRefUndoData> pUndoData;
    if (bRecord)
    {
        pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
        SCTAB nCount = rDoc.GetTableCount();

        pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true );     // only nTab with Flags
        pUndoDoc->AddUndoTab( 0, nCount-1 );                    // all sheets for references

        rDoc.CopyToDocument(0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, InsertDeleteFlags::ALL,false, *pUndoDoc );
        OUString aOldName;
        rDoc.GetName( nTab, aOldName );
        pUndoDoc->RenameTab( nTab, aOldName );
        if (bWasLinked)
            pUndoDoc->SetLink( nTab, rDoc.GetLinkMode(nTab), rDoc.GetLinkDoc(nTab),
                                rDoc.GetLinkFlt(nTab), rDoc.GetLinkOpt(nTab),
                                rDoc.GetLinkTab(nTab),
                                rDoc.GetLinkRefreshDelay(nTab) );

        if ( rDoc.IsScenario(nTab) )
        {
            pUndoDoc->SetScenario( nTab, true );
            OUString aComment;
            Color  aColor;
            ScScenarioFlags nScenFlags;
            rDoc.GetScenarioData( nTab, aComment, aColor, nScenFlags );
            pUndoDoc->SetScenarioData( nTab, aComment, aColor, nScenFlags );
            bool bActive = rDoc.IsActiveScenario( nTab );
            pUndoDoc->SetActiveScenario( nTab, bActive );
        }
        pUndoDoc->SetVisible( nTab, rDoc.IsVisible( nTab ) );
        pUndoDoc->SetTabBgColor( nTab, rDoc.GetTabBgColor(nTab) );
        auto pSheetEvents = rDoc.GetSheetEvents( nTab );
        pUndoDoc->SetSheetEvents( nTab, std::unique_ptr<ScSheetEvents>(pSheetEvents ? new ScSheetEvents(*pSheetEvents) : nullptr) );

        //  Drawing-Layer has to take care of its own undo!!!
        rDoc.BeginDrawUndo();                          //  DeleteTab generates SdrUndoDelPage

        pUndoData.reset(new ScRefUndoData( rDoc ));
    }

    if (rDoc.DeleteTab(nTab))
    {
        if (bRecord)
        {
            std::vector<SCTAB> theTabs;
            theTabs.push_back(nTab);
            rDocShell.GetUndoManager()->AddUndoAction(
                        std::make_unique<ScUndoDeleteTab>( rDocShell, theTabs, std::move(pUndoDoc), std::move(pUndoData) ));
        }
        //  Update views:
        if( bVbaEnabled )
        {
            OUString sCodeName;
            if( rDoc.GetCodeName( nTab, sCodeName ) )
            {
                VBA_DeleteModule( rDocShell, sCodeName );
            }
        }
        rDocShell.Broadcast( ScTablesHint( SC_TAB_DELETED, nTab ) );

        if (bWasLinked)
        {
            rDocShell.UpdateLinks();                // update Link-Manager
            SfxBindings* pBindings = rDocShell.GetViewBindings();
            if (pBindings)
                pBindings->Invalidate(SID_LINKS);
        }

        rDocShell.PostPaintExtras();
        aModificator.SetDocumentModified();

        SfxApplication* pSfxApp = SfxGetpApp();                                // Navigator
        pSfxApp->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) );
        pSfxApp->Broadcast( SfxHint( SfxHintId::ScAreasChanged ) );
        pSfxApp->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) );
        pSfxApp->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) );

        bSuccess = true;
    }
    return bSuccess;
}

void ScDocFunc::SetTableVisible( SCTAB nTab, bool bVisible, bool bApi )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bUndo(rDoc.IsUndoEnabled());
    if ( rDoc.IsVisible( nTab ) == bVisible )
        return;                                // nothing to do - ok

    if ( !rDoc.IsDocEditable() )
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_PROTECTIONERR);
        return;
    }

    ScDocShellModificator aModificator( rDocShell );

    if ( !bVisible && !rDoc.IsImportingXML() )     // #i57869# allow hiding in any order for loading
    {
        // do not disable all sheets

        sal_uInt16 nVisCount = 0;
        SCTAB nCount = rDoc.GetTableCount();
        for (SCTAB i=0; i<nCount && nVisCount<2; i++)
            if (rDoc.IsVisible(i))
                ++nVisCount;

        if (nVisCount <= 1)
        {
            if (!bApi)
                rDocShell.ErrorMessage(STR_PROTECTIONERR);  //! separate error message?
            return;
        }
    }

    rDoc.SetVisible( nTab, bVisible );
    if (bUndo)
    {
        std::vector<SCTAB> undoTabs { nTab };
        rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoShowHideTab>( rDocShell, std::move(undoTabs), bVisible ) );
    }

    //  update views
    if (!bVisible)
        rDocShell.Broadcast( ScTablesHint( SC_TAB_HIDDEN, nTab ) );

    SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) );
    rDocShell.PostPaint(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB, PaintPartFlags::Extras);
    aModificator.SetDocumentModified();
}

bool ScDocFunc::SetLayoutRTL( SCTAB nTab, bool bRTL )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bUndo(rDoc.IsUndoEnabled());
    if ( rDoc.IsLayoutRTL( nTab ) == bRTL )
        return true;                                // nothing to do - ok

    //! protection (sheet or document?)

    ScDocShellModificator aModificator( rDocShell );

    rDoc.SetLayoutRTL( nTab, bRTL, ScObjectHandling::MirrorRTLMode);

    if (bUndo)
    {
        rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoLayoutRTL>( rDocShell, nTab, bRTL ) );
    }

    rDocShell.PostPaint( 0,0,nTab,rDoc.MaxCol(),rDoc.MaxRow(),nTab, PaintPartFlags::All );
    aModificator.SetDocumentModified();

    SfxBindings* pBindings = rDocShell.GetViewBindings();
    if (pBindings)
    {
        pBindings->Invalidate( FID_TAB_RTL );
        pBindings->Invalidate( SID_ATTR_SIZE );
    }

    return true;
}

bool ScDocFunc::RenameTable( SCTAB nTab, const OUString& rName, bool bRecord, bool bApi )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;
    if ( !rDoc.IsDocEditable() )
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_PROTECTIONERR);
        return false;
    }

    ScDocShellModificator aModificator( rDocShell );

    bool bSuccess = false;
    OUString sOldName;
    rDoc.GetName(nTab, sOldName);
    if (rDoc.RenameTab( nTab, rName ))
    {
        if (bRecord)
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                            std::make_unique<ScUndoRenameTab>( rDocShell, nTab, sOldName, rName));
        }
        rDocShell.PostPaintExtras();
        aModificator.SetDocumentModified();
        SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) );
        SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreasChanged ) );

        bSuccess = true;
    }
    return bSuccess;
}

bool ScDocFunc::SetTabBgColor( SCTAB nTab, const Color& rColor, bool bRecord, bool bApi )
{

    ScDocument& rDoc = rDocShell.GetDocument();
    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;
    if ( !rDoc.IsDocEditable() || rDoc.IsTabProtected(nTab) )
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_PROTECTIONERR); //TODO Check to see what this string is...
        return false;
    }

    Color aOldTabBgColor = rDoc.GetTabBgColor(nTab);

    bool bSuccess = false;
    rDoc.SetTabBgColor(nTab, rColor);
    if ( rDoc.GetTabBgColor(nTab) == rColor)
        bSuccess = true;
    if (bSuccess)
    {
        if (bRecord)
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoTabColor>( rDocShell, nTab, aOldTabBgColor, rColor));
        }
        rDocShell.PostPaintExtras();
        ScDocShellModificator aModificator( rDocShell );
        aModificator.SetDocumentModified();
        SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) );

        bSuccess = true;
    }
    return bSuccess;
}

bool ScDocFunc::SetTabBgColor(
    ScUndoTabColorInfo::List& rUndoTabColorList, bool bApi )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bRecord = true;
    if (!rDoc.IsUndoEnabled())
        bRecord = false;

    if ( !rDoc.IsDocEditable() )
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_PROTECTIONERR); //TODO Get a better String Error...
        return false;
    }

    sal_uInt16 nTab;
    Color aNewTabBgColor;
    bool bSuccess = true;
    size_t nTabProtectCount = 0;
    size_t nTabListCount = rUndoTabColorList.size();
    for ( size_t i = 0; i < nTabListCount; ++i )
    {
        ScUndoTabColorInfo& rInfo = rUndoTabColorList[i];
        nTab = rInfo.mnTabId;
        if ( !rDoc.IsTabProtected(nTab) )
        {
            aNewTabBgColor = rInfo.maNewTabBgColor;
            rInfo.maOldTabBgColor = rDoc.GetTabBgColor(nTab);
            rDoc.SetTabBgColor(nTab, aNewTabBgColor);
            if ( rDoc.GetTabBgColor(nTab) != aNewTabBgColor)
            {
                bSuccess = false;
                break;
            }
        }
        else
        {
            nTabProtectCount++;
        }
    }

    if ( nTabProtectCount == nTabListCount )
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_PROTECTIONERR); //TODO Get a better String Error...
        return false;
    }

    if (bSuccess)
    {
        if (bRecord)
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoTabColor>( rDocShell, std::vector(rUndoTabColorList)));
        }
        rDocShell.PostPaintExtras();
        ScDocShellModificator aModificator( rDocShell );
        aModificator.SetDocumentModified();
    }
    return bSuccess;
}

//! SetWidthOrHeight - duplicated in ViewFunc !!!!!!
//! Problems:
//! - Optimal height of text cells is different for a printer and a screen
//! - Optimal width needs a selection in order to take only selected cells into account

static sal_uInt16 lcl_GetOptimalColWidth( ScDocShell& rDocShell, SCCOL nCol, SCTAB nTab )
{
    ScSizeDeviceProvider aProv(rDocShell);
    OutputDevice* pDev = aProv.GetDevice();         // has pixel MapMode
    double nPPTX = aProv.GetPPTX();
    double nPPTY = aProv.GetPPTY();

    ScDocument& rDoc = rDocShell.GetDocument();
    Fraction aOne(1,1);
    sal_uInt16 nTwips = rDoc.GetOptimalColWidth( nCol, nTab, pDev, nPPTX, nPPTY, aOne, aOne,
                                                false/*bFormula*/ );

    return nTwips;
}

bool ScDocFunc::SetWidthOrHeight(
    bool bWidth, const std::vector<sc::ColRowSpan>& rRanges, SCTAB nTab,
    ScSizeMode eMode, sal_uInt16 nSizeTwips, bool bRecord, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    if (rRanges.empty())
        return true;

    ScDocument& rDoc = rDocShell.GetDocument();
    if ( bRecord && !rDoc.IsUndoEnabled() )
        bRecord = false;

    // import into read-only document is possible
    if ( !rDoc.IsChangeReadOnlyEnabled() && !rDocShell.IsEditable() )
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_PROTECTIONERR);      //! separate error message?
        return false;
    }

    SCCOLROW nStart = rRanges[0].mnStart;
    SCCOLROW nEnd = rRanges[0].mnEnd;

    if ( eMode == SC_SIZE_OPTIMAL )
    {
        //! Option "Show formulas" - but where to get them from?
    }

    ScDocumentUniquePtr pUndoDoc;
    std::unique_ptr<ScOutlineTable> pUndoTab;
    std::vector<sc::ColRowSpan> aUndoRanges;

    if ( bRecord )
    {
        rDoc.BeginDrawUndo();                          // Drawing Updates

        pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
        if (bWidth)
        {
            pUndoDoc->InitUndo( rDoc, nTab, nTab, true );
            rDoc.CopyToDocument( static_cast<SCCOL>(nStart), 0, nTab, static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc );
        }
        else
        {
            pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true );
            rDoc.CopyToDocument( 0, static_cast<SCROW>(nStart), nTab, rDoc.MaxCol(), static_cast<SCROW>(nEnd), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc );
        }

        aUndoRanges = rRanges;

        ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab );
        if (pTable)
            pUndoTab.reset(new ScOutlineTable( *pTable ));
    }

    bool bShow = nSizeTwips > 0 || eMode != SC_SIZE_DIRECT;
    bool bOutline = false;

    for (const sc::ColRowSpan& rRange : rRanges)
    {
        SCCOLROW nStartNo = rRange.mnStart;
        SCCOLROW nEndNo   = rRange.mnEnd;

        if ( !bWidth )                      // deal with heights always in blocks
        {
            if ( eMode==SC_SIZE_OPTIMAL || eMode==SC_SIZE_VISOPT )
            {
                bool bAll = ( eMode==SC_SIZE_OPTIMAL );
                if (!bAll)
                {
                    //  delete for all that have CRFlags::ManualSize enabled
                    //  then SetOptimalHeight with bShrink = FALSE
                    for (SCROW nRow=nStartNo; nRow<=nEndNo; nRow++)
                    {
                        CRFlags nOld = rDoc.GetRowFlags(nRow,nTab);
                        SCROW nLastRow = -1;
                        bool bHidden = rDoc.RowHidden(nRow, nTab, nullptr, &nLastRow);
                        if ( !bHidden && ( nOld & CRFlags::ManualSize ) )
                            rDoc.SetRowFlags( nRow, nTab, nOld & ~CRFlags::ManualSize );
                    }
                }

                ScSizeDeviceProvider aProv( rDocShell );
                Fraction aOne(1,1);
                sc::RowHeightContext aCxt(rDoc.MaxRow(), aProv.GetPPTX(), aProv.GetPPTY(), aOne, aOne, aProv.GetDevice());
                aCxt.setForceAutoSize(bAll);
                rDoc.SetOptimalHeight(aCxt, nStartNo, nEndNo, nTab, bApi);

                if (bAll)
                    rDoc.ShowRows( nStartNo, nEndNo, nTab, true );

                //  Manual flag will be set already in SetOptimalHeight if bAll=true
                //  (it is on when Extra-Height, otherwise off).
            }
            else if ( eMode==SC_SIZE_DIRECT || eMode==SC_SIZE_ORIGINAL )
            {
                if (nSizeTwips)
                {
                    rDoc.SetRowHeightRange( nStartNo, nEndNo, nTab, nSizeTwips );
                    rDoc.SetManualHeight( nStartNo, nEndNo, nTab, true );          // height was set manually
                }
                if ( eMode != SC_SIZE_ORIGINAL )
                    rDoc.ShowRows( nStartNo, nEndNo, nTab, nSizeTwips != 0 );
            }
            else if ( eMode==SC_SIZE_SHOW )
            {
                rDoc.ShowRows( nStartNo, nEndNo, nTab, true );
            }
        }
        else                                // Column widths
        {
            for (SCCOL nCol=static_cast<SCCOL>(nStartNo); nCol<=static_cast<SCCOL>(nEndNo); nCol++)
            {
                if ( eMode != SC_SIZE_VISOPT || !rDoc.ColHidden(nCol, nTab) )
                {
                    sal_uInt16 nThisSize = nSizeTwips;

                    if ( eMode==SC_SIZE_OPTIMAL || eMode==SC_SIZE_VISOPT )
                        nThisSize = nSizeTwips +
                                    lcl_GetOptimalColWidth( rDocShell, nCol, nTab );
                    if ( nThisSize )
                        rDoc.SetColWidth( nCol, nTab, nThisSize );

                    if ( eMode != SC_SIZE_ORIGINAL )
                        rDoc.ShowCol( nCol, nTab, bShow );
                }
            }
        }

                            //  adjust outlines

        if ( eMode != SC_SIZE_ORIGINAL )
        {
            if (bWidth)
                bOutline = bOutline || rDoc.UpdateOutlineCol(
                        static_cast<SCCOL>(nStartNo),
                        static_cast<SCCOL>(nEndNo), nTab, bShow );
            else
                bOutline = bOutline || rDoc.UpdateOutlineRow(
                        static_cast<SCROW>(nStartNo),
                        static_cast<SCROW>(nEndNo), nTab, bShow );
        }
    }
    rDoc.SetDrawPageSize(nTab);

    if (!bOutline)
        pUndoTab.reset();

    if (bRecord)
    {
        ScMarkData aMark(rDoc.GetSheetLimits());
        aMark.SelectOneTable( nTab );
        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoWidthOrHeight>(
                rDocShell, aMark, nStart, nTab, nEnd, nTab, std::move(pUndoDoc),
                std::move(aUndoRanges), std::move(pUndoTab), eMode, nSizeTwips, bWidth));
    }

    rDoc.UpdatePageBreaks( nTab );

    ScTabViewShell* pViewSh = rDocShell.GetBestViewShell();
    if (pViewSh)
        pViewSh->OnLOKSetWidthOrHeight(nStart, bWidth);

    rDocShell.PostPaint(0,0,nTab,rDoc.MaxCol(),rDoc.MaxRow(),nTab,PaintPartFlags::All);
    aModificator.SetDocumentModified();

    return false;
}

bool ScDocFunc::InsertPageBreak( bool bColumn, const ScAddress& rPos,
                                bool bRecord, bool bSetModified )
{
    ScDocShellModificator aModificator( rDocShell );

    ScDocument& rDoc = rDocShell.GetDocument();
    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;
    SCTAB nTab = rPos.Tab();
    SfxBindings* pBindings = rDocShell.GetViewBindings();

    SCCOLROW nPos = bColumn ? static_cast<SCCOLROW>(rPos.Col()) :
        static_cast<SCCOLROW>(rPos.Row());
    if (nPos == 0)
        return false;                   // first column / row

    ScBreakType nBreak = bColumn ?
        rDoc.HasColBreak(static_cast<SCCOL>(nPos), nTab) :
        rDoc.HasRowBreak(static_cast<SCROW>(nPos), nTab);
    if (nBreak & ScBreakType::Manual)
        return true;

    if (bRecord)
        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoPageBreak>( rDocShell, rPos.Col(), rPos.Row(), nTab, bColumn, true ) );

    if (bColumn)
        rDoc.SetColBreak(static_cast<SCCOL>(nPos), nTab, false, true);
    else
        rDoc.SetRowBreak(static_cast<SCROW>(nPos), nTab, false, true);

    rDoc.InvalidatePageBreaks(nTab);
    rDoc.UpdatePageBreaks( nTab );

    rDoc.SetStreamValid(nTab, false);

    if (bColumn)
    {
        rDocShell.PostPaint( static_cast<SCCOL>(nPos)-1, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid );
        if (pBindings)
        {
            pBindings->Invalidate( FID_INS_COLBRK );
            pBindings->Invalidate( FID_DEL_COLBRK );
        }
    }
    else
    {
        rDocShell.PostPaint( 0, static_cast<SCROW>(nPos)-1, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid );
        if (pBindings)
        {
            pBindings->Invalidate( FID_INS_ROWBRK );
            pBindings->Invalidate( FID_DEL_ROWBRK );
        }
    }
    if (pBindings)
        pBindings->Invalidate( FID_DEL_MANUALBREAKS );

    if (bSetModified)
        aModificator.SetDocumentModified();

    return true;
}

bool ScDocFunc::RemovePageBreak( bool bColumn, const ScAddress& rPos,
                                bool bRecord, bool bSetModified )
{
    ScDocShellModificator aModificator( rDocShell );

    ScDocument& rDoc = rDocShell.GetDocument();
    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;
    SCTAB nTab = rPos.Tab();
    SfxBindings* pBindings = rDocShell.GetViewBindings();

    SCCOLROW nPos = bColumn ? static_cast<SCCOLROW>(rPos.Col()) :
        static_cast<SCCOLROW>(rPos.Row());

    ScBreakType nBreak;
    if (bColumn)
        nBreak = rDoc.HasColBreak(static_cast<SCCOL>(nPos), nTab);
    else
        nBreak = rDoc.HasRowBreak(static_cast<SCROW>(nPos), nTab);
    if (!(nBreak & ScBreakType::Manual))
        // There is no manual break.
        return false;

    if (bRecord)
        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoPageBreak>( rDocShell, rPos.Col(), rPos.Row(), nTab, bColumn, false ) );

    if (bColumn)
        rDoc.RemoveColBreak(static_cast<SCCOL>(nPos), nTab, false, true);
    else
        rDoc.RemoveRowBreak(static_cast<SCROW>(nPos), nTab, false, true);

    rDoc.UpdatePageBreaks( nTab );

    rDoc.SetStreamValid(nTab, false);

    if (bColumn)
    {
        rDocShell.PostPaint( static_cast<SCCOL>(nPos)-1, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid );
        if (pBindings)
        {
            pBindings->Invalidate( FID_INS_COLBRK );
            pBindings->Invalidate( FID_DEL_COLBRK );
        }
    }
    else
    {
        rDocShell.PostPaint( 0, nPos-1, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid );
        if (pBindings)
        {
            pBindings->Invalidate( FID_INS_ROWBRK );
            pBindings->Invalidate( FID_DEL_ROWBRK );
        }
    }
    if (pBindings)
        pBindings->Invalidate( FID_DEL_MANUALBREAKS );

    if (bSetModified)
        aModificator.SetDocumentModified();

    return true;
}

void ScDocFunc::ProtectSheet( SCTAB nTab, const ScTableProtection& rProtect )
{
    ScDocument& rDoc = rDocShell.GetDocument();

    std::unique_ptr<ScTableProtection> p;
    if (!rProtect.isProtected() && rDoc.IsUndoEnabled())
    {
        // In case of unprotecting, use a copy of passed ScTableProtection object for undo
        p = std::make_unique<ScTableProtection>(rProtect);
    }
    rDoc.SetTabProtection(nTab, &rProtect);
    if (rDoc.IsUndoEnabled())
    {
        if (!p)
        {
            // For protection case, use a copy of resulting ScTableProtection for undo
            const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab);
            p = std::make_unique<ScTableProtection>(*pProtect);
        }
        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoTabProtect>(rDocShell, nTab, std::move(p)));
        // ownership of unique_ptr now transferred to ScUndoTabProtect.
    }
    for (SfxViewFrame* fr = SfxViewFrame::GetFirst(&rDocShell); fr;
         fr = SfxViewFrame::GetNext(*fr, &rDocShell))
        if (ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(fr->GetViewShell()))
            pTabViewShell->SetTabProtectionSymbol(nTab, rProtect.isProtected());
    rDocShell.PostPaintGridAll();
    ScDocShellModificator aModificator(rDocShell);
    aModificator.SetDocumentModified();
}

void ScDocFunc::ProtectDocument(const ScDocProtection& rProtect)
{
    ScDocument& rDoc = rDocShell.GetDocument();

    std::unique_ptr<ScDocProtection> p;
    if (!rProtect.isProtected() && rDoc.IsUndoEnabled())
    {
        // In case of unprotecting, use a copy of passed ScTableProtection object for undo
        p = std::make_unique<ScDocProtection>(rProtect);
    }
    rDoc.SetDocProtection(&rProtect);
    if (rDoc.IsUndoEnabled())
    {
        if (!p)
        {
            // For protection case, use a copy of resulting ScTableProtection for undo
            ScDocProtection* pProtect = rDoc.GetDocProtection();
            p = std::make_unique<ScDocProtection>(*pProtect);
        }
        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoDocProtect>(rDocShell, std::move(p)));
        // ownership of unique_ptr now transferred to ScUndoTabProtect.
    }

    rDocShell.PostPaintGridAll();
    ScDocShellModificator aModificator(rDocShell);
    aModificator.SetDocumentModified();
}

bool ScDocFunc::Protect( SCTAB nTab, const OUString& rPassword )
{
    if (nTab == TABLEID_DOC)
    {
        // document protection
        ScDocProtection aProtection;
        aProtection.setProtected(true);
        aProtection.setPassword(rPassword);
        ProtectDocument(aProtection);

    }
    else
    {
        // sheet protection

        const ScTableProtection* pOldProtection = rDocShell.GetDocument().GetTabProtection(nTab);
        ::std::unique_ptr<ScTableProtection> pNewProtection(pOldProtection ? new ScTableProtection(*pOldProtection) : new ScTableProtection());
        pNewProtection->setProtected(true);
        pNewProtection->setPassword(rPassword);
        ProtectSheet(nTab, *pNewProtection);
    }
    return true;
}

bool ScDocFunc::Unprotect( SCTAB nTab, std::u16string_view rPassword, bool bApi )
{
    ScDocument& rDoc = rDocShell.GetDocument();

    if (nTab == TABLEID_DOC)
    {
        // document protection

        ScDocProtection* pDocProtect = rDoc.GetDocProtection();
        if (!pDocProtect || !pDocProtect->isProtected())
            // already unprotected (should not happen)!
            return true;

        if (!pDocProtect->verifyPassword(rPassword))
        {
            if (!bApi)
            {
                std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
                                                              VclMessageType::Info, VclButtonsType::Ok,
                                                              ScResId(SCSTR_WRONGPASSWORD)));
                xInfoBox->run();
            }
            return false;
        }

        ScDocProtection aNewProtection(*pDocProtect);
        aNewProtection.setProtected(false);
        ProtectDocument(aNewProtection);

    }
    else
    {
        // sheet protection

        const ScTableProtection* pTabProtect = rDoc.GetTabProtection(nTab);
        if (!pTabProtect || !pTabProtect->isProtected())
            // already unprotected (should not happen)!
            return true;
        if (!pTabProtect->verifyPassword(rPassword))
        {
            if (!bApi)
            {
                std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
                                                              VclMessageType::Info, VclButtonsType::Ok,
                                                              ScResId(SCSTR_WRONGPASSWORD)));
                xInfoBox->run();
            }
            return false;
        }

        ScTableProtection aNewProtection(*pTabProtect);
        aNewProtection.setProtected(false);
        ProtectSheet(nTab, aNewProtection);
    }

    return true;
}

void ScDocFunc::ClearItems( const ScMarkData& rMark, const sal_uInt16* pWhich, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    ScDocument& rDoc = rDocShell.GetDocument();
    bool bUndo (rDoc.IsUndoEnabled());

    if (!CheckSheetViewProtection(sc::Operation::ClearItems))
        return;

    ScEditableTester aTester = ScEditableTester::CreateAndTestSelection(rDoc, rMark);
    if (!aTester.IsEditable())
    {
        if (!bApi)
            rDocShell.ErrorMessage(aTester.GetMessageId());
        return;
    }

    //  #i12940# ClearItems is called (from setPropertyToDefault) directly with uno object's cached
    //  MarkData (GetMarkData), so rMark must be changed to multi selection for ClearSelectionItems
    //  here.

    ScMarkData aMultiMark = rMark;
    aMultiMark.SetMarking(false);       // for MarkToMulti
    aMultiMark.MarkToMulti();
    const ScRange& aMarkRange = aMultiMark.GetMultiMarkArea();

    if (bUndo)
    {
        SCTAB nStartTab = aMarkRange.aStart.Tab();
        SCTAB nEndTab = aMarkRange.aEnd.Tab();

        ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
        pUndoDoc->InitUndo( rDoc, nStartTab, nEndTab );
        rDoc.CopyToDocument( aMarkRange, InsertDeleteFlags::ATTRIB, true, *pUndoDoc, &aMultiMark );

        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoClearItems>( rDocShell, aMultiMark, std::move(pUndoDoc), pWhich ) );
    }

    rDoc.ClearSelectionItems( pWhich, aMultiMark );

    rDocShell.PostPaint( aMarkRange, PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE );
    aModificator.SetDocumentModified();

    //! Bindings-Invalidate etc.?
}

bool ScDocFunc::ChangeIndent( const ScMarkData& rMark, bool bIncrement, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    ScDocument& rDoc = rDocShell.GetDocument();
    bool bUndo(rDoc.IsUndoEnabled());

    if (!CheckSheetViewProtection(sc::Operation::ChangeIndent))
        return false;

    ScEditableTester aTester = ScEditableTester::CreateAndTestSelection(rDoc, rMark);
    if (!aTester.IsEditable())
    {
        if (!bApi)
            rDocShell.ErrorMessage(aTester.GetMessageId());
        return false;
    }

    const ScRange& aMarkRange = rMark.GetMultiMarkArea();

    if (bUndo)
    {
        SCTAB nStartTab = aMarkRange.aStart.Tab();
        SCTAB nTabCount = rDoc.GetTableCount();

        ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
        pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab );
        for (const auto& rTab : rMark)
        {
            if (rTab >= nTabCount)
                break;

            if (rTab != nStartTab)
                pUndoDoc->AddUndoTab( rTab, rTab );
        }

        ScRange aCopyRange = aMarkRange;
        aCopyRange.aStart.SetTab(0);
        aCopyRange.aEnd.SetTab(nTabCount-1);
        rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, true, *pUndoDoc, &rMark );

        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoIndent>( rDocShell, rMark, std::move(pUndoDoc), bIncrement ) );
    }

    rDoc.ChangeSelectionIndent( bIncrement, rMark );

    rDocShell.PostPaint( aMarkRange, PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE );
    aModificator.SetDocumentModified();

    SfxBindings* pBindings = rDocShell.GetViewBindings();
    if (pBindings)
    {
        pBindings->Invalidate( SID_ALIGNLEFT );         // ChangeIndent aligns left
        pBindings->Invalidate( SID_ALIGNRIGHT );
        pBindings->Invalidate( SID_ALIGNBLOCK );
        pBindings->Invalidate( SID_ALIGNCENTERHOR );
        pBindings->Invalidate( SID_ATTR_LRSPACE );
        pBindings->Invalidate( SID_ATTR_PARA_ADJUST_LEFT );
        pBindings->Invalidate( SID_ATTR_PARA_ADJUST_RIGHT );
        pBindings->Invalidate( SID_ATTR_PARA_ADJUST_BLOCK );
        pBindings->Invalidate( SID_ATTR_PARA_ADJUST_CENTER);
        pBindings->Invalidate( SID_ATTR_PARA_ADJUST_START );
        pBindings->Invalidate( SID_ATTR_PARA_ADJUST_END );
        // pseudo slots for Format menu
        pBindings->Invalidate( SID_ALIGN_ANY_HDEFAULT );
        pBindings->Invalidate( SID_ALIGN_ANY_LEFT );
        pBindings->Invalidate( SID_ALIGN_ANY_HCENTER );
        pBindings->Invalidate( SID_ALIGN_ANY_RIGHT );
        pBindings->Invalidate( SID_ALIGN_ANY_START );
        pBindings->Invalidate( SID_ALIGN_ANY_END );
        pBindings->Invalidate( SID_ALIGN_ANY_JUSTIFIED );
    }

    return true;
}

bool ScDocFunc::AutoFormat( const ScRange& rRange, const ScMarkData* pTabMark,
                            sal_uInt16 nFormatNo, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    ScDocument& rDoc = rDocShell.GetDocument();
    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();

    bool bRecord = true;
    if (!rDoc.IsUndoEnabled())
        bRecord = false;
    ScMarkData aMark(rDoc.GetSheetLimits());
    if (pTabMark)
        aMark = *pTabMark;
    else
    {
        for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++)
            aMark.SelectTable( nTab, true );
    }

    ScAutoFormat* pAutoFormat = ScGlobal::GetOrCreateAutoFormat();

    if (!CheckSheetViewProtection(sc::Operation::AutoFormat))
        return false;

    ScEditableTester aTester = ScEditableTester::CreateAndTestSelectedBlock(rDoc, nStartCol, nStartRow, nEndCol, nEndRow, aMark);
    if ( nFormatNo < pAutoFormat->size() && aTester.IsEditable() )
    {
        weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );

        bool bSize = pAutoFormat->findByIndex(nFormatNo)->GetIncludeWidthHeight();
        if (sal_uInt64(nEndCol - nStartCol + 1) * sal_uInt64(nEndRow - nStartRow + 1) > AUTOFORMAT_WARN_SIZE)
        {
            std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
                                                           VclMessageType::Warning, VclButtonsType::YesNo,
                                                           ScResId(STR_AUTOFORMAT_WAIT_WARNING)));
            xQueryBox->set_default_response(RET_NO);
            if (xQueryBox->run() != RET_YES)
                return false;
        }

        SCTAB nTabCount = rDoc.GetTableCount();
        ScDocumentUniquePtr pUndoDoc;
        if ( bRecord )
        {
            pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
            pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab, bSize, bSize );
            for (const auto& rTab : aMark)
            {
                if (rTab >= nTabCount)
                    break;

                if (rTab != nStartTab)
                    pUndoDoc->AddUndoTab( rTab, rTab, bSize, bSize );
            }

            ScRange aCopyRange = rRange;
            aCopyRange.aStart.SetTab(0);
            aCopyRange.aStart.SetTab(nTabCount-1);
            rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, false, *pUndoDoc, &aMark );
            if (bSize)
            {
                rDoc.CopyToDocument( nStartCol,0,0, nEndCol,rDoc.MaxRow(),nTabCount-1,
                                                            InsertDeleteFlags::NONE, false, *pUndoDoc, &aMark );
                rDoc.CopyToDocument( 0,nStartRow,0, rDoc.MaxCol(),nEndRow,nTabCount-1,
                                                            InsertDeleteFlags::NONE, false, *pUndoDoc, &aMark );
            }
            rDoc.BeginDrawUndo();
        }

        rDoc.AutoFormat( nStartCol, nStartRow, nEndCol, nEndRow, nFormatNo, aMark );

        if (bSize)
        {
            std::vector<sc::ColRowSpan> aCols(1, sc::ColRowSpan(nStartCol,nEndCol));
            std::vector<sc::ColRowSpan> aRows(1, sc::ColRowSpan(nStartRow,nEndRow));

            for (const auto& rTab : aMark)
            {
                if (rTab >= nTabCount)
                    break;

                SetWidthOrHeight(true, aCols, rTab, SC_SIZE_VISOPT, STD_EXTRA_WIDTH, false, true);
                SetWidthOrHeight(false, aRows, rTab, SC_SIZE_VISOPT, 0, false, false);
                rDocShell.PostPaint( 0,0,rTab, rDoc.MaxCol(),rDoc.MaxRow(),rTab,
                                PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top );
            }
        }
        else
        {
            for (const auto& rTab : aMark)
            {
                if (rTab >= nTabCount)
                    break;

                bool bAdj = AdjustRowHeight( ScRange(nStartCol, nStartRow, rTab,
                                                     nEndCol, nEndRow, rTab), false, bApi );
                if (bAdj)
                    rDocShell.PostPaint( 0,nStartRow,rTab, rDoc.MaxCol(),rDoc.MaxRow(),rTab,
                                        PaintPartFlags::Grid | PaintPartFlags::Left );
                else
                    rDocShell.PostPaint( nStartCol, nStartRow, rTab,
                                        nEndCol, nEndRow, rTab, PaintPartFlags::Grid );
            }
        }

        if ( bRecord )      // only now is Draw-Undo available
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoAutoFormat>( rDocShell, rRange, std::move(pUndoDoc), aMark, bSize, nFormatNo ) );
        }

        aModificator.SetDocumentModified();
    }
    else if (!bApi)
        rDocShell.ErrorMessage(aTester.GetMessageId());

    return false;
}

bool ScDocFunc::EnterMatrix( const ScRange& rRange, const ScMarkData* pTabMark,
        const ScTokenArray* pTokenArray, const OUString& rString, bool bApi, bool bEnglish,
        const OUString& rFormulaNmsp, const formula::FormulaGrammar::Grammar eGrammar )
{
    if (ScViewData::SelectionFillDOOM( rRange ))
        return false;

    ScDocShellModificator aModificator( rDocShell );

    bool bSuccess = false;
    ScDocument& rDoc = rDocShell.GetDocument();
    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();

    ScMarkData aMark(rDoc.GetSheetLimits());
    if (pTabMark)
        aMark = *pTabMark;
    else
    {
        for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++)
            aMark.SelectTable( nTab, true );
    }

    if (!CheckSheetViewProtection(sc::Operation::EnterMatrix))
        return false;

    ScEditableTester aTester = ScEditableTester::CreateAndTestSelectedBlock(rDoc, nStartCol, nStartRow, nEndCol, nEndRow, aMark);
    if ( aTester.IsEditable() )
    {
        weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );

        ScDocumentUniquePtr pUndoDoc;

        const bool bUndo(rDoc.IsUndoEnabled());
        if (bUndo)
        {
            //! take selected sheets into account also when undoing
            pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
            pUndoDoc->InitUndo( rDoc, nStartTab, nEndTab );
            rDoc.CopyToDocument( rRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc );
        }

        // use TokenArray if given, string (and flags) otherwise
        if ( pTokenArray )
        {
            rDoc.InsertMatrixFormula( nStartCol, nStartRow, nEndCol, nEndRow,
                    aMark, OUString(), pTokenArray, eGrammar);
        }
        else if ( rDoc.IsImportingXML() )
        {
            ScTokenArray aCode(rDoc);
            aCode.AssignXMLString( rString,
                    ((eGrammar == formula::FormulaGrammar::GRAM_EXTERNAL) ? rFormulaNmsp : OUString()));
            rDoc.InsertMatrixFormula( nStartCol, nStartRow, nEndCol, nEndRow,
                    aMark, OUString(), &aCode, eGrammar);
            rDoc.IncXMLImportedFormulaCount( rString.getLength() );
        }
        else if (bEnglish)
        {
            ScCompiler aComp( rDoc, rRange.aStart, eGrammar);
            std::unique_ptr<ScTokenArray> pCode = aComp.CompileString( rString );
            rDoc.InsertMatrixFormula( nStartCol, nStartRow, nEndCol, nEndRow,
                    aMark, OUString(), pCode.get(), eGrammar);
        }
        else
            rDoc.InsertMatrixFormula( nStartCol, nStartRow, nEndCol, nEndRow,
                    aMark, rString, nullptr, eGrammar);

        if (bUndo)
        {
            //! take selected sheets into account also when undoing
            rDocShell.GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoEnterMatrix>( rDocShell, rRange, std::move(pUndoDoc), rString ) );
        }

        //  Err522 painting of DDE-Formulas will be intercepted during interpreting
        rDocShell.PostPaint( nStartCol,nStartRow,nStartTab,nEndCol,nEndRow,nEndTab, PaintPartFlags::Grid );
        aModificator.SetDocumentModified();

        bSuccess = true;
    }
    else if (!bApi)
        rDocShell.ErrorMessage(aTester.GetMessageId());

    return bSuccess;
}

bool ScDocFunc::TabOp( const ScRange& rRange, const ScMarkData* pTabMark,
                            const ScTabOpParam& rParam, bool bRecord, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    bool bSuccess = false;
    ScDocument& rDoc = rDocShell.GetDocument();
    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();

    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;

    ScMarkData aMark(rDoc.GetSheetLimits());
    if (pTabMark)
        aMark = *pTabMark;
    else
    {
        for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++)
            aMark.SelectTable( nTab, true );
    }

    if (!CheckSheetViewProtection(sc::Operation::TabOperation))
        return false;

    ScEditableTester aTester = ScEditableTester::CreateAndTestSelectedBlock(rDoc, nStartCol, nStartRow, nEndCol, nEndRow, aMark);
    if ( aTester.IsEditable() )
    {
        weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );
        rDoc.SetDirty( rRange, false );
        if ( bRecord )
        {
            //! take selected sheets into account also when undoing
            ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
            pUndoDoc->InitUndo( rDoc, nStartTab, nEndTab );
            rDoc.CopyToDocument( rRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc );

            rDocShell.GetUndoManager()->AddUndoAction(
                    std::make_unique<ScUndoTabOp>( rDocShell,
                                     nStartCol, nStartRow, nStartTab,
                                     nEndCol, nEndRow, nEndTab, std::move(pUndoDoc),
                                     rParam.aRefFormulaCell,
                                     rParam.aRefFormulaEnd,
                                     rParam.aRefRowCell,
                                     rParam.aRefColCell,
                                     rParam.meMode) );
        }
        rDoc.InsertTableOp(rParam, nStartCol, nStartRow, nEndCol, nEndRow, aMark);
        rDocShell.PostPaintGridAll();
        aModificator.SetDocumentModified();
        bSuccess = true;
    }
    else if (!bApi)
        rDocShell.ErrorMessage(aTester.GetMessageId());

    return bSuccess;
}

static ScDirection DirFromFillDir( FillDir eDir )
{
    if (eDir==FILL_TO_BOTTOM)
        return DIR_BOTTOM;
    else if (eDir==FILL_TO_RIGHT)
        return DIR_RIGHT;
    else if (eDir==FILL_TO_TOP)
        return DIR_TOP;
    else // if (eDir==FILL_TO_LEFT)
        return DIR_LEFT;
}

namespace {

/**
 * Expand the fill range as necessary, to allow copying of adjacent cell(s)
 * even when those cells are not in the original range.
 */
void adjustFillRangeForAdjacentCopy(const ScDocument &rDoc, ScRange& rRange, FillDir eDir)
{
    switch (eDir)
    {
        case FILL_TO_BOTTOM:
        {
            if (rRange.aStart.Row() == 0)
                return;

            if (rRange.aStart.Row() != rRange.aEnd.Row())
                return;

            // Include the above row.
            ScAddress& s = rRange.aStart;
            s.SetRow(s.Row()-1);
        }
        break;
        case FILL_TO_TOP:
        {
            if (rRange.aStart.Row() == rDoc.MaxRow())
                return;

            if (rRange.aStart.Row() != rRange.aEnd.Row())
                return;

            // Include the row below.
            ScAddress& e = rRange.aEnd;
            e.SetRow(e.Row()+1);
        }
        break;
        case FILL_TO_LEFT:
        {
            if (rRange.aStart.Col() == rDoc.MaxCol())
                return;

            if (rRange.aStart.Col() != rRange.aEnd.Col())
                return;

            // Include the column to the right.
            ScAddress& e = rRange.aEnd;
            e.SetCol(e.Col()+1);
        }
        break;
        case FILL_TO_RIGHT:
        {
            if (rRange.aStart.Col() == 0)
                return;

            if (rRange.aStart.Col() != rRange.aEnd.Col())
                return;

            // Include the column to the left.
            ScAddress& s = rRange.aStart;
            s.SetCol(s.Col()-1);
        }
        break;
        default:
            ;
    }
}

}

bool ScDocFunc::FillSimple( const ScRange& rRange, const ScMarkData* pTabMark,
                            FillDir eDir, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );
    ScDocument& rDoc = rDocShell.GetDocument();

    bool bSuccess = false;
    ScRange aRange = rRange;
    adjustFillRangeForAdjacentCopy(rDoc, aRange, eDir);

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

    bool bRecord = true;
    if (!rDoc.IsUndoEnabled())
        bRecord = false;

    ScMarkData aMark(rDoc.GetSheetLimits());
    if (pTabMark)
        aMark = *pTabMark;
    else
    {
        for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++)
            aMark.SelectTable( nTab, true );
    }

    if (!CheckSheetViewProtection(sc::Operation::FillSimple))
        return false;

    ScEditableTester aTester = ScEditableTester::CreateAndTestSelectedBlock(rDoc, nStartCol, nStartRow, nEndCol, nEndRow, aMark);
    if ( aTester.IsEditable() )
    {
        weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );

        ScRange aSourceArea = aRange;
        ScRange aDestArea   = aRange;

        SCCOLROW nCount = 0;
        switch (eDir)
        {
            case FILL_TO_BOTTOM:
                nCount = aSourceArea.aEnd.Row()-aSourceArea.aStart.Row();
                aSourceArea.aEnd.SetRow( aSourceArea.aStart.Row() );
                break;
            case FILL_TO_RIGHT:
                nCount = aSourceArea.aEnd.Col()-aSourceArea.aStart.Col();
                aSourceArea.aEnd.SetCol( aSourceArea.aStart.Col() );
                break;
            case FILL_TO_TOP:
                nCount = aSourceArea.aEnd.Row()-aSourceArea.aStart.Row();
                aSourceArea.aStart.SetRow( aSourceArea.aEnd.Row() );
                break;
            case FILL_TO_LEFT:
                nCount = aSourceArea.aEnd.Col()-aSourceArea.aStart.Col();
                aSourceArea.aStart.SetCol( aSourceArea.aEnd.Col() );
                break;
        }

        ScDocumentUniquePtr pUndoDoc;
        if ( bRecord )
        {
            SCTAB nTabCount = rDoc.GetTableCount();
            SCTAB nDestStartTab = aDestArea.aStart.Tab();

            pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
            pUndoDoc->InitUndo( rDoc, nDestStartTab, nDestStartTab );
            for (const auto& rTab : aMark)
            {
                if (rTab >= nTabCount)
                    break;

                if (rTab != nDestStartTab)
                    pUndoDoc->AddUndoTab( rTab, rTab );
            }

            ScRange aCopyRange = aDestArea;
            aCopyRange.aStart.SetTab(0);
            aCopyRange.aEnd.SetTab(nTabCount-1);
            rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::AUTOFILL, false, *pUndoDoc, &aMark );
        }

        sal_uLong nProgCount;
        if (eDir == FILL_TO_BOTTOM || eDir == FILL_TO_TOP)
            nProgCount = aSourceArea.aEnd.Col() - aSourceArea.aStart.Col() + 1;
        else
            nProgCount = aSourceArea.aEnd.Row() - aSourceArea.aStart.Row() + 1;
        nProgCount *= nCount;
        ScProgress aProgress( rDoc.GetDocumentShell(),
                ScResId(STR_FILL_SERIES_PROGRESS), nProgCount, true );

        rDoc.Fill( aSourceArea.aStart.Col(), aSourceArea.aStart.Row(),
                aSourceArea.aEnd.Col(), aSourceArea.aEnd.Row(), &aProgress,
                aMark, nCount, eDir, FILL_SIMPLE );
        AdjustRowHeight(aRange, true, bApi);

        if ( bRecord )      // only now is Draw-Undo available
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoAutoFill>( rDocShell, aDestArea, aSourceArea, std::move(pUndoDoc), aMark,
                                    eDir, FILL_SIMPLE, FILL_DAY, MAXDOUBLE, 1.0, 1e307) );
        }

        rDocShell.PostPaintGridAll();
        aModificator.SetDocumentModified();

        bSuccess = true;
    }
    else if (!bApi)
        rDocShell.ErrorMessage(aTester.GetMessageId());

    return bSuccess;
}

bool ScDocFunc::FillSeries( const ScRange& rRange, const ScMarkData* pTabMark,
                            FillDir eDir, FillCmd eCmd, FillDateCmd eDateCmd,
                            double fStart, double fStep, double fMax,
                            bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    bool bSuccess = false;
    ScDocument& rDoc = rDocShell.GetDocument();
    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();

    bool bRecord = true;
    if (!rDoc.IsUndoEnabled())
        bRecord = false;

    ScMarkData aMark(rDoc.GetSheetLimits());
    if (pTabMark)
        aMark = *pTabMark;
    else
    {
        for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++)
            aMark.SelectTable( nTab, true );
    }

    if (!CheckSheetViewProtection(sc::Operation::FillSeries))
        return false;

    ScEditableTester aTester = ScEditableTester::CreateAndTestSelectedBlock(rDoc, nStartCol, nStartRow, nEndCol, nEndRow, aMark);
    if ( aTester.IsEditable() )
    {
        weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );

        ScRange aSourceArea = rRange;
        ScRange aDestArea   = rRange;

        SCSIZE nCount = rDoc.GetEmptyLinesInBlock(
                aSourceArea.aStart.Col(), aSourceArea.aStart.Row(), aSourceArea.aStart.Tab(),
                aSourceArea.aEnd.Col(), aSourceArea.aEnd.Row(), aSourceArea.aEnd.Tab(),
                DirFromFillDir(eDir) );

        //  keep at least one row/column as source range
        SCSIZE nTotLines = ( eDir == FILL_TO_BOTTOM || eDir == FILL_TO_TOP ) ?
            static_cast<SCSIZE>( aSourceArea.aEnd.Row() - aSourceArea.aStart.Row() + 1 ) :
            static_cast<SCSIZE>( aSourceArea.aEnd.Col() - aSourceArea.aStart.Col() + 1 );
        if ( nCount >= nTotLines )
        {
            assert(nTotLines > 0 && "coverity 2023.12.2");
            nCount = nTotLines - 1;
        }

        switch (eDir)
        {
            case FILL_TO_BOTTOM:
                aSourceArea.aEnd.SetRow( sal::static_int_cast<SCROW>( aSourceArea.aEnd.Row() - nCount ) );
                break;
            case FILL_TO_RIGHT:
                aSourceArea.aEnd.SetCol( sal::static_int_cast<SCCOL>( aSourceArea.aEnd.Col() - nCount ) );
                break;
            case FILL_TO_TOP:
                aSourceArea.aStart.SetRow( sal::static_int_cast<SCROW>( aSourceArea.aStart.Row() + nCount ) );
                break;
            case FILL_TO_LEFT:
                aSourceArea.aStart.SetCol( sal::static_int_cast<SCCOL>( aSourceArea.aStart.Col() + nCount ) );
                break;
        }

        ScDocumentUniquePtr pUndoDoc;
        if ( bRecord )
        {
            SCTAB nTabCount = rDoc.GetTableCount();
            SCTAB nDestStartTab = aDestArea.aStart.Tab();

            pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
            pUndoDoc->InitUndo( rDoc, nDestStartTab, nDestStartTab );
            for (const auto& rTab : aMark)
            {
                if (rTab >= nTabCount)
                    break;

                if (rTab != nDestStartTab)
                    pUndoDoc->AddUndoTab( rTab, rTab );
            }

            rDoc.CopyToDocument(
                aDestArea.aStart.Col(), aDestArea.aStart.Row(), 0,
                aDestArea.aEnd.Col(), aDestArea.aEnd.Row(), nTabCount-1,
                InsertDeleteFlags::AUTOFILL, false, *pUndoDoc, &aMark );
        }

        if (aDestArea.aStart.Col() <= aDestArea.aEnd.Col() &&
            aDestArea.aStart.Row() <= aDestArea.aEnd.Row())
        {
            if ( fStart != MAXDOUBLE )
            {
                SCCOL nValX = (eDir == FILL_TO_LEFT) ? aDestArea.aEnd.Col() : aDestArea.aStart.Col();
                SCROW nValY = (eDir == FILL_TO_TOP ) ? aDestArea.aEnd.Row() : aDestArea.aStart.Row();
                SCTAB nTab = aDestArea.aStart.Tab();
                rDoc.SetValue( nValX, nValY, nTab, fStart );
            }

            sal_uLong nProgCount;
            if (eDir == FILL_TO_BOTTOM || eDir == FILL_TO_TOP)
                nProgCount = aSourceArea.aEnd.Col() - aSourceArea.aStart.Col() + 1;
            else
                nProgCount = aSourceArea.aEnd.Row() - aSourceArea.aStart.Row() + 1;
            nProgCount *= nCount;
            ScProgress aProgress( rDoc.GetDocumentShell(),
                    ScResId(STR_FILL_SERIES_PROGRESS), nProgCount, true );

            rDoc.Fill( aSourceArea.aStart.Col(), aSourceArea.aStart.Row(),
                        aSourceArea.aEnd.Col(), aSourceArea.aEnd.Row(), &aProgress,
                        aMark, nCount, eDir, eCmd, eDateCmd, fStep, fMax );
            AdjustRowHeight(rRange, true, bApi);

            rDocShell.PostPaintGridAll();
            aModificator.SetDocumentModified();
        }

        if ( bRecord )      // only now is Draw-Undo available
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoAutoFill>( rDocShell, aDestArea, aSourceArea, std::move(pUndoDoc), aMark,
                                    eDir, eCmd, eDateCmd, fStart, fStep, fMax) );
        }

        bSuccess = true;
    }
    else if (!bApi)
        rDocShell.ErrorMessage(aTester.GetMessageId());

    return bSuccess;
}

bool ScDocFunc::FillAuto( ScRange& rRange, const ScMarkData* pTabMark,
                            FillDir eDir, sal_uLong nCount, bool bApi )
{
    return FillAuto( rRange, pTabMark, eDir, FILL_AUTO, FILL_DAY, nCount, 1.0/*fStep*/, MAXDOUBLE/*fMax*/, true/*bRecord*/, bApi );
}

bool ScDocFunc::FillAuto( ScRange& rRange, const ScMarkData* pTabMark, FillDir eDir, FillCmd eCmd, FillDateCmd  eDateCmd, sal_uLong nCount, double fStep, double fMax,  bool bRecord, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    ScDocument& rDoc = rDocShell.GetDocument();
    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();

    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;

    ScMarkData aMark(rDoc.GetSheetLimits());
    if (pTabMark)
        aMark = *pTabMark;
    else
    {
        for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++)
            aMark.SelectTable( nTab, true );
    }

    ScRange aSourceArea = rRange;
    ScRange aDestArea   = rRange;

    switch (eDir)
    {
        case FILL_TO_BOTTOM:
            aDestArea.aEnd.SetRow( sal::static_int_cast<SCROW>( aSourceArea.aEnd.Row() + nCount ) );
            break;
        case FILL_TO_TOP:
            if (nCount > sal::static_int_cast<sal_uLong>( aSourceArea.aStart.Row() ))
            {
                OSL_FAIL("FillAuto: Row < 0");
                nCount = aSourceArea.aStart.Row();
            }
            aDestArea.aStart.SetRow( sal::static_int_cast<SCROW>( aSourceArea.aStart.Row() - nCount ) );
            break;
        case FILL_TO_RIGHT:
            aDestArea.aEnd.SetCol( sal::static_int_cast<SCCOL>( aSourceArea.aEnd.Col() + nCount ) );
            break;
        case FILL_TO_LEFT:
            if (nCount > sal::static_int_cast<sal_uLong>( aSourceArea.aStart.Col() ))
            {
                OSL_FAIL("FillAuto: Col < 0");
                nCount = aSourceArea.aStart.Col();
            }
            aDestArea.aStart.SetCol( sal::static_int_cast<SCCOL>( aSourceArea.aStart.Col() - nCount ) );
            break;
        default:
            OSL_FAIL("Wrong direction with FillAuto");
            break;
    }

    if (!CheckSheetViewProtection(sc::Operation::FillAuto))
        return false;

    //      Test for cell protection
    //!     Source range can be protected !!!
    //!     but can't contain matrix fragments !!!

    ScEditableTester aTester = ScEditableTester::CreateAndTestRange(rDoc, aDestArea, sc::EditAction::Unknown);
    if ( !aTester.IsEditable() )
    {
        if (!bApi)
            rDocShell.ErrorMessage(aTester.GetMessageId());
        return false;
    }

    if ( rDoc.HasSelectedBlockMatrixFragment( nStartCol, nStartRow,
            nEndCol, nEndRow, aMark ) )
    {
        if (!bApi)
            rDocShell.ErrorMessage(STR_MATRIXFRAGMENTERR);
        return false;
    }

    // FID_FILL_... slots should already had been disabled, check here for API
    // calls, no message.
    if (ScViewData::SelectionFillDOOM( aDestArea))
        return false;

    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );

    ScDocumentUniquePtr pUndoDoc;
    if ( bRecord )
    {
        SCTAB nTabCount = rDoc.GetTableCount();
        SCTAB nDestStartTab = aDestArea.aStart.Tab();

        pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
        pUndoDoc->InitUndo( rDoc, nDestStartTab, nDestStartTab );
        for (const auto& rTab : aMark)
        {
            if (rTab >= nTabCount)
                break;

            if (rTab != nDestStartTab)
                pUndoDoc->AddUndoTab( rTab, rTab );
        }

        // do not clone note captions in undo document
        rDoc.CopyToDocument(
            aDestArea.aStart.Col(), aDestArea.aStart.Row(), 0,
            aDestArea.aEnd.Col(), aDestArea.aEnd.Row(), nTabCount-1,
            InsertDeleteFlags::AUTOFILL, false, *pUndoDoc, &aMark );
    }

    sal_uLong nProgCount;
    if (eDir == FILL_TO_BOTTOM || eDir == FILL_TO_TOP)
        nProgCount = aSourceArea.aEnd.Col() - aSourceArea.aStart.Col() + 1;
    else
        nProgCount = aSourceArea.aEnd.Row() - aSourceArea.aStart.Row() + 1;
    nProgCount *= nCount;
    ScProgress aProgress( rDoc.GetDocumentShell(),
            ScResId(STR_FILL_SERIES_PROGRESS), nProgCount, true );

    rDoc.Fill( aSourceArea.aStart.Col(), aSourceArea.aStart.Row(),
            aSourceArea.aEnd.Col(), aSourceArea.aEnd.Row(), &aProgress,
            aMark, nCount, eDir, eCmd, eDateCmd, fStep, fMax );

    AdjustRowHeight(aDestArea, true, bApi);

    if ( bRecord )      // only now is Draw-Undo available
    {
        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoAutoFill>( rDocShell, aDestArea, aSourceArea, std::move(pUndoDoc), aMark,
                                eDir, eCmd, eDateCmd, MAXDOUBLE, fStep, fMax) );
    }

    rDocShell.PostPaintGridAll();
    aModificator.SetDocumentModified();

    rRange = aDestArea;         // return destination range (for marking)
    return true;
}

bool ScDocFunc::MergeCells( const ScCellMergeOption& rOption, bool bContents, bool bRecord, bool bApi, bool bEmptyMergedCells /*=false*/ )
{
    ScDocShellModificator aModificator( rDocShell );

    SCCOL nStartCol = rOption.mnStartCol;
    SCROW nStartRow = rOption.mnStartRow;
    SCCOL nEndCol = rOption.mnEndCol;
    SCROW nEndRow = rOption.mnEndRow;
    if ((nStartCol == nEndCol && nStartRow == nEndRow) || rOption.maTabs.empty())
    {
        // Nothing to do.  Bail out quickly
        return true;
    }

    ScDocument& rDoc = rDocShell.GetDocument();
    SCTAB nTab1 = *rOption.maTabs.begin(), nTab2 = *rOption.maTabs.rbegin();

    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;

    if (!CheckSheetViewProtection(sc::Operation::MergeCells))
        return false;

    for (const auto& rTab : rOption.maTabs)
    {

        ScEditableTester aTester = ScEditableTester::CreateAndTestBlock(rDoc, rTab, nStartCol, nStartRow, nEndCol, nEndRow);
        if (!aTester.IsEditable())
        {
            if (!bApi)
                rDocShell.ErrorMessage(aTester.GetMessageId());
            return false;
        }

        if ( rDoc.HasAttrib( nStartCol, nStartRow, rTab, nEndCol, nEndRow, rTab,
                                HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
        {
            // "Merge of already merged cells not possible"
            if (!bApi)
                rDocShell.ErrorMessage(STR_MSSG_MERGECELLS_0);
            return false;
        }
    }

    ScDocumentUniquePtr pUndoDoc;
    bool bNeedContentsUndo = false;
    for (const SCTAB nTab : rOption.maTabs)
    {
        bool bIsBlockEmpty = ( nStartRow == nEndRow )
                             ? rDoc.IsEmptyData( nStartCol+1,nStartRow, nEndCol,nEndRow, nTab )
                             : rDoc.IsEmptyData( nStartCol,nStartRow+1, nStartCol,nEndRow, nTab ) &&
                               rDoc.IsEmptyData( nStartCol+1,nStartRow, nEndCol,nEndRow, nTab );
        bool bNeedContents = bContents && !bIsBlockEmpty;
        bool bNeedEmpty = bEmptyMergedCells && !bIsBlockEmpty && !bNeedContents; // if DoMergeContents then cells are emptied

        if (bRecord)
        {
            // test if the range contains other notes which also implies that we need an undo document
            bool bHasNotes = rDoc.HasNote(nTab, nStartCol, nStartRow, nEndCol, nEndRow);
            if (!pUndoDoc)
            {
                pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
                pUndoDoc->InitUndo(rDoc, nTab1, nTab2);
            }
            // note captions are collected by drawing undo
            rDoc.CopyToDocument( nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab,
                                  InsertDeleteFlags::ALL|InsertDeleteFlags::NOCAPTIONS, false, *pUndoDoc );
            if( bHasNotes )
                rDoc.BeginDrawUndo();
        }

        if (bNeedContents)
            rDoc.DoMergeContents( nStartCol,nStartRow, nEndCol,nEndRow,  nTab );
        else if ( bNeedEmpty )
            rDoc.DoEmptyBlock( nStartCol,nStartRow, nEndCol,nEndRow, nTab );
        rDoc.DoMerge( nStartCol,nStartRow, nEndCol,nEndRow, nTab );

        if (rOption.mbCenter)
        {
            rDoc.ApplyAttr( nStartCol, nStartRow, nTab, SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY ) );
            rDoc.ApplyAttr( nStartCol, nStartRow, nTab, SvxVerJustifyItem( SvxCellVerJustify::Center, ATTR_VER_JUSTIFY ) );
        }

        if ( !AdjustRowHeight( ScRange( 0,nStartRow,nTab, rDoc.MaxCol(),nEndRow,nTab ), true, bApi ) )
            rDocShell.PostPaint( nStartCol, nStartRow, nTab,
                                 nEndCol, nEndRow, nTab, PaintPartFlags::Grid );
        if (bNeedContents || rOption.mbCenter)
        {
            ScRange aRange(nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab);
            rDoc.SetDirty(aRange, true);
        }

        bool bDone = ScDetectiveFunc(rDoc, nTab).DeleteAll( ScDetectiveDelete::Circles );
        if(bDone)
           DetectiveMarkInvalid(nTab);

        bNeedContentsUndo |= bNeedContents;
    }

    if (pUndoDoc)
    {
        std::unique_ptr<SdrUndoGroup> pDrawUndo = rDoc.GetDrawLayer() ? rDoc.GetDrawLayer()->GetCalcUndo() : nullptr;
        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoMerge>(rDocShell, rOption, bNeedContentsUndo, std::move(pUndoDoc), std::move(pDrawUndo)) );
    }

    aModificator.SetDocumentModified();

    SfxBindings* pBindings = rDocShell.GetViewBindings();
    if (pBindings)
    {
        pBindings->Invalidate( FID_MERGE_ON );
        pBindings->Invalidate( FID_MERGE_OFF );
        pBindings->Invalidate( FID_MERGE_TOGGLE );
    }

    return true;
}

bool ScDocFunc::UnmergeCells( const ScRange& rRange, bool bRecord, ScUndoRemoveMerge* pUndoRemoveMerge )
{
    ScCellMergeOption aOption(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
    SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = rRange.aEnd.Tab();
    for (SCTAB i = nTab1; i <= nTab2; ++i)
        aOption.maTabs.insert(i);

    return UnmergeCells(aOption, bRecord, pUndoRemoveMerge);
}

bool ScDocFunc::UnmergeCells( const ScCellMergeOption& rOption, bool bRecord, ScUndoRemoveMerge* pUndoRemoveMerge )
{
    if (rOption.maTabs.empty())
        // Nothing to unmerge.
        return true;

    ScDocShellModificator aModificator( rDocShell );
    ScDocument& rDoc = rDocShell.GetDocument();

    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;

    ScDocument* pUndoDoc = (pUndoRemoveMerge ? pUndoRemoveMerge->GetUndoDoc() : nullptr);
    assert( pUndoDoc || !pUndoRemoveMerge );
    for (const SCTAB nTab : rOption.maTabs)
    {
        ScRange aRange = rOption.getSingleRange(nTab);
        if ( !rDoc.HasAttrib(aRange, HasAttrFlags::Merged) )
            continue;

        ScRange aExtended = aRange;
        rDoc.ExtendMerge(aExtended);
        ScRange aRefresh = aExtended;
        rDoc.ExtendOverlapped(aRefresh);

        if (bRecord)
        {
            if (!pUndoDoc)
            {
                pUndoDoc = new ScDocument( SCDOCMODE_UNDO );
                pUndoDoc->InitUndo(rDoc, *rOption.maTabs.begin(), *rOption.maTabs.rbegin());
            }
            rDoc.CopyToDocument(aExtended, InsertDeleteFlags::ATTRIB, false, *pUndoDoc);
        }

        const SfxPoolItem& rDefAttr = rDoc.GetPool()->GetUserOrPoolDefaultItem( ATTR_MERGE );
        ScPatternAttr aPattern(rDoc.getCellAttributeHelper());
        aPattern.ItemSetPut(rDefAttr);
        rDoc.ApplyPatternAreaTab( aRange.aStart.Col(), aRange.aStart.Row(),
                                   aRange.aEnd.Col(), aRange.aEnd.Row(), nTab,
                                   aPattern );

        rDoc.RemoveFlagsTab( aExtended.aStart.Col(), aExtended.aStart.Row(),
                              aExtended.aEnd.Col(), aExtended.aEnd.Row(), nTab,
                              ScMF::Hor | ScMF::Ver );

        rDoc.ExtendMerge( aRefresh, true );

        if ( !AdjustRowHeight( aExtended, true, true ) )
            rDocShell.PostPaint( aExtended, PaintPartFlags::Grid );

        bool bDone = ScDetectiveFunc(rDoc, nTab).DeleteAll( ScDetectiveDelete::Circles );
        if(bDone)
           DetectiveMarkInvalid(nTab);
    }

    if (bRecord)
    {
        if (pUndoRemoveMerge)
        {
            // If pUndoRemoveMerge was passed, the caller is responsible for
            // adding it to Undo. Just add the current option.
            pUndoRemoveMerge->AddCellMergeOption( rOption);
        }
        else
        {
            rDocShell.GetUndoManager()->AddUndoAction(
                    std::make_unique<ScUndoRemoveMerge>( rDocShell, rOption, ScDocumentUniquePtr(pUndoDoc) ) );
        }
    }
    aModificator.SetDocumentModified();

    return true;
}

void ScDocFunc::ModifyRangeNames( const ScRangeName& rNewRanges, SCTAB nTab )
{
    SetNewRangeNames( std::unique_ptr<ScRangeName>(new ScRangeName(rNewRanges)), true, nTab );
}

void ScDocFunc::SetNewRangeNames( std::unique_ptr<ScRangeName> pNewRanges, bool bModifyDoc, SCTAB nTab )     // takes ownership of pNewRanges
{
    ScDocShellModificator aModificator( rDocShell );

    OSL_ENSURE( pNewRanges, "pNewRanges is 0" );
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bUndo(rDoc.IsUndoEnabled());

    if (bUndo)
    {
        ScRangeName* pOld;
        if (nTab >=0)
        {
            pOld = rDoc.GetRangeName(nTab);
        }
        else
        {
            pOld = rDoc.GetRangeName();
        }
        std::unique_ptr<ScRangeName> pUndoRanges(new ScRangeName(*pOld));
        std::unique_ptr<ScRangeName> pRedoRanges(new ScRangeName(*pNewRanges));
        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoRangeNames>( rDocShell, std::move(pUndoRanges), std::move(pRedoRanges), nTab ) );
    }

    // #i55926# While loading XML, formula cells only have a single string token,
    // so CompileNameFormula would never find any name (index) tokens, and would
    // unnecessarily loop through all cells.
    bool bCompile = ( !rDoc.IsImportingXML() && rDoc.GetNamedRangesLockCount() == 0 );

    if ( bCompile )
        rDoc.PreprocessRangeNameUpdate();
    if (nTab >= 0)
        rDoc.SetRangeName( nTab, std::move(pNewRanges) ); // takes ownership
    else
        rDoc.SetRangeName( std::move(pNewRanges) );       // takes ownership
    if ( bCompile )
        rDoc.CompileHybridFormula();

    if (bModifyDoc)
    {
        aModificator.SetDocumentModified();
        SfxGetpApp()->Broadcast( SfxHint(SfxHintId::ScAreasChanged) );
    }
}

void ScDocFunc::ModifyAllRangeNames(const std::map<OUString, ScRangeName>& rRangeMap)
{
    ScDocShellModificator aModificator(rDocShell);
    ScDocument& rDoc = rDocShell.GetDocument();

    if (rDoc.IsUndoEnabled())
    {
        std::map<OUString, ScRangeName*> aOldRangeMap;
        rDoc.GetRangeNameMap(aOldRangeMap);
        rDocShell.GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoAllRangeNames>(rDocShell, aOldRangeMap, rRangeMap));
    }

    rDoc.PreprocessAllRangeNamesUpdate(rRangeMap);
    rDoc.SetAllRangeNames(rRangeMap);
    rDoc.CompileHybridFormula();

    aModificator.SetDocumentModified();
    SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScAreasChanged));
}

void ScDocFunc::CreateOneName( ScRangeName& rList,
                                SCCOL nPosX, SCROW nPosY, SCTAB nTab,
                                SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2,
                                bool& rCancel, bool bApi )
{
    if (rCancel)
        return;

    ScDocument& rDoc = rDocShell.GetDocument();
    if (rDoc.HasValueData( nPosX, nPosY, nTab ))
        return;

    OUString aName = rDoc.GetString(nPosX, nPosY, nTab);
    ScRangeData::MakeValidName(rDoc, aName);
    if (aName.isEmpty())
        return;

    OUString aContent( ScRange( nX1, nY1, nTab, nX2, nY2, nTab ).Format(
                rDoc, ScRefFlags::RANGE_ABS_3D, ScAddress::Details( rDoc.GetAddressConvention(), nPosY, nPosX)));

    bool bInsert = false;
    ScRangeData* pOld = rList.findByUpperName(ScGlobal::getCharClass().uppercase(aName));
    if (pOld)
    {
        OUString aOldStr = pOld->GetSymbol();
        if (aOldStr != aContent)
        {
            if (bApi)
                bInsert = true;     // don't check via API
            else
            {
                OUString aTemplate = ScResId( STR_CREATENAME_REPLACE );
                OUString aMessage = o3tl::getToken(aTemplate, 0, '#' ) + aName + o3tl::getToken(aTemplate, 1, '#' );

                std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
                                                               VclMessageType::Question, VclButtonsType::YesNo,
                                                               aMessage));
                xQueryBox->add_button(GetStandardText(StandardButtonType::Cancel), RET_CANCEL);
                xQueryBox->set_default_response(RET_YES);

                short nResult = xQueryBox->run();
                if ( nResult == RET_YES )
                {
                    rList.erase(*pOld);
                    bInsert = true;
                }
                else if ( nResult == RET_CANCEL )
                    rCancel = true;
            }
        }
    }
    else
        bInsert = true;

    if (bInsert)
    {
        ScRangeData* pData = new ScRangeData( rDoc, aName, aContent,
                ScAddress( nPosX, nPosY, nTab));
        if (!rList.insert(pData))
        {
            OSL_FAIL("nanu?");
        }
    }
}

bool ScDocFunc::CreateNames( const ScRange& rRange, CreateNameFlags nFlags, bool bApi, SCTAB aTab )
{
    if (nFlags == CreateNameFlags::NONE)
        return false;       // was nothing

    ScDocShellModificator aModificator( rDocShell );

    bool bDone = false;
    SCCOL nStartCol = rRange.aStart.Col();
    SCROW nStartRow = rRange.aStart.Row();
    SCCOL nEndCol = rRange.aEnd.Col();
    SCROW nEndRow = rRange.aEnd.Row();
    SCTAB nTab = rRange.aStart.Tab();
    OSL_ENSURE(rRange.aEnd.Tab() == nTab, "CreateNames: multiple tables not possible");

    bool bValid = true;
    if ( nFlags & ( CreateNameFlags::Top | CreateNameFlags::Bottom ) )
        if ( nStartRow == nEndRow )
            bValid = false;
    if ( nFlags & ( CreateNameFlags::Left | CreateNameFlags::Right ) )
        if ( nStartCol == nEndCol )
            bValid = false;

    if (bValid)
    {
        ScDocument& rDoc = rDocShell.GetDocument();
        ScRangeName* pNames;
        if (aTab >=0)
            pNames = rDoc.GetRangeName(nTab);
        else
            pNames = rDoc.GetRangeName();

        if (!pNames)
            return false;   // shouldn't happen
        ScRangeName aNewRanges( *pNames );

        bool bTop   ( nFlags & CreateNameFlags::Top );
        bool bLeft  ( nFlags & CreateNameFlags::Left );
        bool bBottom( nFlags & CreateNameFlags::Bottom );
        bool bRight ( nFlags & CreateNameFlags::Right );

        SCCOL nContX1 = nStartCol;
        SCROW nContY1 = nStartRow;
        SCCOL nContX2 = nEndCol;
        SCROW nContY2 = nEndRow;

        if ( bTop )
            ++nContY1;
        if ( bLeft )
            ++nContX1;
        if ( bBottom )
            --nContY2;
        if ( bRight )
            --nContX2;

        bool bCancel = false;
        SCCOL i;
        SCROW j;

        if ( bTop )
            for (i=nContX1; i<=nContX2; i++)
                CreateOneName( aNewRanges, i,nStartRow,nTab, i,nContY1,i,nContY2, bCancel, bApi );
        if ( bLeft )
            for (j=nContY1; j<=nContY2; j++)
                CreateOneName( aNewRanges, nStartCol,j,nTab, nContX1,j,nContX2,j, bCancel, bApi );
        if ( bBottom )
            for (i=nContX1; i<=nContX2; i++)
                CreateOneName( aNewRanges, i,nEndRow,nTab, i,nContY1,i,nContY2, bCancel, bApi );
        if ( bRight )
            for (j=nContY1; j<=nContY2; j++)
                CreateOneName( aNewRanges, nEndCol,j,nTab, nContX1,j,nContX2,j, bCancel, bApi );

        if ( bTop && bLeft )
            CreateOneName( aNewRanges, nStartCol,nStartRow,nTab, nContX1,nContY1,nContX2,nContY2, bCancel, bApi );
        if ( bTop && bRight )
            CreateOneName( aNewRanges, nEndCol,nStartRow,nTab, nContX1,nContY1,nContX2,nContY2, bCancel, bApi );
        if ( bBottom && bLeft )
            CreateOneName( aNewRanges, nStartCol,nEndRow,nTab, nContX1,nContY1,nContX2,nContY2, bCancel, bApi );
        if ( bBottom && bRight )
            CreateOneName( aNewRanges, nEndCol,nEndRow,nTab, nContX1,nContY1,nContX2,nContY2, bCancel, bApi );

        ModifyRangeNames( aNewRanges, aTab );
        bDone = true;

    }

    return bDone;
}

bool ScDocFunc::InsertNameList( const ScAddress& rStartPos, bool bApi )
{
    ScDocShellModificator aModificator( rDocShell );

    bool bDone = false;
    ScDocument& rDoc = rDocShell.GetDocument();
    const bool bRecord = rDoc.IsUndoEnabled();
    SCTAB nTab = rStartPos.Tab();

    //local names have higher priority than global names
    ScRangeName* pLocalList = rDoc.GetRangeName(nTab);
    sal_uInt16 nValidCount = 0;
    for (const auto& rEntry : *pLocalList)
    {
        const ScRangeData& r = *rEntry.second;
        if (!r.HasType(ScRangeData::Type::Database))
            ++nValidCount;
    }
    ScRangeName* pList = rDoc.GetRangeName();
    for (const auto& rEntry : *pList)
    {
        const ScRangeData& r = *rEntry.second;
        if (!r.HasType(ScRangeData::Type::Database) && !pLocalList->findByUpperName(r.GetUpperName()))
            ++nValidCount;
    }

    if (nValidCount)
    {
        SCCOL nStartCol = rStartPos.Col();
        SCROW nStartRow = rStartPos.Row();
        SCCOL nEndCol = nStartCol + 1;
        SCROW nEndRow = nStartRow + static_cast<SCROW>(nValidCount) - 1;

        if (!CheckSheetViewProtection(sc::Operation::InsertNameList))
            return false;

        ScEditableTester aTester = ScEditableTester::CreateAndTestBlock(rDoc, nTab, nStartCol, nStartRow, nEndCol, nEndRow);
        if (aTester.IsEditable())
        {
            ScDocumentUniquePtr pUndoDoc;

            if (bRecord)
            {
                pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
                pUndoDoc->InitUndo( rDoc, nTab, nTab );
                rDoc.CopyToDocument(nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab,
                                    InsertDeleteFlags::ALL, false, *pUndoDoc);

                rDoc.BeginDrawUndo();      // because of adjusting heights
            }

            std::unique_ptr<ScRangeData*[]> ppSortArray(new ScRangeData* [ nValidCount ]);
            sal_uInt16 j = 0;
            for (const auto& rEntry : *pLocalList)
            {
                ScRangeData& r = *rEntry.second;
                if (!r.HasType(ScRangeData::Type::Database))
                    ppSortArray[j++] = &r;
            }
            for (const auto& [rName, rxData] : *pList)
            {
                ScRangeData& r = *rxData;
                if (!r.HasType(ScRangeData::Type::Database) && !pLocalList->findByUpperName(rName))
                    ppSortArray[j++] = &r;
            }
            qsort( static_cast<void*>(ppSortArray.get()), nValidCount, sizeof(ScRangeData*),
                &ScRangeData_QsortNameCompare );
            OUString aName;
            OUStringBuffer aContent;
            OUString aFormula;
            SCROW nOutRow = nStartRow;
            for (j=0; j<nValidCount; j++)
            {
                ScRangeData* pData = ppSortArray[j];
                pData->GetName(aName);
                // adjust relative references to the left column in Excel-compliant way:
                pData->UpdateSymbol(aContent, ScAddress( nStartCol, nOutRow, nTab ));
                aFormula = "=" + aContent;
                ScSetStringParam aParam;
                aParam.setTextInput();
                rDoc.SetString(ScAddress(nStartCol,nOutRow,nTab), aName, &aParam);
                rDoc.SetString(ScAddress(nEndCol,nOutRow,nTab), aFormula, &aParam);
                ++nOutRow;
            }

            ppSortArray.reset();

            if (bRecord)
            {
                ScDocumentUniquePtr pRedoDoc(new ScDocument( SCDOCMODE_UNDO ));
                pRedoDoc->InitUndo( rDoc, nTab, nTab );
                rDoc.CopyToDocument(nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab,
                                    InsertDeleteFlags::ALL, false, *pRedoDoc);

                rDocShell.GetUndoManager()->AddUndoAction(
                    std::make_unique<ScUndoListNames>( rDocShell,
                                ScRange( nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab ),
                                std::move(pUndoDoc), std::move(pRedoDoc) ) );
            }

            if (!AdjustRowHeight(ScRange(0,nStartRow,nTab,rDoc.MaxCol(),nEndRow,nTab), true, true))
                rDocShell.PostPaint( nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab, PaintPartFlags::Grid );

            aModificator.SetDocumentModified();
            bDone = true;
        }
        else if (!bApi)
            rDocShell.ErrorMessage(aTester.GetMessageId());
    }
    return bDone;
}

void ScDocFunc::ResizeMatrix( const ScRange& rOldRange, const ScAddress& rNewEnd )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    SCCOL nStartCol = rOldRange.aStart.Col();
    SCROW nStartRow = rOldRange.aStart.Row();
    SCTAB nTab = rOldRange.aStart.Tab();

    OUString aFormula = rDoc.GetFormula( nStartCol, nStartRow, nTab );
    if ( !(aFormula.startsWith("{") && aFormula.endsWith("}")) )
        return;

    OUString aUndo = ScResId( STR_UNDO_RESIZEMATRIX );
    bool bUndo(rDoc.IsUndoEnabled());
    if (bUndo)
    {
        ViewShellId nViewShellId(1);
        if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell())
            nViewShellId = pViewSh->GetViewShellId();
        rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, nViewShellId );
    }

    aFormula = aFormula.copy(1, aFormula.getLength()-2);

    ScMarkData aMark(rDoc.GetSheetLimits());
    aMark.SetMarkArea( rOldRange );
    aMark.SelectTable( nTab, true );
    ScRange aNewRange( rOldRange.aStart, rNewEnd );

    if ( DeleteContents( aMark, InsertDeleteFlags::CONTENTS, true, false/*bApi*/ ) )
    {
        // Formula string was obtained in document grammar.
        if (!EnterMatrix( aNewRange, &aMark, nullptr, aFormula, false/*bApi*/, false, OUString(), rDoc.GetGrammar() ))
        {
            // try to restore the previous state
            EnterMatrix( rOldRange, &aMark, nullptr, aFormula, false/*bApi*/, false, OUString(), rDoc.GetGrammar() );
        }
    }

    if (bUndo)
        rDocShell.GetUndoManager()->LeaveListAction();
}

void ScDocFunc::InsertAreaLink( const OUString& rFile, const OUString& rFilter,
                                const OUString& rOptions, const OUString& rSource,
                                const ScRange& rDestRange, sal_Int32 nRefreshDelaySeconds,
                                bool bFitBlock, bool bApi )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bUndo (rDoc.IsUndoEnabled());

    sfx2::LinkManager* pLinkManager = rDoc.GetLinkManager();

    //  #i52120# if other area links exist at the same start position,
    //  remove them first (file format specifies only one link definition
    //  for a cell)

    sal_uInt16 nLinkCount = pLinkManager->GetLinks().size();
    sal_uInt16 nRemoved = 0;
    sal_uInt16 nLinkPos = 0;
    while (nLinkPos<nLinkCount)
    {
        ::sfx2::SvBaseLink* pBase = pLinkManager->GetLinks()[nLinkPos].get();
        ScAreaLink* pLink = dynamic_cast<ScAreaLink*>(pBase);
        if (pLink && pLink->GetDestArea().aStart == rDestRange.aStart)
        {
            if ( bUndo )
            {
                if ( !nRemoved )
                {
                    // group all remove and the insert action
                    OUString aUndo = ScResId( STR_UNDO_INSERTAREALINK );
                    ViewShellId nViewShellId(-1);
                    if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell())
                        nViewShellId = pViewSh->GetViewShellId();
                    rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, nViewShellId );
                }

                ScAreaLink* pOldArea = static_cast<ScAreaLink*>(pBase);
                rDocShell.GetUndoManager()->AddUndoAction(
                    std::make_unique<ScUndoRemoveAreaLink>( rDocShell,
                        pOldArea->GetFile(), pOldArea->GetFilter(), pOldArea->GetOptions(),
                        pOldArea->GetSource(), pOldArea->GetDestArea(), pOldArea->GetRefreshDelaySeconds() ) );
            }
            pLinkManager->Remove( pBase );
            nLinkCount = pLinkManager->GetLinks().size();
            ++nRemoved;
        }
        else
            ++nLinkPos;
    }

    OUString aFilterName = rFilter;
    OUString aNewOptions = rOptions;
    if (aFilterName.isEmpty())
        ScDocumentLoader::GetFilterName( rFile, aFilterName, aNewOptions, true, !bApi );

    //  remove application prefix from filter name here, so the filter options
    //  aren't reset when the filter name is changed in ScAreaLink::DataChanged
    ScDocumentLoader::RemoveAppPrefix( aFilterName );

    ScAreaLink* pLink = new ScAreaLink( rDocShell, rFile, aFilterName,
                                        aNewOptions, rSource, rDestRange, nRefreshDelaySeconds );
    OUString aTmp = aFilterName;
    pLinkManager->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, rFile, &aTmp, &rSource );

    //  Undo for an empty link

    if (bUndo)
    {
        rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoInsertAreaLink>( rDocShell,
                                                    rFile, aFilterName, aNewOptions,
                                                    rSource, rDestRange, nRefreshDelaySeconds ) );
        if ( nRemoved )
            rDocShell.GetUndoManager()->LeaveListAction();  // undo for link update is still separate
    }

    //  Update has its own undo
    if (rDoc.IsExecuteLinkEnabled())
    {
        pLink->SetDoInsert(bFitBlock);  // if applicable, don't insert anything on first update
        pLink->Update();                // no SetInCreate -> carry out update
    }
    pLink->SetDoInsert(true);       // Default = true

    SfxBindings* pBindings = rDocShell.GetViewBindings();
    if (pBindings)
        pBindings->Invalidate( SID_LINKS );

    SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) );     // Navigator
}

void ScDocFunc::ReplaceConditionalFormat( sal_uLong nOldFormat, std::unique_ptr<ScConditionalFormat> pFormat, SCTAB nTab, const ScRangeList& rRanges )
{
    ScDocShellModificator aModificator(rDocShell);
    ScDocument& rDoc = rDocShell.GetDocument();
    if(rDoc.IsTabProtected(nTab))
        return;

    ScRange aCombinedRange = rRanges.Combine();
    std::unique_ptr<ScUndoConditionalFormat> pUndo;
    if (rDoc.IsUndoEnabled())
        pUndo.reset(new ScUndoConditionalFormat(rDocShell, nTab));

    std::unique_ptr<ScRange> pRepaintRange;
    if(nOldFormat)
    {
        ScConditionalFormat* pOldFormat = rDoc.GetCondFormList(nTab)->GetFormat(nOldFormat);
        if(pOldFormat)
        {
            pRepaintRange.reset(new ScRange( pOldFormat->GetRange().Combine() ));
            rDoc.RemoveCondFormatData(pOldFormat->GetRange(), nTab, pOldFormat->GetKey());
        }

        rDoc.DeleteConditionalFormat(nOldFormat, nTab);
        rDoc.SetStreamValid(nTab, false);
    }
    if(pFormat)
    {
        if(pRepaintRange)
            pRepaintRange->ExtendTo(aCombinedRange);
        else
            pRepaintRange.reset(new ScRange(aCombinedRange));

        sal_uInt32 nIndex = rDoc.AddCondFormat(std::move(pFormat), nTab);

        rDoc.AddCondFormatData(rRanges, nTab, nIndex);
        rDoc.SetStreamValid(nTab, false);
    }

    if (pUndo)
    {
        pUndo->setRedoData();
        rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo));
    }

    if(pRepaintRange)
        rDocShell.PostPaint(*pRepaintRange, PaintPartFlags::Grid, SC_PF_TESTMERGE);

    aModificator.SetDocumentModified();
    SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScAreasChanged));
}

void ScDocFunc::SetConditionalFormatList( ScConditionalFormatList* pList, SCTAB nTab )
{
    ScDocShellModificator aModificator(rDocShell);
    ScDocument& rDoc = rDocShell.GetDocument();
    if(rDoc.IsTabProtected(nTab))
        return;

    bool bUndo = rDoc.IsUndoEnabled();
    ScDocumentUniquePtr pUndoDoc;
    if (bUndo)
    {
        pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO));
        pUndoDoc->InitUndo( rDoc, nTab, nTab );

        ScConditionalFormatList* pOld = rDoc.GetCondFormList(nTab);

        if (pOld)
            pUndoDoc->SetCondFormList(pOld->Clone(*pUndoDoc), nTab);
        else
            pUndoDoc->SetCondFormList(nullptr, nTab);

    }

    // first remove all old entries
    ScConditionalFormatList* pOldList = rDoc.GetCondFormList(nTab);
    pOldList->RemoveFromDocument(rDoc);

    // then set new entries
    pList->AddToDocument(rDoc);

    rDoc.SetCondFormList(pList, nTab);
    rDocShell.PostPaintGridAll();

    if(bUndo)
    {
        ScDocumentUniquePtr pRedoDoc(new ScDocument(SCDOCMODE_UNDO));
        pRedoDoc->InitUndo( rDoc, nTab, nTab );
        pRedoDoc->SetCondFormList(pList->Clone(*pRedoDoc), nTab);

        rDocShell.GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoConditionalFormatList>(rDocShell, std::move(pUndoDoc), std::move(pRedoDoc), nTab));
    }

    rDoc.SetStreamValid(nTab, false);
    aModificator.SetDocumentModified();
    SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScAreasChanged));
}

void ScDocFunc::ConvertFormulaToValue( const ScRange& rRange, bool bInteraction )
{
    ScDocShellModificator aModificator(rDocShell);
    ScDocument& rDoc = rDocShell.GetDocument();
    bool bRecord = true;
    if (!rDoc.IsUndoEnabled())
        bRecord = false;

    if (!CheckSheetViewProtection(sc::Operation::ConvertFormulaToValue))
        return;

    ScEditableTester aTester = ScEditableTester::CreateAndTestRange(rDoc, rRange, sc::EditAction::Unknown);
    if (!aTester.IsEditable())
    {
        if (bInteraction)
            rDocShell.ErrorMessage(aTester.GetMessageId());
        return;
    }

    sc::TableValues aUndoVals(rRange);
    sc::TableValues* pUndoVals = bRecord ? &aUndoVals : nullptr;

    rDoc.ConvertFormulaToValue(rRange, pUndoVals);

    if (bRecord && pUndoVals)
    {
        rDocShell.GetUndoManager()->AddUndoAction(
            std::make_unique<sc::UndoFormulaToValue>(rDocShell, *pUndoVals));
    }

    rDocShell.PostPaint(rRange, PaintPartFlags::Grid);
    rDocShell.PostDataChanged();
    rDoc.BroadcastCells(rRange, SfxHintId::ScDataChanged);
    aModificator.SetDocumentModified();
}

void ScDocFunc::EnterListAction(TranslateId pNameResId)
{
    OUString aUndo(ScResId(pNameResId));
    ViewShellId nViewShellId(-1);
    if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell())
        nViewShellId = pViewSh->GetViewShellId();
    rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, nViewShellId );
}

void ScDocFunc::EndListAction()
{
    rDocShell.GetUndoManager()->LeaveListAction();
}

bool ScDocFunc::InsertSparklines(ScRange const& rDataRange, ScRange const& rSparklineRange,
                                 const std::shared_ptr<sc::SparklineGroup>& pSparklineGroup)
{
    if (!CheckSheetViewProtection(sc::Operation::SparklineInsert))
        return false;

    std::vector<sc::SparklineData> aSparklineDataVector;

    if (rSparklineRange.aStart.Col() == rSparklineRange.aEnd.Col())
    {
        sal_Int32 nOutputRowSize = rSparklineRange.aEnd.Row() - rSparklineRange.aStart.Row();

        auto eInputOrientation = sc::calculateOrientation(nOutputRowSize, rDataRange);

        if (eInputOrientation == sc::RangeOrientation::Unknown)
            return false;

        sal_Int32 nIndex = 0;

        for (ScAddress aAddress = rSparklineRange.aStart; aAddress.Row() <= rSparklineRange.aEnd.Row();
             aAddress.IncRow())
        {
            ScRange aInputRangeSlice = rDataRange;
            if (eInputOrientation == sc::RangeOrientation::Row)
            {
                aInputRangeSlice.aStart.SetRow(rDataRange.aStart.Row() + nIndex);
                aInputRangeSlice.aEnd.SetRow(rDataRange.aStart.Row() + nIndex);
            }
            else
            {
                aInputRangeSlice.aStart.SetCol(rDataRange.aStart.Col() + nIndex);
                aInputRangeSlice.aEnd.SetCol(rDataRange.aStart.Col() + nIndex);
            }

            aSparklineDataVector.emplace_back(aAddress, aInputRangeSlice);

            nIndex++;
        }
    }
    else if (rSparklineRange.aStart.Row() == rSparklineRange.aEnd.Row())
    {
        sal_Int32 nOutputColSize = rSparklineRange.aEnd.Col() - rSparklineRange.aStart.Col();

        auto eInputOrientation = sc::calculateOrientation(nOutputColSize, rDataRange);

        if (eInputOrientation == sc::RangeOrientation::Unknown)
            return false;

        sal_Int32 nIndex = 0;

        for (ScAddress aAddress = rSparklineRange.aStart; aAddress.Col() <= rSparklineRange.aEnd.Col();
             aAddress.IncCol())
        {
            ScRange aInputRangeSlice = rDataRange;
            if (eInputOrientation == sc::RangeOrientation::Row)
            {
                aInputRangeSlice.aStart.SetRow(rDataRange.aStart.Row() + nIndex);
                aInputRangeSlice.aEnd.SetRow(rDataRange.aStart.Row() + nIndex);
            }
            else
            {
                aInputRangeSlice.aStart.SetCol(rDataRange.aStart.Col() + nIndex);
                aInputRangeSlice.aEnd.SetCol(rDataRange.aStart.Col() + nIndex);
            }

            aSparklineDataVector.emplace_back(aAddress, aInputRangeSlice);

            nIndex++;
        }
    }

    if (aSparklineDataVector.empty())
        return false;

    auto pUndoInsertSparkline = std::make_unique<sc::UndoInsertSparkline>(rDocShell, aSparklineDataVector, pSparklineGroup);
    // insert the sparkline by "redoing"
    pUndoInsertSparkline->Redo();
    rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndoInsertSparkline));

    ScDocument& rDoc = rDocShell.GetDocument();
    if (rSparklineRange.aStart == rSparklineRange.aEnd
        && rDoc.HasAttrib(rSparklineRange, HasAttrFlags::Merged))
    {
        ScRange aExtendMergeRange(rSparklineRange);
        rDoc.ExtendMerge(aExtendMergeRange);
        rDocShell.PostPaint(aExtendMergeRange, PaintPartFlags::Grid);
    }

    return true;
}

bool ScDocFunc::DeleteSparkline(ScAddress const& rAddress)
{
    auto& rDocument = rDocShell.GetDocument();

    if (!rDocument.HasSparkline(rAddress))
        return false;

    if (!CheckSheetViewProtection(sc::Operation::SparklineDelete))
        return false;


    auto pUndoDeleteSparkline = std::make_unique<sc::UndoDeleteSparkline>(rDocShell, rAddress);
    // delete sparkline by "redoing"
    pUndoDeleteSparkline->Redo();
    rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndoDeleteSparkline));

    return true;
}

bool ScDocFunc::DeleteSparklineGroup(std::shared_ptr<sc::SparklineGroup> const& pSparklineGroup, SCTAB nTab)
{
    if (!pSparklineGroup)
        return false;

    auto& rDocument = rDocShell.GetDocument();

    if (!rDocument.HasTable(nTab))
        return false;

    if (!CheckSheetViewProtection(sc::Operation::SparklineGroupDelete))
        return false;

    auto pUndo = std::make_unique<sc::UndoDeleteSparklineGroup>(rDocShell, pSparklineGroup, nTab);
    // delete sparkline group  by "redoing"
    pUndo->Redo();
    rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo));
    return true;
}

bool ScDocFunc::ChangeSparklineGroupAttributes(std::shared_ptr<sc::SparklineGroup> const& pExistingSparklineGroup,
                                               sc::SparklineAttributes const& rNewAttributes)
{
    if (!CheckSheetViewProtection(sc::Operation::SparklineGroupChange))
        return false;


    auto pUndo = std::make_unique<sc::UndoEditSparklneGroup>(rDocShell, pExistingSparklineGroup, rNewAttributes);
    // change sparkline group attributes by "redoing"
    pUndo->Redo();
    rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo));
    return true;
}

bool ScDocFunc::GroupSparklines(ScRange const& rRange, std::shared_ptr<sc::SparklineGroup> const& rpGroup)
{
    if (!CheckSheetViewProtection(sc::Operation::SparklineGroup))
        return false;

    auto pUndo = std::make_unique<sc::UndoGroupSparklines>(rDocShell, rRange, rpGroup);
    // group sparklines by "redoing"
    pUndo->Redo();
    rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo));
    return true;
}

bool ScDocFunc::UngroupSparklines(ScRange const& rRange)
{
    if (!CheckSheetViewProtection(sc::Operation::SparklineUngroup))
        return false;

    auto pUndo = std::make_unique<sc::UndoUngroupSparklines>(rDocShell, rRange);
    // ungroup sparklines by "redoing"
    pUndo->Redo();
    rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo));
    return true;
}

bool ScDocFunc::ChangeSparkline(std::shared_ptr<sc::Sparkline> const& rpSparkline, SCTAB nTab, ScRangeList const& rDataRange)
{
    if (!CheckSheetViewProtection(sc::Operation::SparklineChange))
        return false;

    auto pUndo = std::make_unique<sc::UndoEditSparkline>(rDocShell, rpSparkline, nTab, rDataRange);
    // change sparkline by "redoing"
    pUndo->Redo();
    rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo));
    return true;
}

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