/* -*- 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 <sal/config.h>

#include <memory>

#include <scitems.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/justifyitem.hxx>
#include <svl/srchitem.hxx>
#include <sfx2/linkmgr.hxx>
#include <utility>
#include <vcl/virdev.hxx>
#include <sfx2/app.hxx>
#include <svx/svdundo.hxx>
#include <osl/diagnose.h>

#include <undoblk.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <global.hxx>
#include <arealink.hxx>
#include <patattr.hxx>
#include <target.hxx>
#include <document.hxx>
#include <docpool.hxx>
#include <docsh.hxx>
#include <tabvwsh.hxx>
#include <undoolk.hxx>
#include <undoutil.hxx>
#include <chgtrack.hxx>
#include <paramisc.hxx>
#include <postit.hxx>
#include <progress.hxx>
#include <editutil.hxx>
#include <editdataarray.hxx>
#include <rowheightcontext.hxx>

// TODO:
/*A*/   // SetOptimalHeight on Document, when no View

ScUndoDeleteContents::ScUndoDeleteContents(
                ScDocShell& rNewDocShell,
                const ScMarkData& rMark, const ScRange& rRange,
                ScDocumentUniquePtr&& pNewUndoDoc, bool bNewMulti,
                InsertDeleteFlags nNewFlags, bool bObjects )
    :   ScSimpleUndo( rNewDocShell ),
        aRange      ( rRange ),
        aMarkData   ( rMark ),
        pUndoDoc    ( std::move(pNewUndoDoc) ),
        nFlags      ( nNewFlags ),
        bMulti      ( bNewMulti )   // unnecessary
{
    if (bObjects)
        pDrawUndo = GetSdrUndoAction( &rDocShell.GetDocument() );

    if ( !(aMarkData.IsMarked() || aMarkData.IsMultiMarked()) )     // if no cell is selected:
        aMarkData.SetMarkArea( aRange );                            // select cell under cursor

    SetChangeTrack();
}

ScUndoDeleteContents::~ScUndoDeleteContents()
{
    pUndoDoc.reset();
    pDrawUndo.reset();
}

OUString ScUndoDeleteContents::GetComment() const
{
    return ScResId( STR_UNDO_DELETECONTENTS );    // "Delete"
}

void ScUndoDeleteContents::SetDataSpans( const std::shared_ptr<DataSpansType>& pSpans )
{
    mpDataSpans = pSpans;
}

void ScUndoDeleteContents::SetChangeTrack()
{
    ScChangeTrack* pChangeTrack = rDocShell.GetDocument().GetChangeTrack();
    if ( pChangeTrack && (nFlags & InsertDeleteFlags::CONTENTS) )
        pChangeTrack->AppendContentRange( aRange, *pUndoDoc,
            nStartChangeAction, nEndChangeAction );
    else
        nStartChangeAction = nEndChangeAction = 0;
}

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

    SetViewMarkData( aMarkData );

    sal_uInt16 nExtFlags = 0;

    if (bUndo)  // only Undo
    {
        InsertDeleteFlags nUndoFlags = InsertDeleteFlags::NONE; // copy either all or none of the content
        if (nFlags & InsertDeleteFlags::CONTENTS)        // (Only the correct ones have been copied into UndoDoc)
            nUndoFlags |= InsertDeleteFlags::CONTENTS;
        if (nFlags & InsertDeleteFlags::ATTRIB)
            nUndoFlags |= InsertDeleteFlags::ATTRIB;
        if (nFlags & InsertDeleteFlags::EDITATTR)          // Edit-Engine attribute
            nUndoFlags |= InsertDeleteFlags::STRING;       // -> Cells will be changed
        if (nFlags & InsertDeleteFlags::SPARKLINES)
            nUndoFlags |= InsertDeleteFlags::SPARKLINES;
        // do not create clones of note captions, they will be restored via drawing undo
        nUndoFlags |= InsertDeleteFlags::NOCAPTIONS;

        ScRange aCopyRange = aRange;
        SCTAB nTabCount = rDoc.GetTableCount();
        aCopyRange.aStart.SetTab(0);
        aCopyRange.aEnd.SetTab(nTabCount-1);

        pUndoDoc->CopyToDocument(aCopyRange, nUndoFlags, bMulti, rDoc, &aMarkData);

        DoSdrUndoAction( pDrawUndo.get(), &rDoc );

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

        rDocShell.UpdatePaintExt( nExtFlags, aRange );             // content after the change
    }
    else        // only Redo
    {
        rDocShell.UpdatePaintExt( nExtFlags, aRange );             // content before the change

        aMarkData.MarkToMulti();
        RedoSdrUndoAction( pDrawUndo.get() );
        // do not delete objects and note captions, they have been removed via drawing undo
        InsertDeleteFlags nRedoFlags = (nFlags & ~InsertDeleteFlags::OBJECTS) | InsertDeleteFlags::NOCAPTIONS;
        rDoc.DeleteSelection( nRedoFlags, aMarkData );
        aMarkData.MarkToSimple();

        SetChangeTrack();
    }

    if (nFlags & InsertDeleteFlags::CONTENTS)
    {
        // Broadcast only when the content changes. fdo#74687
        if (mpDataSpans)
            BroadcastChanges(*mpDataSpans);
        else
            BroadcastChanges(aRange);
    }

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if ( !( pViewShell && pViewShell->AdjustRowHeight(
                                aRange.aStart.Row(), aRange.aEnd.Row(), true ) ) )
/*A*/   rDocShell.PostPaint( aRange, PaintPartFlags::Grid | PaintPartFlags::Extras, nExtFlags );

    if (pViewShell)
        pViewShell->CellContentChanged();

    ShowTable( aRange );
}

void ScUndoDeleteContents::Undo()
{
    BeginUndo();
    DoChange( true );
    EndUndo();

    HelperNotifyChanges::NotifyIfChangesListeners(rDocShell, aRange, u"undo"_ustr);
}

void ScUndoDeleteContents::Redo()
{
    BeginRedo();
    DoChange( false );
    EndRedo();

    HelperNotifyChanges::NotifyIfChangesListeners(rDocShell, aRange, u"redo"_ustr);
}

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

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

ScUndoFillTable::ScUndoFillTable( ScDocShell& rNewDocShell,
                const ScMarkData& rMark,
                SCCOL nStartX, SCROW nStartY, SCTAB nStartZ,
                SCCOL nEndX, SCROW nEndY, SCTAB nEndZ,
                ScDocumentUniquePtr pNewUndoDoc, bool bNewMulti, SCTAB nSrc,
                InsertDeleteFlags nFlg, ScPasteFunc nFunc, bool bSkip, bool bLink )
    :   ScSimpleUndo( rNewDocShell ),
        aRange      ( nStartX, nStartY, nStartZ, nEndX, nEndY, nEndZ ),
        aMarkData   ( rMark ),
        pUndoDoc    ( std::move(pNewUndoDoc) ),
        nFlags      ( nFlg ),
        nFunction   ( nFunc ),
        nSrcTab     ( nSrc ),
        bMulti      ( bNewMulti ),
        bSkipEmpty  ( bSkip ),
        bAsLink     ( bLink )
{
    SetChangeTrack();
}

ScUndoFillTable::~ScUndoFillTable()
{
}

