/* -*- 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 <utility>
#include <vcl/virdev.hxx>
#include <editeng/boxitem.hxx>
#include <sfx2/app.hxx>
#include <comphelper/lok.hxx>
#include <osl/diagnose.h>

#include <undoblk.hxx>
#include <undoutil.hxx>
#include <document.hxx>
#include <patattr.hxx>
#include <docsh.hxx>
#include <tabvwsh.hxx>
#include <rangenam.hxx>
#include <rangeutl.hxx>
#include <stlpool.hxx>
#include <stlsheet.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <global.hxx>
#include <target.hxx>
#include <docpool.hxx>
#include <docfunc.hxx>
#include <attrib.hxx>
#include <chgtrack.hxx>
#include <transobj.hxx>
#include <refundo.hxx>
#include <undoolk.hxx>
#include <clipparam.hxx>
#include <rowheightcontext.hxx>
#include <refupdatecontext.hxx>
#include <validat.hxx>
#include <gridwin.hxx>
#include <columnspanset.hxx>

#include <memory>
#include <set>

// TODO:
/*A*/   // SetOptimalHeight on Document, if no View
/*B*/   // linked sheets
/*C*/   // ScArea
//?     // check later

ScUndoInsertCells::ScUndoInsertCells( ScDocShell& rNewDocShell,
                                const ScRange& rRange,
                                SCTAB nNewCount, std::unique_ptr<SCTAB[]> pNewTabs, std::unique_ptr<SCTAB[]> pNewScenarios,
                                InsCellCmd eNewCmd, ScDocumentUniquePtr pUndoDocument, std::unique_ptr<ScRefUndoData> pRefData,
                                bool bNewPartOfPaste ) :
    ScMoveUndo( rNewDocShell, std::move(pUndoDocument), std::move(pRefData) ),
    aEffRange( rRange ),
    nCount( nNewCount ),
    pTabs( std::move(pNewTabs) ),
    pScenarios( std::move(pNewScenarios) ),
    eCmd( eNewCmd ),
    bPartOfPaste( bNewPartOfPaste )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    if (eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER)            // whole row?
    {
        aEffRange.aStart.SetCol(0);
        aEffRange.aEnd.SetCol(rDoc.MaxCol());
    }

    if (eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER)            // whole column?
    {
        aEffRange.aStart.SetRow(0);
        aEffRange.aEnd.SetRow(rDoc.MaxRow());
    }

    SetChangeTrack();
}

ScUndoInsertCells::~ScUndoInsertCells()
{
}

OUString ScUndoInsertCells::GetComment() const
{
    return ScResId( pPasteUndo ? STR_UNDO_PASTE : STR_UNDO_INSERTCELLS );
}

bool ScUndoInsertCells::Merge( SfxUndoAction* pNextAction )
{
    //  If a paste undo action has already been added, append (detective) action there.
    if ( pPasteUndo )
        return pPasteUndo->Merge( pNextAction );

    if ( bPartOfPaste )
        if ( auto pWrapper = dynamic_cast<ScUndoWrapper*>( pNextAction) )
        {
            if (dynamic_cast<const ScUndoPaste*>(pWrapper->GetWrappedUndo()))
            {
                //  Store paste action if this is part of paste with inserting cells.
                //  A list action isn't used because Repeat wouldn't work (insert wrong cells).

                // Pass ownership of the wrapped SfxUndoAction* to pPasteUndO
                pPasteUndo = pWrapper->ReleaseWrappedUndo();
                return true;
            }
        }

    //  Call base class for detective handling
    return ScMoveUndo::Merge( pNextAction );
}

void ScUndoInsertCells::SetChangeTrack()
{
    ScChangeTrack* pChangeTrack = rDocShell.GetDocument().GetChangeTrack();
    if ( pChangeTrack )
    {
        pChangeTrack->AppendInsert( aEffRange );
        nEndChangeAction = pChangeTrack->GetActionMax();
    }
    else
        nEndChangeAction = 0;
}

void ScUndoInsertCells::DoChange( const bool bUndo )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    SCTAB i;

    if ( bUndo )
    {
        ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
        if ( pChangeTrack )
            pChangeTrack->Undo( nEndChangeAction, nEndChangeAction );
    }
    else
        SetChangeTrack();

    // refresh of merged cells has to be after inserting/deleting

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    switch (eCmd)
    {
        case INS_INSROWS_BEFORE:
        case INS_INSROWS_AFTER:
        case INS_CELLSDOWN:
            for( i=0; i<nCount; i++ )
            {

                if (bUndo)
                    rDoc.DeleteRow( aEffRange.aStart.Col(), pTabs[i], aEffRange.aEnd.Col(), pTabs[i]+pScenarios[i],
                    aEffRange.aStart.Row(), static_cast<SCSIZE>(aEffRange.aEnd.Row()-aEffRange.aStart.Row()+1));
                else
                    rDoc.InsertRow( aEffRange.aStart.Col(), pTabs[i], aEffRange.aEnd.Col(), pTabs[i]+pScenarios[i],
                    aEffRange.aStart.Row(), static_cast<SCSIZE>(aEffRange.aEnd.Row()-aEffRange.aStart.Row()+1));

                if (pViewShell)
                {
                    const tools::Long nSign = bUndo ? -1 : 1;
                    pViewShell->OnLOKInsertDeleteRow(aEffRange.aStart.Row(), nSign * (aEffRange.aEnd.Row()-aEffRange.aStart.Row()+1));
                }
            }
            break;
        case INS_INSCOLS_BEFORE:
        case INS_INSCOLS_AFTER:
        case INS_CELLSRIGHT:
            for( i=0; i<nCount; i++ )
            {
                if (bUndo)
                    rDoc.DeleteCol( aEffRange.aStart.Row(), pTabs[i], aEffRange.aEnd.Row(), pTabs[i]+pScenarios[i],
                    aEffRange.aStart.Col(), static_cast<SCSIZE>(aEffRange.aEnd.Col()-aEffRange.aStart.Col()+1));
                else
                    rDoc.InsertCol( aEffRange.aStart.Row(), pTabs[i], aEffRange.aEnd.Row(), pTabs[i]+pScenarios[i],
                    aEffRange.aStart.Col(), static_cast<SCSIZE>(aEffRange.aEnd.Col()-aEffRange.aStart.Col()+1));

                if (pViewShell)
                {
                    const tools::Long nSign = bUndo ? -1 : 1;
                    pViewShell->OnLOKInsertDeleteColumn(aEffRange.aStart.Col(), nSign * (aEffRange.aEnd.Col()-aEffRange.aStart.Col()+1));
                }
            }
            break;
        default:
        {
            // added to avoid warnings
        }
    }

    ScRange aWorkRange( aEffRange );
    if ( eCmd == INS_CELLSRIGHT )                   // only "shift right" requires refresh of the moved area
        aWorkRange.aEnd.SetCol(rDoc.MaxCol());
    for( i=0; i<nCount; i++ )
    {
        if ( rDoc.HasAttrib( aWorkRange.aStart.Col(), aWorkRange.aStart.Row(), pTabs[i],
            aWorkRange.aEnd.Col(), aWorkRange.aEnd.Row(), pTabs[i], HasAttrFlags::Merged ) )
        {
            SCCOL nEndCol = aWorkRange.aEnd.Col();
            SCROW nEndRow = aWorkRange.aEnd.Row();
            rDoc.ExtendMerge( aWorkRange.aStart.Col(), aWorkRange.aStart.Row(), nEndCol, nEndRow, pTabs[i], true );
        }
    }

    // Undo for displaced attributes?

    PaintPartFlags nPaint = PaintPartFlags::Grid;

    switch (eCmd)
    {
        case INS_INSROWS_BEFORE:
        case INS_INSROWS_AFTER:
            nPaint |= PaintPartFlags::Left;
            aWorkRange.aEnd.SetRow(rDoc.MaxRow());
            break;
        case INS_CELLSDOWN:
            for( i=0; i<nCount; i++ )
            {
                aWorkRange.aEnd.SetRow(rDoc.MaxRow());
                if ( rDocShell.AdjustRowHeight( aWorkRange.aStart.Row(), aWorkRange.aEnd.Row(), pTabs[i] ))
                {
                    aWorkRange.aStart.SetCol(0);
                    aWorkRange.aEnd.SetCol(rDoc.MaxCol());
                    nPaint |= PaintPartFlags::Left;
                }
            }
            break;
        case INS_INSCOLS_BEFORE:
        case INS_INSCOLS_AFTER:
            nPaint |= PaintPartFlags::Top;                // top bar
            [[fallthrough]];
        case INS_CELLSRIGHT:
            for( i=0; i<nCount; i++ )
            {
                aWorkRange.aEnd.SetCol(rDoc.MaxCol());     // to the far right
                if ( rDocShell.AdjustRowHeight( aWorkRange.aStart.Row(), aWorkRange.aEnd.Row(), pTabs[i]) )
                {                                   // AdjustDraw does not paint PaintPartFlags::Top,
                    aWorkRange.aStart.SetCol(0);    // thus solved like this
                    aWorkRange.aEnd.SetRow(rDoc.MaxRow());
                    nPaint |= PaintPartFlags::Left;
                }
            }
            break;
        default:
        {
            // added to avoid warnings
        }
    }

    for( i=0; i<nCount; i++ )
    {
        rDocShell.PostPaint( aWorkRange.aStart.Col(), aWorkRange.aStart.Row(), pTabs[i],
            aWorkRange.aEnd.Col(), aWorkRange.aEnd.Row(), pTabs[i]+pScenarios[i], nPaint );
    }
    rDocShell.PostDataChanged();
    if (!pViewShell)
        return;

    pViewShell->CellContentChanged();

    if (!comphelper::LibreOfficeKit::isActive())
        return;

    SCTAB nTab = pViewShell->GetViewData().GetTabNumber();
    bool bColsAffected = (eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER || eCmd == INS_CELLSRIGHT);
    bool bRowsAffected = (eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER || eCmd == INS_CELLSDOWN);

    if (bColsAffected)
        ScTabViewShell::notifyAllViewsHeaderInvalidation(pViewShell, COLUMN_HEADER, nTab);

    if (bRowsAffected)
        ScTabViewShell::notifyAllViewsHeaderInvalidation(pViewShell, ROW_HEADER, nTab);

    ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
            pViewShell,
            bColsAffected, bRowsAffected,
            true /* bSizes*/, true /* bHidden */, true /* bFiltered */,
            true /* bGroups */, nTab);
}

void ScUndoInsertCells::Undo()
{
    if ( pPasteUndo )
        pPasteUndo->Undo();     // undo paste first

    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );     // important due to TrackFormulas in UpdateReference
    BeginUndo();
    DoChange( true );
    EndUndo();

    ScDocument& rDoc = rDocShell.GetDocument();
    for (SCTAB i = 0; i < nCount; ++i)
        rDoc.SetDrawPageSize(pTabs[i]);
}

void ScUndoInsertCells::Redo()
{
    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );     // important due to TrackFormulas in UpdateReference
    BeginRedo();
    DoChange( false );
    EndRedo();

    if ( pPasteUndo )
        pPasteUndo->Redo();     // redo paste last

    ScDocument& rDoc = rDocShell.GetDocument();
    for (SCTAB i = 0; i < nCount; ++i)
        rDoc.SetDrawPageSize(pTabs[i]);
}

