sc/Library_sc.mk                                    |    3 
 sc/source/ui/docshell/docfunc.cxx                   |  640 ----------------
 sc/source/ui/inc/docfunc.hxx                        |    2 
 sc/source/ui/inc/operation/InsertCellsOperation.hxx |   44 +
 sc/source/ui/operation/InsertCellsOperation.cxx     |  755 ++++++++++++++++++++
 5 files changed, 806 insertions(+), 638 deletions(-)

New commits:
commit 303d6d48f78b5d0ca259c5ccb9b7ef23b357d252
Author:     Tomaž Vajngerl <[email protected]>
AuthorDate: Thu Feb 19 16:15:08 2026 +0900
Commit:     Miklos Vajna <[email protected]>
CommitDate: Fri Feb 20 14:12:34 2026 +0100

    sc: Introduce InsertCellsOperation and move impl. from ScDocFunc
    
    The implementation was moved into sc::InsertCellsOperation from
    ScDocFunc, but no other functional change was done.
    
    Change-Id: I333da3a2445b1c40190d71b240f577162fc02022
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199685
    Reviewed-by: Miklos Vajna <[email protected]>
    Tested-by: Jenkins CollaboraOffice <[email protected]>

diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk
index ea8223498747..385c96d18ebd 100644
--- a/sc/Library_sc.mk
+++ b/sc/Library_sc.mk
@@ -532,6 +532,7 @@ $(eval $(call gb_Library_add_exception_objects,sc,\
     sc/source/ui/operation/ApplyAttributesOperation \
     sc/source/ui/operation/DeleteCellOperation \
     sc/source/ui/operation/DeleteContentOperation \
+    sc/source/ui/operation/InsertCellsOperation \
     sc/source/ui/operation/Operation \
     sc/source/ui/operation/OperationType \
     sc/source/ui/operation/SetEditTextOperation \
@@ -539,8 +540,8 @@ $(eval $(call gb_Library_add_exception_objects,sc,\
     sc/source/ui/operation/SetNormalStringOperation \
     sc/source/ui/operation/SetStringOperation \
     sc/source/ui/operation/SetValueOperation \
-    sc/source/ui/operation/QueryOperation \
     sc/source/ui/operation/SortOperation \
+    sc/source/ui/operation/QueryOperation \
     sc/source/ui/pagedlg/areasdlg \
     sc/source/ui/pagedlg/tphfedit \
     sc/source/ui/sidebar/AlignmentPropertyPanel \
diff --git a/sc/source/ui/docshell/docfunc.cxx 
b/sc/source/ui/docshell/docfunc.cxx
index b9c15f10770d..a2661304eef1 100644
--- a/sc/source/ui/docshell/docfunc.cxx
+++ b/sc/source/ui/docshell/docfunc.cxx
@@ -119,6 +119,7 @@
 #include <operation/SetFormulaOperation.hxx>
 #include <operation/SetEditTextOperation.hxx>
 #include <operation/ApplyAttributesOperation.hxx>
+#include <operation/InsertCellsOperation.hxx>
 #include <basic/basmgr.hxx>
 #include <set>
 #include <vector>
@@ -1213,100 +1214,6 @@ bool ScDocFunc::ApplyStyle( const ScMarkData& rMark, 
const OUString& rStyleName,
 
 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.
@@ -1385,549 +1292,8 @@ bool canDeleteCellsByPivot(const ScRange& rRange, const 
ScMarkData& rMarkData, D
 bool ScDocFunc::InsertCells( const ScRange& rRange, const ScMarkData* 
pTabMark, InsCellCmd eCmd,
                              bool bRecord, bool bApi, bool bPartOfPaste, 
size_t nInsertCount )
 {
-    ScDocShellModificator aModificator( rDocShell );
-    ScDocument& rDoc = rDocShell.GetDocument();
-
-    if (rDocShell.GetDocument().GetChangeTrack() &&
-        ((eCmd == INS_CELLSDOWN  && (rRange.aStart.Col() != 0 || 
rRange.aEnd.Col() != rDoc.MaxCol())) ||
-         (eCmd == INS_CELLSRIGHT && (rRange.aStart.Row() != 0 || 
rRange.aEnd.Row() != rDoc.MaxRow()))))
-    {
-        // We should not reach this via UI disabled slots.
-        assert(bApi);
-        SAL_WARN("sc.ui","ScDocFunc::InsertCells - no change-tracking of 
partial cell shift");
-        return false;
-    }
-
-    ScRange aTargetRange( rRange );
-
-    // If insertion is for full cols/rows and after the current
-    // selection, then shift the range accordingly
-    if ( eCmd == INS_INSROWS_AFTER )
-    {
-        ScRange aErrorRange( ScAddress::UNINITIALIZED );
-        if (!aTargetRange.Move(0, rRange.aEnd.Row() - rRange.aStart.Row() + 1, 
0, aErrorRange, rDoc))
-        {
-            return false;
-        }
-    }
-    if ( eCmd == INS_INSCOLS_AFTER )
-    {
-        ScRange aErrorRange( ScAddress::UNINITIALIZED );
-        if (!aTargetRange.Move(rRange.aEnd.Col() - rRange.aStart.Col() + 1, 0, 
0, aErrorRange, rDoc))
-        {
-            return false;
-        }
-    }
-
-    SCCOL nStartCol = aTargetRange.aStart.Col();
-    SCROW nStartRow = aTargetRange.aStart.Row();
-    SCTAB nStartTab = aTargetRange.aStart.Tab();
-    SCCOL nEndCol = aTargetRange.aEnd.Col() + nInsertCount;
-    SCROW nEndRow = aTargetRange.aEnd.Row() + nInsertCount;
-    SCTAB nEndTab = aTargetRange.aEnd.Tab();
-
-    if ( !rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow) )
-    {
-        OSL_FAIL("invalid row in InsertCells");
-        return false;
-    }
-
-    SCTAB nTabCount = rDoc.GetTableCount();
-    SCCOL nPaintStartCol = nStartCol;
-    SCROW nPaintStartRow = nStartRow;
-    SCCOL nPaintEndCol = nEndCol;
-    SCROW nPaintEndRow = nEndRow;
-    PaintPartFlags nPaintFlags = PaintPartFlags::Grid;
-    bool bSuccess;
-
-    ScTabViewShell* pViewSh = rDocShell.GetBestViewShell();  //preserve 
current cursor position
-    SCCOL nCursorCol = 0;
-    SCROW nCursorRow = 0;
-    if( pViewSh )
-    {
-        nCursorCol = pViewSh->GetViewData().GetCurX();
-        nCursorRow = pViewSh->GetViewData().GetCurY();
-    }
-
-    if (bRecord && !rDoc.IsUndoEnabled())
-        bRecord = false;
-
-    ScMarkData aMark(rDoc.GetSheetLimits());
-    if (pTabMark)
-        aMark = *pTabMark;
-    else
-    {
-        SCTAB nCount = 0;
-        for( SCTAB i=0; i<nTabCount; i++ )
-        {
-            if( !rDoc.IsScenario(i) )
-            {
-                nCount++;
-                if( nCount == nEndTab+1 )
-                {
-                    aMark.SelectTable( i, true );
-                    break;
-                }
-            }
-        }
-    }
-
-    ScMarkData aFullMark( aMark );          // including scenario sheets
-    for (const auto& rTab : aMark)
-    {
-        if (rTab >= nTabCount)
-            break;
-
-        for( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ )
-            aFullMark.SelectTable( j, true );
-    }
-
-    SCTAB nSelCount = aMark.GetSelectCount();
-
-    // Adjust also related scenarios
-
-    SCCOL nMergeTestStartCol = nStartCol;
-    SCROW nMergeTestStartRow = nStartRow;
-    SCCOL nMergeTestEndCol = nEndCol;
-    SCROW nMergeTestEndRow = nEndRow;
-
-    ScRange aExtendMergeRange( aTargetRange );
-
-    if( aTargetRange.aStart == aTargetRange.aEnd && 
rDoc.HasAttrib(aTargetRange, HasAttrFlags::Merged) )
-    {
-        rDoc.ExtendMerge( aExtendMergeRange );
-        rDoc.ExtendOverlapped( aExtendMergeRange );
-        nMergeTestEndCol = aExtendMergeRange.aEnd.Col();
-        nMergeTestEndRow = aExtendMergeRange.aEnd.Row();
-        nPaintEndCol = nMergeTestEndCol;
-        nPaintEndRow = nMergeTestEndRow;
-    }
-
-    if ( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER )
-    {
-        nMergeTestStartCol = 0;
-        nMergeTestEndCol = rDoc.MaxCol();
-    }
-    if ( eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER )
-    {
-        nMergeTestStartRow = 0;
-        nMergeTestEndRow = rDoc.MaxRow();
-    }
-    if ( eCmd == INS_CELLSDOWN )
-        nMergeTestEndRow = rDoc.MaxRow();
-    if ( eCmd == INS_CELLSRIGHT )
-        nMergeTestEndCol = rDoc.MaxCol();
-
-    bool bNeedRefresh = false;
-
-    SCCOL nEditTestEndCol = (eCmd==INS_INSCOLS_BEFORE || 
eCmd==INS_INSCOLS_AFTER) ? rDoc.MaxCol() : nMergeTestEndCol;
-    SCROW nEditTestEndRow = (eCmd==INS_INSROWS_BEFORE || 
eCmd==INS_INSROWS_AFTER) ? rDoc.MaxRow() : nMergeTestEndRow;
-
-    ScEditableTester aTester;
-    sc::OperationType eOperation = sc::OperationType::Unknown;
-    switch (eCmd)
-    {
-        case INS_INSCOLS_BEFORE:
-            eOperation = sc::OperationType::InsertColumnsBefore;
-            aTester = ScEditableTester::CreateAndTestBlockForAction(
-                rDoc, sc::EditAction::InsertColumnsBefore, nMergeTestStartCol, 
0, nMergeTestEndCol, rDoc.MaxRow(), aMark);
-            break;
-        case INS_INSCOLS_AFTER:
-            eOperation = sc::OperationType::InsertColumnsBefore;
-            aTester = ScEditableTester::CreateAndTestBlockForAction(
-                rDoc, sc::EditAction::InsertColumnsAfter, nMergeTestStartCol, 
0, nMergeTestEndCol, rDoc.MaxRow(), aMark);
-            break;
-        case INS_INSROWS_BEFORE:
-            eOperation = sc::OperationType::InsertRowsBefore;
-            aTester = ScEditableTester::CreateAndTestBlockForAction(
-                rDoc, sc::EditAction::InsertRowsBefore, 0, nMergeTestStartRow, 
rDoc.MaxCol(), nMergeTestEndRow, aMark);
-            break;
-        case INS_INSROWS_AFTER:
-            eOperation = sc::OperationType::InsertRowsAfter;
-            aTester = ScEditableTester::CreateAndTestBlockForAction(
-                rDoc, sc::EditAction::InsertRowsAfter, 0, nMergeTestStartRow, 
rDoc.MaxCol(), nMergeTestEndRow, aMark);
-            break;
-        default:
-            if (eCmd == INS_CELLSDOWN)
-                eOperation = sc::OperationType::InsertCellsDown;
-            else if (eCmd == INS_CELLSRIGHT)
-                eOperation = sc::OperationType::InsertCellsRight;
-
-            aTester = ScEditableTester::CreateAndTestSelectedBlock(
-                rDoc, nMergeTestStartCol, nMergeTestStartRow, nEditTestEndCol, 
nEditTestEndRow, aMark);
-            break;
-    }
-
-    if (!CheckSheetViewProtection(eOperation))
-        return false;
-
-    if (!aTester.IsEditable())
-    {
-        if (!bApi)
-            rDocShell.ErrorMessage(aTester.GetMessageId());
-        return false;
-    }
-
-    // Check if this insertion is allowed with respect to pivot table.
-    if (!canInsertCellsByPivot(aTargetRange, aMark, eCmd, rDoc))
-    {
-        if (!bApi)
-            rDocShell.ErrorMessage(STR_NO_INSERT_DELETE_OVER_PIVOT_TABLE);
-        return false;
-    }
-
-    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );      // 
important due to TrackFormulas at UpdateReference
-
-    ScDocumentUniquePtr pRefUndoDoc;
-    std::unique_ptr<ScRefUndoData> pUndoData;
-    if ( bRecord )
-    {
-        pRefUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
-        pRefUndoDoc->InitUndo( rDoc, 0, nTabCount-1 );
-
-        // pRefUndoDoc is filled in InsertCol / InsertRow
-
-        pUndoData.reset(new ScRefUndoData( &rDoc ));
-
-        rDoc.BeginDrawUndo();
-    }
-
-    // #i8302 : we unmerge overwhelming ranges, before insertion all the 
actions are put in the same ListAction
-    // the patch comes from mloiseleur and maoyg
-    bool bInsertMerge = false;
-    std::vector<ScRange> qIncreaseRange;
-    OUString aUndo = ScResId( STR_UNDO_INSERTCELLS );
-    if (bRecord)
-    {
-        ViewShellId nViewShellId(-1);
-        if (pViewSh)
-            nViewShellId = pViewSh->GetViewShellId();
-        rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, 
nViewShellId );
-    }
-    std::unique_ptr<ScUndoRemoveMerge> pUndoRemoveMerge;
-
-    for (const SCTAB i : aMark)
-    {
-        if (i >= nTabCount)
-            break;
-
-        if( rDoc.HasAttrib( nMergeTestStartCol, nMergeTestStartRow, i, 
nMergeTestEndCol, nMergeTestEndRow, i, HasAttrFlags::Merged | 
HasAttrFlags::Overlapped ) )
-        {
-            if (eCmd==INS_CELLSRIGHT)
-                bNeedRefresh = true;
-
-            SCCOL nMergeStartCol = nMergeTestStartCol;
-            SCROW nMergeStartRow = nMergeTestStartRow;
-            SCCOL nMergeEndCol   = nMergeTestEndCol;
-            SCROW nMergeEndRow   = nMergeTestEndRow;
-
-            rDoc.ExtendMerge( nMergeStartCol, nMergeStartRow, nMergeEndCol, 
nMergeEndRow, i );
-            rDoc.ExtendOverlapped( nMergeStartCol, nMergeStartRow, 
nMergeEndCol, nMergeEndRow, i );
-
-            if(( eCmd == INS_CELLSDOWN && ( nMergeStartCol != 
nMergeTestStartCol || nMergeEndCol != nMergeTestEndCol )) ||
-                (eCmd == INS_CELLSRIGHT && ( nMergeStartRow != 
nMergeTestStartRow || nMergeEndRow != nMergeTestEndRow )) )
-            {
-                if (!bApi)
-                    rDocShell.ErrorMessage(STR_MSSG_INSERTCELLS_0);
-                rDocShell.GetUndoManager()->LeaveListAction();
-                return false;
-            }
-
-            SCCOL nTestCol = -1;
-            SCROW nTestRow1 = -1;
-            SCROW nTestRow2 = -1;
-
-            ScDocAttrIterator aTestIter( rDoc, i, nMergeTestStartCol, 
nMergeTestStartRow, nMergeTestEndCol, nMergeTestEndRow );
-            ScRange aExtendRange( nMergeTestStartCol, nMergeTestStartRow, i, 
nMergeTestEndCol, nMergeTestEndRow, i );
-            const ScPatternAttr* pPattern = nullptr;
-            while ( ( pPattern = aTestIter.GetNext( nTestCol, nTestRow1, 
nTestRow2 ) ) != nullptr )
-            {
-                const ScMergeAttr& rMergeFlag = pPattern->GetItem(ATTR_MERGE);
-                const ScMergeFlagAttr& rMergeFlagAttr = 
pPattern->GetItem(ATTR_MERGE_FLAG);
-                ScMF nNewFlags = rMergeFlagAttr.GetValue() & (ScMF::Hor | 
ScMF::Ver);
-                if (rMergeFlag.IsMerged() || nNewFlags == ScMF::Hor || 
nNewFlags == ScMF::Ver)
-                {
-                    ScRange aRange( nTestCol, nTestRow1, i );
-                    rDoc.ExtendOverlapped(aRange);
-                    rDoc.ExtendMerge(aRange, true);
-
-                    if( nTestRow1 < nTestRow2 && nNewFlags == ScMF::Hor )
-                    {
-                        for( SCROW nTestRow = nTestRow1; nTestRow <= 
nTestRow2; nTestRow++ )
-                        {
-                            ScRange aTestRange( nTestCol, nTestRow, i );
-                            rDoc.ExtendOverlapped( aTestRange );
-                            rDoc.ExtendMerge( aTestRange, true);
-                            ScRange aMergeRange( 
aTestRange.aStart.Col(),aTestRange.aStart.Row(), i );
-                            if( !aExtendRange.Contains( aMergeRange ) )
-                            {
-                                qIncreaseRange.push_back( aTestRange );
-                                bInsertMerge = true;
-                            }
-                        }
-                    }
-                    else
-                    {
-                        ScRange aMergeRange( 
aRange.aStart.Col(),aRange.aStart.Row(), i );
-                        if( !aExtendRange.Contains( aMergeRange ) )
-                        {
-                            qIncreaseRange.push_back( aRange );
-                        }
-                        bInsertMerge = true;
-                    }
-                }
-            }
-
-            if( bInsertMerge )
-            {
-                if( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER || 
eCmd == INS_CELLSDOWN )
-                {
-                    nStartRow = aExtendMergeRange.aStart.Row();
-                    nEndRow = aExtendMergeRange.aEnd.Row();
-
-                    if( eCmd == INS_CELLSDOWN )
-                        nEndCol = nMergeTestEndCol;
-                    else
-                    {
-                        nStartCol = 0;
-                        nEndCol = rDoc.MaxCol();
-                    }
-                }
-                else if( eCmd == INS_CELLSRIGHT || eCmd == INS_INSCOLS_BEFORE 
|| eCmd == INS_INSCOLS_AFTER )
-                {
-
-                    nStartCol = aExtendMergeRange.aStart.Col();
-                    nEndCol = aExtendMergeRange.aEnd.Col();
-                    if( eCmd == INS_CELLSRIGHT )
-                    {
-                        nEndRow = nMergeTestEndRow;
-                    }
-                    else
-                    {
-                        nStartRow = 0;
-                        nEndRow = rDoc.MaxRow();
-                    }
-                }
-
-                if( !qIncreaseRange.empty() )
-                {
-                    if (bRecord && !pUndoRemoveMerge)
-                    {
-                        ScDocumentUniquePtr pUndoDoc(new ScDocument( 
SCDOCMODE_UNDO ));
-                        pUndoDoc->InitUndo( rDoc, *aMark.begin(), 
*aMark.rbegin());
-                        pUndoRemoveMerge.reset( new ScUndoRemoveMerge( 
&rDocShell, rRange, std::move(pUndoDoc) ));
-                    }
-
-                    for( const ScRange& aRange : qIncreaseRange )
-                    {
-                        if( rDoc.HasAttrib( aRange, HasAttrFlags::Overlapped | 
HasAttrFlags::Merged ) )
-                        {
-                            UnmergeCells( aRange, bRecord, 
pUndoRemoveMerge.get() );
-                        }
-                    }
-                }
-            }
-            else
-            {
-                if (!bApi)
-                    rDocShell.ErrorMessage(STR_MSSG_INSERTCELLS_0);
-                rDocShell.GetUndoManager()->LeaveListAction();
-                return false;
-            }
-        }
-    }
-
-    if (bRecord && pUndoRemoveMerge)
-    {
-        rDocShell.GetUndoManager()->AddUndoAction( 
std::move(pUndoRemoveMerge));
-    }
-
-    switch (eCmd)
-    {
-        case INS_CELLSDOWN:
-            bSuccess = rDoc.InsertRow( nStartCol, 0, nEndCol, MAXTAB, 
nStartRow, static_cast<SCSIZE>(nEndRow-nStartRow+1), pRefUndoDoc.get(), 
&aFullMark );
-            nPaintEndRow = rDoc.MaxRow();
-            break;
-        case INS_INSROWS_BEFORE:
-        case INS_INSROWS_AFTER:
-            bSuccess = rDoc.InsertRow( 0, 0, rDoc.MaxCol(), MAXTAB, nStartRow, 
static_cast<SCSIZE>(nEndRow-nStartRow+1), pRefUndoDoc.get(), &aFullMark );
-            nPaintStartCol = 0;
-            nPaintEndCol = rDoc.MaxCol();
-            nPaintEndRow = rDoc.MaxRow();
-            nPaintFlags |= PaintPartFlags::Left;
-            break;
-        case INS_CELLSRIGHT:
-            bSuccess = rDoc.InsertCol( nStartRow, 0, nEndRow, MAXTAB, 
nStartCol, static_cast<SCSIZE>(nEndCol-nStartCol+1), pRefUndoDoc.get(), 
&aFullMark );
-            nPaintEndCol = rDoc.MaxCol();
-            break;
-        case INS_INSCOLS_BEFORE:
-        case INS_INSCOLS_AFTER:
-            bSuccess = rDoc.InsertCol( 0, 0, rDoc.MaxRow(), MAXTAB, nStartCol, 
static_cast<SCSIZE>(nEndCol-nStartCol+1), pRefUndoDoc.get(), &aFullMark );
-            nPaintStartRow = 0;
-            nPaintEndRow = rDoc.MaxRow();
-            nPaintEndCol = rDoc.MaxCol();
-            nPaintFlags |= PaintPartFlags::Top;
-            break;
-        default:
-            OSL_FAIL("Wrong code at inserting");
-            bSuccess = false;
-            break;
-    }
-
-    if ( bSuccess )
-    {
-        SCTAB  nUndoPos  = 0;
-
-        if ( bRecord )
-        {
-            std::unique_ptr<SCTAB[]> pTabs(new SCTAB[nSelCount]);
-            std::unique_ptr<SCTAB[]> pScenarios(new SCTAB[nSelCount]);
-            nUndoPos    = 0;
-            for (const auto& rTab : aMark)
-            {
-                if (rTab >= nTabCount)
-                    break;
-
-                SCTAB nCount = 0;
-                for( SCTAB j=rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ )
-                    nCount ++;
-
-                pScenarios[nUndoPos] = nCount;
-                pTabs[nUndoPos] = rTab;
-                nUndoPos ++;
-            }
-
-            if( !bInsertMerge )
-            {
-                rDocShell.GetUndoManager()->LeaveListAction();
-            }
-
-            rDocShell.GetUndoManager()->AddUndoAction( 
std::make_unique<ScUndoInsertCells>(
-                &rDocShell, ScRange( nStartCol, nStartRow, nStartTab, nEndCol, 
nEndRow, nEndTab ),
-                nUndoPos, std::move(pTabs), std::move(pScenarios), eCmd, 
std::move(pRefUndoDoc), std::move(pUndoData), bPartOfPaste ) );
-        }
-
-        // #i8302 : we remerge growing ranges, with the new part inserted
-
-        while( !qIncreaseRange.empty() )
-        {
-            ScRange aRange = qIncreaseRange.back();
-            if( !rDoc.HasAttrib( aRange, HasAttrFlags::Overlapped | 
HasAttrFlags::Merged ) )
-            {
-                switch (eCmd)
-                {
-                    case INS_CELLSDOWN:
-                    case INS_INSROWS_BEFORE:
-                    case INS_INSROWS_AFTER:
-                        
aRange.aEnd.IncRow(static_cast<SCCOL>(nEndRow-nStartRow+1));
-                        break;
-                    case INS_CELLSRIGHT:
-                    case INS_INSCOLS_BEFORE:
-                    case INS_INSCOLS_AFTER:
-                        
aRange.aEnd.IncCol(static_cast<SCCOL>(nEndCol-nStartCol+1));
-                        break;
-                    default:
-                        break;
-                }
-                ScCellMergeOption aMergeOption(
-                    aRange.aStart.Col(), aRange.aStart.Row(),
-                    aRange.aEnd.Col(), aRange.aEnd.Row() );
-                aMergeOption.maTabs.insert(aRange.aStart.Tab());
-                MergeCells(aMergeOption, false, true, true);
-            }
-            qIncreaseRange.pop_back();
-        }
-
-        if( bInsertMerge )
-            rDocShell.GetUndoManager()->LeaveListAction();
-
-        for (const SCTAB i : aMark)
-        {
-            if (i >= nTabCount)
-                break;
-
-            rDoc.SetDrawPageSize(i);
-
-            if (bNeedRefresh)
-                rDoc.ExtendMerge( nMergeTestStartCol, nMergeTestStartRow, 
nMergeTestEndCol, nMergeTestEndRow, i, true );
-            else
-                rDoc.RefreshAutoFilter( nMergeTestStartCol, 
nMergeTestStartRow, nMergeTestEndCol, nMergeTestEndRow, i );
-
-            if ( eCmd == INS_INSROWS_BEFORE ||eCmd == INS_INSCOLS_BEFORE || 
eCmd == INS_INSROWS_AFTER ||eCmd == INS_INSCOLS_AFTER )
-                rDoc.UpdatePageBreaks( i );
-
-            sal_uInt16 nExtFlags = 0;
-            rDocShell.UpdatePaintExt( nExtFlags, nPaintStartCol, 
nPaintStartRow, i, nPaintEndCol, nPaintEndRow, i );
-
-            SCTAB nScenarioCount = 0;
-
-            for( SCTAB j = i+1; j<nTabCount && rDoc.IsScenario(j); j++ )
-                nScenarioCount ++;
-
-            bool bAdjusted = ( eCmd == INS_INSROWS_BEFORE || eCmd == 
INS_INSROWS_AFTER ) ?
-                        AdjustRowHeight(ScRange(0, nStartRow, i, 
rDoc.MaxCol(), nEndRow, i+nScenarioCount ), true, bApi) :
-                        AdjustRowHeight(ScRange(0, nPaintStartRow, i, 
rDoc.MaxCol(), nPaintEndRow, i+nScenarioCount ), true, bApi);
-            if (bAdjusted)
-            {
-                //  paint only what is not done by AdjustRowHeight
-                if (nPaintFlags & PaintPartFlags::Top)
-                    rDocShell.PostPaint( nPaintStartCol, nPaintStartRow, i, 
nPaintEndCol, nPaintEndRow, i+nScenarioCount, PaintPartFlags::Top );
-            }
-            else
-                rDocShell.PostPaint( nPaintStartCol, nPaintStartRow, i, 
nPaintEndCol, nPaintEndRow, i+nScenarioCount, nPaintFlags, nExtFlags );
-        }
-    }
-    else
-    {
-        if( bInsertMerge )
-        {
-            while( !qIncreaseRange.empty() )
-            {
-                ScRange aRange = qIncreaseRange.back();
-                ScCellMergeOption aMergeOption(
-                    aRange.aStart.Col(), aRange.aStart.Row(),
-                    aRange.aEnd.Col(), aRange.aEnd.Row() );
-                MergeCells(aMergeOption, false, true, true);
-                qIncreaseRange.pop_back();
-            }
-
-            if( pViewSh )
-            {
-                pViewSh->MarkRange( aTargetRange, false );
-                pViewSh->SetCursor( nCursorCol, nCursorRow );
-            }
-        }
-
-        rDocShell.GetUndoManager()->LeaveListAction();
-        rDocShell.GetUndoManager()->RemoveLastUndoAction();
-
-        pRefUndoDoc.reset();
-        if (!bApi)
-            rDocShell.ErrorMessage(STR_INSERT_FULL);        // column/row full
-    }
-
-    // The cursor position needs to be modified earlier than updating
-    // any enabled edit view which is triggered by SetDocumentModified below.
-    if (bSuccess)
-    {
-        bool bInsertCols = ( eCmd == INS_INSCOLS_BEFORE || eCmd == 
INS_INSCOLS_AFTER);
-        bool bInsertRows = ( eCmd == INS_INSROWS_BEFORE || eCmd == 
INS_INSROWS_AFTER );
-
-        if (bInsertCols)
-        {
-            pViewSh->OnLOKInsertDeleteColumn(rRange.aStart.Col() - (eCmd == 
INS_INSCOLS_BEFORE ? 1: 0), 1);
-        }
-
-        if (bInsertRows)
-        {
-            pViewSh->OnLOKInsertDeleteRow(rRange.aStart.Row() - (eCmd == 
INS_INSROWS_BEFORE ? 1: 0), 1);
-        }
-    }
-
-    aModificator.SetDocumentModified();
-
-    SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) );
-    return bSuccess;
+    sc::InsertCellsOperation aOperation(rDocShell, rRange, pTabMark, eCmd, 
bRecord, bApi, bPartOfPaste, nInsertCount);
+    return aOperation.run();
 }
 
 bool ScDocFunc::DeleteCells( const ScRange& rRange, const ScMarkData* 
pTabMark, DelCellCmd eCmd,
diff --git a/sc/source/ui/inc/docfunc.hxx b/sc/source/ui/inc/docfunc.hxx
index 523567c32ffe..5e67ed2c7ad1 100644
--- a/sc/source/ui/inc/docfunc.hxx
+++ b/sc/source/ui/inc/docfunc.hxx
@@ -67,6 +67,7 @@ namespace sc
     class SetEditTextOperation;
     class SetFormulaOperation;
     class ApplyAttributesOperation;
+    class InsertCellsOperation;
 }
 namespace tools
 {
@@ -83,6 +84,7 @@ class ScDocFunc
     friend class sc::SetEditTextOperation;
     friend class sc::SetFormulaOperation;
     friend class sc::ApplyAttributesOperation;
+    friend class sc::InsertCellsOperation;
 
     ScDocShell&     rDocShell;
     static bool CheckSheetViewProtection(sc::OperationType eOperation);
diff --git a/sc/source/ui/inc/operation/InsertCellsOperation.hxx 
b/sc/source/ui/inc/operation/InsertCellsOperation.hxx
new file mode 100644
index 000000000000..db7ad2dadeb9
--- /dev/null
+++ b/sc/source/ui/inc/operation/InsertCellsOperation.hxx
@@ -0,0 +1,44 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include "Operation.hxx"
+#include <global.hxx>
+#include <address.hxx>
+
+class ScDocShell;
+class ScMarkData;
+
+namespace sc
+{
+/** Operation which inserts cells, rows, or columns. */
+class InsertCellsOperation : public Operation
+{
+private:
+    ScDocShell& mrDocShell;
+    ScRange maRange;
+    ScMarkData const* mpTabMark;
+    InsCellCmd meCmd;
+    bool mbPartOfPaste;
+    size_t mnInsertCount;
+
+    bool runImplementation() override;
+
+    static OperationType toOperationType(InsCellCmd eCmd);
+
+public:
+    InsertCellsOperation(ScDocShell& rDocShell, ScRange const& rRange, 
ScMarkData const* pTabMark,
+                         InsCellCmd eCmd, bool bRecord, bool bApi, bool 
bPartOfPaste,
+                         size_t nInsertCount);
+};
+
+} // end sc namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/operation/InsertCellsOperation.cxx 
b/sc/source/ui/operation/InsertCellsOperation.cxx
new file mode 100644
index 000000000000..db36cb6fd848
--- /dev/null
+++ b/sc/source/ui/operation/InsertCellsOperation.cxx
@@ -0,0 +1,755 @@
+/* -*- 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/.
+ */
+
+#include <operation/InsertCellsOperation.hxx>
+
+#include <docfunc.hxx>
+#include <docsh.hxx>
+#include <markdata.hxx>
+#include <editable.hxx>
+#include <dpobject.hxx>
+#include <attrib.hxx>
+#include <dociter.hxx>
+#include <patattr.hxx>
+#include <cellmergeoption.hxx>
+#include <tabvwsh.hxx>
+#include <undoblk.hxx>
+#include <refundo.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <strings.hrc>
+
+#include <comphelper/lok.hxx>
+#include <sfx2/app.hxx>
+#include <vcl/weld.hxx>
+#include <osl/diagnose.h>
+
+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;
+}
+
+} // anonymous namespace
+
+namespace sc
+{
+InsertCellsOperation::InsertCellsOperation(ScDocShell& rDocShell, ScRange 
const& rRange,
+                                           ScMarkData const* pTabMark, 
InsCellCmd eCmd,
+                                           bool bRecord, bool bApi, bool 
bPartOfPaste,
+                                           size_t nInsertCount)
+    : Operation(toOperationType(eCmd), bRecord, bApi)
+    , mrDocShell(rDocShell)
+    , maRange(rRange)
+    , mpTabMark(pTabMark)
+    , meCmd(eCmd)
+    , mbPartOfPaste(bPartOfPaste)
+    , mnInsertCount(nInsertCount)
+{
+}
+
+OperationType InsertCellsOperation::toOperationType(InsCellCmd eCmd)
+{
+    switch (eCmd)
+    {
+        case INS_INSCOLS_BEFORE:
+            return OperationType::InsertColumnsBefore;
+        case INS_INSCOLS_AFTER:
+            return OperationType::InsertColumnsAfter;
+        case INS_INSROWS_BEFORE:
+            return OperationType::InsertRowsBefore;
+        case INS_INSROWS_AFTER:
+            return OperationType::InsertRowsAfter;
+        case INS_CELLSDOWN:
+            return OperationType::InsertCellsDown;
+        case INS_CELLSRIGHT:
+            return OperationType::InsertCellsRight;
+        default:
+            return OperationType::Unknown;
+    }
+}
+
+bool InsertCellsOperation::runImplementation()
+{
+    ScDocShellModificator aModificator(mrDocShell);
+    ScDocument& rDoc = mrDocShell.GetDocument();
+
+    if (mrDocShell.GetDocument().GetChangeTrack()
+        && ((meCmd == INS_CELLSDOWN
+             && (maRange.aStart.Col() != 0 || maRange.aEnd.Col() != 
rDoc.MaxCol()))
+            || (meCmd == INS_CELLSRIGHT
+                && (maRange.aStart.Row() != 0 || maRange.aEnd.Row() != 
rDoc.MaxRow()))))
+    {
+        // We should not reach this via UI disabled slots.
+        assert(mbApi);
+        SAL_WARN("sc.ui", "ScDocFunc::InsertCells - no change-tracking of 
partial cell shift");
+        return false;
+    }
+
+    ScRange aTargetRange(maRange);
+
+    // If insertion is for full cols/rows and after the current
+    // selection, then shift the range accordingly
+    if (meCmd == INS_INSROWS_AFTER)
+    {
+        ScRange aErrorRange(ScAddress::UNINITIALIZED);
+        if (!aTargetRange.Move(0, maRange.aEnd.Row() - maRange.aStart.Row() + 
1, 0, aErrorRange,
+                               rDoc))
+        {
+            return false;
+        }
+    }
+    if (meCmd == INS_INSCOLS_AFTER)
+    {
+        ScRange aErrorRange(ScAddress::UNINITIALIZED);
+        if (!aTargetRange.Move(maRange.aEnd.Col() - maRange.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() + mnInsertCount;
+    SCROW nEndRow = aTargetRange.aEnd.Row() + mnInsertCount;
+    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 = mrDocShell.GetBestViewShell(); //preserve 
current cursor position
+    SCCOL nCursorCol = 0;
+    SCROW nCursorRow = 0;
+    if (pViewSh)
+    {
+        nCursorCol = pViewSh->GetViewData().GetCurX();
+        nCursorRow = pViewSh->GetViewData().GetCurY();
+    }
+
+    if (mbRecord && !rDoc.IsUndoEnabled())
+        mbRecord = false;
+
+    ScMarkData aMark(rDoc.GetSheetLimits());
+    if (mpTabMark)
+        aMark = *mpTabMark;
+    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 (meCmd == INS_INSROWS_BEFORE || meCmd == INS_INSROWS_AFTER)
+    {
+        nMergeTestStartCol = 0;
+        nMergeTestEndCol = rDoc.MaxCol();
+    }
+    if (meCmd == INS_INSCOLS_BEFORE || meCmd == INS_INSCOLS_AFTER)
+    {
+        nMergeTestStartRow = 0;
+        nMergeTestEndRow = rDoc.MaxRow();
+    }
+    if (meCmd == INS_CELLSDOWN)
+        nMergeTestEndRow = rDoc.MaxRow();
+    if (meCmd == INS_CELLSRIGHT)
+        nMergeTestEndCol = rDoc.MaxCol();
+
+    bool bNeedRefresh = false;
+
+    SCCOL nEditTestEndCol = (meCmd == INS_INSCOLS_BEFORE || meCmd == 
INS_INSCOLS_AFTER)
+                                ? rDoc.MaxCol()
+                                : nMergeTestEndCol;
+    SCROW nEditTestEndRow = (meCmd == INS_INSROWS_BEFORE || meCmd == 
INS_INSROWS_AFTER)
+                                ? rDoc.MaxRow()
+                                : nMergeTestEndRow;
+
+    ScEditableTester aTester;
+    switch (meCmd)
+    {
+        case INS_INSCOLS_BEFORE:
+            aTester = ScEditableTester::CreateAndTestBlockForAction(
+                rDoc, sc::EditAction::InsertColumnsBefore, nMergeTestStartCol, 
0, nMergeTestEndCol,
+                rDoc.MaxRow(), aMark);
+            break;
+        case INS_INSCOLS_AFTER:
+            aTester = ScEditableTester::CreateAndTestBlockForAction(
+                rDoc, sc::EditAction::InsertColumnsAfter, nMergeTestStartCol, 
0, nMergeTestEndCol,
+                rDoc.MaxRow(), aMark);
+            break;
+        case INS_INSROWS_BEFORE:
+            aTester = ScEditableTester::CreateAndTestBlockForAction(
+                rDoc, sc::EditAction::InsertRowsBefore, 0, nMergeTestStartRow, 
rDoc.MaxCol(),
+                nMergeTestEndRow, aMark);
+            break;
+        case INS_INSROWS_AFTER:
+            aTester = ScEditableTester::CreateAndTestBlockForAction(
+                rDoc, sc::EditAction::InsertRowsAfter, 0, nMergeTestStartRow, 
rDoc.MaxCol(),
+                nMergeTestEndRow, aMark);
+            break;
+        default:
+            aTester = ScEditableTester::CreateAndTestSelectedBlock(
+                rDoc, nMergeTestStartCol, nMergeTestStartRow, nEditTestEndCol, 
nEditTestEndRow,
+                aMark);
+            break;
+    }
+
+    if (!aTester.IsEditable())
+    {
+        if (!mbApi)
+            mrDocShell.ErrorMessage(aTester.GetMessageId());
+        return false;
+    }
+
+    // Check if this insertion is allowed with respect to pivot table.
+    if (!canInsertCellsByPivot(aTargetRange, aMark, meCmd, rDoc))
+    {
+        if (!mbApi)
+            mrDocShell.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 (mbRecord)
+    {
+        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 (mbRecord)
+    {
+        ViewShellId nViewShellId(-1);
+        if (pViewSh)
+            nViewShellId = pViewSh->GetViewShellId();
+        mrDocShell.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 (meCmd == 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 ((meCmd == INS_CELLSDOWN
+                 && (nMergeStartCol != nMergeTestStartCol || nMergeEndCol != 
nMergeTestEndCol))
+                || (meCmd == INS_CELLSRIGHT
+                    && (nMergeStartRow != nMergeTestStartRow || nMergeEndRow 
!= nMergeTestEndRow)))
+            {
+                if (!mbApi)
+                    mrDocShell.ErrorMessage(STR_MSSG_INSERTCELLS_0);
+                mrDocShell.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 (meCmd == INS_INSROWS_BEFORE || meCmd == INS_INSROWS_AFTER
+                    || meCmd == INS_CELLSDOWN)
+                {
+                    nStartRow = aExtendMergeRange.aStart.Row();
+                    nEndRow = aExtendMergeRange.aEnd.Row();
+
+                    if (meCmd == INS_CELLSDOWN)
+                        nEndCol = nMergeTestEndCol;
+                    else
+                    {
+                        nStartCol = 0;
+                        nEndCol = rDoc.MaxCol();
+                    }
+                }
+                else if (meCmd == INS_CELLSRIGHT || meCmd == INS_INSCOLS_BEFORE
+                         || meCmd == INS_INSCOLS_AFTER)
+                {
+                    nStartCol = aExtendMergeRange.aStart.Col();
+                    nEndCol = aExtendMergeRange.aEnd.Col();
+                    if (meCmd == INS_CELLSRIGHT)
+                    {
+                        nEndRow = nMergeTestEndRow;
+                    }
+                    else
+                    {
+                        nStartRow = 0;
+                        nEndRow = rDoc.MaxRow();
+                    }
+                }
+
+                if (!qIncreaseRange.empty())
+                {
+                    if (mbRecord && !pUndoRemoveMerge)
+                    {
+                        ScDocumentUniquePtr pUndoDoc(new 
ScDocument(SCDOCMODE_UNDO));
+                        pUndoDoc->InitUndo(rDoc, *aMark.begin(), 
*aMark.rbegin());
+                        pUndoRemoveMerge.reset(
+                            new ScUndoRemoveMerge(&mrDocShell, maRange, 
std::move(pUndoDoc)));
+                    }
+
+                    for (const ScRange& aRange : qIncreaseRange)
+                    {
+                        if (rDoc.HasAttrib(aRange, HasAttrFlags::Overlapped | 
HasAttrFlags::Merged))
+                        {
+                            mrDocShell.GetDocFunc().UnmergeCells(aRange, 
mbRecord,
+                                                                 
pUndoRemoveMerge.get());
+                        }
+                    }
+                }
+            }
+            else
+            {
+                if (!mbApi)
+                    mrDocShell.ErrorMessage(STR_MSSG_INSERTCELLS_0);
+                mrDocShell.GetUndoManager()->LeaveListAction();
+                return false;
+            }
+        }
+    }
+
+    if (mbRecord && pUndoRemoveMerge)
+    {
+        
mrDocShell.GetUndoManager()->AddUndoAction(std::move(pUndoRemoveMerge));
+    }
+
+    switch (meCmd)
+    {
+        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 (mbRecord)
+        {
+            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)
+            {
+                mrDocShell.GetUndoManager()->LeaveListAction();
+            }
+
+            
mrDocShell.GetUndoManager()->AddUndoAction(std::make_unique<ScUndoInsertCells>(
+                &mrDocShell, ScRange(nStartCol, nStartRow, nStartTab, nEndCol, 
nEndRow, nEndTab),
+                nUndoPos, std::move(pTabs), std::move(pScenarios), meCmd, 
std::move(pRefUndoDoc),
+                std::move(pUndoData), mbPartOfPaste));
+        }
+
+        // #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 (meCmd)
+                {
+                    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());
+                mrDocShell.GetDocFunc().MergeCells(aMergeOption, false, true, 
true);
+            }
+            qIncreaseRange.pop_back();
+        }
+
+        if (bInsertMerge)
+            mrDocShell.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 (meCmd == INS_INSROWS_BEFORE || meCmd == INS_INSCOLS_BEFORE
+                || meCmd == INS_INSROWS_AFTER || meCmd == INS_INSCOLS_AFTER)
+                rDoc.UpdatePageBreaks(i);
+
+            sal_uInt16 nExtFlags = 0;
+            mrDocShell.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
+                = (meCmd == INS_INSROWS_BEFORE || meCmd == INS_INSROWS_AFTER)
+                      ? mrDocShell.GetDocFunc().AdjustRowHeight(
+                            ScRange(0, nStartRow, i, rDoc.MaxCol(), nEndRow, i 
+ nScenarioCount),
+                            true, mbApi)
+                      : mrDocShell.GetDocFunc().AdjustRowHeight(ScRange(0, 
nPaintStartRow, i,
+                                                                        
rDoc.MaxCol(), nPaintEndRow,
+                                                                        i + 
nScenarioCount),
+                                                                true, mbApi);
+            if (bAdjusted)
+            {
+                //  paint only what is not done by AdjustRowHeight
+                if (nPaintFlags & PaintPartFlags::Top)
+                    mrDocShell.PostPaint(nPaintStartCol, nPaintStartRow, i, 
nPaintEndCol,
+                                         nPaintEndRow, i + nScenarioCount, 
PaintPartFlags::Top);
+            }
+            else
+                mrDocShell.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());
+                mrDocShell.GetDocFunc().MergeCells(aMergeOption, false, true, 
true);
+                qIncreaseRange.pop_back();
+            }
+
+            if (pViewSh)
+            {
+                pViewSh->MarkRange(aTargetRange, false);
+                pViewSh->SetCursor(nCursorCol, nCursorRow);
+            }
+        }
+
+        mrDocShell.GetUndoManager()->LeaveListAction();
+        mrDocShell.GetUndoManager()->RemoveLastUndoAction();
+
+        pRefUndoDoc.reset();
+        if (!mbApi)
+            mrDocShell.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 = (meCmd == INS_INSCOLS_BEFORE || meCmd == 
INS_INSCOLS_AFTER);
+        bool bInsertRows = (meCmd == INS_INSROWS_BEFORE || meCmd == 
INS_INSROWS_AFTER);
+
+        if (bInsertCols)
+        {
+            pViewSh->OnLOKInsertDeleteColumn(
+                maRange.aStart.Col() - (meCmd == INS_INSCOLS_BEFORE ? 1 : 0), 
1);
+        }
+
+        if (bInsertRows)
+        {
+            pViewSh->OnLOKInsertDeleteRow(
+                maRange.aStart.Row() - (meCmd == INS_INSROWS_BEFORE ? 1 : 0), 
1);
+        }
+    }
+
+    aModificator.SetDocumentModified();
+
+    SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScAreaLinksChanged));
+    return bSuccess;
+}
+
+} // end sc namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to