OUString ScUndoFillTable::GetComment() const
{
    return ScResId( STR_FILL_TAB );
}

void ScUndoFillTable::SetChangeTrack()
{
    ScChangeTrack* pChangeTrack = rDocShell.GetDocument().GetChangeTrack();
    if ( pChangeTrack )
    {
        SCTAB nTabCount = rDocShell.GetDocument().GetTableCount();
        ScRange aWorkRange(aRange);
        nStartChangeAction = 0;
        sal_uLong nTmpAction;
        for (const auto& rTab : aMarkData)
        {
            if (rTab >= nTabCount)
                break;
            if (rTab != nSrcTab)
            {
                aWorkRange.aStart.SetTab(rTab);
                aWorkRange.aEnd.SetTab(rTab);
                pChangeTrack->AppendContentRange( aWorkRange, *pUndoDoc,
                    nTmpAction, nEndChangeAction );
                if ( !nStartChangeAction )
                    nStartChangeAction = nTmpAction;
            }
        }
    }
    else
        nStartChangeAction = nEndChangeAction = 0;
}

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

    SetViewMarkData( aMarkData );

    if (bUndo)  // only Undo
    {
        SCTAB nTabCount = rDoc.GetTableCount();
        ScRange aWorkRange(aRange);
        for (const auto& rTab : aMarkData)
        {
            if (rTab >= nTabCount)
                break;
            if (rTab != nSrcTab)
            {
                aWorkRange.aStart.SetTab(rTab);
                aWorkRange.aEnd.SetTab(rTab);
                if (bMulti)
                    rDoc.DeleteSelectionTab( rTab, InsertDeleteFlags::ALL, aMarkData );
                else
                    rDoc.DeleteAreaTab( aWorkRange, InsertDeleteFlags::ALL );
                pUndoDoc->CopyToDocument(aWorkRange, InsertDeleteFlags::ALL, bMulti, rDoc, &aMarkData);
            }
        }

        ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
        if ( pChangeTrack )
            pChangeTrack->Undo( nStartChangeAction, nEndChangeAction );
    }
    else        // only Redo
    {
        aMarkData.MarkToMulti();
        rDoc.FillTabMarked( nSrcTab, aMarkData, nFlags, nFunction, bSkipEmpty, bAsLink );
        aMarkData.MarkToSimple();
        SetChangeTrack();
    }

    rDocShell.PostPaint(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB, PaintPartFlags::Grid|PaintPartFlags::Extras);
    rDocShell.PostDataChanged();

    //  CellContentChanged comes with the selection

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
    {
        SCTAB nTab = pViewShell->GetViewData().CurrentTabForData();
        if ( !aMarkData.GetTableSelect(nTab) )
            pViewShell->SetTabNo( nSrcTab );

        pViewShell->DoneBlockMode();    // causes problems otherwise since selection is on the wrong sheet.
    }
}

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

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

void ScUndoFillTable::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
        pViewTarget->GetViewShell().FillTab( nFlags, nFunction, bSkipEmpty, bAsLink );
}

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

ScUndoSelectionAttr::ScUndoSelectionAttr( ScDocShell& rNewDocShell,
                const ScMarkData& rMark,
                SCCOL nStartX, SCROW nStartY, SCTAB nStartZ,
                SCCOL nEndX, SCROW nEndY, SCTAB nEndZ,
                ScDocumentUniquePtr pNewUndoDoc, bool bNewMulti,
                const ScPatternAttr* pNewApply,
                const SvxBoxItem* pNewOuter, const SvxBoxInfoItem* pNewInner,
                const ScRange* pRangeCover )
    :   ScSimpleUndo( rNewDocShell ),
        aMarkData   ( rMark ),
        aRange      ( nStartX, nStartY, nStartZ, nEndX, nEndY, nEndZ ),
        mpDataArray(new ScEditDataArray),
        pUndoDoc    ( std::move(pNewUndoDoc) ),
        bMulti      ( bNewMulti ),
        aApplyPattern( pNewApply ),
        maLineOuter(*rDocShell.GetDocument().GetPool(), pNewOuter),
        maLineInner(*rDocShell.GetDocument().GetPool(), pNewInner)
{
    aRangeCover = pRangeCover ? *pRangeCover : aRange;
}

ScUndoSelectionAttr::~ScUndoSelectionAttr()
{
    pUndoDoc.reset();
}

OUString ScUndoSelectionAttr::GetComment() const
{
    //"Attribute" "/Lines"
    return ScResId( maLineOuter.getItem() ? STR_UNDO_SELATTRLINES : STR_UNDO_SELATTR );
}

ScEditDataArray* ScUndoSelectionAttr::GetDataArray()
{
    return mpDataArray.get();
}

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

    SetViewMarkData( aMarkData );

    ScRange aEffRange( aRangeCover );
    if ( rDoc.HasAttrib( aEffRange, HasAttrFlags::Merged ) )         // merged cells?
        rDoc.ExtendMerge( aEffRange );

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

    ChangeEditData(bUndo);

    if (bUndo)  // only for Undo
    {
        ScRange aCopyRange = aRangeCover;
        SCTAB nTabCount = rDoc.GetTableCount();
        aCopyRange.aStart.SetTab(0);
        aCopyRange.aEnd.SetTab(nTabCount-1);
        pUndoDoc->CopyToDocument(aCopyRange, InsertDeleteFlags::ATTRIB, bMulti, rDoc, &aMarkData);
    }
    else        // only for Redo
    {
        aMarkData.MarkToMulti();
        rDoc.ApplySelectionPattern( *aApplyPattern.getScPatternAttr(), aMarkData );
        aMarkData.MarkToSimple();

        if (maLineOuter.getItem())
            rDoc.ApplySelectionFrame(aMarkData,
                *static_cast<const SvxBoxItem*>(maLineOuter.getItem()),
                static_cast<const SvxBoxInfoItem*>(maLineInner.getItem()));
    }

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

    ShowTable( aRange );
}

void ScUndoSelectionAttr::ChangeEditData( const bool bUndo )
{
    ScDocument& rDoc = rDocShell.GetDocument();
    for (const ScEditDataArray::Item* pItem = mpDataArray->First(); pItem; pItem = mpDataArray->Next())
    {
        ScAddress aPos(pItem->GetCol(), pItem->GetRow(), pItem->GetTab());
        if (rDoc.GetCellType(aPos) != CELLTYPE_EDIT)
            continue;

        if (bUndo)
        {
            if (pItem->GetOldData())
                rDoc.SetEditText(aPos, *pItem->GetOldData(), nullptr);
            else
                rDoc.SetEmptyCell(aPos);
        }
        else
        {
            if (pItem->GetNewData())
                rDoc.SetEditText(aPos, *pItem->GetNewData(), nullptr);
            else
                rDoc.SetEmptyCell(aPos);
        }
    }
}

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

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

void ScUndoSelectionAttr::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
    {
        ScTabViewShell& rViewShell = pViewTarget->GetViewShell();
        if (maLineOuter.getItem())
            rViewShell.ApplyPatternLines(*aApplyPattern.getScPatternAttr(),
                *static_cast<const SvxBoxItem*>(maLineOuter.getItem()),
                static_cast<const SvxBoxInfoItem*>(maLineInner.getItem()));
        else
            rViewShell.ApplySelectionPattern( *aApplyPattern.getScPatternAttr() );
    }
}

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