void ScUndoInsertCells::Repeat(SfxRepeatTarget& rTarget)
{
    if (dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr)
    {
        if ( pPasteUndo )
        {
            //  Repeat for paste with inserting cells is handled completely
            //  by the Paste undo action

            pPasteUndo->Repeat( rTarget );
        }
        else
            static_cast<ScTabViewTarget&>(rTarget).GetViewShell().InsertCells( eCmd );
    }
}

bool ScUndoInsertCells::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

ScUndoDeleteCells::ScUndoDeleteCells( ScDocShell& rNewDocShell,
                                const ScRange& rRange,
                                SCTAB nNewCount, std::unique_ptr<SCTAB[]> pNewTabs, std::unique_ptr<SCTAB[]> pNewScenarios,
                                DelCellCmd eNewCmd, ScDocumentUniquePtr pUndoDocument, std::unique_ptr<ScRefUndoData> pRefData ) :
    ScMoveUndo( rNewDocShell, std::move(pUndoDocument), std::move(pRefData) ),
    aEffRange( rRange ),
    nCount( nNewCount ),
    pTabs( std::move(pNewTabs) ),
    pScenarios( std::move(pNewScenarios) ),
    eCmd( eNewCmd )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    if (eCmd == DelCellCmd::Rows)            // whole row?
    {
        aEffRange.aStart.SetCol(0);
        aEffRange.aEnd.SetCol(rDoc.MaxCol());
    }

    if (eCmd == DelCellCmd::Cols)            // whole column?
    {
        aEffRange.aStart.SetRow(0);
        aEffRange.aEnd.SetRow(rDoc.MaxRow());
    }

    SetChangeTrack();
}

ScUndoDeleteCells::~ScUndoDeleteCells()
{
}

OUString ScUndoDeleteCells::GetComment() const
{
    return ScResId( STR_UNDO_DELETECELLS ); // "Delete"
}

void ScUndoDeleteCells::SetChangeTrack()
{
    ScChangeTrack* pChangeTrack = rDocShell.GetDocument().GetChangeTrack();
    if ( pChangeTrack )
        pChangeTrack->AppendDeleteRange( aEffRange, pRefUndoDoc.get(),
            nStartChangeAction, nEndChangeAction );
    else
        nStartChangeAction = nEndChangeAction = 0;
}

void ScUndoDeleteCells::DoChange( const bool bUndo )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    SCTAB i;

    if ( bUndo )
    {
        ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
        if ( pChangeTrack )
            pChangeTrack->Undo( nStartChangeAction, nEndChangeAction );
    }
    else
        SetChangeTrack();

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();

    switch (eCmd)
    {
        case DelCellCmd::Rows:
        case DelCellCmd::CellsUp:
            for( i=0; i<nCount; i++ )
            {
                if (bUndo)
                    rDoc.InsertRow( aEffRange.aStart.Col(), pTabs[i], aEffRange.aEnd.Col(), pTabs[i]+pScenarios[i],
                    aEffRange.aStart.Row(), static_cast<SCSIZE>(aEffRange.aEnd.Row()-aEffRange.aStart.Row()+1));
                else
                    rDoc.DeleteRow( aEffRange.aStart.Col(), pTabs[i], aEffRange.aEnd.Col(), pTabs[i]+pScenarios[i],
                    aEffRange.aStart.Row(), static_cast<SCSIZE>(aEffRange.aEnd.Row()-aEffRange.aStart.Row()+1));

                if (pViewShell)
                {
                    const tools::Long nSign = bUndo ? 1 : -1;
                    pViewShell->OnLOKInsertDeleteRow(aEffRange.aStart.Row(), nSign * (aEffRange.aEnd.Row()-aEffRange.aStart.Row()+1));
                }
            }
            break;
        case DelCellCmd::Cols:
        case DelCellCmd::CellsLeft:
            for( i=0; i<nCount; i++ )
            {
                if (bUndo)
                    rDoc.InsertCol( aEffRange.aStart.Row(), pTabs[i], aEffRange.aEnd.Row(), pTabs[i]+pScenarios[i],
                    aEffRange.aStart.Col(), static_cast<SCSIZE>(aEffRange.aEnd.Col()-aEffRange.aStart.Col()+1));
                else
                    rDoc.DeleteCol( aEffRange.aStart.Row(), pTabs[i], aEffRange.aEnd.Row(), pTabs[i]+pScenarios[i],
                    aEffRange.aStart.Col(), static_cast<SCSIZE>(aEffRange.aEnd.Col()-aEffRange.aStart.Col()+1));

                if (pViewShell)
                {
                    const tools::Long nSign = bUndo ? 1 : -1;
                    pViewShell->OnLOKInsertDeleteColumn(aEffRange.aStart.Col(), nSign * (aEffRange.aEnd.Col()-aEffRange.aStart.Col()+1));
                }
            }
            break;
        default:
        {
            // added to avoid warnings
        }
    }

    // if Undo, restore references
    for( i=0; i<nCount && bUndo; i++ )
    {
        pRefUndoDoc->CopyToDocument(aEffRange.aStart.Col(), aEffRange.aStart.Row(), pTabs[i], aEffRange.aEnd.Col(), aEffRange.aEnd.Row(), pTabs[i]+pScenarios[i],
            InsertDeleteFlags::ALL | InsertDeleteFlags::NOCAPTIONS, false, rDoc);
    }

    ScRange aWorkRange( aEffRange );
    if ( eCmd == DelCellCmd::CellsLeft )        // only "shift left" requires refresh of the moved area
        aWorkRange.aEnd.SetCol(rDoc.MaxCol());

    for( i=0; i<nCount; i++ )
    {
        if ( rDoc.HasAttrib( aWorkRange.aStart.Col(), aWorkRange.aStart.Row(), pTabs[i],
            aWorkRange.aEnd.Col(), aWorkRange.aEnd.Row(), pTabs[i], HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
        {
            // #i51445# old merge flag attributes must be deleted also for single cells,
            // not only for whole columns/rows

            if ( !bUndo )
            {
                if ( eCmd==DelCellCmd::Cols || eCmd==DelCellCmd::CellsLeft )
                    aWorkRange.aEnd.SetCol(rDoc.MaxCol());
                if ( eCmd==DelCellCmd::Rows || eCmd==DelCellCmd::CellsUp )
                    aWorkRange.aEnd.SetRow(rDoc.MaxRow());
                ScMarkData aMarkData(rDoc.GetSheetLimits());
                aMarkData.SelectOneTable( aWorkRange.aStart.Tab() );
                ScPatternAttr aPattern(rDoc.getCellAttributeHelper());
                aPattern.ItemSetPut(ScMergeFlagAttr());
                rDoc.ApplyPatternArea( aWorkRange.aStart.Col(), aWorkRange.aStart.Row(),
                    aWorkRange.aEnd.Col(),   aWorkRange.aEnd.Row(),
                    aMarkData, aPattern );
            }

            SCCOL nEndCol = aWorkRange.aEnd.Col();
            SCROW nEndRow = aWorkRange.aEnd.Row();
            rDoc.ExtendMerge( aWorkRange.aStart.Col(), aWorkRange.aStart.Row(), nEndCol, nEndRow, pTabs[i], true );
        }
    }

    // paint
    PaintPartFlags nPaint = PaintPartFlags::Grid;
    switch (eCmd)
    {
        case DelCellCmd::Rows:
            nPaint |= PaintPartFlags::Left;
            aWorkRange.aEnd.SetRow(rDoc.MaxRow());
            break;
        case DelCellCmd::CellsUp:
            for( i=0; i<nCount; i++ )
            {
                aWorkRange.aEnd.SetRow(rDoc.MaxRow());
                if ( rDocShell.AdjustRowHeight( aWorkRange.aStart.Row(), aWorkRange.aEnd.Row(), pTabs[i] ))
                {
                    aWorkRange.aStart.SetCol(0);
                    aWorkRange.aEnd.SetCol(rDoc.MaxCol());
                    nPaint |= PaintPartFlags::Left;
                }
            }
            break;
        case DelCellCmd::Cols:
            nPaint |= PaintPartFlags::Top;                // top bar
            [[fallthrough]];
        case DelCellCmd::CellsLeft:
            for( i=0; i<nCount; i++ )
            {
                aWorkRange.aEnd.SetCol(rDoc.MaxCol());     // to the far right
                if ( rDocShell.AdjustRowHeight( aWorkRange.aStart.Row(), aWorkRange.aEnd.Row(), pTabs[i] ) )
                {
                    aWorkRange.aStart.SetCol(0);
                    aWorkRange.aEnd.SetRow(rDoc.MaxRow());
                    nPaint |= PaintPartFlags::Left;
                }
            }
            break;
        default:
        {
            // added to avoid warnings
        }
    }

    for( i=0; i<nCount; i++ )
    {
        rDocShell.PostPaint( aWorkRange.aStart.Col(), aWorkRange.aStart.Row(), pTabs[i],
            aWorkRange.aEnd.Col(), aWorkRange.aEnd.Row(), pTabs[i]+pScenarios[i], nPaint, SC_PF_LINES );
    }
    // Selection not until EndUndo

    rDocShell.PostDataChanged();
    //  CellContentChanged comes with the selection

    if (!pViewShell)
        return;

    if (!comphelper::LibreOfficeKit::isActive())
        return;

    SCTAB nTab = pViewShell->GetViewData().GetTabNumber();
    bool bColsAffected = (eCmd == DelCellCmd::Cols || eCmd == DelCellCmd::CellsLeft);
    bool bRowsAffected = (eCmd == DelCellCmd::Rows || eCmd == DelCellCmd::CellsUp);

    if (bColsAffected)
        ScTabViewShell::notifyAllViewsHeaderInvalidation(pViewShell, COLUMN_HEADER, nTab);

    if (bRowsAffected)
        ScTabViewShell::notifyAllViewsHeaderInvalidation(pViewShell, ROW_HEADER, nTab);

    ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
            pViewShell,
            bColsAffected, bRowsAffected,
            true /* bSizes*/, true /* bHidden */, true /* bFiltered */,
            true /* bGroups */, nTab);

}

void ScUndoDeleteCells::Undo()
{
    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );     // important because of TrackFormulas in UpdateReference
    BeginUndo();
    DoChange( true );
    EndUndo();

    ScDocument& rDoc = rDocShell.GetDocument();

    // Now that DBData have been restored in ScMoveUndo::EndUndo() via its
    // pRefUndoDoc we can apply the AutoFilter buttons.
    // Add one row for cases undoing deletion right above a cut AutoFilter
    // range so the buttons are removed.
    SCROW nRefreshEndRow = std::min<SCROW>( aEffRange.aEnd.Row() + 1, rDoc.MaxRow());
    for (SCTAB i=0; i < nCount; ++i)
    {
        rDoc.RefreshAutoFilter( aEffRange.aStart.Col(), aEffRange.aStart.Row(),
                aEffRange.aEnd.Col(), nRefreshEndRow, pTabs[i]);
    }

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

    // Selection not until EndUndo
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
    {
        for( SCTAB i=0; i<nCount; i++ )
        {
            pViewShell->MarkRange( ScRange(aEffRange.aStart.Col(), aEffRange.aStart.Row(), pTabs[i], aEffRange.aEnd.Col(), aEffRange.aEnd.Row(), pTabs[i]+pScenarios[i]) );
        }
    }

    for (SCTAB i = 0; i < nCount; ++i)
        rDoc.SetDrawPageSize(pTabs[i]);
}

