/* -*- 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 <config_features.h>
 
#include <memory>
#include <basic/basmgr.hxx>
#include <set>
#include <vector>
#include <sfx2/viewfrm.hxx>
 
using namespace com::sun::star;
using ::std::vector;
 
#define AUTOFORMAT_WARN_SIZE 0x10ffffUL
 
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, vector<ScTokenRef>& rRefTokens, ScDocShell& rDocShell,
    bool bPred)
{
    ScDocument& rDoc = rDocShell.GetDocument();
    vector<ScTokenRef> aRefTokens;
    if (rSrcRanges.empty())
        return;
    ScRange const & rFrontRange = rSrcRanges.front();
    ScDetectiveFunc aDetFunc(rDoc, rFrontRange.aStart.Tab());
    for (size_t i = 0, n = rSrcRanges.size(); i < n; ++i)
    {
        ScRange const & r = rSrcRanges[i];
        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, vector<ScTokenRef>& rRefTokens)
{
    lcl_collectAllPredOrSuccRanges(rSrcRanges, rRefTokens, rDocShell, true);
}
 
void ScDocFunc::DetectiveCollectAllSuccs(const ScRangeList& rSrcRanges, 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;
 
    ScEditableTester aTester( 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;
 
    ScEditableTester aTester(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;
 
    ScEditableTester aTester( 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());
    ScEditableTester aTester( 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* pItem = rDoc.GetAttr(rPos, ATTR_VALIDDATA);
    const ScValidationData* pData = rDoc.GetValidationEntry(pItem->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 = SC_MOD()->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.GetItemSet().ClearItem( 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();
    ScEditableTester aTester( 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();
    ScEditableTester aTester( 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();
    // 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 )
        rDocShell.UpdatePaintExt( nExtFlags, aMultiRange );     // content before the change
 
    bool bChanged = false;
    rDoc.ApplySelectionPattern( rPattern, rMark, nullptr, &bChanged );
 
    if(bChanged)
    {
        if ( !bImportingXML )
            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;
 
    switch (eCmd)
    {
        case INS_INSCOLS_BEFORE:
            aTester = ScEditableTester(
                rDoc, sc::ColRowEditAction::InsertColumnsBefore, nMergeTestStartCol, nMergeTestEndCol, aMark);
            break;
        case INS_INSCOLS_AFTER:
            aTester = ScEditableTester(
                rDoc, sc::ColRowEditAction::InsertColumnsAfter, nMergeTestStartCol, nMergeTestEndCol, aMark);
            break;
        case INS_INSROWS_BEFORE:
            aTester = ScEditableTester(
                rDoc, sc::ColRowEditAction::InsertRowsBefore, nMergeTestStartRow, nMergeTestEndRow, aMark);
            break;
        case INS_INSROWS_AFTER:
            aTester = ScEditableTester(
                rDoc, sc::ColRowEditAction::InsertRowsAfter, nMergeTestStartRow, nMergeTestEndRow, aMark);
            break;
        default:
            aTester = ScEditableTester(
                rDoc, nMergeTestStartCol, nMergeTestStartRow, nEditTestEndCol, nEditTestEndRow, aMark);
    }
 
    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;
 
    switch (eCmd)
    {
        case DelCellCmd::Cols:
            aTester = ScEditableTester(
                rDoc, sc::ColRowEditAction::DeleteColumns, nUndoStartCol, nUndoEndCol, aMark);
            break;
        case DelCellCmd::Rows:
            aTester = ScEditableTester(
                rDoc, sc::ColRowEditAction::DeleteRows, nUndoStartRow, nUndoEndRow, aMark);
            break;
        default:
            aTester = ScEditableTester(
                rDoc, nUndoStartCol, nUndoStartRow, nEditTestEndX, nEditTestEndY, aMark);
    }
 
    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.GetItemSet().Put( 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;
    }
 
    //  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)
        {
            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, const OUString& 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());
    ScEditableTester aTester( 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());
    ScEditableTester aTester( 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);
        // 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_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();
    ScEditableTester aTester( 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 );
    }
 
    ScEditableTester aTester( 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 );
    }
 
    ScEditableTester aTester( 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 );
    }
 
    ScEditableTester aTester( 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 );
    }
 
    ScEditableTester aTester( 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;
    }
 
    //      Test for cell protection
    //!     Source range can be protected !!!
    //!     but can't contain matrix fragments !!!
 
    ScEditableTester aTester( rDoc, aDestArea );
    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*/ )
{
    using ::std::set;
 
    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;
 
    for (const auto& rTab : rOption.maTabs)
    {
        ScEditableTester aTester( 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 )
{
    using ::std::set;
 
    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.GetItemSet().Put( 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;
 
        ScEditableTester aTester( 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(new ScConditionalFormatList(*pUndoDoc, *pOld), 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(new ScConditionalFormatList(*pRedoDoc, *pList), 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;
 
    ScEditableTester aTester(rDoc, rRange);
    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)
{
    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));
 
    return true;
}
 
bool ScDocFunc::DeleteSparkline(ScAddress const& rAddress)
{
    auto& rDocument = rDocShell.GetDocument();
 
    if (!rDocument.HasSparkline(rAddress))
        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;
 
    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)
{
    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)
{
    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)
{
    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)
{
    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: */

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

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

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

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

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

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

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

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

V1028 Possible overflow. Consider casting operands of the 'nEndCol - nStartCol + 1' operator to the 'sal_uInt64' type, not the result.

V1028 Possible overflow. Consider casting operands of the 'nEndRow - nStartRow + 1' operator to the 'sal_uInt64' type, not the result.

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

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

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

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

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