ScUndoAutoFill::ScUndoAutoFill( ScDocShell& rNewDocShell,
                const ScRange& rRange, const ScRange& rSourceArea,
                ScDocumentUniquePtr pNewUndoDoc, const ScMarkData& rMark,
                FillDir eNewFillDir, FillCmd eNewFillCmd, FillDateCmd eNewFillDateCmd,
                double fNewStartValue, double fNewStepValue, double fNewMaxValue )
    :   ScBlockUndo( rNewDocShell, rRange, SC_UNDO_AUTOHEIGHT ),
        aSource         ( rSourceArea ),
        aMarkData       ( rMark ),
        pUndoDoc        ( std::move(pNewUndoDoc) ),
        eFillDir        ( eNewFillDir ),
        eFillCmd        ( eNewFillCmd ),
        eFillDateCmd    ( eNewFillDateCmd ),
        fStartValue     ( fNewStartValue ),
        fStepValue      ( fNewStepValue ),
        fMaxValue       ( fNewMaxValue )
{
    SetChangeTrack();
}

ScUndoAutoFill::~ScUndoAutoFill()
{
}

OUString ScUndoAutoFill::GetComment() const
{
    return ScResId( STR_UNDO_AUTOFILL ); //"Fill"
}

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

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

    ScDocument& rDoc = rDocShell.GetDocument();

    SCTAB nTabCount = rDoc.GetTableCount();
    for (const auto& rTab : aMarkData)
    {
        if (rTab >= nTabCount)
            break;
        ScRange aWorkRange = aBlockRange;
        aWorkRange.aStart.SetTab(rTab);
        aWorkRange.aEnd.SetTab(rTab);

        sal_uInt16 nExtFlags = 0;
        rDocShell.UpdatePaintExt( nExtFlags, aWorkRange );
        rDoc.DeleteAreaTab( aWorkRange, InsertDeleteFlags::AUTOFILL );
        pUndoDoc->CopyToDocument(aWorkRange, InsertDeleteFlags::AUTOFILL, false, rDoc);

        // Actually we'd only need to broadcast the cells inserted during
        // CopyToDocument(), as DeleteAreaTab() broadcasts deleted cells. For
        // this we'd need to either record the span sets or let
        // CopyToDocument() broadcast.
        BroadcastChanges( aWorkRange);

        rDoc.ExtendMerge( aWorkRange, true );
        rDocShell.PostPaint( aWorkRange, PaintPartFlags::Grid, nExtFlags );
    }
    rDocShell.PostDataChanged();
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
        pViewShell->CellContentChanged();

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

    EndUndo();
}

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

//! Select sheet

    SCCOLROW nCount = 0;
    switch (eFillDir)
    {
        case FILL_TO_BOTTOM:
            nCount = aBlockRange.aEnd.Row() - aSource.aEnd.Row();
            break;
        case FILL_TO_RIGHT:
            nCount = aBlockRange.aEnd.Col() - aSource.aEnd.Col();
            break;
        case FILL_TO_TOP:
            nCount = aSource.aStart.Row() - aBlockRange.aStart.Row();
            break;
        case FILL_TO_LEFT:
            nCount = aSource.aStart.Col() - aBlockRange.aStart.Col();
            break;
    }

    ScDocument& rDoc = rDocShell.GetDocument();
    if ( fStartValue != MAXDOUBLE )
    {
        SCCOL nValX = (eFillDir == FILL_TO_LEFT) ? aSource.aEnd.Col() : aSource.aStart.Col();
        SCROW nValY = (eFillDir == FILL_TO_TOP ) ? aSource.aEnd.Row() : aSource.aStart.Row();
        SCTAB nTab = aSource.aStart.Tab();
        rDoc.SetValue( nValX, nValY, nTab, fStartValue );
    }
    sal_uLong nProgCount;
    if (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_TOP)
        nProgCount = aSource.aEnd.Col() - aSource.aStart.Col() + 1;
    else
        nProgCount = aSource.aEnd.Row() - aSource.aStart.Row() + 1;
    nProgCount *= nCount;
    ScProgress aProgress( rDoc.GetDocumentShell(),
            ScResId(STR_FILL_SERIES_PROGRESS), nProgCount, true );

    rDoc.Fill( aSource.aStart.Col(), aSource.aStart.Row(),
            aSource.aEnd.Col(), aSource.aEnd.Row(), &aProgress,
            aMarkData, nCount,
            eFillDir, eFillCmd, eFillDateCmd,
            fStepValue, fMaxValue );

    SetChangeTrack();

    rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid );
    rDocShell.PostDataChanged();
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
        pViewShell->CellContentChanged();

    EndRedo();
}

void ScUndoAutoFill::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
    {
        ScTabViewShell& rViewShell = pViewTarget->GetViewShell();
        if (eFillCmd==FILL_SIMPLE)
            rViewShell.FillSimple( eFillDir );
        else
            rViewShell.FillSeries( eFillDir, eFillCmd, eFillDateCmd,
                                   fStartValue, fStepValue, fMaxValue );
    }
}

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

ScUndoMerge::ScUndoMerge(ScDocShell& rNewDocShell, ScCellMergeOption aOption,
                         bool bMergeContents, ScDocumentUniquePtr pUndoDoc, std::unique_ptr<SdrUndoAction> pDrawUndo)
    : ScSimpleUndo(rNewDocShell)
    , maOption(std::move(aOption))
    , mbMergeContents(bMergeContents)
    , mxUndoDoc(std::move(pUndoDoc))
    , mpDrawUndo(std::move(pDrawUndo))
{
}

ScUndoMerge::~ScUndoMerge()
{
    mpDrawUndo.reset();
}

OUString ScUndoMerge::GetComment() const
{
    return ScResId( STR_UNDO_MERGE );
}