void ScUndoDeleteCells::Redo()
{
    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );     // important because of TrackFormulas in UpdateReference
    BeginRedo();
    DoChange( false);
    EndRedo();

    ScDocument& rDoc = rDocShell.GetDocument();

    for (SCTAB i=0; i < nCount; ++i)
    {
        rDoc.RefreshAutoFilter( aEffRange.aStart.Col(), aEffRange.aStart.Row(),
                aEffRange.aEnd.Col(), aEffRange.aEnd.Row(), pTabs[i]);
    }

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

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
        pViewShell->DoneBlockMode();            // current way

    for (SCTAB i = 0; i < nCount; ++i)
        rDoc.SetDrawPageSize(pTabs[i]);
}

void ScUndoDeleteCells::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget) )
        pViewTarget->GetViewShell().DeleteCells( eCmd );
}

bool ScUndoDeleteCells::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

// delete cells in multiselection
ScUndoDeleteMulti::ScUndoDeleteMulti(
    ScDocShell& rNewDocShell,
    bool bNewRows, bool bNeedsRefresh, SCTAB nNewTab,
    std::vector<sc::ColRowSpan>&& rSpans,
    ScDocumentUniquePtr pUndoDocument, std::unique_ptr<ScRefUndoData> pRefData ) :
    ScMoveUndo( rNewDocShell, std::move(pUndoDocument), std::move(pRefData) ),
    mbRows(bNewRows),
    mbRefresh(bNeedsRefresh),
    nTab( nNewTab ),
    maSpans(std::move(rSpans))
{
    SetChangeTrack();
}

ScUndoDeleteMulti::~ScUndoDeleteMulti()
{
}

OUString ScUndoDeleteMulti::GetComment() const
{
    return ScResId( STR_UNDO_DELETECELLS );  // like DeleteCells
}

void ScUndoDeleteMulti::DoChange() const
{
    SCCOL nStartCol;
    SCROW nStartRow;
    PaintPartFlags nPaint;
    ScDocument& rDoc = rDocShell.GetDocument();
    if (mbRows)
    {
        nStartCol = 0;
        nStartRow = static_cast<SCROW>(maSpans[0].mnStart);
        nPaint = PaintPartFlags::Grid | PaintPartFlags::Left;
    }
    else
    {
        nStartCol = static_cast<SCCOL>(maSpans[0].mnStart);
        nStartRow = 0;
        nPaint = PaintPartFlags::Grid | PaintPartFlags::Top;
    }

    if (mbRefresh)
    {
        SCCOL nEndCol = rDoc.MaxCol();
        SCROW nEndRow = rDoc.MaxRow();
        rDoc.RemoveFlagsTab( nStartCol, nStartRow, nEndCol, nEndRow, nTab, ScMF::Hor | ScMF::Ver );
        rDoc.ExtendMerge( nStartCol, nStartRow, nEndCol, nEndRow, nTab, true );
    }

    rDocShell.PostPaint( nStartCol, nStartRow, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, nPaint );
    rDocShell.PostDataChanged();
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
        pViewShell->CellContentChanged();

    ShowTable( nTab );
}

void ScUndoDeleteMulti::SetChangeTrack()
{
    ScChangeTrack* pChangeTrack = rDocShell.GetDocument().GetChangeTrack();
    if ( pChangeTrack )
    {
        ScDocument& rDoc = rDocShell.GetDocument();
        nStartChangeAction = pChangeTrack->GetActionMax() + 1;
        ScRange aRange( 0, 0, nTab, 0, 0, nTab );
        if (mbRows)
            aRange.aEnd.SetCol( rDoc.MaxCol() );
        else
            aRange.aEnd.SetRow( rDoc.MaxRow() );
        // delete in reverse
        std::vector<sc::ColRowSpan>::const_reverse_iterator ri = maSpans.rbegin(), riEnd = maSpans.rend();
        for (; ri != riEnd; ++ri)
        {
            SCCOLROW nEnd = ri->mnEnd;
            SCCOLROW nStart = ri->mnStart;
            if (mbRows)
            {
                aRange.aStart.SetRow( nStart );
                aRange.aEnd.SetRow( nEnd );
            }
            else
            {
                aRange.aStart.SetCol( static_cast<SCCOL>(nStart) );
                aRange.aEnd.SetCol( static_cast<SCCOL>(nEnd) );
            }
            sal_uLong nDummyStart;
            pChangeTrack->AppendDeleteRange( aRange, pRefUndoDoc.get(),
                nDummyStart, nEndChangeAction );
        }
    }
    else
        nStartChangeAction = nEndChangeAction = 0;
}

void ScUndoDeleteMulti::Undo()
{
    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );     // important because of TrackFormulas in UpdateReference
    BeginUndo();

    ScDocument& rDoc = rDocShell.GetDocument();

    // reverse delete -> forward insert
    for (const auto& rSpan : maSpans)
    {
        SCCOLROW nStart = rSpan.mnStart;
        SCCOLROW nEnd = rSpan.mnEnd;
        if (mbRows)
            rDoc.InsertRow( 0,nTab, rDoc.MaxCol(),nTab, nStart,static_cast<SCSIZE>(nEnd-nStart+1) );
        else
            rDoc.InsertCol( 0,nTab, rDoc.MaxRow(),nTab, static_cast<SCCOL>(nStart), static_cast<SCSIZE>(nEnd-nStart+1) );
    }

    for (const auto& rSpan : maSpans)
    {
        SCCOLROW nStart = rSpan.mnStart;
        SCCOLROW nEnd = rSpan.mnEnd;
        if (mbRows)
            pRefUndoDoc->CopyToDocument(0, nStart, nTab, rDoc.MaxCol(), nEnd, nTab, InsertDeleteFlags::ALL, false, rDoc);
        else
            pRefUndoDoc->CopyToDocument(static_cast<SCCOL>(nStart),0,nTab,
                    static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, InsertDeleteFlags::ALL, false, rDoc);
    }

    ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
    if ( pChangeTrack )
        pChangeTrack->Undo( nStartChangeAction, nEndChangeAction );

    DoChange();

    //! redrawing the selection is not possible at the moment
    //! since no data for selection exist

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

void ScUndoDeleteMulti::Redo()
{
    weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() );     // important because of TrackFormulas in UpdateReference
    BeginRedo();

    ScDocument& rDoc = rDocShell.GetDocument();

    // reverse delete
    std::vector<sc::ColRowSpan>::const_reverse_iterator ri = maSpans.rbegin(), riEnd = maSpans.rend();
    for (; ri != riEnd; ++ri)
    {
        SCCOLROW nEnd = ri->mnEnd;
        SCCOLROW nStart = ri->mnStart;
        if (mbRows)
            rDoc.DeleteRow( 0,nTab, rDoc.MaxCol(),nTab, nStart,static_cast<SCSIZE>(nEnd-nStart+1) );
        else
            rDoc.DeleteCol( 0,nTab, rDoc.MaxRow(),nTab, static_cast<SCCOL>(nStart), static_cast<SCSIZE>(nEnd-nStart+1) );
    }

    SetChangeTrack();

    DoChange();

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

void ScUndoDeleteMulti::Repeat(SfxRepeatTarget& rTarget)
{
    // if single selection
    if (auto pTabViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
        pTabViewTarget->GetViewShell().DeleteCells( DelCellCmd::Rows );
}

bool ScUndoDeleteMulti::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

ScUndoCut::ScUndoCut(ScDocShell& rNewDocShell, const ScRange& aRange, const ScAddress& aOldEnd,
                     const ScMarkData& rMark, ScDocumentUniquePtr pNewUndoDoc)
    : ScBlockUndo(rNewDocShell, ScRange(aRange.aStart, aOldEnd), SC_UNDO_AUTOHEIGHT)
    , aMarkData(rMark)
    , pUndoDoc(std::move(pNewUndoDoc))
    , aExtendedRange(aRange)
{
    SetChangeTrack();
}

ScUndoCut::~ScUndoCut()
{
}

OUString ScUndoCut::GetComment() const
{
    return ScResId( STR_UNDO_CUT ); // "cut"
}

void ScUndoCut::SetChangeTrack()
{
    ScChangeTrack* pChangeTrack = rDocShell.GetDocument().GetChangeTrack();
    if ( pChangeTrack )
        pChangeTrack->AppendContentRange( aBlockRange, *pUndoDoc,
            nStartChangeAction, nEndChangeAction, SC_CACM_CUT );
    else
        nStartChangeAction = nEndChangeAction = 0;
}

void ScUndoCut::DoChange( const bool bUndo )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    sal_uInt16 nExtFlags = 0;

    // do not undo/redo objects and note captions, they are handled via drawing undo
    InsertDeleteFlags nUndoFlags = (InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS) | InsertDeleteFlags::NOCAPTIONS;

    if (bUndo)  // only for Undo
    {
        //  all sheets - CopyToDocument skips those that don't exist in pUndoDoc
        SCTAB nTabCount = rDoc.GetTableCount();
        ScRange aCopyRange = aExtendedRange;
        aCopyRange.aStart.SetTab(0);
        aCopyRange.aEnd.SetTab(nTabCount-1);
        pUndoDoc->CopyToDocument(aCopyRange, nUndoFlags, false, rDoc);
        ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
        if ( pChangeTrack )
            pChangeTrack->Undo( nStartChangeAction, nEndChangeAction );

        BroadcastChanges(aCopyRange);
    }
    else        // only for Redo
    {
        rDocShell.UpdatePaintExt( nExtFlags, aExtendedRange );
        rDoc.DeleteArea( aBlockRange.aStart.Col(), aBlockRange.aStart.Row(),
                          aBlockRange.aEnd.Col(), aBlockRange.aEnd.Row(), aMarkData, nUndoFlags );
        SetChangeTrack();
    }

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if ( !( pViewShell && pViewShell->AdjustBlockHeight() ) )
/*A*/   rDocShell.PostPaint( aExtendedRange, PaintPartFlags::Grid, nExtFlags );

    if ( !bUndo )                               //   draw redo after updating row heights
        RedoSdrUndoAction( pDrawUndo.get() );         //! include in ScBlockUndo?

    rDocShell.PostDataChanged();
    if (pViewShell)
        pViewShell->CellContentChanged();
}

void ScUndoCut::Undo()
{
    BeginUndo();
    DoChange( true );
    EndUndo();
}

void ScUndoCut::Redo()
{
    BeginRedo();
    ScDocument& rDoc = rDocShell.GetDocument();
    EnableDrawAdjust( &rDoc, false );                //! include in ScBlockUndo?
    DoChange( false );
    EnableDrawAdjust( &rDoc, true );                 //! include in ScBlockUndo?
    EndRedo();
}

void ScUndoCut::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
        pViewTarget->GetViewShell().CutToClip();
}

bool ScUndoCut::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

ScUndoPaste::ScUndoPaste( ScDocShell& rNewDocShell, const ScRangeList& rRanges,
                const ScMarkData& rMark,
                ScDocumentUniquePtr pNewUndoDoc, ScDocumentUniquePtr pNewRedoDoc,
                InsertDeleteFlags nNewFlags,
                std::unique_ptr<ScRefUndoData> pRefData,
                bool bRedoIsFilled, const ScUndoPasteOptions* pOptions ) :
    ScMultiBlockUndo( rNewDocShell, rRanges ),
    aMarkData( rMark ),
    pUndoDoc( std::move(pNewUndoDoc) ),
    pRedoDoc( std::move(pNewRedoDoc) ),
    nFlags( nNewFlags ),
    pRefUndoData( std::move(pRefData) ),
    bRedoFilled( bRedoIsFilled )
{
    if ( pRefUndoData )
        pRefUndoData->DeleteUnchanged( rDocShell.GetDocument() );

    if ( pOptions )
        aPasteOptions = *pOptions;      // used only for Repeat

    SetChangeTrack();
}