void ScUndoMerge::DoChange( bool bUndo ) const
{
    if (maOption.maTabs.empty())
        // Nothing to do.
        return;

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

    ScRange aCurRange = maOption.getSingleRange(ScDocShell::GetCurTab());
    ScUndoUtil::MarkSimpleBlock(rDocShell, aCurRange);

    for (const SCTAB nTab : maOption.maTabs)
    {
        ScRange aRange = maOption.getSingleRange(nTab);

        if (bUndo)
            // remove merge (contents are copied back below from undo document)
            rDoc.RemoveMerge( aRange.aStart.Col(), aRange.aStart.Row(), aRange.aStart.Tab() );
        else
        {
            // repeat merge, but do not remove note captions (will be done by drawing redo below)
            rDoc.DoMerge( aRange.aStart.Col(), aRange.aStart.Row(),
                          aRange.aEnd.Col(),   aRange.aEnd.Row(),
                          aRange.aStart.Tab(), false );

            if (maOption.mbCenter)
            {
                rDoc.ApplyAttr( aRange.aStart.Col(), aRange.aStart.Row(),
                                 aRange.aStart.Tab(),
                                 SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY ) );
                rDoc.ApplyAttr( aRange.aStart.Col(), aRange.aStart.Row(),
                                 aRange.aStart.Tab(),
                                 SvxVerJustifyItem( SvxCellVerJustify::Center, ATTR_VER_JUSTIFY ) );
            }
        }

        // undo -> copy back deleted contents
        if (bUndo && mxUndoDoc)
        {
            // If there are note captions to be deleted during Undo they were
            // kept or moved during the merge and copied to the Undo document
            // without cloning the caption. Forget the target area's caption
            // pointer that is identical to the one in the Undo document
            // instead of deleting it.
            rDoc.DeleteAreaTab( aRange,
                    InsertDeleteFlags::CONTENTS | InsertDeleteFlags::NOCAPTIONS | InsertDeleteFlags::FORGETCAPTIONS );
            mxUndoDoc->CopyToDocument(aRange, InsertDeleteFlags::ALL|InsertDeleteFlags::NOCAPTIONS, false, rDoc);
        }

        // redo -> merge contents again
        else if (!bUndo && mbMergeContents)
        {
            rDoc.DoMergeContents( aRange.aStart.Col(), aRange.aStart.Row(),
                                  aRange.aEnd.Col(), aRange.aEnd.Row(),
                                  aRange.aStart.Tab() );
        }

        if (bUndo)
            DoSdrUndoAction( mpDrawUndo.get(), &rDoc );
        else
            RedoSdrUndoAction( mpDrawUndo.get() );

        bool bDidPaint = false;
        if ( pViewShell )
        {
            pViewShell->SetTabNo(nTab);
            bDidPaint = pViewShell->AdjustRowHeight(maOption.mnStartRow, maOption.mnEndRow, true);
        }

        if (!bDidPaint)
            ScUndoUtil::PaintMore(rDocShell, aRange);

        rDoc.BroadcastCells(aRange, SfxHintId::ScDataChanged);
    }

    ShowTable(aCurRange);
}

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

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

void ScUndoMerge::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
    {
        ScTabViewShell& rViewShell = pViewTarget->GetViewShell();
        rViewShell.MergeCells( false, false, false, 0 );
    }
}

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

ScUndoAutoFormat::ScUndoAutoFormat( ScDocShell& rNewDocShell,
                        const ScRange& rRange, ScDocumentUniquePtr pNewUndoDoc,
                        const ScMarkData& rMark, bool bNewSize, sal_uInt16 nNewFormatNo )
    :   ScBlockUndo( rNewDocShell, rRange, bNewSize ? SC_UNDO_MANUALHEIGHT : SC_UNDO_AUTOHEIGHT ),
        pUndoDoc    ( std::move(pNewUndoDoc) ),
        aMarkData   ( rMark ),
        bSize       ( bNewSize ),
        nFormatNo   ( nNewFormatNo )
{
}

ScUndoAutoFormat::~ScUndoAutoFormat()
{
}

OUString ScUndoAutoFormat::GetComment() const
{
    return ScResId( STR_UNDO_AUTOFORMAT );   //"Auto-Format"
}

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

    ScDocument& rDoc = rDocShell.GetDocument();

    SCTAB nTabCount = rDoc.GetTableCount();
    rDoc.DeleteArea( aBlockRange.aStart.Col(), aBlockRange.aStart.Row(),
                      aBlockRange.aEnd.Col(), aBlockRange.aEnd.Row(),
                      aMarkData, InsertDeleteFlags::ATTRIB );
    ScRange aCopyRange = aBlockRange;
    aCopyRange.aStart.SetTab(0);
    aCopyRange.aEnd.SetTab(nTabCount-1);
    pUndoDoc->CopyToDocument(aCopyRange, InsertDeleteFlags::ATTRIB, false, rDoc, &aMarkData);

    // cell heights and widths (InsertDeleteFlags::NONE)
    if (bSize)
    {
        SCCOL nStartX = aBlockRange.aStart.Col();
        SCROW nStartY = aBlockRange.aStart.Row();
        SCTAB nStartZ = aBlockRange.aStart.Tab();
        SCCOL nEndX = aBlockRange.aEnd.Col();
        SCROW nEndY = aBlockRange.aEnd.Row();
        SCTAB nEndZ = aBlockRange.aEnd.Tab();

        pUndoDoc->CopyToDocument( nStartX, 0, 0, nEndX, rDoc.MaxRow(), nTabCount-1,
                                    InsertDeleteFlags::NONE, false, rDoc, &aMarkData );
        pUndoDoc->CopyToDocument( 0, nStartY, 0, rDoc.MaxCol(), nEndY, nTabCount-1,
                                    InsertDeleteFlags::NONE, false, rDoc, &aMarkData );
        rDocShell.PostPaint( 0, 0, nStartZ, rDoc.MaxCol(), rDoc.MaxRow(), nEndZ,
                              PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top, SC_PF_LINES );
    }
    else
        rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid, SC_PF_LINES );

    EndUndo();
}

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

    ScDocument& rDoc = rDocShell.GetDocument();

    SCCOL nStartX = aBlockRange.aStart.Col();
    SCROW nStartY = aBlockRange.aStart.Row();
    SCTAB nStartZ = aBlockRange.aStart.Tab();
    SCCOL nEndX = aBlockRange.aEnd.Col();
    SCROW nEndY = aBlockRange.aEnd.Row();
    SCTAB nEndZ = aBlockRange.aEnd.Tab();

    rDoc.AutoFormat( nStartX, nStartY, nEndX, nEndY, nFormatNo, aMarkData );

    if (bSize)
    {
        ScopedVclPtrInstance< VirtualDevice > pVirtDev;
        Fraction aZoomX(1,1);
        Fraction aZoomY = aZoomX;
        double nPPTX,nPPTY;
        ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
        if (pViewShell)
        {
            ScViewData& rData = pViewShell->GetViewData();
            nPPTX = rData.GetPPTX();
            nPPTY = rData.GetPPTY();
            aZoomX = rData.GetZoomX();
            aZoomY = rData.GetZoomY();
        }
        else
        {
            // Keep zoom at 100
            nPPTX = ScGlobal::nScreenPPTX;
            nPPTY = ScGlobal::nScreenPPTY;
        }

        sc::RowHeightContext aCxt(rDoc.MaxRow(), nPPTX, nPPTY, aZoomX, aZoomY, pVirtDev);
        for (SCTAB nTab=nStartZ; nTab<=nEndZ; nTab++)
        {
            ScMarkData aDestMark(rDoc.GetSheetLimits());
            aDestMark.SelectOneTable( nTab );
            aDestMark.SetMarkArea( ScRange( nStartX, nStartY, nTab, nEndX, nEndY, nTab ) );
            aDestMark.MarkToMulti();

            // as SC_SIZE_VISOPT
            for (SCROW nRow=nStartY; nRow<=nEndY; nRow++)
            {
                CRFlags nOld = rDoc.GetRowFlags(nRow,nTab);
                bool bHidden = rDoc.RowHidden(nRow, nTab);
                if ( !bHidden && ( nOld & CRFlags::ManualSize ) )
                    rDoc.SetRowFlags( nRow, nTab, nOld & ~CRFlags::ManualSize );
            }

            bool bChanged = rDoc.SetOptimalHeight(aCxt, nStartY, nEndY, nTab, true);

            for (SCCOL nCol=nStartX; nCol<=nEndX; nCol++)
                if (!rDoc.ColHidden(nCol, nTab))
                {
                    sal_uInt16 nThisSize = STD_EXTRA_WIDTH + rDoc.GetOptimalColWidth( nCol, nTab,
                                                pVirtDev, nPPTX, nPPTY, aZoomX, aZoomY, false/*bFormula*/,
                                                &aDestMark );
                    rDoc.SetColWidth( nCol, nTab, nThisSize );
                    rDoc.ShowCol( nCol, nTab, true );
                }

            // tdf#76183: recalculate objects' positions
            if (bChanged)
                rDoc.SetDrawPageSize(nTab);
        }

        rDocShell.PostPaint( 0,      0,      nStartZ,
                              rDoc.MaxCol(), rDoc.MaxRow(), nEndZ,
                              PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top, SC_PF_LINES);
    }
    else
        rDocShell.PostPaint( aBlockRange, PaintPartFlags::Grid, SC_PF_LINES );

    EndRedo();
}

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

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