ScUndoPaste::~ScUndoPaste()
{
    pUndoDoc.reset();
    pRedoDoc.reset();
    pRefUndoData.reset();
    pRefRedoData.reset();
}

OUString ScUndoPaste::GetComment() const
{
    return ScResId( STR_UNDO_PASTE ); // "paste"
}

void ScUndoPaste::SetChangeTrack()
{
    ScChangeTrack* pChangeTrack = rDocShell.GetDocument().GetChangeTrack();
    if ( pChangeTrack && (nFlags & InsertDeleteFlags::CONTENTS) )
    {
        for (size_t i = 0, n = maBlockRanges.size(); i < n; ++i)
        {
            pChangeTrack->AppendContentRange(maBlockRanges[i], *pUndoDoc,
                nStartChangeAction, nEndChangeAction, SC_CACM_PASTE );
        }
    }
    else
        nStartChangeAction = nEndChangeAction = 0;
}

void ScUndoPaste::DoChange(bool bUndo)
{
    ScDocument& rDoc = rDocShell.GetDocument();

    //  RefUndoData for redo is created before first undo
    //  (with DeleteUnchanged after the DoUndo call)
    bool bCreateRedoData = ( bUndo && pRefUndoData && !pRefRedoData );
    if ( bCreateRedoData )
        pRefRedoData.reset( new ScRefUndoData( rDoc ) );

    ScRefUndoData* pWorkRefData = bUndo ? pRefUndoData.get() : pRefRedoData.get();

    // Always back-up either all or none of the content for Undo
    InsertDeleteFlags nUndoFlags = InsertDeleteFlags::NONE;
    if (nFlags & InsertDeleteFlags::CONTENTS)
        nUndoFlags |= InsertDeleteFlags::CONTENTS;
    if (nFlags & InsertDeleteFlags::ATTRIB)
        nUndoFlags |= InsertDeleteFlags::ATTRIB;

    // do not undo/redo objects and note captions, they are handled via drawing undo
    nUndoFlags &= ~InsertDeleteFlags::OBJECTS;
    nUndoFlags |= InsertDeleteFlags::NOCAPTIONS;

    bool bPaintAll = false;

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();

    SCTAB nTabCount = rDoc.GetTableCount();
    if ( bUndo && !bRedoFilled )
    {
        if (!pRedoDoc)
        {
            bool bColInfo = true;
            bool bRowInfo = true;
            for (size_t i = 0, n = maBlockRanges.size(); i < n; ++i)
            {
                const ScRange& r = maBlockRanges[i];
                bColInfo &= (r.aStart.Row() == 0 && r.aEnd.Row() == rDoc.MaxRow());
                bRowInfo &= (r.aStart.Col() == 0 && r.aEnd.Col() == rDoc.MaxCol());
                if (!bColInfo && !bRowInfo)
                    break;
            }

            pRedoDoc.reset( new ScDocument( SCDOCMODE_UNDO ) );
            pRedoDoc->InitUndoSelected( rDoc, aMarkData, bColInfo, bRowInfo );
        }
        //  read "redo" data from the document in the first undo
        //  all sheets - CopyToDocument skips those that don't exist in pRedoDoc
        for (size_t i = 0, n = maBlockRanges.size(); i < n; ++i)
        {
            ScRange aCopyRange = maBlockRanges[i];
            aCopyRange.aStart.SetTab(0);
            aCopyRange.aEnd.SetTab(nTabCount-1);
            rDoc.CopyToDocument(aCopyRange, nUndoFlags, false, *pRedoDoc);
            bRedoFilled = true;
        }
    }

    sal_uInt16 nExtFlags = 0;
    rDocShell.UpdatePaintExt(nExtFlags, maBlockRanges.Combine());
    // tdf#167042 - cell comments texts should not be deleted but
    // the note text should be remembered in maNoteData to be able
    // to later reconstruct a caption from it.
    rDoc.ForgetNoteCaptions(maBlockRanges, true);
    aMarkData.MarkToMulti();
    rDoc.DeleteSelection(nUndoFlags, aMarkData, false); // no broadcasting here
    for (size_t i = 0, n = maBlockRanges.size(); i < n; ++i)
        rDoc.BroadcastCells(maBlockRanges[i], SfxHintId::ScDataChanged);

    aMarkData.MarkToSimple();

    SCTAB nFirstSelected = aMarkData.GetFirstSelected();

    if ( !bUndo && pRedoDoc )       // Redo: UndoToDocument before handling RefData
    {
        for (size_t i = 0, n = maBlockRanges.size(); i < n; ++i)
        {
            ScRange aRange = maBlockRanges[i];
            aRange.aStart.SetTab(nFirstSelected);
            aRange.aEnd.SetTab(nFirstSelected);
            pRedoDoc->UndoToDocument(aRange, nUndoFlags, false, rDoc);
            for (const auto& rTab : aMarkData)
            {
                if (rTab >= nTabCount)
                    break;

                if (rTab == nFirstSelected)
                    continue;

                aRange.aStart.SetTab(rTab);
                aRange.aEnd.SetTab(rTab);
                pRedoDoc->CopyToDocument(aRange, nUndoFlags, false, rDoc);
            }
        }
    }

    if (pWorkRefData)
    {
        pWorkRefData->DoUndo( rDoc, true );     // true = bSetChartRangeLists for SetChartListenerCollection
        if (!maBlockRanges.empty() &&
            rDoc.RefreshAutoFilter(0, 0, rDoc.MaxCol(), rDoc.MaxRow(), maBlockRanges[0].aStart.Tab()))
            bPaintAll = true;
    }

    if ( bCreateRedoData && pRefRedoData )
        pRefRedoData->DeleteUnchanged( rDoc );

    if (bUndo)      // Undo: UndoToDocument after handling RefData
    {
        for (size_t i = 0, n = maBlockRanges.size(); i < n; ++i)
        {
            ScRange aRange = maBlockRanges[i];
            for (const auto& rTab : aMarkData)
            {
                if (rTab >= nTabCount)
                    break;
                aRange.aStart.SetTab(rTab);
                aRange.aEnd.SetTab(rTab);
                pUndoDoc->UndoToDocument(aRange, nUndoFlags, false, rDoc);
            }
        }
    }

    if ( bUndo )
    {
        ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
        if ( pChangeTrack )
            pChangeTrack->Undo( nStartChangeAction, nEndChangeAction );
    }
    else
        SetChangeTrack();

    ScRangeList aDrawRanges(maBlockRanges);
    PaintPartFlags nPaint = PaintPartFlags::Grid;

    // For sheet geometry invalidation.
    bool bColsAffected = false;
    bool bRowsAffected = false;

    for (size_t i = 0, n = aDrawRanges.size(); i < n; ++i)
    {
        ScRange& rDrawRange = aDrawRanges[i];
        rDoc.ExtendMerge(rDrawRange, true);      // only needed for single sheet (text/rtf etc.)
        ScRangeList aRangeList(rDrawRange);
        ScMarkData aData(rDoc.GetSheetLimits(), aRangeList);
        if (bPaintAll)
        {
            rDrawRange.aStart.SetCol(0);
            rDrawRange.aStart.SetRow(0);
            rDrawRange.aEnd.SetCol(rDoc.MaxCol());
            rDrawRange.aEnd.SetRow(rDoc.MaxRow());
            nPaint |= PaintPartFlags::Top | PaintPartFlags::Left;
            if (pViewShell)
                pViewShell->AdjustBlockHeight(false, &aData);
        }
        else
        {
            if (maBlockRanges[i].aStart.Row() == 0 && maBlockRanges[i].aEnd.Row() == rDoc.MaxRow()) // whole column
            {
                nPaint |= PaintPartFlags::Top;
                rDrawRange.aEnd.SetCol(rDoc.MaxCol());
                bColsAffected = true;
            }
            if (maBlockRanges[i].aStart.Col() == 0 && maBlockRanges[i].aEnd.Col() == rDoc.MaxCol()) // whole row
            {
                nPaint |= PaintPartFlags::Left;
                rDrawRange.aEnd.SetRow(rDoc.MaxRow());
                bRowsAffected = true;
            }
            if (pViewShell && pViewShell->AdjustBlockHeight(false, &aData))
            {
                rDrawRange.aStart.SetCol(0);
                rDrawRange.aStart.SetRow(0);
                rDrawRange.aEnd.SetCol(rDoc.MaxCol());
                rDrawRange.aEnd.SetRow(rDoc.MaxRow());
                nPaint |= PaintPartFlags::Left;
            }
            rDocShell.UpdatePaintExt(nExtFlags, rDrawRange);
        }
    }

    if ( !bUndo )                               //   draw redo after updating row heights
        RedoSdrUndoAction(mpDrawUndo.get());

    rDocShell.PostPaint(aDrawRanges, nPaint, nExtFlags);

    rDocShell.PostDataChanged();
    if (!pViewShell)
        return;

    pViewShell->CellContentChanged();

    if (bColsAffected || bRowsAffected)
        ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
            pViewShell,
            bColsAffected, bRowsAffected,
            true /* bSizes*/, true /* bHidden */, true /* bFiltered */,
            true /* bGroups */, aDrawRanges[0].aStart.Tab());
}

void ScUndoPaste::Undo()
{
    BeginUndo();
    DoChange(true);
    if (!maBlockRanges.empty())
        ShowTable(maBlockRanges.front());
    EndUndo();
    SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) );
}

void ScUndoPaste::Redo()
{
    BeginRedo();
    ScDocument& rDoc = rDocShell.GetDocument();
    EnableDrawAdjust( &rDoc, false );                //! include in ScBlockUndo?
    DoChange( false );
    EnableDrawAdjust( &rDoc, true );                 //! include in ScBlockUndo?
    EndRedo();
    SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) );
}

void ScUndoPaste::Repeat(SfxRepeatTarget& rTarget)
{
    auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget);
    if (!pViewTarget)
        return;

    ScTabViewShell& rViewSh = pViewTarget->GetViewShell();
    // keep a reference in case the clipboard is changed during PasteFromClip
    const ScTransferObj* pOwnClip = ScTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(rViewSh.GetViewData().GetActiveWin()));
    if (pOwnClip)
    {
        rViewSh.PasteFromClip( nFlags, pOwnClip->GetDocument(),
                                aPasteOptions.nFunction, aPasteOptions.bSkipEmptyCells, aPasteOptions.bTranspose,
                                aPasteOptions.bAsLink, aPasteOptions.eMoveMode, InsertDeleteFlags::NONE,
                                true );     // allow warning dialog
    }
}

bool ScUndoPaste::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

ScUndoDragDrop::ScUndoDragDrop( ScDocShell& rNewDocShell,
                    const ScRange& rRange, const ScAddress& aNewDestPos, bool bNewCut,
                    ScDocumentUniquePtr pUndoDocument, bool bScenario ) :
    ScMoveUndo( rNewDocShell, std::move(pUndoDocument), nullptr ),
    mnPaintExtFlags( 0 ),
    aSrcRange( rRange ),
    bCut( bNewCut ),
    bKeepScenarioFlags( bScenario )
{
    ScAddress aDestEnd(aNewDestPos);
    aDestEnd.IncRow(aSrcRange.aEnd.Row() - aSrcRange.aStart.Row());
    aDestEnd.IncCol(aSrcRange.aEnd.Col() - aSrcRange.aStart.Col());
    aDestEnd.IncTab(aSrcRange.aEnd.Tab() - aSrcRange.aStart.Tab());

    bool bIncludeFiltered = bCut;
    if ( !bIncludeFiltered )
    {
        // find number of non-filtered rows
        SCROW nPastedCount = rDocShell.GetDocument().CountNonFilteredRows(
            aSrcRange.aStart.Row(), aSrcRange.aEnd.Row(), aSrcRange.aStart.Tab());

        if ( nPastedCount == 0 )
            nPastedCount = 1;
        aDestEnd.SetRow( aNewDestPos.Row() + nPastedCount - 1 );
    }

    aDestRange.aStart = aNewDestPos;
    aDestRange.aEnd = aDestEnd;

    SetChangeTrack();
}

ScUndoDragDrop::~ScUndoDragDrop()
{
}

OUString ScUndoDragDrop::GetComment() const
{   // "Move" : "Copy"
    return bCut ?
        ScResId( STR_UNDO_MOVE ) :
        ScResId( STR_UNDO_COPY );
}

void ScUndoDragDrop::SetChangeTrack()
{
    ScChangeTrack* pChangeTrack = rDocShell.GetDocument().GetChangeTrack();
    if ( pChangeTrack )
    {
        if ( bCut )
        {
            nStartChangeAction = pChangeTrack->GetActionMax() + 1;
            pChangeTrack->AppendMove( aSrcRange, aDestRange, pRefUndoDoc.get() );
            nEndChangeAction = pChangeTrack->GetActionMax();
        }
        else
            pChangeTrack->AppendContentRange( aDestRange, *pRefUndoDoc,
                nStartChangeAction, nEndChangeAction );
    }
    else
        nStartChangeAction = nEndChangeAction = 0;
}

void ScUndoDragDrop::PaintArea( ScRange aRange, sal_uInt16 nExtFlags ) const
{
    PaintPartFlags nPaint = PaintPartFlags::Grid;
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    ScDocument& rDoc = rDocShell.GetDocument();

    if (pViewShell)
    {
        ScopedVclPtrInstance< VirtualDevice > pVirtDev;
        ScViewData& rViewData = pViewShell->GetViewData();
        sc::RowHeightContext aCxt(
            rDoc.MaxRow(), rViewData.GetPPTX(), rViewData.GetPPTY(), rViewData.GetZoomX(), rViewData.GetZoomY(),
            pVirtDev);

        if (rDoc.SetOptimalHeight(aCxt, aRange.aStart.Row(), aRange.aEnd.Row(), aRange.aStart.Tab(), true))
        {
            // tdf#76183: recalculate objects' positions
            rDoc.SetDrawPageSize(aRange.aStart.Tab());
            aRange.aStart.SetCol(0);
            aRange.aEnd.SetCol(rDoc.MaxCol());
            aRange.aEnd.SetRow(rDoc.MaxRow());
            nPaint |= PaintPartFlags::Left;
        }
    }

    if ( bKeepScenarioFlags )
    {
        //  Copy scenario -> also paint scenario boarder
        aRange.aStart.SetCol(0);
        aRange.aStart.SetRow(0);
        aRange.aEnd.SetCol(rDoc.MaxCol());
        aRange.aEnd.SetRow(rDoc.MaxRow());
    }

    //  column/row info (width/height) included if whole columns/rows were copied
    if ( aSrcRange.aStart.Col() == 0 && aSrcRange.aEnd.Col() == rDoc.MaxCol() )
    {
        nPaint |= PaintPartFlags::Left;
        aRange.aEnd.SetRow(rDoc.MaxRow());
    }
    if ( aSrcRange.aStart.Row() == 0 && aSrcRange.aEnd.Row() == rDoc.MaxRow() )
    {
        nPaint |= PaintPartFlags::Top;
        aRange.aEnd.SetCol(rDoc.MaxCol());
    }

    rDocShell.PostPaint( aRange, nPaint, nExtFlags );
}

void ScUndoDragDrop::DoUndo( ScRange aRange )
{
    ScDocument& rDoc = rDocShell.GetDocument();

    ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
    if ( pChangeTrack )
        pChangeTrack->Undo( nStartChangeAction, nEndChangeAction );

    // Database range before data, so that the Autofilter button match up in ExtendMerge

    ScRange aPaintRange = aRange;
    rDoc.ExtendMerge( aPaintRange );           // before deleting

    rDocShell.UpdatePaintExt(mnPaintExtFlags, aPaintRange);

    // do not undo objects and note captions, they are handled via drawing undo
    InsertDeleteFlags nUndoFlags = (InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS) | InsertDeleteFlags::NOCAPTIONS;

    // Additionally discard/forget caption ownership during deletion, as
    // Drag&Drop is a special case in that the Undo holds captions of the
    // transferred target range, which would get deleted and
    // SdrGroupUndo::Undo() would attempt to access invalidated captions and
    // crash, tdf#92995
    InsertDeleteFlags nDelFlags = nUndoFlags | InsertDeleteFlags::FORGETCAPTIONS;

    rDoc.DeleteAreaTab( aRange, nDelFlags );
    pRefUndoDoc->CopyToDocument(aRange, nUndoFlags, false, rDoc);
    if ( rDoc.HasAttrib( aRange, HasAttrFlags::Merged ) )
        rDoc.ExtendMerge( aRange, true );

    aPaintRange.aEnd.SetCol( std::max( aPaintRange.aEnd.Col(), aRange.aEnd.Col() ) );
    aPaintRange.aEnd.SetRow( std::max( aPaintRange.aEnd.Row(), aRange.aEnd.Row() ) );

    rDocShell.UpdatePaintExt(mnPaintExtFlags, aPaintRange);
    maPaintRanges.Join(aPaintRange);

    if (ScTabViewShell* pTabViewShell = ScTabViewShell::GetActiveViewShell())
    {
        if (comphelper::LibreOfficeKit::isActive())
        {
            pTabViewShell->OnLOKSetWidthOrHeight(aPaintRange.aStart.Col(), true);
            pTabViewShell->OnLOKSetWidthOrHeight(aPaintRange.aStart.Row(), false);
        }

        ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
            pTabViewShell,
            true /* bColumns */, true /* bRows */,
            true /* bSizes */, true /* bHidden */, true /* bFiltered */,
            true /* bGroups */, aPaintRange.aStart.Tab());
    }

}

void ScUndoDragDrop::Undo()
{
    mnPaintExtFlags = 0;
    maPaintRanges.RemoveAll();

    BeginUndo();

    if (bCut)
    {
        // During undo, we move cells from aDestRange to aSrcRange.

        ScDocument& rDoc = rDocShell.GetDocument();

        SCCOL nColDelta = aSrcRange.aStart.Col() - aDestRange.aStart.Col();
        SCROW nRowDelta = aSrcRange.aStart.Row() - aDestRange.aStart.Row();
        SCTAB nTabDelta = aSrcRange.aStart.Tab() - aDestRange.aStart.Tab();

        sc::RefUpdateContext aCxt(rDoc);
        aCxt.meMode = URM_MOVE;
        aCxt.maRange = aSrcRange;
        aCxt.mnColDelta = nColDelta;
        aCxt.mnRowDelta = nRowDelta;
        aCxt.mnTabDelta = nTabDelta;

        // Global range names.
        ScRangeName* pName = rDoc.GetRangeName();
        if (pName)
            pName->UpdateReference(aCxt);

        SCTAB nTabCount = rDoc.GetTableCount();
        for (SCTAB nTab = 0; nTab < nTabCount; ++nTab)
        {
            // Sheet-local range names.
            pName = rDoc.GetRangeName(nTab);
            if (pName)
                pName->UpdateReference(aCxt, nTab);
        }

        ScValidationDataList* pValidList = rDoc.GetValidationList();
        if (pValidList)
        {
            // Update the references of validation entries.
            pValidList->UpdateReference(aCxt);
        }

        DoUndo(aDestRange);
        DoUndo(aSrcRange);

        rDoc.BroadcastCells(aSrcRange, SfxHintId::ScDataChanged, false);
    }
    else
        DoUndo(aDestRange);

    for (size_t i = 0; i < maPaintRanges.size(); ++i)
    {
        const ScRange& r = maPaintRanges[i];
        PaintArea(r, mnPaintExtFlags);
    }

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

void ScUndoDragDrop::Redo()
{
    BeginRedo();

    ScDocument& rDoc = rDocShell.GetDocument();
    ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP ));

    EnableDrawAdjust( &rDoc, false );                //! include in ScBlockUndo?

    // do not undo/redo objects and note captions, they are handled via drawing undo
    constexpr InsertDeleteFlags nRedoFlags = (InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS) | InsertDeleteFlags::NOCAPTIONS;

    /*  TODO: Redoing note captions is quite tricky due to the fact that a
        helper clip document is used. While (re-)pasting the contents to the
        destination area, the original pointers to the captions created while
        dropping have to be restored. A simple CopyFromClip() would create new
        caption objects that are not tracked by drawing undo, and the captions
        restored by drawing redo would live without cell note objects pointing
        to them. So, first, CopyToClip() and CopyFromClip() are called without
        cloning the caption objects. This leads to cell notes pointing to the
        wrong captions from source area that will be removed by drawing redo
        later. Second, the pointers to the new captions have to be restored.
        Sadly, currently these pointers are not stored anywhere but in the list
        of drawing undo actions. */

    SCTAB nTab;
    ScMarkData aSourceMark(rDoc.GetSheetLimits());
    for (nTab=aSrcRange.aStart.Tab(); nTab<=aSrcRange.aEnd.Tab(); nTab++)
        aSourceMark.SelectTable( nTab, true );

    // do not clone objects and note captions into clipdoc (see above)
    // but at least copy notes
    ScClipParam aClipParam(aSrcRange, bCut);
    rDoc.CopyToClip(aClipParam, pClipDoc.get(), &aSourceMark, bKeepScenarioFlags, false);

    if (bCut)
    {
        ScRange aSrcPaintRange = aSrcRange;
        rDoc.ExtendMerge( aSrcPaintRange );            // before deleting
        sal_uInt16 nExtFlags = 0;
        rDocShell.UpdatePaintExt( nExtFlags, aSrcPaintRange );
        rDoc.DeleteAreaTab( aSrcRange, nRedoFlags );
        PaintArea( aSrcPaintRange, nExtFlags );
    }

    ScMarkData aDestMark(rDoc.GetSheetLimits());
    for (nTab=aDestRange.aStart.Tab(); nTab<=aDestRange.aEnd.Tab(); nTab++)
        aDestMark.SelectTable( nTab, true );

    bool bIncludeFiltered = bCut;
    // TODO: restore old note captions instead of cloning new captions...
    rDoc.CopyFromClip( aDestRange, aDestMark, InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS, nullptr, pClipDoc.get(), true, false, bIncludeFiltered );

    if (bCut)
        for (nTab=aSrcRange.aStart.Tab(); nTab<=aSrcRange.aEnd.Tab(); nTab++)
            rDoc.RefreshAutoFilter( aSrcRange.aStart.Col(), aSrcRange.aStart.Row(),
                                     aSrcRange.aEnd.Col(),   aSrcRange.aEnd.Row(), nTab );

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

    for (nTab=aDestRange.aStart.Tab(); nTab<=aDestRange.aEnd.Tab(); nTab++)
    {
        SCCOL nEndCol = aDestRange.aEnd.Col();
        SCROW nEndRow = aDestRange.aEnd.Row();
        rDoc.ExtendMerge( aDestRange.aStart.Col(), aDestRange.aStart.Row(),
                            nEndCol, nEndRow, nTab, true );
        PaintArea( ScRange( aDestRange.aStart.Col(), aDestRange.aStart.Row(), nTab,
                            nEndCol, nEndRow, nTab ), 0 );
    }

    SetChangeTrack();

    pClipDoc.reset();
    ShowTable( aDestRange.aStart.Tab() );

    RedoSdrUndoAction( pDrawUndo.get() );        //! include in ScBlockUndo?
    EnableDrawAdjust( &rDoc, true );             //! include in ScBlockUndo?

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

    if (comphelper::LibreOfficeKit::isActive())
    {
        SCCOL nStartCol = aDestRange.aStart.Col();
        SCROW nStartRow = aDestRange.aStart.Row();
        if (bCut)
        {
            nStartCol = std::min(nStartCol, aSrcRange.aStart.Col());
            nStartRow = std::min(nStartRow, aSrcRange.aStart.Row());
        }

        if (ScTabViewShell* pTabViewShell = ScTabViewShell::GetActiveViewShell())
        {
            pTabViewShell->OnLOKSetWidthOrHeight(nStartCol, true);
            pTabViewShell->OnLOKSetWidthOrHeight(nStartRow, false);

            SCTAB nStartTab = aDestRange.aStart.Tab();
            SCTAB nEndTab = aDestRange.aEnd.Tab();
            if (bCut)
            {
                nStartTab = std::min(nStartTab, aSrcRange.aStart.Tab());
                nEndTab = std::max(nEndTab, aSrcRange.aEnd.Tab());
            }
            for (nTab = nStartTab; nTab <= nEndTab; ++nTab)
            {
                ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
                    pTabViewShell, true /* bColumns */, true /* bRows */,
                    true /* bSizes */, true /* bHidden */, true /* bFiltered */, true /* bGroups */,
                    nTab);
            }
        }
    }
}