ScUndoReplace::ScUndoReplace( ScDocShell& rNewDocShell, const ScMarkData& rMark,
                                    SCCOL nCurX, SCROW nCurY, SCTAB nCurZ,
                                    OUString aNewUndoStr, ScDocumentUniquePtr pNewUndoDoc,
                                    const SvxSearchItem* pItem )
    :   ScSimpleUndo( rNewDocShell ),
        aCursorPos  ( nCurX, nCurY, nCurZ ),
        aMarkData   ( rMark ),
        aUndoStr    (std::move( aNewUndoStr )),
        pUndoDoc    ( std::move(pNewUndoDoc) )
{
    pSearchItem.reset( new SvxSearchItem( *pItem ) );
    SetChangeTrack();
}

ScUndoReplace::~ScUndoReplace()
{
    pUndoDoc.reset();
    pSearchItem.reset();
}

void ScUndoReplace::SetChangeTrack()
{
    ScDocument& rDoc = rDocShell.GetDocument();
    ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
    if ( pChangeTrack )
    {
        if ( pUndoDoc )
        {   //! UndoDoc includes only the changed cells,
            // that is why an Iterator can be used
            pChangeTrack->AppendContentsIfInRefDoc( *pUndoDoc,
                nStartChangeAction, nEndChangeAction );
        }
        else
        {
            nStartChangeAction = pChangeTrack->GetActionMax() + 1;
            ScChangeActionContent* pContent = new ScChangeActionContent(
                ScRange( aCursorPos) );
            ScCellValue aCell;
            aCell.assign(rDoc, aCursorPos);
            pContent->SetOldValue( aUndoStr, &rDoc );
            pContent->SetNewValue(aCell, &rDoc);
            pChangeTrack->Append( pContent );
            nEndChangeAction = pChangeTrack->GetActionMax();
        }
    }
    else
        nStartChangeAction = nEndChangeAction = 0;
}

OUString ScUndoReplace::GetComment() const
{
    return ScResId( STR_UNDO_REPLACE );  // "Replace"
}

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

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

    ShowTable( aCursorPos.Tab() );

    if (pUndoDoc)       // only for ReplaceAll !!
    {
        OSL_ENSURE(pSearchItem->GetCommand() == SvxSearchCmd::REPLACE_ALL,
                   "ScUndoReplace:: Wrong Mode");

        SetViewMarkData( aMarkData );

//! selected sheet
//! select range ?

        // Undo document has no row/column information, thus copy with
        // bColRowFlags = FALSE to not destroy Outline groups

        InsertDeleteFlags nUndoFlags = (pSearchItem->GetPattern()) ? InsertDeleteFlags::ATTRIB : InsertDeleteFlags::CONTENTS;
        pUndoDoc->CopyToDocument( 0,      0,      0,
                                  rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB,
                                  nUndoFlags, false, rDoc, nullptr, false );   // without row flags
        rDocShell.PostPaintGridAll();
    }
    else if (pSearchItem->GetPattern() &&
             pSearchItem->GetCommand() == SvxSearchCmd::REPLACE)
    {
        OUString aTempStr = pSearchItem->GetSearchString();       // toggle
        pSearchItem->SetSearchString(pSearchItem->GetReplaceString());
        pSearchItem->SetReplaceString(aTempStr);
        rDoc.ReplaceStyle( *pSearchItem,
                            aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab(),
                            aMarkData);
        pSearchItem->SetReplaceString(pSearchItem->GetSearchString());
        pSearchItem->SetSearchString(aTempStr);
        if (pViewShell)
            pViewShell->MoveCursorAbs( aCursorPos.Col(), aCursorPos.Row(),
                                       SC_FOLLOW_JUMP, false, false );
        rDocShell.PostPaintGridAll();
    }
    else if (pSearchItem->GetCellType() == SvxSearchCellType::NOTE)
    {
        ScPostIt* pNote = rDoc.GetNote(aCursorPos);
        OSL_ENSURE( pNote, "ScUndoReplace::Undo - cell does not contain a note" );
        if (pNote)
            pNote->SetText( aCursorPos, aUndoStr );
        if (pViewShell)
            pViewShell->MoveCursorAbs( aCursorPos.Col(), aCursorPos.Row(),
                                       SC_FOLLOW_JUMP, false, false );
    }
    else
    {
        // aUndoStr may contain line breaks
        if ( aUndoStr.indexOf('\n') != -1 )
        {
            ScFieldEditEngine& rEngine = rDoc.GetEditEngine();
            rEngine.SetTextCurrentDefaults(aUndoStr);
            rDoc.SetEditText(aCursorPos, rEngine.CreateTextObject());
        }
        else
            rDoc.SetString( aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab(), aUndoStr );
        if (pViewShell)
            pViewShell->MoveCursorAbs( aCursorPos.Col(), aCursorPos.Row(),
                                       SC_FOLLOW_JUMP, false, false );
        rDocShell.PostPaintGridAll();
    }

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

    EndUndo();
}

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

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

    if (pViewShell)
        pViewShell->MoveCursorAbs( aCursorPos.Col(), aCursorPos.Row(),
                                   SC_FOLLOW_JUMP, false, false );
    if (pUndoDoc)
    {
        if (pViewShell)
        {
            SetViewMarkData( aMarkData );

            pViewShell->SearchAndReplace( pSearchItem.get(), false, true );
        }
    }
    else if (pSearchItem->GetPattern() &&
             pSearchItem->GetCommand() == SvxSearchCmd::REPLACE)
    {
        rDoc.ReplaceStyle( *pSearchItem,
                            aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab(),
                            aMarkData);
        rDocShell.PostPaintGridAll();
    }
    else
        if (pViewShell)
            pViewShell->SearchAndReplace( pSearchItem.get(), false, true );

    SetChangeTrack();

    EndRedo();
}

void ScUndoReplace::Repeat(SfxRepeatTarget& rTarget)
{
    if (auto pViewTarget = dynamic_cast<ScTabViewTarget*>( &rTarget))
        pViewTarget->GetViewShell().SearchAndReplace( pSearchItem.get(), true, false );
}

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

// multi-operation (only simple blocks)
ScUndoTabOp::ScUndoTabOp( ScDocShell& rNewDocShell,
                SCCOL nStartX, SCROW nStartY, SCTAB nStartZ,
                SCCOL nEndX, SCROW nEndY, SCTAB nEndZ, ScDocumentUniquePtr pNewUndoDoc,
                const ScRefAddress& rFormulaCell,
                const ScRefAddress& rFormulaEnd,
                const ScRefAddress& rRowCell,
                const ScRefAddress& rColCell,
                ScTabOpParam::Mode eMode )
    :   ScSimpleUndo( rNewDocShell ),
        aRange          ( nStartX, nStartY, nStartZ, nEndX, nEndY, nEndZ ),
        pUndoDoc        ( std::move(pNewUndoDoc) ),
        theFormulaCell  ( rFormulaCell ),
        theFormulaEnd   ( rFormulaEnd ),
        theRowCell      ( rRowCell ),
        theColCell      ( rColCell ),
        meMode(eMode)
{
}

ScUndoTabOp::~ScUndoTabOp()
{
}

OUString ScUndoTabOp::GetComment() const
{
    return ScResId( STR_UNDO_TABOP );    // "Multiple operation"
}

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

    ScUndoUtil::MarkSimpleBlock( rDocShell, aRange );

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

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

    EndUndo();
}

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

    ScUndoUtil::MarkSimpleBlock( rDocShell, aRange );

    ScTabOpParam aParam(theFormulaCell, theFormulaEnd, theRowCell, theColCell, meMode);

    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
        pViewShell->TabOp( aParam, false);

    EndRedo();
}

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

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

ScUndoConversion::ScUndoConversion(
        ScDocShell& rNewDocShell, const ScMarkData& rMark,
        SCCOL nCurX, SCROW nCurY, SCTAB nCurZ, ScDocumentUniquePtr pNewUndoDoc,
        SCCOL nNewX, SCROW nNewY, SCTAB nNewZ, ScDocumentUniquePtr pNewRedoDoc,
        ScConversionParam aConvParam ) :
    ScSimpleUndo( rNewDocShell ),
    aMarkData( rMark ),
    aCursorPos( nCurX, nCurY, nCurZ ),
    pUndoDoc( std::move(pNewUndoDoc) ),
    aNewCursorPos( nNewX, nNewY, nNewZ ),
    pRedoDoc( std::move(pNewRedoDoc) ),
    maConvParam(std::move( aConvParam ))
{
    SetChangeTrack();
}

ScUndoConversion::~ScUndoConversion()
{
    pUndoDoc.reset();
    pRedoDoc.reset();
}

void ScUndoConversion::SetChangeTrack()
{
    ScDocument& rDoc = rDocShell.GetDocument();
    ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack();
    if ( pChangeTrack )
    {
        if ( pUndoDoc )
            pChangeTrack->AppendContentsIfInRefDoc( *pUndoDoc,
                nStartChangeAction, nEndChangeAction );
        else
        {
            OSL_FAIL( "ScUndoConversion::SetChangeTrack: no UndoDoc" );
            nStartChangeAction = nEndChangeAction = 0;
        }
    }
    else
        nStartChangeAction = nEndChangeAction = 0;
}

OUString ScUndoConversion::GetComment() const
{
    OUString aText;
    switch( maConvParam.GetType() )
    {
        case SC_CONVERSION_SPELLCHECK:      aText = ScResId( STR_UNDO_SPELLING );    break;
        case SC_CONVERSION_HANGULHANJA:     aText = ScResId( STR_UNDO_HANGULHANJA ); break;
        case SC_CONVERSION_CHINESE_TRANSL:  aText = ScResId( STR_UNDO_CHINESE_TRANSLATION ); break;
    }
    return aText;
}

void ScUndoConversion::DoChange( ScDocument* pRefDoc, const ScAddress& rCursorPos )
{
    if (pRefDoc)
    {
        ScDocument& rDoc = rDocShell.GetDocument();
        ShowTable( rCursorPos.Tab() );

        SetViewMarkData( aMarkData );

        SCTAB nTabCount = rDoc.GetTableCount();
        //  Undo/Redo-doc has only selected tables

        bool bMulti = aMarkData.IsMultiMarked();
        pRefDoc->CopyToDocument( 0,      0,      0,
                                 rDoc.MaxCol(), rDoc.MaxRow(), nTabCount-1,
                                 InsertDeleteFlags::CONTENTS, bMulti, rDoc, &aMarkData );

        // Reset the spell checking results to re-check on paint, otherwise
        // we show the previous spelling markers (or lack thereof on misspellings).
        if (ScViewData* pViewData = ScDocShell::GetViewData())
            pViewData->GetActiveWin()->ResetAutoSpell();
        rDocShell.PostPaintGridAll();
    }
    else
    {
        OSL_FAIL("no Un-/RedoDoc for Un-/RedoSpelling");
    }
}

void ScUndoConversion::Undo()
{
    BeginUndo();
    DoChange( pUndoDoc.get(), aCursorPos );
    ScChangeTrack* pChangeTrack = rDocShell.GetDocument().GetChangeTrack();
    if ( pChangeTrack )
        pChangeTrack->Undo( nStartChangeAction, nEndChangeAction );
    EndUndo();
}

void ScUndoConversion::Redo()
{
    BeginRedo();
    DoChange( pRedoDoc.get(), aNewCursorPos );
    SetChangeTrack();
    EndRedo();
}

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

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

ScUndoRefConversion::ScUndoRefConversion( ScDocShell& rNewDocShell,
                                         const ScRange& aMarkRange, const ScMarkData& rMark,
                                         ScDocumentUniquePtr pNewUndoDoc, ScDocumentUniquePtr pNewRedoDoc, bool bNewMulti) :
ScSimpleUndo( rNewDocShell ),
aMarkData   ( rMark ),
pUndoDoc    ( std::move(pNewUndoDoc) ),
pRedoDoc    ( std::move(pNewRedoDoc) ),
aRange      ( aMarkRange ),
bMulti      ( bNewMulti )
{
    assert(pUndoDoc && pRedoDoc);
    SetChangeTrack();
}

ScUndoRefConversion::~ScUndoRefConversion()
{
    pUndoDoc.reset();
    pRedoDoc.reset();
}

OUString ScUndoRefConversion::GetComment() const
{
    return ScResId( STR_UNDO_ENTERDATA ); // "Input"
}

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

void ScUndoRefConversion::DoChange( ScDocument* pRefDoc)
{
    ScDocument& rDoc = rDocShell.GetDocument();

    ShowTable(aRange);

    SetViewMarkData( aMarkData );

    ScRange aCopyRange = aRange;
    SCTAB nTabCount = rDoc.GetTableCount();
    aCopyRange.aStart.SetTab(0);
    aCopyRange.aEnd.SetTab(nTabCount-1);
    pRefDoc->CopyToDocument( aCopyRange, InsertDeleteFlags::ALL, bMulti, rDoc, &aMarkData );
    rDocShell.PostPaint( aRange, PaintPartFlags::Grid);
    rDocShell.PostDataChanged();
    ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
    if (pViewShell)
        pViewShell->CellContentChanged();
}

void ScUndoRefConversion::Undo()
{
    BeginUndo();
    if (pUndoDoc)
        DoChange(pUndoDoc.get());
    ScChangeTrack* pChangeTrack = rDocShell.GetDocument().GetChangeTrack();
    if ( pChangeTrack )
        pChangeTrack->Undo( nStartChangeAction, nEndChangeAction );
    EndUndo();
}