void ScUndoDragDrop::Repeat(SfxRepeatTarget& /* rTarget */)
{
}

bool ScUndoDragDrop::CanRepeat(SfxRepeatTarget& /* rTarget */) const
{
    return false;           // not possible
}

// Insert list containing range names
// (Insert|Name|Insert =>[List])
ScUndoListNames::ScUndoListNames(ScDocShell& rNewDocShell, const ScRange& rRange,
                                 ScDocumentUniquePtr pNewUndoDoc, ScDocumentUniquePtr pNewRedoDoc)
    : ScBlockUndo(rNewDocShell, rRange, SC_UNDO_AUTOHEIGHT)
    , xUndoDoc(std::move(pNewUndoDoc))
    , xRedoDoc(std::move(pNewRedoDoc))
{
}

OUString ScUndoListNames::GetComment() const
{
    return ScResId( STR_UNDO_LISTNAMES );
}

void ScUndoListNames::DoChange( ScDocument* pSrcDoc ) const
{
    ScDocument& rDoc = rDocShell.GetDocument();

    rDoc.DeleteAreaTab( aBlockRange, InsertDeleteFlags::ALL );
    pSrcDoc->CopyToDocument(aBlockRange, InsertDeleteFlags::ALL, false, rDoc);
    rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid );
    rDocShell.PostDataChanged();
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
        pViewShell->CellContentChanged();
}

void ScUndoListNames::Undo()
{
    BeginUndo();
    DoChange(xUndoDoc.get());
    EndUndo();
}

void ScUndoListNames::Redo()
{
    BeginRedo();
    DoChange(xRedoDoc.get());
    EndRedo();
}

void ScUndoListNames::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pTabViewTarget = dynamic_cast<ScTabViewTarget*>(&rTarget))
        pTabViewTarget->GetViewShell().InsertNameList();
}

bool ScUndoListNames::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

ScUndoConditionalFormat::ScUndoConditionalFormat(ScDocShell& rNewDocShell, SCTAB nTab):
    ScSimpleUndo( rNewDocShell ),
    mnTab(nTab),
    mpUndoDoc(createUndoRedoData())
{
}

ScUndoConditionalFormat::~ScUndoConditionalFormat()
{
}

ScDocumentUniquePtr ScUndoConditionalFormat::createUndoRedoData()
{
    ScDocument& rDoc = rDocShell.GetDocument();
    ScDocumentUniquePtr pUndoRedoDoc(new ScDocument(SCDOCMODE_UNDO));
    pUndoRedoDoc->InitUndo(rDoc, mnTab, mnTab);
    if (const auto* pList = rDoc.GetCondFormList(mnTab))
        pUndoRedoDoc->SetCondFormList(pList->Clone(*pUndoRedoDoc), mnTab);
    return pUndoRedoDoc;
}

OUString ScUndoConditionalFormat::GetComment() const
{
    return ScResId( STR_UNDO_CONDFORMAT );
}

void ScUndoConditionalFormat::Undo()
{
    DoChange(mpUndoDoc.get());
}

void ScUndoConditionalFormat::Redo()
{
    DoChange(mpRedoDoc.get());
}

void ScUndoConditionalFormat::DoChange(const ScDocument* pSrcDoc)
{
    ScDocument& rDoc = rDocShell.GetDocument();

    // Restore all conditional formats in the tab. This is simpler and more reliable, than
    // restoring formats in a specific range, and then trying to join selectively the restored
    // formats with the other formats in the tab, to get the correct state.
    ScRangeList aCombinedRange;
    if (const auto* pOldList = rDoc.GetCondFormList(mnTab))
    {
        aCombinedRange = pOldList->GetCombinedRange();
        // Clear all existing CF keys from cells' attributes
        for (auto& pFormat : *pOldList)
            rDoc.RemoveCondFormatData(aCombinedRange, mnTab, pFormat->GetKey());
    }

    if (const auto* pNewList = pSrcDoc->GetCondFormList(mnTab))
    {
        for (const auto& cond : *pNewList)
        {
            // Restore the CF keys to cell attributes
            rDoc.AddCondFormatData(cond->GetRange(), mnTab, cond->GetKey());
            for (const auto& range : cond->GetRange())
                aCombinedRange.Join(range);
        }
        rDoc.SetCondFormList(pNewList->Clone(rDoc), mnTab);
    }
    else
    {
        rDoc.SetCondFormList(nullptr, mnTab);
    }
    rDocShell.PostPaint(aCombinedRange, PaintPartFlags::Grid);
    rDocShell.PostDataChanged();
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
        pViewShell->CellContentChanged();
}

void ScUndoConditionalFormat::Repeat(SfxRepeatTarget& )
{
}

bool ScUndoConditionalFormat::CanRepeat(SfxRepeatTarget& ) const
{
    return false;
}

ScUndoConditionalFormatList::ScUndoConditionalFormatList(ScDocShell& rNewDocShell,
        ScDocumentUniquePtr pUndoDoc, ScDocumentUniquePtr pRedoDoc, SCTAB nTab):
    ScSimpleUndo( rNewDocShell ),
    mpUndoDoc(std::move(pUndoDoc)),
    mpRedoDoc(std::move(pRedoDoc)),
    mnTab(nTab)
{
}

ScUndoConditionalFormatList::~ScUndoConditionalFormatList()
{
}

OUString ScUndoConditionalFormatList::GetComment() const
{
    return ScResId( STR_UNDO_CONDFORMAT_LIST );
}

void ScUndoConditionalFormatList::Undo()
{
    DoChange(mpUndoDoc.get());
}

void ScUndoConditionalFormatList::Redo()
{
    DoChange(mpRedoDoc.get());
}

void ScUndoConditionalFormatList::DoChange(const ScDocument* pSrcDoc)
{
    ScDocument& rDoc = rDocShell.GetDocument();

    if (pSrcDoc == mpUndoDoc.get())
    {
        mpRedoDoc->GetCondFormList(mnTab)->RemoveFromDocument(rDoc);
        mpUndoDoc->GetCondFormList(mnTab)->AddToDocument(rDoc);
    }
    else
    {
        mpUndoDoc->GetCondFormList(mnTab)->RemoveFromDocument(rDoc);
        mpRedoDoc->GetCondFormList(mnTab)->AddToDocument(rDoc);
    }
    rDoc.SetCondFormList(pSrcDoc->GetCondFormList(mnTab)->Clone(rDoc), mnTab);

    rDocShell.PostPaintGridAll();
    rDocShell.PostDataChanged();
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
        pViewShell->CellContentChanged();
}

void ScUndoConditionalFormatList::Repeat(SfxRepeatTarget& )
{
}

bool ScUndoConditionalFormatList::CanRepeat(SfxRepeatTarget& ) const
{
    return false;
}

ScUndoUseScenario::ScUndoUseScenario( ScDocShell& rNewDocShell,
                        const ScMarkData& rMark,
                        const ScArea& rDestArea,
                              ScDocumentUniquePtr pNewUndoDoc,
                        OUString aNewName ) :
    ScSimpleUndo( rNewDocShell ),
    pUndoDoc( std::move(pNewUndoDoc) ),
    aMarkData( rMark ),
    aName(std::move( aNewName ))
{
    aRange.aStart.SetCol(rDestArea.nColStart);
    aRange.aStart.SetRow(rDestArea.nRowStart);
    aRange.aStart.SetTab(rDestArea.nTab);
    aRange.aEnd.SetCol(rDestArea.nColEnd);
    aRange.aEnd.SetRow(rDestArea.nRowEnd);
    aRange.aEnd.SetTab(rDestArea.nTab);
}

ScUndoUseScenario::~ScUndoUseScenario()
{
}

OUString ScUndoUseScenario::GetComment() const
{
    return ScResId( STR_UNDO_USESCENARIO );
}

void ScUndoUseScenario::Undo()
{
    BeginUndo();

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
    {
        pViewShell->DoneBlockMode();
        pViewShell->InitOwnBlockMode( aRange );
    }

    ScDocument& rDoc = rDocShell.GetDocument();
    rDoc.DeleteSelection( InsertDeleteFlags::ALL, aMarkData );
    pUndoDoc->CopyToDocument(aRange, InsertDeleteFlags::ALL, true, rDoc, &aMarkData);

    // scenario table
    bool bFrame = false;
    SCTAB nTab = aRange.aStart.Tab();
    SCTAB nEndTab = nTab;
    while ( pUndoDoc->HasTable(nEndTab+1) && pUndoDoc->IsScenario(nEndTab+1) )
        ++nEndTab;
    for (SCTAB i = nTab+1; i<=nEndTab; i++)
    {
        // Flags always
        OUString aComment;
        Color  aColor;
        ScScenarioFlags nScenFlags;
        pUndoDoc->GetScenarioData( i, aComment, aColor, nScenFlags );
        rDoc.SetScenarioData( i, aComment, aColor, nScenFlags );
        bool bActive = pUndoDoc->IsActiveScenario( i );
        rDoc.SetActiveScenario( i, bActive );
        //  For copy-back scenario also consider content
        if ( nScenFlags & ScScenarioFlags::TwoWay )
        {
            rDoc.DeleteAreaTab( 0,0, rDoc.MaxCol(),rDoc.MaxRow(), i, InsertDeleteFlags::ALL );
            pUndoDoc->CopyToDocument(0,0,i, rDoc.MaxCol(),rDoc.MaxRow(),i, InsertDeleteFlags::ALL,false, rDoc);
        }
        if ( nScenFlags & ScScenarioFlags::ShowFrame )
            bFrame = true;
    }

    // if visible borders, then paint all
    if (bFrame)
        rDocShell.PostPaint( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, PaintPartFlags::Grid | PaintPartFlags::Extras );
    else
        rDocShell.PostPaint( aRange, PaintPartFlags::Grid | PaintPartFlags::Extras );
    rDocShell.PostDataChanged();
    if (pViewShell)
        pViewShell->CellContentChanged();

    ShowTable( aRange.aStart.Tab() );

    EndUndo();
}

void ScUndoUseScenario::Redo()
{
    SCTAB nTab = aRange.aStart.Tab();
    BeginRedo();

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
    {
        pViewShell->SetTabNo( nTab );
        pViewShell->DoneBlockMode();
        pViewShell->InitOwnBlockMode( aRange );
    }

    rDocShell.UseScenario( nTab, aName, false );

    EndRedo();
}

void ScUndoUseScenario::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
    {
        OUString aTemp = aName;
        pViewTarget->GetViewShell().UseScenario(aTemp);
    }
}

bool ScUndoUseScenario::CanRepeat(SfxRepeatTarget& rTarget) const
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
    {
        ScViewData& rViewData = pViewTarget->GetViewShell().GetViewData();
        return !rViewData.GetDocument().IsScenario( rViewData.CurrentTabForData() );
    }
    return false;
}

ScUndoSelectionStyle::ScUndoSelectionStyle( ScDocShell& rNewDocShell,
                                      const ScMarkData& rMark,
                                      const ScRange& rRange,
                                      OUString aName,
                                            ScDocumentUniquePtr pNewUndoDoc ) :
    ScSimpleUndo( rNewDocShell ),
    aMarkData( rMark ),
    pUndoDoc( std::move(pNewUndoDoc) ),
    aStyleName(std::move( aName )),
    aRange( rRange )
{
    aMarkData.MarkToMulti();
}

ScUndoSelectionStyle::~ScUndoSelectionStyle()
{
}

OUString ScUndoSelectionStyle::GetComment() const
{
    return ScResId( STR_UNDO_APPLYCELLSTYLE );
}

void ScUndoSelectionStyle::DoChange( const bool bUndo )
{
    ScDocument& rDoc = rDocShell.GetDocument();

    SetViewMarkData( aMarkData );

    ScRange aWorkRange( aRange );
    if ( rDoc.HasAttrib( aWorkRange, HasAttrFlags::Merged ) )        // Merged cells?
        rDoc.ExtendMerge( aWorkRange, true );

    sal_uInt16 nExtFlags = 0;
    rDocShell.UpdatePaintExt( nExtFlags, aWorkRange );

    if (bUndo)      // if Undo then push back all old data again
    {
        SCTAB nTabCount = rDoc.GetTableCount();
        ScRange aCopyRange = aWorkRange;
        aCopyRange.aStart.SetTab(0);
        aCopyRange.aEnd.SetTab(nTabCount-1);
        pUndoDoc->CopyToDocument(aCopyRange, InsertDeleteFlags::ATTRIB, true, rDoc, &aMarkData);
    }
    else            // if Redo, then reapply style
    {
        ScStyleSheetPool* pStlPool = rDoc.GetStyleSheetPool();
        ScStyleSheet* pStyleSheet =
            static_cast<ScStyleSheet*>( pStlPool->Find( aStyleName, SfxStyleFamily::Para ) );
        if (!pStyleSheet)
        {
            OSL_FAIL("StyleSheet not found");
            return;
        }
        rDoc.ApplySelectionStyle( *pStyleSheet, aMarkData );
    }

    rDocShell.UpdatePaintExt( nExtFlags, aWorkRange );

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if ( !( pViewShell && pViewShell->AdjustBlockHeight() ) )
/*A*/   rDocShell.PostPaint( aWorkRange, PaintPartFlags::Grid | PaintPartFlags::Extras, nExtFlags );

    ShowTable( aWorkRange.aStart.Tab() );
}

void ScUndoSelectionStyle::Undo()
{
    BeginUndo();
    DoChange( true );
    EndUndo();
}

void ScUndoSelectionStyle::Redo()
{
    BeginRedo();
    DoChange( false );
    EndRedo();
}

void ScUndoSelectionStyle::Repeat(SfxRepeatTarget& rTarget)
{
    if (dynamic_cast<const ScTabViewTarget*>( &rTarget) ==  nullptr)
        return;

    ScDocument& rDoc = rDocShell.GetDocument();
    ScStyleSheetPool* pStlPool = rDoc.GetStyleSheetPool();
    ScStyleSheet* pStyleSheet = static_cast<ScStyleSheet*>( pStlPool->
                                        Find( aStyleName, SfxStyleFamily::Para ));
    if (!pStyleSheet)
    {
        OSL_FAIL("StyleSheet not found");
        return;
    }

    ScTabViewShell& rViewShell = static_cast<ScTabViewTarget&>(rTarget).GetViewShell();
    rViewShell.SetStyleSheetToMarked( pStyleSheet );
}

bool ScUndoSelectionStyle::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

ScUndoEnterMatrix::ScUndoEnterMatrix( ScDocShell& rNewDocShell, const ScRange& rArea,
                                      ScDocumentUniquePtr pNewUndoDoc, OUString aForm ) :
    ScBlockUndo( rNewDocShell, rArea, SC_UNDO_SIMPLE ),
    pUndoDoc( std::move(pNewUndoDoc) ),
    aFormula(std::move( aForm ))
{
    SetChangeTrack();
}

ScUndoEnterMatrix::~ScUndoEnterMatrix()
{
}

OUString ScUndoEnterMatrix::GetComment() const
{
    return ScResId( STR_UNDO_ENTERMATRIX );
}

void ScUndoEnterMatrix::SetChangeTrack()
{
    ScDocument& rDoc = rDocShell.GetDocument();
    ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
    if ( pChangeTrack )
        pChangeTrack->AppendContentRange( aBlockRange, *pUndoDoc,
            nStartChangeAction, nEndChangeAction );
    else
        nStartChangeAction = nEndChangeAction = 0;
}

void ScUndoEnterMatrix::Undo()
{
    BeginUndo();

    ScDocument& rDoc = rDocShell.GetDocument();

    rDoc.DeleteAreaTab( aBlockRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE );
    pUndoDoc->CopyToDocument(aBlockRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, rDoc);
    rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid );
    rDocShell.PostDataChanged();
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
        pViewShell->CellContentChanged();

    ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
    if ( pChangeTrack )
        pChangeTrack->Undo( nStartChangeAction, nEndChangeAction );

    EndUndo();
}

void ScUndoEnterMatrix::Redo()
{
    BeginRedo();

    ScDocument& rDoc = rDocShell.GetDocument();

    ScMarkData aDestMark(rDoc.GetSheetLimits());
    aDestMark.SelectOneTable( aBlockRange.aStart.Tab() );
    aDestMark.SetMarkArea( aBlockRange );

    rDoc.InsertMatrixFormula( aBlockRange.aStart.Col(), aBlockRange.aStart.Row(),
                               aBlockRange.aEnd.Col(),   aBlockRange.aEnd.Row(),
                               aDestMark, aFormula );

    SetChangeTrack();

    EndRedo();
}

void ScUndoEnterMatrix::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pTabViewTarget = dynamic_cast<ScTabViewTarget*>(&rTarget))
    {
        OUString aTemp = aFormula;
        ScDocument& rDoc = rDocShell.GetDocument();
        pTabViewTarget->GetViewShell().EnterMatrix(aTemp, rDoc.GetGrammar());
    }
}

bool ScUndoEnterMatrix::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

static ScRange lcl_GetMultiMarkRange( const ScMarkData& rMark )
{
    OSL_ENSURE( rMark.IsMultiMarked(), "wrong mark type" );
    return rMark.GetMultiMarkArea();
}

ScUndoIndent::ScUndoIndent( ScDocShell& rNewDocShell, const ScMarkData& rMark,
                            ScDocumentUniquePtr pNewUndoDoc, bool bIncrement ) :
    ScBlockUndo( rNewDocShell, lcl_GetMultiMarkRange(rMark), SC_UNDO_AUTOHEIGHT ),
    aMarkData( rMark ),
    pUndoDoc( std::move(pNewUndoDoc) ),
    bIsIncrement( bIncrement )
{
}

ScUndoIndent::~ScUndoIndent()
{
}

OUString ScUndoIndent::GetComment() const
{
    TranslateId pId = bIsIncrement ? STR_UNDO_INC_INDENT : STR_UNDO_DEC_INDENT;
    return ScResId(pId);
}

void ScUndoIndent::Undo()
{
    BeginUndo();

    ScDocument& rDoc = rDocShell.GetDocument();
    SCTAB nTabCount = rDoc.GetTableCount();
    ScRange aCopyRange = aBlockRange;
    aCopyRange.aStart.SetTab(0);
    aCopyRange.aEnd.SetTab(nTabCount-1);
    pUndoDoc->CopyToDocument(aCopyRange, InsertDeleteFlags::ATTRIB, true, rDoc, &aMarkData);
    rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE );

    EndUndo();
}

void ScUndoIndent::Redo()
{
    BeginRedo();

    ScDocument& rDoc = rDocShell.GetDocument();
    rDoc.ChangeSelectionIndent( bIsIncrement, aMarkData );
    rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE );

    EndRedo();
}

void ScUndoIndent::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
        pViewTarget->GetViewShell().ChangeIndent( bIsIncrement );
}

bool ScUndoIndent::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

ScUndoTransliterate::ScUndoTransliterate( ScDocShell& rNewDocShell, const ScMarkData& rMark,
                            ScDocumentUniquePtr pNewUndoDoc, TransliterationFlags nType ) :
    ScBlockUndo( rNewDocShell, lcl_GetMultiMarkRange(rMark), SC_UNDO_AUTOHEIGHT ),
    aMarkData( rMark ),
    pUndoDoc( std::move(pNewUndoDoc) ),
    nTransliterationType( nType )
{
}

ScUndoTransliterate::~ScUndoTransliterate()
{
}

OUString ScUndoTransliterate::GetComment() const
{
    return ScResId( STR_UNDO_TRANSLITERATE );
}

void ScUndoTransliterate::Undo()
{
    BeginUndo();

    ScDocument& rDoc = rDocShell.GetDocument();
    SCTAB nTabCount = rDoc.GetTableCount();
    ScRange aCopyRange = aBlockRange;
    aCopyRange.aStart.SetTab(0);
    aCopyRange.aEnd.SetTab(nTabCount-1);
    pUndoDoc->CopyToDocument(aCopyRange, InsertDeleteFlags::CONTENTS, true, rDoc, &aMarkData);
    rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE );

    EndUndo();
}

void ScUndoTransliterate::Redo()
{
    BeginRedo();

    ScDocument& rDoc = rDocShell.GetDocument();
    rDoc.TransliterateText( aMarkData, nTransliterationType );
    rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE );

    EndRedo();
}

void ScUndoTransliterate::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
        pViewTarget->GetViewShell().TransliterateText( nTransliterationType );
}

bool ScUndoTransliterate::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