void ScUndoRefConversion::Redo()
{
    BeginRedo();
    if (pRedoDoc)
        DoChange(pRedoDoc.get());
    SetChangeTrack();
    EndRedo();
}

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

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

ScUndoRefreshLink::ScUndoRefreshLink(ScDocShell& rNewDocShell,
                                     ScDocumentUniquePtr pNewUndoDoc)
    : ScSimpleUndo(rNewDocShell)
    , xUndoDoc(std::move(pNewUndoDoc))
{
}

OUString ScUndoRefreshLink::GetComment() const
{
    return ScResId( STR_UNDO_UPDATELINK );
}

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

    bool bMakeRedo = !xRedoDoc;
    if (bMakeRedo)
        xRedoDoc.reset(new ScDocument(SCDOCMODE_UNDO));

    bool bFirst = true;
    ScDocument& rDoc = rDocShell.GetDocument();
    SCTAB nCount = rDoc.GetTableCount();
    for (SCTAB nTab=0; nTab<nCount; nTab++)
        if (xUndoDoc->HasTable(nTab))
        {
            ScRange aRange(0,0,nTab,rDoc.MaxCol(),rDoc.MaxRow(),nTab);
            if (bMakeRedo)
            {
                if (bFirst)
                    xRedoDoc->InitUndo(rDoc, nTab, nTab, true, true);
                else
                    xRedoDoc->AddUndoTab(nTab, nTab, true, true);
                bFirst = false;
                rDoc.CopyToDocument(aRange, InsertDeleteFlags::ALL, false, *xRedoDoc);
                xRedoDoc->SetLink(nTab,
                                  rDoc.GetLinkMode(nTab),
                                  rDoc.GetLinkDoc(nTab),
                                  rDoc.GetLinkFlt(nTab),
                                  rDoc.GetLinkOpt(nTab),
                                  rDoc.GetLinkTab(nTab),
                                  rDoc.GetLinkRefreshDelay(nTab));
                xRedoDoc->SetTabBgColor( nTab, rDoc.GetTabBgColor(nTab) );
            }

            rDoc.DeleteAreaTab( aRange,InsertDeleteFlags::ALL );
            xUndoDoc->CopyToDocument(aRange, InsertDeleteFlags::ALL, false, rDoc);
            rDoc.SetLink(nTab, xUndoDoc->GetLinkMode(nTab), xUndoDoc->GetLinkDoc(nTab),
                               xUndoDoc->GetLinkFlt(nTab),  xUndoDoc->GetLinkOpt(nTab),
                               xUndoDoc->GetLinkTab(nTab),
                               xUndoDoc->GetLinkRefreshDelay(nTab) );
            rDoc.SetTabBgColor(nTab, xUndoDoc->GetTabBgColor(nTab));
        }

    rDocShell.PostPaintGridAll();
    rDocShell.PostPaintExtras();

    EndUndo();
}

void ScUndoRefreshLink::Redo()
{
    OSL_ENSURE(xRedoDoc, "No RedoDoc for ScUndoRefreshLink::Redo");

    BeginUndo();

    ScDocument& rDoc = rDocShell.GetDocument();
    SCTAB nCount = rDoc.GetTableCount();
    for (SCTAB nTab=0; nTab<nCount; nTab++)
        if (xRedoDoc->HasTable(nTab))
        {
            ScRange aRange(0,0,nTab,rDoc.MaxCol(),rDoc.MaxRow(),nTab);

            rDoc.DeleteAreaTab( aRange, InsertDeleteFlags::ALL );
            xRedoDoc->CopyToDocument(aRange, InsertDeleteFlags::ALL, false, rDoc);
            rDoc.SetLink(nTab,
                         xRedoDoc->GetLinkMode(nTab),
                         xRedoDoc->GetLinkDoc(nTab),
                         xRedoDoc->GetLinkFlt(nTab),
                         xRedoDoc->GetLinkOpt(nTab),
                         xRedoDoc->GetLinkTab(nTab),
                         xRedoDoc->GetLinkRefreshDelay(nTab) );
            rDoc.SetTabBgColor(nTab, xRedoDoc->GetTabBgColor(nTab));
        }

    rDocShell.PostPaintGridAll();
    rDocShell.PostPaintExtras();

    EndUndo();
}

void ScUndoRefreshLink::Repeat(SfxRepeatTarget& /* rTarget */)
{
    // makes no sense
}

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

static ScAreaLink* lcl_FindAreaLink( const sfx2::LinkManager* pLinkManager, std::u16string_view rDoc,
                            std::u16string_view rFlt, std::u16string_view rOpt,
                            std::u16string_view rSrc, const ScRange& rDest )
{
    const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks();
    sal_uInt16 nCount = pLinkManager->GetLinks().size();
    for (sal_uInt16 i=0; i<nCount; i++)
    {
        ::sfx2::SvBaseLink* pBase = rLinks[i].get();
        if (auto pAreaLink = dynamic_cast<ScAreaLink*>( pBase))
            if ( pAreaLink->IsEqual( rDoc, rFlt, rOpt, rSrc, rDest ) )
                return pAreaLink;
    }

    OSL_FAIL("ScAreaLink not found");
    return nullptr;
}

ScUndoInsertAreaLink::ScUndoInsertAreaLink( ScDocShell& rShell,
                            OUString aDoc,
                            OUString aFlt, OUString aOpt,
                            OUString aArea, const ScRange& rDestRange,
                            sal_uLong nRefresh )
    :   ScSimpleUndo    ( rShell ),
        aDocName        (std::move( aDoc )),
        aFltName        (std::move( aFlt )),
        aOptions        (std::move( aOpt )),
        aAreaName       (std::move( aArea )),
        aRange          ( rDestRange ),
        nRefreshDelay   ( nRefresh )
{
}

ScUndoInsertAreaLink::~ScUndoInsertAreaLink()
{
}

OUString ScUndoInsertAreaLink::GetComment() const
{
    return ScResId( STR_UNDO_INSERTAREALINK );
}

void ScUndoInsertAreaLink::Undo()
{
    ScDocument& rDoc = rDocShell.GetDocument();
    sfx2::LinkManager* pLinkManager = rDoc.GetLinkManager();

    ScAreaLink* pLink = lcl_FindAreaLink( pLinkManager, aDocName, aFltName, aOptions,
                                            aAreaName, aRange );
    if (pLink)
        pLinkManager->Remove( pLink );

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

void ScUndoInsertAreaLink::Redo()
{
    ScDocument& rDoc = rDocShell.GetDocument();
    sfx2::LinkManager* pLinkManager = rDoc.GetLinkManager();

    ScAreaLink* pLink = new ScAreaLink( rDocShell, aDocName, aFltName, aOptions,
                                            aAreaName, ScRange(aRange.aStart), nRefreshDelay );
    pLink->SetInCreate( true );
    pLink->SetDestArea( aRange );
    pLinkManager->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, aDocName, &aFltName, &aAreaName );
    pLink->Update();
    pLink->SetInCreate( false );

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

void ScUndoInsertAreaLink::Repeat(SfxRepeatTarget& /* rTarget */)
{
    // makes no sense
}

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

ScUndoRemoveAreaLink::ScUndoRemoveAreaLink( ScDocShell& rShell,
                            OUString aDoc, OUString aFlt, OUString aOpt,
                            OUString aArea, const ScRange& rDestRange,
                            sal_uLong nRefresh )
    :   ScSimpleUndo    ( rShell ),
        aDocName        (std::move( aDoc )),
        aFltName        (std::move( aFlt )),
        aOptions        (std::move( aOpt )),
        aAreaName       (std::move( aArea )),
        aRange          ( rDestRange ),
        nRefreshDelay   ( nRefresh )
{
}

ScUndoRemoveAreaLink::~ScUndoRemoveAreaLink()
{
}

OUString ScUndoRemoveAreaLink::GetComment() const
{
    return ScResId( STR_UNDO_REMOVELINK );   //! own text ??
}

void ScUndoRemoveAreaLink::Undo()
{
    ScDocument& rDoc = rDocShell.GetDocument();
    sfx2::LinkManager* pLinkManager = rDoc.GetLinkManager();

    ScAreaLink* pLink = new ScAreaLink( rDocShell, aDocName, aFltName, aOptions,
                                        aAreaName, ScRange(aRange.aStart), nRefreshDelay );
    pLink->SetInCreate( true );
    pLink->SetDestArea( aRange );
    pLinkManager->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, aDocName, &aFltName, &aAreaName );
    pLink->Update();
    pLink->SetInCreate( false );

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

void ScUndoRemoveAreaLink::Redo()
{
    ScDocument& rDoc = rDocShell.GetDocument();
    sfx2::LinkManager* pLinkManager = rDoc.GetLinkManager();

    ScAreaLink* pLink = lcl_FindAreaLink( pLinkManager, aDocName, aFltName, aOptions,
                                            aAreaName, aRange );
    if (pLink)
        pLinkManager->Remove( pLink );

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

void ScUndoRemoveAreaLink::Repeat(SfxRepeatTarget& /* rTarget */)
{
    // makes no sense
}

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

ScUndoUpdateAreaLink::ScUndoUpdateAreaLink( ScDocShell& rShell,
                            OUString aOldD, OUString aOldF, OUString aOldO,
                            OUString aOldA, const ScRange& rOldR, sal_uLong nOldRD,
                            OUString aNewD, OUString aNewF, OUString aNewO,
                            OUString aNewA, const ScRange& rNewR, sal_uLong nNewRD,
                            ScDocumentUniquePtr pUndo, ScDocumentUniquePtr pRedo, bool bDoInsert )
    :   ScSimpleUndo( rShell ),
        aOldDoc     (std::move( aOldD )),
        aOldFlt     (std::move( aOldF )),
        aOldOpt     (std::move( aOldO )),
        aOldArea    (std::move( aOldA )),
        aOldRange   ( rOldR ),
        aNewDoc     (std::move( aNewD )),
        aNewFlt     (std::move( aNewF )),
        aNewOpt     (std::move( aNewO )),
        aNewArea    (std::move( aNewA )),
        aNewRange   ( rNewR ),
        xUndoDoc    ( std::move(pUndo) ),
        xRedoDoc    ( std::move(pRedo) ),
        nOldRefresh ( nOldRD ),
        nNewRefresh ( nNewRD ),
        bWithInsert ( bDoInsert )
{
    OSL_ENSURE( aOldRange.aStart == aNewRange.aStart, "AreaLink moved ?" );
}

OUString ScUndoUpdateAreaLink::GetComment() const
{
    return ScResId( STR_UNDO_UPDATELINK );   //! own text ??
}

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

    SCCOL nEndX = std::max( aOldRange.aEnd.Col(), aNewRange.aEnd.Col() );
    SCROW nEndY = std::max( aOldRange.aEnd.Row(), aNewRange.aEnd.Row() );
    SCTAB nEndZ = std::max( aOldRange.aEnd.Tab(), aNewRange.aEnd.Tab() );    //?

    if ( bUndo )
    {
        if ( bWithInsert )
        {
            rDoc.FitBlock( aNewRange, aOldRange );
            rDoc.DeleteAreaTab( aOldRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE );
            xUndoDoc->UndoToDocument(aOldRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, rDoc);
        }
        else
        {
            ScRange aCopyRange( aOldRange.aStart, ScAddress(nEndX,nEndY,nEndZ) );
            rDoc.DeleteAreaTab( aCopyRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE );
            xUndoDoc->CopyToDocument(aCopyRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, rDoc);
        }
    }
    else
    {
        if ( bWithInsert )
        {
            rDoc.FitBlock( aOldRange, aNewRange );
            rDoc.DeleteAreaTab( aNewRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE );
            xRedoDoc->CopyToDocument(aNewRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, rDoc);
        }
        else
        {
            ScRange aCopyRange( aOldRange.aStart, ScAddress(nEndX,nEndY,nEndZ) );
            rDoc.DeleteAreaTab( aCopyRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE );
            xRedoDoc->CopyToDocument(aCopyRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, rDoc);
        }
    }

    ScRange aWorkRange( aNewRange.aStart, ScAddress( nEndX, nEndY, nEndZ ) );
    rDoc.ExtendMerge( aWorkRange, true );

    //  Paint

    if ( aNewRange.aEnd.Col() != aOldRange.aEnd.Col() )
        aWorkRange.aEnd.SetCol(rDoc.MaxCol());
    if ( aNewRange.aEnd.Row() != aOldRange.aEnd.Row() )
        aWorkRange.aEnd.SetRow(rDoc.MaxRow());

    if ( !rDocShell.AdjustRowHeight( aWorkRange.aStart.Row(), aWorkRange.aEnd.Row(), aWorkRange.aStart.Tab() ) )
        rDocShell.PostPaint( aWorkRange, PaintPartFlags::Grid );

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

void ScUndoUpdateAreaLink::Undo()
{
    ScDocument& rDoc = rDocShell.GetDocument();
    sfx2::LinkManager* pLinkManager = rDoc.GetLinkManager();
    ScAreaLink* pLink = lcl_FindAreaLink( pLinkManager, aNewDoc, aNewFlt, aNewOpt,
                                            aNewArea, aNewRange );
    if (pLink)
    {
        pLink->SetSource( aOldDoc, aOldFlt, aOldOpt, aOldArea );        // old data in Link
        pLink->SetDestArea( aOldRange );
        pLink->SetRefreshDelay( nOldRefresh );
    }

    DoChange(true);
}

void ScUndoUpdateAreaLink::Redo()
{
    ScDocument& rDoc = rDocShell.GetDocument();
    sfx2::LinkManager* pLinkManager = rDoc.GetLinkManager();
    ScAreaLink* pLink = lcl_FindAreaLink( pLinkManager, aOldDoc, aOldFlt, aOldOpt,
                                            aOldArea, aOldRange );
    if (pLink)
    {
        pLink->SetSource( aNewDoc, aNewFlt, aNewOpt, aNewArea );        // new values in link
        pLink->SetDestArea( aNewRange );
        pLink->SetRefreshDelay( nNewRefresh );
    }

    DoChange(false);
}

void ScUndoUpdateAreaLink::Repeat(SfxRepeatTarget& /* rTarget */)
{
    // makes no sense
}

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

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