ScUndoClearItems::ScUndoClearItems( ScDocShell& rNewDocShell, const ScMarkData& rMark,
                            ScDocumentUniquePtr pNewUndoDoc, const sal_uInt16* pW ) :
    ScBlockUndo( rNewDocShell, lcl_GetMultiMarkRange(rMark), SC_UNDO_AUTOHEIGHT ),
    aMarkData( rMark ),
    pUndoDoc( std::move(pNewUndoDoc) )
{
    assert(pW && "ScUndoClearItems: Which-Pointer is Null");

    sal_uInt16 nCount = 0;
    while ( pW[nCount] )
        ++nCount;
    pWhich.reset( new sal_uInt16[nCount+1] );
    for (sal_uInt16 i=0; i<=nCount; i++)
        pWhich[i] = pW[i];
}

ScUndoClearItems::~ScUndoClearItems()
{
}

OUString ScUndoClearItems::GetComment() const
{
    return ScResId( STR_UNDO_DELETECONTENTS );
}

void ScUndoClearItems::Undo()
{
    BeginUndo();

    ScDocument& rDoc = rDocShell.GetDocument();
    pUndoDoc->CopyToDocument(aBlockRange, InsertDeleteFlags::ATTRIB, true, rDoc, &aMarkData);
    rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE );

    EndUndo();
}

void ScUndoClearItems::Redo()
{
    BeginRedo();

    ScDocument& rDoc = rDocShell.GetDocument();
    rDoc.ClearSelectionItems( pWhich.get(), aMarkData );
    rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE );

    EndRedo();
}

void ScUndoClearItems::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
    {
        ScViewData& rViewData = pViewTarget->GetViewShell().GetViewData();
        rViewData.GetDocFunc().ClearItems( rViewData.GetMarkData(), pWhich.get(), false );
    }
}

bool ScUndoClearItems::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

// remove all line breaks of a table
ScUndoRemoveBreaks::ScUndoRemoveBreaks( ScDocShell& rNewDocShell,
                                    SCTAB nNewTab, ScDocumentUniquePtr pNewUndoDoc ) :
    ScSimpleUndo( rNewDocShell ),
    nTab( nNewTab ),
    pUndoDoc( std::move(pNewUndoDoc) )
{
}

ScUndoRemoveBreaks::~ScUndoRemoveBreaks()
{
}

OUString ScUndoRemoveBreaks::GetComment() const
{
    return ScResId( STR_UNDO_REMOVEBREAKS );
}

void ScUndoRemoveBreaks::Undo()
{
    BeginUndo();

    ScDocument& rDoc = rDocShell.GetDocument();
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();

    pUndoDoc->CopyToDocument(0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, InsertDeleteFlags::NONE, false, rDoc);
    if (pViewShell)
        pViewShell->UpdatePageBreakData( true );
    rDocShell.PostPaint( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, PaintPartFlags::Grid );

    EndUndo();
}

void ScUndoRemoveBreaks::Redo()
{
    BeginRedo();

    ScDocument& rDoc = rDocShell.GetDocument();
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();

    rDoc.RemoveManualBreaks(nTab);
    rDoc.UpdatePageBreaks(nTab);
    if (pViewShell)
        pViewShell->UpdatePageBreakData( true );
    rDocShell.PostPaint( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, PaintPartFlags::Grid );

    EndRedo();
}

void ScUndoRemoveBreaks::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
    {
        ScTabViewShell& rViewShell = pViewTarget->GetViewShell();
        rViewShell.RemoveManualBreaks();
    }
}

bool ScUndoRemoveBreaks::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

ScUndoRemoveMerge::ScUndoRemoveMerge( ScDocShell& rNewDocShell,
                                      const ScCellMergeOption& rOption, ScDocumentUniquePtr pNewUndoDoc ) :
    ScBlockUndo( rNewDocShell, rOption.getFirstSingleRange(), SC_UNDO_SIMPLE ),
    pUndoDoc( std::move(pNewUndoDoc) )
{
    maOptions.push_back( rOption);
}

ScUndoRemoveMerge::ScUndoRemoveMerge( ScDocShell& rNewDocShell,
                                      const ScRange& rRange, ScDocumentUniquePtr pNewUndoDoc ) :
    ScBlockUndo( rNewDocShell, rRange, SC_UNDO_SIMPLE ),
    pUndoDoc( std::move(pNewUndoDoc) )
{
}

ScUndoRemoveMerge::~ScUndoRemoveMerge()
{
}

OUString ScUndoRemoveMerge::GetComment() const
{
    return ScResId( STR_UNDO_REMERGE );  // "remove merge"
}

ScDocument* ScUndoRemoveMerge::GetUndoDoc()
{
    return pUndoDoc.get();
}

void ScUndoRemoveMerge::AddCellMergeOption( const ScCellMergeOption& rOption )
{
    maOptions.push_back( rOption);
}

void ScUndoRemoveMerge::Undo()
{
    SetCurTab();
    BeginUndo();

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();

    ScDocument& rDoc = rDocShell.GetDocument();
    for (const auto & rOption : maOptions)
    {
        for (const auto& rTab : rOption.maTabs)
        {
            OSL_ENSURE(pUndoDoc, "NULL pUndoDoc!");
            if (!pUndoDoc)
                continue;
            // There is no need to extend merge area because it's already been extended.
            ScRange aRange = rOption.getSingleRange(rTab);
            rDoc.DeleteAreaTab(aRange, InsertDeleteFlags::ATTRIB);
            pUndoDoc->CopyToDocument(aRange, InsertDeleteFlags::ATTRIB, false, rDoc);

            bool bDidPaint = false;
            if ( pViewShell )
            {
                pViewShell->SetTabNo(rTab);
                bDidPaint = pViewShell->AdjustRowHeight(rOption.mnStartRow, rOption.mnEndRow, true);
            }
            if (!bDidPaint)
                ScUndoUtil::PaintMore(rDocShell, aRange);
        }
    }

    EndUndo();
}

void ScUndoRemoveMerge::Redo()
{
    SetCurTab();
    BeginRedo();

    ScDocument& rDoc = rDocShell.GetDocument();
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();

    for (const auto & rOption : maOptions)
    {
        for (const SCTAB nTab : rOption.maTabs)
        {
            // There is no need to extend merge area because it's already been extended.
            ScRange aRange = rOption.getSingleRange(nTab);

            const SfxPoolItem& rDefAttr = rDoc.GetPool()->GetUserOrPoolDefaultItem( ATTR_MERGE );
            ScPatternAttr aPattern(rDoc.getCellAttributeHelper());
            aPattern.ItemSetPut(rDefAttr);
            rDoc.ApplyPatternAreaTab( rOption.mnStartCol, rOption.mnStartRow,
                    rOption.mnEndCol, rOption.mnEndRow, nTab,
                    aPattern );

            rDoc.RemoveFlagsTab( rOption.mnStartCol, rOption.mnStartRow,
                    rOption.mnEndCol, rOption.mnEndRow, nTab,
                    ScMF::Hor | ScMF::Ver );

            rDoc.ExtendMerge(aRange, true);

            //  Paint

            bool bDidPaint = false;
            if ( pViewShell )
            {
                pViewShell->SetTabNo(nTab);
                bDidPaint = pViewShell->AdjustRowHeight(rOption.mnStartRow, rOption.mnEndRow, true);
            }
            if (!bDidPaint)
                ScUndoUtil::PaintMore(rDocShell, aRange);
        }
    }

    EndRedo();
}

void ScUndoRemoveMerge::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
        pViewTarget->GetViewShell().RemoveMerge();
}

bool ScUndoRemoveMerge::CanRepeat(SfxRepeatTarget& rTarget) const
{
    return dynamic_cast<const ScTabViewTarget*>( &rTarget) !=  nullptr;
}

void ScUndoRemoveMerge::SetCurTab()
{
    SCTAB nCurTab = ScDocShell::GetCurTab();
    aBlockRange.aStart.SetTab(nCurTab);
    aBlockRange.aEnd.SetTab(nCurTab);
}

/** set only border, for ScRangeList (StarOne) */
static ScRange lcl_TotalRange( const ScRangeList& rRanges )
{
    ScRange aTotal;
    if ( !rRanges.empty() )
    {
        aTotal = rRanges[ 0 ];
        for ( size_t i = 1, nCount = rRanges.size(); i < nCount; ++i )
        {
            ScRange const & rRange = rRanges[ i ];
            if (rRange.aStart.Col() < aTotal.aStart.Col()) aTotal.aStart.SetCol(rRange.aStart.Col());
            if (rRange.aStart.Row() < aTotal.aStart.Row()) aTotal.aStart.SetRow(rRange.aStart.Row());
            if (rRange.aStart.Tab() < aTotal.aStart.Tab()) aTotal.aStart.SetTab(rRange.aStart.Tab());
            if (rRange.aEnd.Col()   > aTotal.aEnd.Col()  ) aTotal.aEnd.SetCol(  rRange.aEnd.Col()  );
            if (rRange.aEnd.Row()   > aTotal.aEnd.Row()  ) aTotal.aEnd.SetRow(  rRange.aEnd.Row()  );
            if (rRange.aEnd.Tab()   > aTotal.aEnd.Tab()  ) aTotal.aEnd.SetTab(rRange.aEnd.Tab()    );
        }
    }
    return aTotal;
}

ScUndoBorder::ScUndoBorder(ScDocShell& rNewDocShell,
                           const ScRangeList& rRangeList, ScDocumentUniquePtr pNewUndoDoc,
                           const SvxBoxItem& rNewOuter, const SvxBoxInfoItem& rNewInner)
    : ScBlockUndo(rNewDocShell, lcl_TotalRange(rRangeList), SC_UNDO_SIMPLE)
    , xUndoDoc(std::move(pNewUndoDoc))
{
    xRanges.reset(new ScRangeList(rRangeList));
    xOuter.reset(new SvxBoxItem(rNewOuter));
    xInner.reset(new SvxBoxInfoItem(rNewInner));
}

OUString ScUndoBorder::GetComment() const
{
    return ScResId( STR_UNDO_SELATTRLINES );     //! own string?
}

void ScUndoBorder::Undo()
{
    BeginUndo();

    ScDocument& rDoc = rDocShell.GetDocument();
    ScMarkData aMarkData(rDoc.GetSheetLimits());
    aMarkData.MarkFromRangeList(*xRanges, false);
    xUndoDoc->CopyToDocument(aBlockRange, InsertDeleteFlags::ATTRIB, true, rDoc, &aMarkData);
    rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE );

    EndUndo();
}

void ScUndoBorder::Redo()
{
    BeginRedo();

    ScDocument& rDoc = rDocShell.GetDocument();        // call function at docfunc
    size_t nCount = xRanges->size();
    for (size_t i = 0; i < nCount; ++i )
    {
        ScRange const & rRange = (*xRanges)[i];
        SCTAB nTab = rRange.aStart.Tab();

        ScMarkData aMark(rDoc.GetSheetLimits());
        aMark.SetMarkArea( rRange );
        aMark.SelectTable( nTab, true );

        rDoc.ApplySelectionFrame(aMark, *xOuter, xInner.get());
    }
    for (size_t i = 0; i < nCount; ++i)
        rDocShell.PostPaint( (*xRanges)[i], PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE );

    EndRedo();
}

void ScUndoBorder::Repeat(SfxRepeatTarget& /* rTarget */)
{
    //TODO later (when the function has moved from cellsuno to docfunc)
}

bool ScUndoBorder::CanRepeat(SfxRepeatTarget& /* rTarget */) const
{
    return false;   // See above
}

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