/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source eCode 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 <sfx2/app.hxx>
#include <sfx2/request.hxx>
#include <editeng/borderline.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/lineitem.hxx>
#include <editeng/scriptsetitem.hxx>
#include <svl/srchitem.hxx>
#include <sfx2/linkmgr.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/docfilt.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/objitem.hxx>
#include <sfx2/viewfrm.hxx>
#include <svl/numformat.hxx>
#include <svl/stritem.hxx>
#include <svl/zforlist.hxx>
#include <svx/srchdlg.hxx>
#include <svx/svdview.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <osl/diagnose.h>

#include <viewfunc.hxx>
#include <vcl/uitest/logger.hxx>
#include <vcl/uitest/eventdescription.hxx>

#include <sc.hrc>
#include <globstr.hrc>
#include <scresid.hxx>

#include <attrib.hxx>
#include <autoform.hxx>
#include <formulacell.hxx>
#include <cellmergeoption.hxx>
#include <compiler.hxx>
#include <docfunc.hxx>
#include <docpool.hxx>
#include <docsh.hxx>
#include <global.hxx>
#include <patattr.hxx>
#include <printfun.hxx>
#include <refundo.hxx>
#include <table.hxx>
#include <tablink.hxx>
#include <tabvwsh.hxx>
#include <uiitems.hxx>
#include <undoblk.hxx>
#include <undotab.hxx>
#include <sizedev.hxx>
#include <editable.hxx>
#include <docuno.hxx>
#include <charthelper.hxx>
#include <tabbgcolor.hxx>
#include <clipparam.hxx>
#include <prnsave.hxx>
#include <searchresults.hxx>
#include <tokenarray.hxx>
#include <rowheightcontext.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <comphelper/lok.hxx>
#include <mergecellsdialog.hxx>
#include <sheetevents.hxx>
#include <columnspanset.hxx>
#include <SheetViewManager.hxx>

#include <vector>
#include <memory>
#include <boost/property_tree/json_parser.hpp>
#include <tools/json_writer.hxx>

#include <officecfg/Office/Calc.hxx>
#include <sfx2/lokhelper.hxx>

using namespace com::sun::star;
using ::editeng::SvxBorderLine;

namespace {

void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& rAction)
{
    EventDescription aDescription;
    aDescription.aID = "grid_window";
    aDescription.aAction = rAction;
    aDescription.aParameters = std::move(aParameters);
    aDescription.aParent = "MainWindow";
    aDescription.aKeyWord = "ScGridWinUIObject";

    UITestLogger::getInstance().logEvent(aDescription);
}
}

bool ScViewFunc::AdjustBlockHeight( bool bPaint, ScMarkData* pMarkData, bool bRangeWidthChanged )
{
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    if (!pMarkData)
        pMarkData = &GetViewData().GetMarkData();

    ScDocument& rDoc = pDocSh->GetDocument();
    std::vector<sc::ColRowSpan> aMarkedRows = pMarkData->GetMarkedRowSpans();

    if (aMarkedRows.empty())
    {
        SCROW nCurRow = GetViewData().GetCurY();
        aMarkedRows.emplace_back(nCurRow, nCurRow);
    }

    if (comphelper::LibreOfficeKit::isActive())
    {
        SCCOLROW nStart = aMarkedRows[0].mnStart;
        OnLOKSetWidthOrHeight(nStart, /*width: */ false);
    }

    double nPPTX = GetViewData().GetPPTX();
    double nPPTY = GetViewData().GetPPTY();
    Fraction aZoomX = GetViewData().GetZoomX();
    Fraction aZoomY = GetViewData().GetZoomY();

    ScSizeDeviceProvider aProv(*pDocSh);
    if (aProv.IsPrinter())
    {
        nPPTX = aProv.GetPPTX();
        nPPTY = aProv.GetPPTY();
        aZoomX = aZoomY = Fraction( 1, 1 );
    }

    sc::RowHeightContext aCxt(rDoc.MaxRow(), nPPTX, nPPTY, aZoomX, aZoomY, aProv.GetDevice());
    bool bAnyChanged = false;
    for (const SCTAB& nTab : *pMarkData)
    {
        bool bChanged = false;
        SCROW nPaintY = 0;
        for (const auto& rRow : aMarkedRows)
        {
            SCROW nStartNo = rRow.mnStart;
            SCROW nEndNo = rRow.mnEnd;
            ScAddress aTopLeft(0, nStartNo, nTab);
            rDoc.UpdateScriptTypes(aTopLeft, rDoc.GetSheetLimits().GetMaxColCount(), nEndNo-nStartNo+1);
            if (rDoc.SetOptimalHeight(aCxt, nStartNo, nEndNo, nTab, true))
            {
                if (!bChanged)
                    nPaintY = nStartNo;
                bAnyChanged = bChanged = true;
            }
        }
        // tdf#76183: recalculate objects' positions
        if (bChanged)
            rDoc.SetDrawPageSize(nTab);
        if ( bPaint && bChanged )
            pDocSh->PostPaint( 0, nPaintY, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab,
                                                PaintPartFlags::Grid | PaintPartFlags::Left );
    }

    if ( bPaint && bAnyChanged )
        pDocSh->UpdateOle(GetViewData());

    if (comphelper::LibreOfficeKit::isActive())
    {
        ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
                GetViewData().GetViewShell(),
                bRangeWidthChanged /* bColumns */, true /* bRows */,
                true /* bSizes*/, false /* bHidden */, false /* bFiltered */,
                false /* bGroups */, GetViewData().GetTabNumber());
        ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), ROW_HEADER, GetViewData().GetTabNumber());
    }

    return bAnyChanged;
}

bool ScViewFunc::AdjustRowHeight( SCROW nStartRow, SCROW nEndRow, bool bApi )
{
    if (comphelper::LibreOfficeKit::isActive())
    {
        OnLOKSetWidthOrHeight(nStartRow, /*width: */ false);
    }

    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = pDocSh->GetDocument();
    SCTAB nTab = GetViewData().CurrentTabForData();
    double nPPTX = GetViewData().GetPPTX();
    double nPPTY = GetViewData().GetPPTY();
    Fraction aZoomX = GetViewData().GetZoomX();
    Fraction aZoomY = GetViewData().GetZoomY();
    sal_uInt16 nOldPixel = 0;
    if (nStartRow == nEndRow)
        nOldPixel = static_cast<sal_uInt16>(rDoc.GetRowHeight(nStartRow,nTab) * nPPTY);

    ScSizeDeviceProvider aProv(*pDocSh);
    if (aProv.IsPrinter())
    {
        nPPTX = aProv.GetPPTX();
        nPPTY = aProv.GetPPTY();
        aZoomX = aZoomY = Fraction( 1, 1 );
    }
    sc::RowHeightContext aCxt(rDoc.MaxRow(), nPPTX, nPPTY, aZoomX, aZoomY, aProv.GetDevice());
    bool bChanged = rDoc.SetOptimalHeight(aCxt, nStartRow, nEndRow, nTab, bApi);

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

    if (bChanged && ( nStartRow == nEndRow ))
    {
        sal_uInt16 nNewPixel = static_cast<sal_uInt16>(rDoc.GetRowHeight(nStartRow,nTab) * nPPTY);
        if ( nNewPixel == nOldPixel )
            bChanged = false;
    }

    if ( bChanged )
        pDocSh->PostPaint( 0, nStartRow, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab,
                                            PaintPartFlags::Grid | PaintPartFlags::Left );

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

    return bChanged;
}

namespace {

enum ScAutoSum
{
    ScAutoSumNone = 0,
    ScAutoSumData,
    ScAutoSumSum,
    ScAutoSumAverage,
    ScAutoSumMax,
    ScAutoSumMin,
    ScAutoSumCount,
    ScAutoSumCountA,
    ScAutoSumProduct,
    ScAutoSumStDev,
    ScAutoSumStDevP,
    ScAutoSumVar,
    ScAutoSumVarP,
    ScAutoSumEnd
};

}

static ScAutoSum lcl_IsAutoSumData( ScDocument& rDoc, SCCOL nCol, SCROW nRow,
        SCTAB nTab, ScDirection eDir, SCCOLROW& nExtend )
{
    ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, nTab));
    if (aCell.hasNumeric())
    {
        if (aCell.getType() == CELLTYPE_FORMULA)
        {
            ScAutoSum val = ScAutoSumNone;
            ScTokenArray* pCode = aCell.getFormula()->GetCode();
            if ( pCode )
            {
                switch( pCode->GetOuterFuncOpCode() )
                {
                    case ocSum     : val = ScAutoSumSum;
                        break;
                    case ocAverage : val = ScAutoSumAverage;
                        break;
                    case ocMax     : val = ScAutoSumMax;
                        break;
                    case ocMin     : val = ScAutoSumMin;
                        break;
                    case ocCount   : val = ScAutoSumCount;
                        break;
                    case ocCount2  : val = ScAutoSumCountA;
                        break;
                    case ocProduct : val = ScAutoSumProduct;
                        break;
                    case ocStDev   : val = ScAutoSumStDev;
                        break;
                    case ocStDevP  : val = ScAutoSumStDevP;
                        break;
                    case ocVar     : val = ScAutoSumVar;
                        break;
                    case ocVarP    : val = ScAutoSumVarP;
                        break;
                    default        :
                        break;
                }
                if ( pCode->GetAdjacentExtendOfOuterFuncRefs( nExtend,
                        ScAddress( nCol, nRow, nTab ), eDir ) )
                    return val;
            }
        }
        return ScAutoSumData;
    }
    return ScAutoSumNone;
}

#define SC_AUTOSUM_MAXCOUNT     20

static ScAutoSum lcl_SeekAutoSumData( ScDocument& rDoc, SCCOL& nCol, SCROW& nRow,
        SCTAB nTab, ScDirection eDir, SCCOLROW& nExtend )
{
    sal_uInt16 nCount = 0;
    while (nCount < SC_AUTOSUM_MAXCOUNT)
    {
        if ( eDir == DIR_TOP )
        {
            if (nRow > 0)
                --nRow;
            else
                return ScAutoSumNone;
        }
        else
        {
            if (nCol > 0)
                --nCol;
            else
                return ScAutoSumNone;
        }
        ScAutoSum eSum;
        if ( (eSum = lcl_IsAutoSumData(
                rDoc, nCol, nRow, nTab, eDir, nExtend )) != ScAutoSumNone )
            return eSum;
        ++nCount;
    }
    return ScAutoSumNone;
}

#undef SC_AUTOSUM_MAXCOUNT

static bool lcl_FindNextSumEntryInColumn( ScDocument& rDoc, SCCOL nCol, SCROW& nRow,
                                   SCTAB nTab, SCCOLROW& nExtend, SCROW nMinRow )
{
    const SCROW nTmp = nRow;
    ScAutoSum eSkip = ScAutoSumNone;
    for (;;)
    {
        eSkip = lcl_IsAutoSumData( rDoc, nCol, nRow, nTab, DIR_TOP, nExtend );
        if (eSkip != ScAutoSumData || nRow <= nMinRow )
            break;
        --nRow;
    }
    return eSkip >= ScAutoSumSum && nRow < nTmp;
}

static bool lcl_FindNextSumEntryInRow( ScDocument& rDoc, SCCOL& nCol, SCROW nRow,
                                SCTAB nTab, SCCOLROW& nExtend, SCCOL nMinCol )
{
    const SCCOL nTmp = nCol;
    ScAutoSum eSkip = ScAutoSumNone;
    for (;;)
    {
        eSkip = lcl_IsAutoSumData( rDoc, nCol, nRow, nTab, DIR_LEFT, nExtend );
        if (eSkip != ScAutoSumData || nCol <= nMinCol )
            break;
        --nCol;
    }
    return eSkip >= ScAutoSumSum && nCol < nTmp;
}

static ScAutoSum lcl_GetAutoSumForColumnRange( ScDocument& rDoc, ScRangeList& rRangeList, const ScRange& rRange )
{
    const ScAddress aStart = rRange.aStart;
    const ScAddress aEnd = rRange.aEnd;
    if ( aStart.Col() != aEnd.Col() )
    {
        return ScAutoSumNone;
    }

    const SCTAB nTab = aEnd.Tab();
    const SCCOL nCol = aEnd.Col();
    SCROW nEndRow = aEnd.Row();
    SCROW nStartRow = nEndRow;
    SCCOLROW nExtend = 0;
    ScAutoSum eSum = lcl_IsAutoSumData( rDoc, nCol, nEndRow, nTab, DIR_TOP, nExtend /*out*/ );

    if ( eSum >= ScAutoSumSum )
    {
        bool bContinue = false;
        do
        {
            rRangeList.push_back( ScRange( nCol, nStartRow, nTab, nCol, nEndRow, nTab ) );
            nEndRow = static_cast< SCROW >( nExtend );
            bContinue = lcl_FindNextSumEntryInColumn( rDoc, nCol, nEndRow /*inout*/, nTab, nExtend /*out*/, aStart.Row() );
            if ( bContinue )
            {
                nStartRow = nEndRow;
            }
        } while ( bContinue );
    }
    else
    {
        while ( nStartRow > aStart.Row() )
        {
            eSum = lcl_IsAutoSumData( rDoc, nCol, nStartRow-1, nTab, DIR_TOP, nExtend /*out*/ );
            if (eSum >= ScAutoSumSum )
                break;
            --nStartRow;
        }
        rRangeList.push_back( ScRange( nCol, nStartRow, nTab, nCol, nEndRow, nTab ) );
        if (eSum == ScAutoSumNone)
            eSum = ScAutoSumData;
    }

    return eSum;
}

static ScAutoSum lcl_GetAutoSumForRowRange( ScDocument& rDoc, ScRangeList& rRangeList, const ScRange& rRange )
{
    const ScAddress aStart = rRange.aStart;
    const ScAddress aEnd = rRange.aEnd;
    if ( aStart.Row() != aEnd.Row() )
    {
        return ScAutoSumNone;
    }

    const SCTAB nTab = aEnd.Tab();
    const SCROW nRow = aEnd.Row();
    SCCOL nEndCol = aEnd.Col();
    SCCOL nStartCol = nEndCol;
    SCCOLROW nExtend = 0;
    ScAutoSum eSum = lcl_IsAutoSumData( rDoc, nEndCol, nRow, nTab, DIR_LEFT, nExtend /*out*/ );

    if ( eSum >= ScAutoSumSum )
    {
        bool bContinue = false;
        do
        {
            rRangeList.push_back( ScRange( nStartCol, nRow, nTab, nEndCol, nRow, nTab ) );
            nEndCol = static_cast< SCCOL >( nExtend );
            bContinue = lcl_FindNextSumEntryInRow( rDoc, nEndCol /*inout*/, nRow, nTab, nExtend /*out*/, aStart.Col() );
            if ( bContinue )
            {
                nStartCol = nEndCol;
            }
        } while ( bContinue );
    }
    else
    {
        while ( nStartCol > aStart.Col() )
        {
            eSum = lcl_IsAutoSumData( rDoc, nStartCol-1, nRow, nTab, DIR_LEFT, nExtend /*out*/ );
            if (eSum >= ScAutoSumSum )
                break;
            --nStartCol;
        }
        rRangeList.push_back( ScRange( nStartCol, nRow, nTab, nEndCol, nRow, nTab ) );
        if (eSum == ScAutoSumNone)
            eSum = ScAutoSumData;
    }

    return eSum;
}

static sal_Int8 GetSubTotal( const OpCode eCode )
{
    sal_Int8 val;
    switch ( eCode )
    {
        case ocSum     : val = 9;
            break;
        case ocAverage : val = 1;
            break;
        case ocMax     : val = 4;
            break;
        case ocMin     : val = 5;
            break;
        case ocCount   : val = 2;
            break;
        case ocCount2  : val = 3;
            break;
        case ocProduct : val = 6;
            break;
        case ocStDev   : val = 7;
            break;
        case ocStDevP  : val = 8;
            break;
        case ocVar     : val = 10;
            break;
        case ocVarP    : val = 11;
            break;
        default        : val = 9;
    }

    return val;
}

bool ScViewFunc::GetAutoSumArea( ScRangeList& rRangeList )
{
    ScDocument& rDoc = GetViewData().GetDocument();
    SCTAB nTab = GetViewData().CurrentTabForData();

    SCCOL nCol = GetViewData().GetCurX();
    SCROW nRow = GetViewData().GetCurY();

    SCCOL nStartCol = nCol;
    SCROW nStartRow = nRow;
    SCCOL nEndCol    = nCol;
    SCROW nEndRow    = nRow;
    SCCOL nSeekCol   = nCol;
    SCROW nSeekRow   = nRow;
    SCCOLROW nExtend;       // will become valid via reference for ScAutoSumSum

    bool bCol = false;
    bool bRow = false;

    ScAutoSum eSum;
    if ( nRow != 0
            && ((eSum = lcl_IsAutoSumData( rDoc, nCol, nRow-1, nTab,
                DIR_TOP, nExtend /*out*/ )) == ScAutoSumData )
            && ((eSum = lcl_IsAutoSumData( rDoc, nCol, nRow-1, nTab,
                DIR_LEFT, nExtend /*out*/ )) == ScAutoSumData )
        )
    {
        bRow = true;
        nSeekRow = nRow - 1;
    }
    else if ( nCol != 0 && (eSum = lcl_IsAutoSumData( rDoc, nCol-1, nRow, nTab,
            DIR_LEFT, nExtend /*out*/ )) == ScAutoSumData )
    {
        bCol = true;
        nSeekCol = nCol - 1;
    }
    else if ( (eSum = lcl_SeekAutoSumData( rDoc, nCol, nSeekRow, nTab, DIR_TOP, nExtend /*out*/ )) != ScAutoSumNone )
        bRow = true;
    else if (( eSum = lcl_SeekAutoSumData( rDoc, nSeekCol, nRow, nTab, DIR_LEFT, nExtend /*out*/ )) != ScAutoSumNone )
        bCol = true;

    if ( bCol || bRow )
    {
        if ( bRow )
        {
            nStartRow = nSeekRow;       // nSeekRow might be adjusted via reference
            if ( eSum >= ScAutoSumSum  && eSum < ScAutoSumEnd )
                nEndRow = nStartRow;        // only sum sums
            else
                nEndRow = nRow - 1;     // maybe extend data area at bottom
        }
        else
        {
            nStartCol = nSeekCol;       // nSeekCol might be adjusted via reference
            if ( eSum >= ScAutoSumSum )
                nEndCol = nStartCol;        // only sum sums
            else
                nEndCol = nCol - 1;     // maybe extend data area to the right
        }
        bool bContinue = false;
        do
        {
            if ( eSum == ScAutoSumData )
            {
                if ( bRow )
                {
                    while ( nStartRow != 0 && lcl_IsAutoSumData( rDoc, nCol,
                            nStartRow-1, nTab, DIR_TOP, nExtend /*out*/ ) == eSum )
                        --nStartRow;
                }
                else
                {
                    while ( nStartCol != 0 && lcl_IsAutoSumData( rDoc, nStartCol-1,
                            nRow, nTab, DIR_LEFT, nExtend /*out*/ ) == eSum )
                        --nStartCol;
                }
            }
            rRangeList.push_back(
                ScRange( nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab ) );
            if ( eSum >= ScAutoSumSum )
            {
                if ( bRow )
                {
                    nEndRow = static_cast< SCROW >( nExtend );
                    bContinue = lcl_FindNextSumEntryInColumn( rDoc, nCol, nEndRow /*inout*/, nTab, nExtend /*out*/, 0 );
                    if ( bContinue )
                    {
                        nStartRow = nEndRow;
                    }
                }
                else
                {
                    nEndCol = static_cast< SCCOL >( nExtend );
                    bContinue = lcl_FindNextSumEntryInRow( rDoc, nEndCol /*inout*/, nRow, nTab, nExtend /*out*/, 0 );
                    if ( bContinue )
                    {
                        nStartCol = nEndCol;
                    }
                }
            }
        } while ( bContinue );
        return true;
    }
    return false;
}

void ScViewFunc::EnterAutoSum(const ScRangeList& rRangeList, bool bSubTotal, const ScAddress& rAddr, const OpCode eCode)
{
    OUString aFormula = GetAutoSumFormula( rRangeList, bSubTotal, rAddr , eCode);
    EnterBlock( aFormula, nullptr );
}

bool ScViewFunc::AutoSum( const ScRange& rRange, bool bSubTotal, bool bSetCursor, bool bContinue , const OpCode eCode)
{
    ScDocument& rDoc = GetViewData().GetDocument();
    const SCTAB nTab = rRange.aStart.Tab();
    SCCOL nStartCol = rRange.aStart.Col();
    SCROW nStartRow = rRange.aStart.Row();
    const SCCOL nEndCol = rRange.aEnd.Col();
    const SCROW nEndRow = rRange.aEnd.Row();
    SCCOLROW nExtend = 0; // out parameter for lcl_IsAutoSumData

    // ignore rows at the top of the given range which don't contain autosum data
    bool bRowData = false;
    for ( SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow )
    {
        for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol )
        {
            if ( lcl_IsAutoSumData( rDoc, nCol, nRow, nTab, DIR_TOP, nExtend ) != ScAutoSumNone )
            {
                bRowData = true;
                break;
            }
        }
        if ( bRowData )
        {
            nStartRow = nRow;
            break;
        }
    }
    if ( !bRowData )
    {
        return false;
    }

    // ignore columns at the left of the given range which don't contain autosum data
    bool bColData = false;
    for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol )
    {
        for ( SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow )
        {
            if ( lcl_IsAutoSumData( rDoc, nCol, nRow, nTab, DIR_LEFT, nExtend ) != ScAutoSumNone )
            {
                bColData = true;
                break;
            }
        }
        if ( bColData )
        {
            nStartCol = nCol;
            break;
        }
    }
    if ( !bColData )
    {
        return false;
    }

    const bool bEndRowEmpty = rDoc.IsBlockEmpty( nStartCol, nEndRow, nEndCol, nEndRow, nTab );
    const bool bEndColEmpty = rDoc.IsBlockEmpty( nEndCol, nStartRow, nEndCol, nEndRow, nTab );
    bool bRow = ( nStartRow != nEndRow ) && ( bEndRowEmpty || !bEndColEmpty );
    bool bCol = ( nStartCol != nEndCol ) && ( bEndColEmpty || nStartRow == nEndRow );

    // find an empty row for entering the result
    SCROW nInsRow = nEndRow;
    if ( bRow && !bEndRowEmpty )
    {
        if ( nInsRow < rDoc.MaxRow() )
        {
            ++nInsRow;
            while ( !rDoc.IsBlockEmpty( nStartCol, nInsRow, nEndCol, nInsRow, nTab ) )
            {
                if ( nInsRow < rDoc.MaxRow() )
                {
                    ++nInsRow;
                }
                else
                {
                    bRow = false;
                    break;
                }
            }
        }
        else
        {
            bRow = false;
        }
    }

    // find an empty column for entering the result
    SCCOL nInsCol = nEndCol;
    if ( bCol && !bEndColEmpty )
    {
        if ( nInsCol < rDoc.MaxCol() )
        {
            ++nInsCol;
            while ( !rDoc.IsBlockEmpty( nInsCol, nStartRow, nInsCol, nEndRow, nTab ) )
            {
                if ( nInsCol < rDoc.MaxCol() )
                {
                    ++nInsCol;
                }
                else
                {
                    bCol = false;
                    break;
                }
            }
        }
        else
        {
            bCol = false;
        }
    }

    if ( !bRow && !bCol )
    {
        return false;
    }

    SCCOL nMarkEndCol = nEndCol;
    SCROW nMarkEndRow = nEndRow;
    ScAutoSum eSum = ScAutoSumNone;
    SCROW nColSums = 0;
    SCCOL nRowSums = 0;
    SCROW nColSumsStartRow = 0;
    SCCOL nRowSumsStartCol = 0;

    if ( bRow )
    {
        // calculate the row sums for all columns of the given range

        SCROW nSumEndRow = nEndRow;

        if ( bEndRowEmpty )
        {
            // the last row of the given range is empty;
            // don't take into account for calculating the autosum
            --nSumEndRow;
        }
        else
        {
            // increase mark range
            ++nMarkEndRow;
        }

        for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol )
        {
            if ( !rDoc.IsBlockEmpty( nCol, nStartRow, nCol, nSumEndRow, nTab ) )
            {
                ScRangeList aRangeList;
                // Include the originally selected start row.
                const ScRange aRange( nCol, rRange.aStart.Row(), nTab, nCol, nSumEndRow, nTab );
                if ( (eSum = lcl_GetAutoSumForColumnRange( rDoc, aRangeList, aRange )) != ScAutoSumNone )
                {
                    if (++nRowSums == 1)
                        nRowSumsStartCol = aRangeList[0].aStart.Col();
                    const OUString aFormula = GetAutoSumFormula(
                        aRangeList, bSubTotal, ScAddress(nCol, nInsRow, nTab), eCode);
                    EnterData( nCol, nInsRow, nTab, aFormula );
                }
            }
        }
    }

    if ( bCol )
    {
        // calculate the column sums for all rows of the given range

        SCCOL nSumEndCol = nEndCol;

        if ( bEndColEmpty )
        {
            // the last column of the given range is empty;
            // don't take into account for calculating the autosum
            --nSumEndCol;
        }
        else
        {
            // increase mark range
            ++nMarkEndCol;
        }

        for ( SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow )
        {
            if ( !rDoc.IsBlockEmpty( nStartCol, nRow, nSumEndCol, nRow, nTab ) )
            {
                ScRangeList aRangeList;
                // Include the originally selected start column.
                const ScRange aRange( rRange.aStart.Col(), nRow, nTab, nSumEndCol, nRow, nTab );
                if ( (eSum = lcl_GetAutoSumForRowRange( rDoc, aRangeList, aRange )) != ScAutoSumNone )
                {
                    if (++nColSums == 1)
                        nColSumsStartRow = aRangeList[0].aStart.Row();
                    const OUString aFormula = GetAutoSumFormula( aRangeList, bSubTotal, ScAddress(nInsCol, nRow, nTab), eCode );
                    EnterData( nInsCol, nRow, nTab, aFormula );
                }
            }
        }
    }

    // Set new mark range and cursor position.
    // For sum of sums (and data until sum) mark the actual resulting range if
    // there is only one, or the data range if more than one. Otherwise use the
    // original selection. All extended by end column/row where the sum is put.
    const ScRange aMarkRange(
            (eSum >= ScAutoSumSum ?
             (nRowSums == 1 ? nRowSumsStartCol : nStartCol) :
             rRange.aStart.Col()),
            (eSum >= ScAutoSumSum ?
             (nColSums == 1 ? nColSumsStartRow : nStartRow) :
             rRange.aStart.Row()),
            nTab, nMarkEndCol, nMarkEndRow, nTab );
    MarkRange( aMarkRange, false, bContinue );
    if ( bSetCursor )
    {
        SetCursor( nMarkEndCol, nMarkEndRow );
    }

    return true;
}

OUString ScViewFunc::GetAutoSumFormula( const ScRangeList& rRangeList, bool bSubTotal, const ScAddress& rAddr , const OpCode eCode)
{
    ScViewData& rViewData = GetViewData();
    ScDocument& rDoc = rViewData.GetDocument();
    ScTokenArray aArray(rDoc);

    aArray.AddOpCode(bSubTotal ? ocSubTotal : eCode);
    aArray.AddOpCode(ocOpen);

    if (bSubTotal)
    {
        aArray.AddDouble( GetSubTotal( eCode ) );
        aArray.AddOpCode(ocSep);
    }

    if(!rRangeList.empty())
    {
        size_t ListSize = rRangeList.size();
        for ( size_t i = 0; i < ListSize; ++i )
        {
            const ScRange & r = rRangeList[i];
            if (i != 0)
                aArray.AddOpCode(ocSep);
            ScComplexRefData aRef;
            aRef.InitRangeRel(rDoc, r, rAddr);
            aArray.AddDoubleReference(aRef);
        }
    }

    aArray.AddOpCode(ocClose);

    ScCompiler aComp(rDoc, rAddr, aArray, rDoc.GetGrammar());
    OUStringBuffer aBuf;
    aComp.CreateStringFromTokenArray(aBuf);
    aBuf.insert(0, "=");
    return aBuf.makeStringAndClear();
}

void ScViewFunc::EnterBlock( const OUString& rString, const EditTextObject* pData )
{
    //  test for multi selection

    SCCOL nCol = GetViewData().GetCurX();
    SCROW nRow = GetViewData().GetCurY();
    SCTAB nTab = GetViewData().CurrentTabForData();
    ScMarkData& rMark = GetViewData().GetMarkData();
    if ( rMark.IsMultiMarked() )
    {
        rMark.MarkToSimple();
        if ( rMark.IsMultiMarked() )
        {       // "Insert into multi selection not possible"
            ErrorMessage(STR_MSSG_PASTEFROMCLIP_0);

            //  insert into single cell
            if ( pData )
                EnterData(nCol, nRow, nTab, *pData);
            else
                EnterData( nCol, nRow, nTab, rString );
            return;
        }
    }

    if (GetViewData().SelectionForbidsCellFill())
    {
        PaintArea(nCol, nRow, nCol, nRow);        // possibly the edit-engine is still painted there
        return;
    }

    ScDocument& rDoc = GetViewData().GetDocument();
    OUString aNewStr = rString;
    if ( pData )
    {
        const ScPatternAttr* pOldPattern = rDoc.GetPattern( nCol, nRow, nTab );
        ScTabEditEngine aEngine( *pOldPattern, rDoc.GetEditEnginePool(), rDoc );
        aEngine.SetTextCurrentDefaults(*pData);

        ScEditAttrTester aTester( &aEngine );
        if (!aTester.NeedsObject())
        {
            aNewStr = aEngine.GetText();
            pData = nullptr;
        }
    }

    //  Insert via PasteFromClip
    weld::WaitObject aWait(GetViewData().GetDialogParent());

    ScAddress aPos( nCol, nRow, nTab );

    ScDocumentUniquePtr pInsDoc(new ScDocument( SCDOCMODE_CLIP ));
    pInsDoc->ResetClip( &rDoc, nTab );

    if (aNewStr[0] == '=')                      // Formula ?
    {
        //  SetString not possible, because in Clipboard-Documents nothing will be compiled!
        pInsDoc->SetFormulaCell(aPos, new ScFormulaCell(rDoc, aPos, aNewStr));
    }
    else if ( pData )
    {
        // A copy of pData will be stored.
        pInsDoc->SetEditText(aPos, *pData, rDoc.GetEditEnginePool());
    }
    else
        pInsDoc->SetString( nCol, nRow, nTab, aNewStr );

    pInsDoc->SetClipArea( ScRange(aPos) );
    // insert Block, with Undo etc.
    if ( !PasteFromClip( InsertDeleteFlags::CONTENTS, pInsDoc.get(), ScPasteFunc::NONE, false, false,
            false, INS_NONE, InsertDeleteFlags::ATTRIB ) )
        return;

    const SfxUInt32Item& rItem = pInsDoc->GetAttr(nCol, nRow, nTab, ATTR_VALUE_FORMAT);
    // set number format if incompatible
    // MarkData was already MarkToSimple'ed in PasteFromClip
    const ScRange& aRange = rMark.GetMarkArea();
    ScPatternAttr aPattern(rDoc.getCellAttributeHelper());
    aPattern.ItemSetPut(rItem);
    SvNumFormatType nNewType = rDoc.GetFormatTable()->GetType(rItem.GetValue());
    rDoc.ApplyPatternIfNumberformatIncompatible( aRange, rMark,
        aPattern, nNewType );
}

//  manual page break

void ScViewFunc::InsertPageBreak( bool bColumn, bool bRecord, const ScAddress* pPos,
                                    bool bSetModified )
{
    SCTAB nTab = GetViewData().CurrentTabForData();
    ScAddress aCursor;
    if (pPos)
        aCursor = *pPos;
    else
        aCursor = ScAddress( GetViewData().GetCurX(), GetViewData().GetCurY(), nTab );

    bool bSuccess = GetViewData().GetDocShell()->GetDocFunc().
                        InsertPageBreak( bColumn, aCursor, bRecord, bSetModified );

    if ( bSuccess && bSetModified )
        UpdatePageBreakData( true );    // for PageBreak-Mode
}

void ScViewFunc::DeletePageBreak( bool bColumn, bool bRecord, const ScAddress* pPos,
                                    bool bSetModified )
{
    SCTAB nTab = GetViewData().CurrentTabForData();
    ScAddress aCursor;
    if (pPos)
        aCursor = *pPos;
    else
        aCursor = ScAddress( GetViewData().GetCurX(), GetViewData().GetCurY(), nTab );

    bool bSuccess = GetViewData().GetDocShell()->GetDocFunc().
                        RemovePageBreak( bColumn, aCursor, bRecord, bSetModified );

    if ( bSuccess && bSetModified )
        UpdatePageBreakData( true );    // for PageBreak-Mode
}

void ScViewFunc::RemoveManualBreaks()
{
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = pDocSh->GetDocument();
    SCTAB nTab = GetViewData().CurrentTabForData();
    bool bUndo(rDoc.IsUndoEnabled());

    if (bUndo)
    {
        ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
        pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true );
        rDoc.CopyToDocument( 0,0,nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc );
        pDocSh->GetUndoManager()->AddUndoAction(
                                std::make_unique<ScUndoRemoveBreaks>( *pDocSh, nTab, std::move(pUndoDoc) ) );
    }

    rDoc.RemoveManualBreaks(nTab);
    rDoc.UpdatePageBreaks(nTab);

    UpdatePageBreakData( true );
    pDocSh->SetDocumentModified();
    pDocSh->PostPaint( 0,0,nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid );
}

void ScViewFunc::SetPrintZoom(sal_uInt16 nScale)
{
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    SCTAB nTab = GetViewData().CurrentTabForData();
    pDocSh->SetPrintZoom( nTab, nScale, 0/*nPages*/ );
}

void ScViewFunc::AdjustPrintZoom()
{
    ScRange aRange;
    if ( GetViewData().GetSimpleArea( aRange ) != SC_MARK_SIMPLE )
        aRange = GetViewData().GetMarkData().GetMultiMarkArea();
    GetViewData().GetDocShell()->AdjustPrintZoom( aRange );
}

void ScViewFunc::SetPrintRanges( bool bEntireSheet, const OUString* pPrint,
                                const OUString* pRepCol, const OUString* pRepRow,
                                bool bAddPrint )
{
    //  on all selected tables

    ScDocShell* pDocSh  = GetViewData().GetDocShell();
    ScDocument& rDoc    = pDocSh->GetDocument();
    ScMarkData& rMark   = GetViewData().GetMarkData();
    bool bUndo (rDoc.IsUndoEnabled());

    std::unique_ptr<ScPrintRangeSaver> pOldRanges = rDoc.CreatePrintRangeSaver();

    ScAddress::Details aDetails(rDoc.GetAddressConvention(), 0, 0);

    for (const SCTAB& nTab : rMark)
    {
        ScRange aRange( 0,0,nTab );

        //  print ranges

        if( !bAddPrint )
        {
            rDoc.ClearPrintRanges( nTab );
            rDoc.ClearPrintNamedRanges(nTab);
        }

        if( bEntireSheet )
        {
            rDoc.SetPrintEntireSheet( nTab );
        }
        else if ( pPrint )
        {
            if ( !pPrint->isEmpty() )
            {
                const sal_Unicode sep = ScCompiler::GetNativeSymbolChar(ocSep);
                sal_Int32 nPos = 0;
                do
                {
                    const OUString aToken = pPrint->getToken(0, sep, nPos);
                    if ( aRange.ParseAny( aToken, rDoc, aDetails ) & ScRefFlags::VALID )
                        rDoc.AddPrintRange( nTab, aRange );
                }
                while (nPos >= 0);
            }
        }
        else    // NULL = use selection (print range is always set), use empty string to delete all ranges
        {
            if ( GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE )
            {
                rDoc.AddPrintRange( nTab, aRange );
            }
            else if ( rMark.IsMultiMarked() )
            {
                rMark.MarkToMulti();
                ScRangeListRef pList( new ScRangeList );
                rMark.FillRangeListWithMarks( pList.get(), false );
                for (size_t i = 0, n = pList->size(); i < n; ++i)
                {
                    const ScRange & rR = (*pList)[i];
                    rDoc.AddPrintRange(nTab, rR);
                }
            }
        }

        //  repeat columns

        if ( pRepCol )
        {
            if ( pRepCol->isEmpty() )
                rDoc.SetRepeatColRange( nTab, std::nullopt );
            else
                if ( aRange.ParseAny( *pRepCol, rDoc, aDetails ) & ScRefFlags::VALID )
                    rDoc.SetRepeatColRange( nTab, std::move(aRange) );
        }

        //  repeat rows

        if ( pRepRow )
        {
            if ( pRepRow->isEmpty() )
                rDoc.SetRepeatRowRange( nTab, std::nullopt );
            else
                if ( aRange.ParseAny( *pRepRow, rDoc, aDetails ) & ScRefFlags::VALID )
                    rDoc.SetRepeatRowRange( nTab, std::move(aRange) );
        }
    }

    //  undo (for all tables)
    if (bUndo)
    {
        SCTAB nCurTab = GetViewData().CurrentTabForData();
        std::unique_ptr<ScPrintRangeSaver> pNewRanges = rDoc.CreatePrintRangeSaver();
        if (comphelper::LibreOfficeKit::isActive())
        {
            tools::JsonWriter aJsonWriter;
            pNewRanges->GetPrintRangesInfo(aJsonWriter);

            SfxViewShell* pViewShell = GetViewData().GetViewShell();
            const OString message = aJsonWriter.finishAndGetAsOString();
            pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_PRINT_RANGES, message);
        }

        pDocSh->GetUndoManager()->AddUndoAction(
                    std::make_unique<ScUndoPrintRange>( *pDocSh, nCurTab, std::move(pOldRanges), std::move(pNewRanges) ) );
    }
    else
        pOldRanges.reset();

    //  update page breaks

    for (const auto& rTab : rMark)
        ScPrintFunc( *pDocSh, pDocSh->GetPrinter(), rTab ).UpdatePages();

    SfxBindings& rBindings = GetViewData().GetBindings();
    rBindings.Invalidate( SID_DELETE_PRINTAREA );

    pDocSh->SetDocumentModified();
}

//  Merge cells

bool ScViewFunc::TestMergeCells()           // pre-test (for menu)
{
    //  simple test: true if there's a selection but no multi selection and not filtered

    const ScMarkData& rMark = GetViewData().GetMarkData();
    if ( rMark.IsMarked() || rMark.IsMultiMarked() )
    {
        ScRange aRange;
        bool bMergeable = ( GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE );
        bMergeable = bMergeable && ( aRange.aStart.Col() != aRange.aEnd.Col() ||
                                   aRange.aStart.Row() != aRange.aEnd.Row() );
        return bMergeable;
    }
    else
        return false;
}

void ScViewFunc::MergeCells( bool bApi, bool bDoContents, bool bCenter,
                             const sal_uInt16 nSlot )
{
    //  Editable- and Being-Nested- test must be at the beginning (in DocFunc too),
    //  so that the Contents-QueryBox won't appear
    ScEditableTester aTester = ScEditableTester::CreateAndTestView(this);
    if (!aTester.IsEditable())
    {
        ErrorMessage(aTester.GetMessageId());
        return;
    }

    ScMarkData& rMark = GetViewData().GetMarkData();
    rMark.MarkToSimple();
    if (!rMark.IsMarked())
    {
        ErrorMessage(STR_NOMULTISELECT);
        return;
    }

    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = pDocSh->GetDocument();

    const ScRange& aMarkRange = rMark.GetMarkArea();
    SCCOL nStartCol = aMarkRange.aStart.Col();
    SCROW nStartRow = aMarkRange.aStart.Row();
    SCTAB nStartTab = aMarkRange.aStart.Tab();
    SCCOL nEndCol = aMarkRange.aEnd.Col();
    SCROW nEndRow = aMarkRange.aEnd.Row();
    SCTAB nEndTab = aMarkRange.aEnd.Tab();
    if ( nStartCol == nEndCol && nStartRow == nEndRow )
    {
        // nothing to do
        return;
    }

    if ( rDoc.HasAttrib( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab,
                            HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
    {       // "Don't nest merging  !"
        ErrorMessage(STR_MSSG_MERGECELLS_0);
        return;
    }

    // Check for the contents of all selected tables.
    bool bAskDialog = false;
    ScCellMergeOption aMergeOption(nStartCol, nStartRow, nEndCol, nEndRow, bCenter);
    for (const SCTAB& i : rMark)
    {
        aMergeOption.maTabs.insert(i);

        sc::MultiDataCellState aState = rDoc.HasMultipleDataCells(aMergeOption.getSingleRange(i));
        switch (aState.meState)
        {
            case sc::MultiDataCellState::HasMultipleCells:
            {
                // this range contains multiple data cells.
                bAskDialog = true;
                break;
            }
            case sc::MultiDataCellState::HasOneCell:
            {
                // this range contains only one data cell.
                if (nStartCol != aState.mnCol1 || nStartRow != aState.mnRow1)
                    bDoContents = true; // move the value to the top-left.
                break;
            }
            default:
                ;
        }
    }

    bool bEmptyMergedCells = officecfg::Office::Calc::Compatibility::MergeCells::EmptyMergedCells::get();

    auto doMerge = [this, pDocSh, aMergeOption=std::move(aMergeOption),
                    bApi, nStartCol, nStartRow, aMarkRange]
        (bool bNowDoContents, bool bNowEmptyMergedCells)
    {
        if (pDocSh->GetDocFunc().MergeCells(aMergeOption, bNowDoContents, true/*bRecord*/,
                                             bApi, bNowEmptyMergedCells))
        {
            SetCursor( nStartCol, nStartRow );
            // DoneBlockMode( sal_False);
            Unmark();

            pDocSh->UpdateOle(GetViewData());
            UpdateInputLine();

            OUString aStartAddress = aMarkRange.aStart.GetColRowString();
            OUString aEndAddress = aMarkRange.aEnd.GetColRowString();

            collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, u"MERGE_CELLS"_ustr);
        }
    };

    if (bAskDialog)
    {
        bool bShowDialog = officecfg::Office::Calc::Compatibility::MergeCells::ShowDialog::get();
        if (!bApi && bShowDialog)
        {
            auto pBox = std::make_shared<ScMergeCellsDialog>(GetViewData().GetDialogParent());

            SfxViewShell* pViewShell = GetViewData().GetViewShell();

            weld::DialogController::runAsync(pBox, [pBox, bDoContents, bEmptyMergedCells, pViewShell,
                                                    nSlot, bApi, doMerge=std::move(doMerge)](sal_Int32 nRetVal) {
                if (nRetVal == RET_OK)
                {
                    bool bRealDoContents = bDoContents;
                    bool bRealEmptyMergedCells = bEmptyMergedCells;
                    switch (pBox->GetMergeCellsOption())
                    {
                    case MoveContentHiddenCells:
                        bRealDoContents = true;
                        break;
                    case KeepContentHiddenCells:
                        bRealEmptyMergedCells = false;
                        break;
                    case EmptyContentHiddenCells:
                        bRealEmptyMergedCells = true;
                        break;
                    default:
                        assert(!"Unknown option for merge cells.");
                        break;
                    }

                    doMerge(bRealDoContents, bRealEmptyMergedCells);

                    if (nSlot != 0)
                    {
                        SfxRequest aReq(pViewShell->GetViewFrame(), nSlot);
                        if (!bApi && bRealDoContents)
                            aReq.AppendItem(SfxBoolItem(nSlot, bDoContents));
                        SfxBindings& rBindings = pViewShell->GetViewFrame().GetBindings();
                        rBindings.Invalidate(nSlot);
                        aReq.Done();
                    }
                }
                // else cancelled
            });
        }
    } else
        doMerge(bDoContents, bEmptyMergedCells);
}

bool ScViewFunc::TestRemoveMerge()
{
    bool bMerged = false;
    ScRange aRange;
    if (GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE)
    {
        ScDocument& rDoc = GetViewData().GetDocument();
        if ( rDoc.HasAttrib( aRange, HasAttrFlags::Merged ) )
            bMerged = true;
    }
    return bMerged;
}

static bool lcl_extendMergeRange(ScCellMergeOption& rOption, const ScRange& rRange)
{
    bool bExtended = false;
    if (rOption.mnStartCol > rRange.aStart.Col())
    {
        rOption.mnStartCol = rRange.aStart.Col();
        bExtended = true;
    }
    if (rOption.mnStartRow > rRange.aStart.Row())
    {
        rOption.mnStartRow = rRange.aStart.Row();
        bExtended = true;
    }
    if (rOption.mnEndCol < rRange.aEnd.Col())
    {
        rOption.mnEndCol = rRange.aEnd.Col();
        bExtended = true;
    }
    if (rOption.mnEndRow < rRange.aEnd.Row())
    {
        rOption.mnEndRow = rRange.aEnd.Row();
        bExtended = true;
    }
    return bExtended;
}

bool ScViewFunc::RemoveMerge()
{
    ScRange aRange;
    ScEditableTester aTester = ScEditableTester::CreateAndTestView(this);
    if (!aTester.IsEditable())
    {
        ErrorMessage(aTester.GetMessageId());
        return false;
    }
    else if (GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE)
    {
        ScDocument& rDoc = GetViewData().GetDocument();
        ScRange aExtended( aRange );
        rDoc.ExtendMerge( aExtended );
        ScDocShell* pDocSh = GetViewData().GetDocShell();
        const ScMarkData& rMark = GetViewData().GetMarkData();
        ScCellMergeOption aOption(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row());
        bool bExtended = false;
        do
        {
            bExtended = false;
            for (const SCTAB& i : rMark)
            {
                aOption.maTabs.insert(i);
                aExtended.aStart.SetTab(i);
                aExtended.aEnd.SetTab(i);
                rDoc.ExtendMerge(aExtended);
                rDoc.ExtendOverlapped(aExtended);

                // Expand the current range to be inclusive of all merged
                // areas on all sheets.
                bExtended = lcl_extendMergeRange(aOption, aExtended);
            }
        }
        while (bExtended);

        bool bOk = pDocSh->GetDocFunc().UnmergeCells(aOption, true/*bRecord*/, nullptr);
        aExtended = aOption.getFirstSingleRange();
        MarkRange( aExtended );

        if (bOk)
            pDocSh->UpdateOle(GetViewData());
    }

    OUString aCellLocation = aRange.aStart.GetColRowString();
    collectUIInformation({{"CELL", aCellLocation}}, u"UNMERGE_CELL"_ustr);

    return true;        //! bOk ??
}

void ScViewFunc::FillSimple( FillDir eDir )
{
    ScRange aRange;
    if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE)
    {
        ScDocShell* pDocSh = GetViewData().GetDocShell();
        const ScMarkData& rMark = GetViewData().GetMarkData();
        bool bSuccess = pDocSh->GetDocFunc().FillSimple( aRange, &rMark, eDir, false );
        if (bSuccess)
        {
            pDocSh->UpdateOle(GetViewData());
            UpdateScrollBars();

            auto& rDoc = pDocSh->GetDocument();
            const ScTabViewShell* pTabViewShell = GetViewData().GetViewShell();
            const bool bDoAutoSpell = pTabViewShell && pTabViewShell->IsAutoSpell();
            if ( bDoAutoSpell )
            {
                // Copy AutoSpellData from above(left/right/below) if no selection.
                switch (eDir)
                {
                    case FILL_TO_BOTTOM:
                        if (aRange.aStart.Row() > 0 && aRange.aStart.Row() == aRange.aEnd.Row())
                            aRange.aStart.IncRow(-1);
                    break;
                    case FILL_TO_TOP:
                        if (aRange.aEnd.Row() < rDoc.MaxRow() && aRange.aStart.Row() == aRange.aEnd.Row())
                            aRange.aEnd.IncRow(1);
                    break;
                    case FILL_TO_RIGHT:
                        if (aRange.aStart.Col() > 0 && aRange.aStart.Col() == aRange.aEnd.Col())
                            aRange.aStart.IncCol(-1);
                    break;
                    case FILL_TO_LEFT:
                        if (aRange.aEnd.Col() < rDoc.MaxCol() && aRange.aStart.Col() == aRange.aEnd.Col())
                            aRange.aEnd.IncCol(1);
                    break;
                }
                CopyAutoSpellData(eDir, aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row(),
                        ::std::numeric_limits<sal_uLong>::max());
            }

            // Invalidate cell slots and update input line with new content.
            CellContentChanged();
        }
    }
    else
        ErrorMessage(STR_NOMULTISELECT);
}

void ScViewFunc::FillSeries( FillDir eDir, FillCmd eCmd, FillDateCmd eDateCmd,
                             double fStart, double fStep, double fMax )
{
    ScRange aRange;
    if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE)
    {
        ScDocShell* pDocSh = GetViewData().GetDocShell();
        const ScMarkData& rMark = GetViewData().GetMarkData();
        bool bSuccess = pDocSh->GetDocFunc().
                        FillSeries( aRange, &rMark, eDir, eCmd, eDateCmd,
                                    fStart, fStep, fMax, false );
        if (bSuccess)
        {
            pDocSh->UpdateOle(GetViewData());
            UpdateScrollBars();

            HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, aRange);
        }
    }
    else
        ErrorMessage(STR_NOMULTISELECT);
}

void ScViewFunc::FillAuto( FillDir eDir, SCCOL nStartCol, SCROW nStartRow,
                            SCCOL nEndCol, SCROW nEndRow, sal_uLong nCount )
{
    SCTAB nTab = GetViewData().CurrentTabForData();
    ScRange aRange( nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab );
    ScRange aSourceRange( aRange );
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    const ScMarkData& rMark = GetViewData().GetMarkData();
    bool bSuccess = pDocSh->GetDocFunc().
                    FillAuto( aRange, &rMark, eDir, nCount, false );
    if (!bSuccess)
        return;

    MarkRange( aRange, false );         // aRange was modified in FillAuto
    pDocSh->UpdateOle(GetViewData());
    UpdateScrollBars();

    const ScTabViewShell* pTabViewShell = GetViewData().GetViewShell();
    const bool bDoAutoSpell = pTabViewShell && pTabViewShell->IsAutoSpell();
    if ( bDoAutoSpell )
        CopyAutoSpellData(eDir, nStartCol, nStartRow, nEndCol, nEndRow, nCount);

    ScModelObj* pModelObj = pDocSh->GetModel();

    ScRangeList aChangeRanges;
    ScRange aChangeRange( aRange );
    switch (eDir)
    {
        case FILL_TO_BOTTOM:
            aChangeRange.aStart.SetRow( aSourceRange.aEnd.Row() + 1 );
            break;
        case FILL_TO_TOP:
            aChangeRange.aEnd.SetRow( aSourceRange.aStart.Row() - 1 );
            break;
        case FILL_TO_RIGHT:
            aChangeRange.aStart.SetCol( aSourceRange.aEnd.Col() + 1 );
            break;
        case FILL_TO_LEFT:
            aChangeRange.aEnd.SetCol( aSourceRange.aStart.Col() - 1 );
            break;
        default:
            break;
    }
    aChangeRanges.push_back( aChangeRange );

    if (HelperNotifyChanges::getMustPropagateChangesModel(pModelObj))
        HelperNotifyChanges::Notify(*pModelObj, aChangeRanges);
    else if (pModelObj)
        HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, u"data-area-invalidate"_ustr);
}

void ScViewFunc::CopyAutoSpellData( FillDir eDir, SCCOL nStartCol, SCROW nStartRow,
                                   SCCOL nEndCol, SCROW nEndRow, sal_uLong nCount )
{
    const ScDocument* pDoc = &GetViewData().GetDocument();
    SCTAB nTab = GetViewData().CurrentTabForData();
    CellType eCellType;

    ScGridWindow* pWin = GetActiveWin();
    if ( pWin->InsideVisibleRange(nStartCol, nStartRow) && pWin->InsideVisibleRange(nEndCol, nEndRow) )
    {
        if ( nCount == ::std::numeric_limits<sal_uLong>::max() )
        {
            switch( eDir )
            {
                case FILL_TO_BOTTOM:
                    for ( SCCOL nColItr = nStartCol; nColItr <= nEndCol; ++nColItr )
                    {
                        eCellType = pDoc->GetCellType(nColItr, nStartRow, nTab); // We need this optimization only for EditTextObject source cells
                        if (eCellType != CELLTYPE_EDIT)
                            continue;

                        sc::MisspellRangeResult aRangeResult = pWin->GetAutoSpellData(nColItr, nStartRow);
                        if (!aRangeResult.HasRanges())
                            continue;
                        for ( SCROW nRowItr = nStartRow + 1; nRowItr <= nEndRow; ++nRowItr )
                            pWin->SetAutoSpellData(nColItr, nRowItr, aRangeResult);
                    }
                    break;
                case FILL_TO_TOP:
                    for ( SCCOL nColItr = nStartCol; nColItr <= nEndCol; ++nColItr )
                    {
                        eCellType = pDoc->GetCellType(nColItr, nEndRow, nTab); // We need this optimization only for EditTextObject source cells
                        if (eCellType != CELLTYPE_EDIT)
                            continue;

                        sc::MisspellRangeResult aRangeResult = pWin->GetAutoSpellData(nColItr, nEndRow);
                        if (!aRangeResult.HasRanges())
                            continue;
                        for ( SCROW nRowItr = nEndRow - 1; nRowItr >= nStartRow; --nRowItr )
                            pWin->SetAutoSpellData(nColItr, nRowItr, aRangeResult);
                    }
                    break;
                case FILL_TO_RIGHT:
                    for ( SCROW nRowItr = nStartRow; nRowItr <= nEndRow; ++nRowItr )
                    {
                        eCellType = pDoc->GetCellType(nStartCol, nRowItr, nTab); // We need this optimization only for EditTextObject source cells
                        if (eCellType != CELLTYPE_EDIT)
                            continue;

                        sc::MisspellRangeResult aRangeResult = pWin->GetAutoSpellData(nStartCol, nRowItr);
                        if (!aRangeResult.HasRanges())
                            continue;
                        for ( SCCOL nColItr = nStartCol + 1; nColItr <= nEndCol; ++nColItr )
                            pWin->SetAutoSpellData(nColItr, nRowItr, aRangeResult);
                    }
                    break;
                case FILL_TO_LEFT:
                    for ( SCROW nRowItr = nStartRow; nRowItr <= nEndRow; ++nRowItr )
                    {
                        eCellType = pDoc->GetCellType(nEndCol, nRowItr, nTab); // We need this optimization only for EditTextObject source cells
                        if (eCellType != CELLTYPE_EDIT)
                            continue;

                        sc::MisspellRangeResult aRangeResult = pWin->GetAutoSpellData(nEndCol, nRowItr);
                        if (!aRangeResult.HasRanges())
                            continue;
                        for ( SCCOL nColItr = nEndCol - 1; nColItr >= nStartCol; --nColItr )
                            pWin->SetAutoSpellData(nColItr, nRowItr, aRangeResult);
                    }
                    break;
            }
            return;
        }

        SCROW nRowRepeatSize = nEndRow - nStartRow + 1;
        SCCOL nColRepeatSize = nEndCol - nStartCol + 1;
        SCROW nTillRow = 0;
        SCCOL nTillCol = 0;
        std::vector<std::vector<sc::MisspellRangeResult>> aSourceSpellRanges(nRowRepeatSize, std::vector<sc::MisspellRangeResult>(nColRepeatSize));

        for ( SCROW nRowIdx = 0; nRowIdx < nRowRepeatSize; ++nRowIdx )
        {
            for ( SCCOL nColIdx = 0; nColIdx < nColRepeatSize; ++nColIdx )
            {
                eCellType = pDoc->GetCellType(nStartCol + nColIdx, nStartRow + nRowIdx, nTab); // We need this optimization only for EditTextObject source cells
                if (eCellType != CELLTYPE_EDIT)
                    continue;

                aSourceSpellRanges[nRowIdx][nColIdx] = pWin->GetAutoSpellData( nStartCol + nColIdx, nStartRow + nRowIdx );
            }
        }

        switch( eDir )
        {
            case FILL_TO_BOTTOM:
                nTillRow = nEndRow + nCount;
                for ( SCCOL nColItr = nStartCol; nColItr <= nEndCol; ++nColItr )
                {
                    for ( SCROW nRowItr = nEndRow + 1; nRowItr <= nTillRow; ++nRowItr )
                    {
                        size_t nSourceRowIdx = ( nRowItr - nEndRow - 1 ) % nRowRepeatSize;
                        sc::MisspellRangeResult aRangeResult = aSourceSpellRanges[nSourceRowIdx][nColItr - nStartCol];
                        if (!aRangeResult.HasRanges())
                            continue;
                        pWin->SetAutoSpellData(nColItr, nRowItr, aRangeResult);
                    }
                }
                break;

            case FILL_TO_TOP:
                nTillRow = nStartRow - nCount;
                for ( SCCOL nColItr = nStartCol; nColItr <= nEndCol; ++nColItr )
                {
                    for ( SCROW nRowItr = nStartRow - 1; nRowItr >= nTillRow; --nRowItr )
                    {
                        size_t nSourceRowIdx = nRowRepeatSize - 1 - ( ( nStartRow - 1 - nRowItr ) % nRowRepeatSize );
                        sc::MisspellRangeResult aRangeResult = aSourceSpellRanges[nSourceRowIdx][nColItr - nStartCol];
                        if (!aRangeResult.HasRanges())
                            continue;
                        pWin->SetAutoSpellData(nColItr, nRowItr, aRangeResult);
                    }
                }
                break;

            case FILL_TO_RIGHT:
                nTillCol = nEndCol + nCount;
                for ( SCCOL nColItr = nEndCol + 1; nColItr <= nTillCol; ++nColItr )
                {
                    size_t nSourceColIdx = ( nColItr - nEndCol - 1 ) % nColRepeatSize;
                    for ( SCROW nRowItr = nStartRow; nRowItr <= nEndRow; ++nRowItr )
                    {
                        sc::MisspellRangeResult aRangeResult = aSourceSpellRanges[nRowItr - nStartRow][nSourceColIdx];
                        if (!aRangeResult.HasRanges())
                            continue;
                        pWin->SetAutoSpellData(nColItr, nRowItr, aRangeResult);
                    }
                }
                break;

            case FILL_TO_LEFT:
                nTillCol = nStartCol - nCount;
                for ( SCCOL nColItr = nStartCol - 1; nColItr >= nTillCol; --nColItr )
                {
                    size_t nSourceColIdx = nColRepeatSize - 1 - ( ( nStartCol - 1 - nColItr ) % nColRepeatSize );
                    for ( SCROW nRowItr = nStartRow; nRowItr <= nEndRow; ++nRowItr )
                    {
                        sc::MisspellRangeResult aRangeResult = aSourceSpellRanges[nRowItr - nStartRow][nSourceColIdx];
                        if (!aRangeResult.HasRanges())
                            continue;
                        pWin->SetAutoSpellData(nColItr, nRowItr, aRangeResult);
                    }
                }
                break;
        }
    }
    else
        pWin->ResetAutoSpellForContentChange();

}

void ScViewFunc::FillTab( InsertDeleteFlags nFlags, ScPasteFunc nFunction, bool bSkipEmpty, bool bAsLink )
{
    //! allow source sheet to be protected
    ScEditableTester aTester = ScEditableTester::CreateAndTestView(this);
    if (!aTester.IsEditable())
    {
        ErrorMessage(aTester.GetMessageId());
        return;
    }

    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = pDocSh->GetDocument();
    ScMarkData& rMark = GetViewData().GetMarkData();
    SCTAB nTab = GetViewData().CurrentTabForData();
    bool bUndo(rDoc.IsUndoEnabled());

    ScRange aMarkRange;
    rMark.MarkToSimple();
    bool bMulti = rMark.IsMultiMarked();
    if (bMulti)
        aMarkRange = rMark.GetMultiMarkArea();
    else if (rMark.IsMarked())
        aMarkRange = rMark.GetMarkArea();
    else
        aMarkRange = ScRange( GetViewData().GetCurX(), GetViewData().GetCurY(), nTab );

    ScDocumentUniquePtr pUndoDoc;

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

        for (const SCTAB& i : rMark)
            if (i != nTab )
            {
                pUndoDoc->AddUndoTab( i, i );
                aMarkRange.aStart.SetTab( i );
                aMarkRange.aEnd.SetTab( i );
                rDoc.CopyToDocument( aMarkRange, InsertDeleteFlags::ALL, bMulti, *pUndoDoc );
            }
    }

    if (bMulti)
        rDoc.FillTabMarked( nTab, rMark, nFlags, nFunction, bSkipEmpty, bAsLink );
    else
    {
        aMarkRange.aStart.SetTab( nTab );
        aMarkRange.aEnd.SetTab( nTab );
        rDoc.FillTab( aMarkRange, rMark, nFlags, nFunction, bSkipEmpty, bAsLink );
    }

    if (bUndo)
    {   //! for ChangeTrack not until the end
        pDocSh->GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoFillTable>( *pDocSh, rMark,
                                aMarkRange.aStart.Col(), aMarkRange.aStart.Row(), nTab,
                                aMarkRange.aEnd.Col(), aMarkRange.aEnd.Row(), nTab,
                                std::move(pUndoDoc), bMulti, nTab, nFlags, nFunction, bSkipEmpty, bAsLink ) );
    }

    pDocSh->PostPaintGridAll();
    pDocSh->PostDataChanged();
}

/** Downward fill of selected cell(s) by double-clicking cross-hair cursor

    Either, extends a current selection if non-empty cells exist immediately
    below the selection, overwriting cells below the selection up to the
    minimum row of already filled cells.

    Or, extends a current selection down to the last non-empty cell of an
    adjacent column when the lower-right corner of the selection is
    double-clicked. It uses a left-adjoining non-empty column as a guide if
    such is available, otherwise a right-adjoining non-empty column is used.

    @return No return value

    @see #i12313#
*/
void ScViewFunc::FillCrossDblClick()
{
    ScRange aRange;
    GetViewData().GetSimpleArea( aRange );
    aRange.PutInOrder();

    SCTAB nTab = GetViewData().GetCurPos().Tab();
    SCCOL nStartX = aRange.aStart.Col();
    SCROW nStartY = aRange.aStart.Row();
    SCCOL nEndX   = aRange.aEnd.Col();
    SCROW nEndY   = aRange.aEnd.Row();

    ScDocument& rDoc = GetViewData().GetDocument();

    if (nEndY >= rDoc.MaxRow())
        // Nothing to fill.
        return;

    // Make sure the selection is not empty
    if ( rDoc.IsBlockEmpty( nStartX, nStartY, nEndX, nEndY, nTab ) )
        return;

    // If there is data in all columns immediately below the selection then
    // switch to overwriting fill.
    SCROW nOverWriteEndRow = rDoc.MaxRow();
    for (SCCOL nCol = nStartX; nCol <= nEndX; ++nCol)
    {
        if (rDoc.HasData( nCol, nEndY + 1, nTab))
        {
            // Determine the shortest data column to end the fill.
            SCROW nY = nEndY + 1;
            // FindAreaPos() returns the start row of the next data block if
            // the current row is the last row of a data block and an empty
            // cell follows. Somewhat unexpected behaviour...
            // So check beforehand if there is one non-empty cell following.
            if (rDoc.HasData( nCol, nY + 1, nTab))
            {
                rDoc.FindAreaPos( nCol, nY, nTab, SC_MOVE_DOWN);
                if (nOverWriteEndRow > nY)
                    nOverWriteEndRow = nY;
            }
            else
            {
                nOverWriteEndRow = nY;
            }
        }
        else
        {
            nOverWriteEndRow = 0;
            break;  // for
        }
    }

    if (nOverWriteEndRow > nEndY)
    {
        FillAuto( FILL_TO_BOTTOM, nStartX, nStartY, nEndX, nEndY, nOverWriteEndRow - nEndY);
        return;
    }

    // Non-overwriting fill follows.

    const bool bDataLeft = (nStartX > 0);
    if (!bDataLeft && nEndX >= rDoc.MaxCol())
        // Absolutely no data left or right of selection.
        return;

    // Check that there is
    // 1) data immediately left (preferred) or right of start (row) of selection
    // 2) data there below
    // 3) no data immediately below selection

    SCCOL nMovX = (bDataLeft ? nStartX - 1 : nEndX + 1);
    SCROW nMovY = nStartY;
    bool bDataFound = (rDoc.HasData( nMovX, nStartY, nTab) && rDoc.HasData( nMovX, nStartY + 1, nTab));
    if (!bDataFound && bDataLeft && nEndX < rDoc.MaxCol())
    {
        nMovX = nEndX + 1;  // check right
        bDataFound = (rDoc.HasData( nMovX, nStartY, nTab) && rDoc.HasData( nMovX, nStartY + 1, nTab));
    }

    if (!(bDataFound && rDoc.IsEmptyData( nStartX, nEndY + 1, nEndX, nEndY + 1, nTab )))
        return;

    // Get end of data left or right.
    rDoc.FindAreaPos( nMovX, nMovY, nTab, SC_MOVE_DOWN);
    // Find minimum end row of below empty area and data right.
    for (SCCOL nX = nStartX; nX <= nEndX; ++nX)
    {
        SCROW nY = nEndY + 1;
        // Get next row with data in this column.
        rDoc.FindAreaPos( nX, nY, nTab, SC_MOVE_DOWN);
        if (nMovY == rDoc.MaxRow() && nY == rDoc.MaxRow())
        {
            // FindAreaPos() returns MAXROW also if there is no data at all
            // from the start, so check if that contains data if the nearby
            // (left or right) data ends there and increment if no data
            // here, pretending the next data would be thereafter so nMovY
            // will not be decremented.
            if (!rDoc.HasData( nX, nY, nTab))
                ++nY;
        }
        if (nMovY > nY - 1)
            nMovY = nY - 1;
    }

    if (nMovY > nEndY)
    {
        FillAuto( FILL_TO_BOTTOM, nStartX, nStartY, nEndX, nEndY, nMovY - nEndY);
    }
}

void ScViewFunc::ConvertFormulaToValue()
{
    ScRange aRange;
    GetViewData().GetSimpleArea(aRange);
    aRange.PutInOrder();

    ScDocShell* pDocSh = GetViewData().GetDocShell();
    pDocSh->GetDocFunc().ConvertFormulaToValue(aRange, true);
    // tdf#131326 - invalidate cell slots and update input line with new content
    CellContentChanged();
    pDocSh->PostPaint(aRange, PaintPartFlags::Grid);
}

void ScViewFunc::TransliterateText( TransliterationFlags nType )
{
    ScMarkData aFuncMark = GetViewData().GetMarkData();
    if ( !aFuncMark.IsMarked() && !aFuncMark.IsMultiMarked() )
    {
        //  no selection -> use cursor position

        ScAddress aCursor( GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().CurrentTabForData() );
        aFuncMark.SetMarkArea( ScRange( aCursor ) );
    }

    bool bSuccess = GetViewData().GetDocShell()->GetDocFunc().
                        TransliterateText( aFuncMark, nType, false );
    if (bSuccess)
    {
        GetViewData().GetViewShell()->UpdateInputHandler();
    }
}

//  AutoFormat

ScAutoFormatData* ScViewFunc::CreateAutoFormatData()
{
    ScAutoFormatData* pData = nullptr;
    SCCOL nStartCol;
    SCROW nStartRow;
    SCTAB nStartTab;
    SCCOL nEndCol;
    SCROW nEndRow;
    SCTAB nEndTab;
    if (GetViewData().GetSimpleArea(nStartCol,nStartRow,nStartTab,nEndCol,nEndRow,nEndTab) == SC_MARK_SIMPLE)
    {
        if ( nEndCol-nStartCol >= 3 && nEndRow-nStartRow >= 3 )
        {
            ScDocument& rDoc = GetViewData().GetDocument();
            pData = new ScAutoFormatData;
            rDoc.GetAutoFormatData( nStartTab, nStartCol,nStartRow,nEndCol,nEndRow, *pData );
        }
    }
    return pData;
}

void ScViewFunc::AutoFormat( sal_uInt16 nFormatNo )
{
    ScRange aRange;
    if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE)
    {
        ScDocShell* pDocSh = GetViewData().GetDocShell();
        ScMarkData& rMark = GetViewData().GetMarkData();

        bool bSuccess = pDocSh->GetDocFunc().AutoFormat( aRange, &rMark, nFormatNo, false );
        if (bSuccess)
            pDocSh->UpdateOle(GetViewData());
    }
    else
        ErrorMessage(STR_NOMULTISELECT);
}

// Search & Replace

bool ScViewFunc::SearchAndReplace( const SvxSearchItem* pSearchItem,
                                        bool bAddUndo, bool bIsApi )
{
    SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::Empty);
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = pDocSh->GetDocument();
    ScMarkData& rMark = GetViewData().GetMarkData();
    if (bAddUndo && !rDoc.IsUndoEnabled())
        bAddUndo = false;

    bool bCursorMoved = false;
    SCCOL nOrigPosX = GetViewData().GetCurX();
    SCROW nOrigPosY = GetViewData().GetCurY();
    if ( !rMark.IsMarked() && !rMark.IsMultiMarked() && (pSearchItem->HasStartPoint()) )
    {
        // No selection -> but we have a start point (top left corner of the
        // current view), start searching from there, not from the current
        // cursor position.
        SCCOL nPosX;
        SCROW nPosY;

        int nPixelX = pSearchItem->GetStartPointX() * GetViewData().GetPPTX();
        int nPixelY = pSearchItem->GetStartPointY() * GetViewData().GetPPTY();

        GetViewData().GetPosFromPixel(nPixelX, nPixelY, GetViewData().GetActivePart(), nPosX, nPosY);

        AlignToCursor( nPosX, nPosY, SC_FOLLOW_JUMP );
        SetCursor( nPosX, nPosY, true );
        bCursorMoved = true;
    }

    SCCOL nCol, nOldCol;
    SCROW nRow, nOldRow;
    SCTAB nTab, nOldTab;
    nCol = nOldCol = GetViewData().GetCurX();
    nRow = nOldRow = GetViewData().GetCurY();
    nTab = nOldTab = GetViewData().CurrentTabForData();

    SvxSearchCmd nCommand = pSearchItem->GetCommand();
    bool bAllTables = pSearchItem->IsAllTables();
    std::set<SCTAB> aOldSelectedTables;
    SCTAB nLastTab = rDoc.GetTableCount() - 1;
    SCTAB nStartTab, nEndTab;
    if ( bAllTables )
    {
        nStartTab = 0;
        nEndTab = nLastTab;
        std::set<SCTAB> aTmp(rMark.begin(), rMark.end());
        aOldSelectedTables.swap(aTmp);
    }
    else
    {   //! at least one is always selected
        nStartTab = rMark.GetFirstSelected();
        nEndTab = rMark.GetLastSelected();
    }

    if (   nCommand == SvxSearchCmd::FIND
        || nCommand == SvxSearchCmd::FIND_ALL)
        bAddUndo = false;

    //!     account for bAttrib during Undo !!!

    ScDocumentUniquePtr pUndoDoc;
    std::unique_ptr<ScMarkData> pUndoMark;
    OUString aUndoStr;
    if (bAddUndo)
    {
        pUndoMark.reset(new ScMarkData(rMark));                // Mark is being modified
        if ( nCommand == SvxSearchCmd::REPLACE_ALL )
        {
            pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO));
            pUndoDoc->InitUndo( rDoc, nStartTab, nEndTab );
        }
    }

    if ( bAllTables )
    {   //! select all, after pUndoMark has been created
        for ( SCTAB j = nStartTab; j <= nEndTab; j++ )
        {
            rMark.SelectTable( j, true );
        }
    }

    DoneBlockMode(true);                // don't delete mark
    InitOwnBlockMode( ScRange( nCol, nRow, nStartTab, nCol, nRow, nEndTab));

    //  If search starts at the beginning don't ask again whether it shall start at the beginning
    bool bFirst = true;
    if ( nCol == 0 && nRow == 0 && nTab == nStartTab && !pSearchItem->GetBackward()  )
        bFirst = false;

    bool bFound = false;
    while (true)
    {
        GetFrameWin()->EnterWait();
        ScRangeList aMatchedRanges;
        bool bMatchedRangesWereClamped = false;
        if (rDoc.SearchAndReplace(*pSearchItem, nCol, nRow, nTab, rMark, aMatchedRanges, aUndoStr, pUndoDoc.get(), bMatchedRangesWereClamped))
        {
            bFound = true;
            if (bAddUndo)
            {
                GetViewData().GetDocShell()->GetUndoManager()->AddUndoAction(
                    std::make_unique<ScUndoReplace>( *GetViewData().GetDocShell(), *pUndoMark,
                                        nCol, nRow, nTab,
                                        aUndoStr, std::move(pUndoDoc), pSearchItem ) );
            }

            if (nCommand == SvxSearchCmd::FIND_ALL || nCommand == SvxSearchCmd::REPLACE_ALL)
            {
                SfxViewFrame* pViewFrm = SfxViewFrame::Current();
                bool bShow = GetViewData().GetViewShell()->GetViewData().GetOptions().GetOption(sc::ViewOption::SUMMARY);

                if (bShow && pViewFrm && !comphelper::LibreOfficeKit::isActive())
                {
                    pViewFrm->ShowChildWindow(sc::SearchResultsDlgWrapper::GetChildWindowId());
                    SfxChildWindow* pWnd = pViewFrm->GetChildWindow(sc::SearchResultsDlgWrapper::GetChildWindowId());
                    if (pWnd)
                    {
                        sc::SearchResultsDlg* pDlg = static_cast<sc::SearchResultsDlg*>(pWnd->GetController().get());
                        if (pDlg)
                        {
                            const bool bCellNotes = (pSearchItem->GetCellType() == SvxSearchCellType::NOTE);
                            // ScCellIterator iterates over cells with content,
                            // for empty cells iterate over match positions.
                            const bool bEmptyCells = (!bCellNotes
                                    && ((nCommand == SvxSearchCmd::FIND_ALL
                                            && ScDocument::IsEmptyCellSearch(*pSearchItem))
                                        || (nCommand == SvxSearchCmd::REPLACE_ALL
                                            && pSearchItem->GetReplaceString().isEmpty())));
                            pDlg->FillResults(rDoc, aMatchedRanges, bCellNotes, bEmptyCells, bMatchedRangesWereClamped);
                        }
                    }
                }

                rMark.ResetMark();
                for (size_t i = 0, n = aMatchedRanges.size(); i < n; ++i)
                {
                    const ScRange& r = aMatchedRanges[i];
                    if (r.aStart.Tab() == nTab)
                        rMark.SetMultiMarkArea(r);
                }
            }

            break;                  // break 'while (TRUE)'
        }
        else if ( bFirst && (nCommand == SvxSearchCmd::FIND ||
                nCommand == SvxSearchCmd::REPLACE) )
        {
            bFirst = false;
            GetFrameWin()->LeaveWait();
            if (!bIsApi)
            {
                if ( nStartTab == nEndTab )
                    SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::EndSheet);
                else
                    SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::End);

                rDoc.GetSearchAndReplaceStart( *pSearchItem, nCol, nRow );
                if (pSearchItem->GetBackward())
                    nTab = nEndTab;
                else
                    nTab = nStartTab;
            }
            else
            {
                break;                  // break 'while (TRUE)'
            }
        }
        else                            // nothing found
        {
            if ( nCommand == SvxSearchCmd::FIND_ALL || nCommand == SvxSearchCmd::REPLACE_ALL )
            {
                pDocSh->PostPaintGridAll();                             // Mark
            }

            GetFrameWin()->LeaveWait();
            if (!bIsApi)
            {
                GetViewData().GetViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_SEARCH_NOT_FOUND, pSearchItem->GetSearchString().toUtf8());
                SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::NotFound);
            }

            break;                      // break 'while (TRUE)'
        }
    }                               // of while true

    if (!aOldSelectedTables.empty())
    {
        // restore originally selected table
        for (SCTAB i = 0; i <= nEndTab; ++i)
            rMark.SelectTable(i, false);

        for (const auto& rTab : aOldSelectedTables)
            rMark.SelectTable(rTab, true);

        if ( bFound )
        {   // if a table is selected as a "match" it remains selected.
            rMark.SelectTable( nTab, true );
            // It's a swap if only one table was selected before
            //! otherwise now one table more might be selected
            if ( aOldSelectedTables.size() == 1 && nTab != nOldTab )
                rMark.SelectTable( nOldTab, false );
        }
    }

    // Avoid LOK selection notifications before we have all the results.
    GetViewData().GetViewShell()->setTiledSearching(true);
    MarkDataChanged();
    GetViewData().GetViewShell()->setTiledSearching(false);

    if ( bFound )
    {
        if ( nTab != GetViewData().CurrentTabForData() )
            SetTabNo( nTab );

        //  if nothing is marked, DoneBlockMode, then marking can start
        //  directly from this place via Shift-Cursor
        if (!rMark.IsMarked() && !rMark.IsMultiMarked())
            DoneBlockMode(true);

        AlignToCursor( nCol, nRow, SC_FOLLOW_JUMP );
        SetCursor( nCol, nRow, true );

        if (comphelper::LibreOfficeKit::isActive())
        {
            Point aCurPos = GetViewData().GetScrPos(nCol, nRow, GetViewData().GetActivePart());

            // just update the cell selection
            ScGridWindow* pGridWindow = GetViewData().GetActiveWin();
            // Don't move cell selection handles for find-all: selection of all but the first result would be lost.
            if (pGridWindow && nCommand == SvxSearchCmd::FIND)
            {
                // move the cell selection handles
                pGridWindow->SetCellSelectionPixel(LOK_SETTEXTSELECTION_RESET, aCurPos.X(), aCurPos.Y());
                pGridWindow->SetCellSelectionPixel(LOK_SETTEXTSELECTION_START, aCurPos.X(), aCurPos.Y());
                pGridWindow->SetCellSelectionPixel(LOK_SETTEXTSELECTION_END, aCurPos.X(), aCurPos.Y());
            }

            if (pGridWindow)
            {
                std::vector<tools::Rectangle> aLogicRects;
                pGridWindow->GetCellSelection(aLogicRects);

                boost::property_tree::ptree aTree;
                aTree.put("searchString", pSearchItem->GetSearchString().toUtf8().getStr());
                aTree.put("highlightAll", nCommand == SvxSearchCmd::FIND_ALL);

                boost::property_tree::ptree aSelections;
                for (const tools::Rectangle& rLogicRect : aLogicRects)
                {
                    boost::property_tree::ptree aSelection;
                    aSelection.put("part", OString::number(nTab).getStr());
                    aSelection.put("rectangles", rLogicRect.toString().getStr());
                    aSelections.push_back(std::make_pair("", aSelection));
                }
                aTree.add_child("searchResultSelection", aSelections);

                std::stringstream aStream;
                boost::property_tree::write_json(aStream, aTree);
                OString aPayload( aStream.str() );
                SfxViewShell* pViewShell = GetViewData().GetViewShell();
                pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SEARCH_RESULT_SELECTION, aPayload);

                // Trigger LOK_CALLBACK_TEXT_SELECTION now.
                MarkDataChanged();
            }
        }

        if (   nCommand == SvxSearchCmd::REPLACE
            || nCommand == SvxSearchCmd::REPLACE_ALL )
        {
            if ( nCommand == SvxSearchCmd::REPLACE )
            {
                pDocSh->PostPaint( nCol,nRow,nTab, nCol,nRow,nTab, PaintPartFlags::Grid );

                // jump to next cell if we replaced everything in the cell
                // where the cursor was positioned (but avoid switching tabs)
                if ( nCol == nOldCol && nRow == nOldRow && nTab == nOldTab )
                {
                    SvxSearchItem aSearchItem = ScGlobal::GetSearchItem();
                    aSearchItem.SetCommand(SvxSearchCmd::FIND);
                    aSearchItem.SetWhich(SID_SEARCH_ITEM);

                    ScRangeList aMatchedRanges;
                    bool bMatchedRangesWereClamped;
                    ScTable::UpdateSearchItemAddressForReplace( aSearchItem, nCol, nRow );
                    if ( rDoc.SearchAndReplace( aSearchItem, nCol, nRow, nTab, rMark, aMatchedRanges, aUndoStr, nullptr, bMatchedRangesWereClamped ) &&
                            ( nTab == nOldTab ) &&
                            ( nCol != nOldCol || nRow != nOldRow ) )
                    {
                        AlignToCursor(nCol, nRow, SC_FOLLOW_JUMP);
                        SetCursor( nCol, nRow, true );
                    }
                }
            }
            else
                pDocSh->PostPaintGridAll();
            pDocSh->SetDocumentModified();
        }
        else if ( nCommand == SvxSearchCmd::FIND_ALL )
            pDocSh->PostPaintGridAll();                             // mark
        GetFrameWin()->LeaveWait();
    }
    else if (bCursorMoved)
    {
        SetCursor(nOrigPosX, nOrigPosY, true);
    }
    return bFound;
}

// Goal Seek

void ScViewFunc::Solve( const ScSolveParam& rParam )
{
    ScDocument& rDoc = GetViewData().GetDocument();

    SCCOL nDestCol = rParam.aRefVariableCell.Col();
    SCROW nDestRow = rParam.aRefVariableCell.Row();
    SCTAB nDestTab = rParam.aRefVariableCell.Tab();

    ScEditableTester aTester = ScEditableTester::CreateAndTestBlock(rDoc, nDestTab, nDestCol, nDestRow, nDestCol, nDestRow);
    if (!aTester.IsEditable())
    {
        ErrorMessage(aTester.GetMessageId());
        return;
    }

    OUString  aTargetValStr;
    if ( rParam.pStrTargetVal )
        aTargetValStr = *rParam.pStrTargetVal;

    OUString  aMsgStr;
    OUString  aResStr;
    double  nSolveResult;
    GetFrameWin()->EnterWait();

    bool    bExact =
                rDoc.Solver(
                    rParam.aRefFormulaCell.Col(),
                    rParam.aRefFormulaCell.Row(),
                    rParam.aRefFormulaCell.Tab(),
                    nDestCol, nDestRow, nDestTab,
                    aTargetValStr,
                    nSolveResult);

    GetFrameWin()->LeaveWait();

    SvNumberFormatter* pFormatter = rDoc.GetFormatTable();
    sal_uLong nFormat = 0;
    const ScPatternAttr* pPattern = rDoc.GetPattern( nDestCol, nDestRow, nDestTab );
    if ( pPattern )
        nFormat = pPattern->GetNumberFormat( pFormatter );
    const Color* p;
    pFormatter->GetOutputString( nSolveResult, nFormat, aResStr, &p );

    if ( bExact )
    {
        aMsgStr += ScResId( STR_MSSG_SOLVE_0 ) +
            aResStr +
            ScResId( STR_MSSG_SOLVE_1 );
    }
    else
    {
        aMsgStr  = ScResId( STR_MSSG_SOLVE_2 ) +
            ScResId( STR_MSSG_SOLVE_3 ) +
            aResStr +
            ScResId( STR_MSSG_SOLVE_4 );
    }

    std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetViewData().GetDialogParent(),
                                              VclMessageType::Question, VclButtonsType::YesNo, aMsgStr));
    xBox->set_title(ScResId(STR_MSSG_DOSUBTOTALS_0));
    xBox->set_default_response(RET_NO);
    int nResponse = xBox->run();
    if (nResponse == RET_YES)
        EnterValue( nDestCol, nDestRow, nDestTab, nSolveResult );

    GetViewData().GetViewShell()->UpdateInputHandler( true );
}

//  multi operation

void ScViewFunc::TabOp( const ScTabOpParam& rParam, bool bRecord )
{
    ScRange aRange;
    if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE)
    {
        ScDocShell* pDocSh = GetViewData().GetDocShell();
        ScMarkData& rMark = GetViewData().GetMarkData();
        pDocSh->GetDocFunc().TabOp( aRange, &rMark, rParam, bRecord, false );
    }
    else
        ErrorMessage(STR_NOMULTISELECT);
}

void ScViewFunc::MakeScenario( const OUString& rName, const OUString& rComment,
                                    const Color& rColor, ScScenarioFlags nFlags )
{
    ScDocShell* pDocSh  = GetViewData().GetDocShell();
    ScMarkData& rMark   = GetViewData().GetMarkData();
    SCTAB       nTab    = GetViewData().CurrentTabForData();

    SCTAB nNewTab = pDocSh->MakeScenario( nTab, rName, rComment, rColor, nFlags, rMark );
    if (nFlags & ScScenarioFlags::CopyAll)
        SetTabNo( nNewTab, true );          // ScScenarioFlags::CopyAll -> visible
    else
    {
        SfxBindings& rBindings = GetViewData().GetBindings();
        rBindings.Invalidate( SID_STATUS_DOCPOS );      // Statusbar
        rBindings.Invalidate( SID_ROWCOL_SELCOUNT );    // Statusbar
        rBindings.Invalidate( SID_TABLES_COUNT );
        rBindings.Invalidate( SID_SELECT_SCENARIO );
        rBindings.Invalidate( FID_TABLE_SHOW );
    }
}

void ScViewFunc::ExtendScenario()
{
    ScEditableTester aTester = ScEditableTester::CreateAndTestView(this);
    if (!aTester.IsEditable())
    {
        ErrorMessage(aTester.GetMessageId());
        return;
    }

        //  Undo: apply attributes

    ScDocument& rDoc = GetViewData().GetDocument();
    ScPatternAttr aPattern(rDoc.getCellAttributeHelper());
    aPattern.ItemSetPut(ScMergeFlagAttr(ScMF::Scenario));
    aPattern.ItemSetPut(ScProtectionAttr(true));
    ApplySelectionPattern(aPattern);
}

void ScViewFunc::UseScenario( const OUString& rName )
{
    ScDocShell* pDocSh  = GetViewData().GetDocShell();
    SCTAB       nTab    = GetViewData().CurrentTabForData();

    DoneBlockMode();
    InitOwnBlockMode( ScRange( GetViewData().GetCurX(), GetViewData().GetCurY(), nTab));
    pDocSh->UseScenario( nTab, rName );
}

//  Insert table

bool ScViewFunc::InsertTable( const OUString& rName, SCTAB nTab, bool bRecord )
{
    //  Order Table/Name is inverted for DocFunc
    bool bSuccess = GetViewData().GetDocShell()->GetDocFunc().
                        InsertTable( nTab, rName, bRecord, false );
    if (bSuccess)
        SetTabNo( nTab, true );

    return bSuccess;
}

//  Insert tables

void ScViewFunc::InsertTables(std::vector<OUString>& aNames, SCTAB nTab,
                                            SCTAB nCount, bool bRecord )
{
    ScDocShell* pDocSh    = GetViewData().GetDocShell();
    ScDocument& rDoc     = pDocSh->GetDocument();
    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;

    weld::WaitObject aWait(GetViewData().GetDialogParent());

    if (bRecord)
    {
        rDoc.BeginDrawUndo();                            //    InsertTab creates a SdrUndoNewPage
    }

    bool bFlag=false;

    if(aNames.empty())
    {
        rDoc.CreateValidTabNames(aNames, nCount);
    }
    if (rDoc.InsertTabs(nTab, aNames))
    {
        pDocSh->Broadcast( ScTablesHint( SC_TABS_INSERTED, nTab, nCount ) );
        bFlag = true;
    }

    if (!bFlag)
        return;

    if (bRecord)
        pDocSh->GetUndoManager()->AddUndoAction(
                    std::make_unique<ScUndoInsertTables>( *pDocSh, nTab, std::move(aNames)));

    //    Update views

    SetTabNo( nTab, true );
    pDocSh->PostPaintExtras();
    pDocSh->SetDocumentModified();
    SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) );
}

bool ScViewFunc::AppendTable( const OUString& rName, bool bRecord )
{
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc   = pDocSh->GetDocument();
    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;

    weld::WaitObject aWait(GetViewData().GetDialogParent());

    if (bRecord)
        rDoc.BeginDrawUndo();                          //  InsertTab creates a SdrUndoNewPage

    if (rDoc.InsertTab( SC_TAB_APPEND, rName ))
    {
        SCTAB nTab = rDoc.GetTableCount()-1;
        if (bRecord)
            pDocSh->GetUndoManager()->AddUndoAction(
                        std::make_unique<ScUndoInsertTab>( *pDocSh, nTab, true, rName));
        GetViewData().InsertTab( nTab );
        SetTabNo( nTab, true );
        pDocSh->PostPaintExtras();
        pDocSh->SetDocumentModified();
        SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) );
        return true;
    }
    else
    {
        return false;
    }
}

void ScViewFunc::DeleteTable( SCTAB nTab, bool bRecord )
{
    ScDocShell* pDocSh  = GetViewData().GetDocShell();
    ScDocument& rDoc    = pDocSh->GetDocument();

    bool bSuccess = pDocSh->GetDocFunc().DeleteTable( nTab, bRecord );
    if (bSuccess)
    {
        SCTAB nNewTab = nTab;
        if ( nNewTab >= rDoc.GetTableCount() )
            --nNewTab;
        SetTabNo( nNewTab, true );
    }
}

//only use this method for undo for now, all sheets must be connected
//this method doesn't support undo for now, merge it when it with the other method later
void ScViewFunc::DeleteTables( const SCTAB nTab, SCTAB nSheets )
{
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc    = pDocSh->GetDocument();
    bool bVbaEnabled = rDoc.IsInVBAMode();
    SCTAB       nNewTab = nTab;
    weld::WaitObject aWait(GetViewData().GetDialogParent());

    while ( nNewTab > 0 && !rDoc.IsVisible( nNewTab ) )
        --nNewTab;

    if (!rDoc.DeleteTabs(nTab, nSheets))
        return;

    if( bVbaEnabled )
    {
        for (SCTAB aTab = 0; aTab < nSheets; ++aTab)
        {
            OUString sCodeName;
            bool bHasCodeName = rDoc.GetCodeName( nTab + aTab, sCodeName );
            if ( bHasCodeName )
                VBA_DeleteModule( *pDocSh, sCodeName );
        }
    }

    pDocSh->Broadcast( ScTablesHint( SC_TABS_DELETED, nTab, nSheets ) );
    if ( nNewTab >= rDoc.GetTableCount() )
        nNewTab = rDoc.GetTableCount() - 1;
    SetTabNo( nNewTab, true );

    pDocSh->PostPaintExtras();
    pDocSh->SetDocumentModified();

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

bool ScViewFunc::DeleteTables(const std::vector<SCTAB>& rTabs, bool bRecord)
{
    ScDocShell* pDocSh  = GetViewData().GetDocShell();
    ScDocument& rDoc    = pDocSh->GetDocument();
    bool bVbaEnabled = rDoc.IsInVBAMode();
    SCTAB nNewTab = rTabs.front();
    weld::WaitObject aWait(GetViewData().GetDialogParent());
    if (bRecord && !rDoc.IsUndoEnabled())
        bRecord = false;
    if ( bVbaEnabled )
        bRecord = false;

    while ( nNewTab > 0 && !rDoc.IsVisible( nNewTab ) )
        --nNewTab;

    bool bWasLinked = false;
    ScDocumentUniquePtr pUndoDoc;
    std::unique_ptr<ScRefUndoData> pUndoData;
    if (bRecord)
    {
        pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
        SCTAB nCount = rDoc.GetTableCount();

        OUString aOldName;
        bool isFirstTab = true;
        for (SCTAB nTab : rTabs)
        {
            if (isFirstTab)
            {
                pUndoDoc->InitUndo( rDoc, nTab,nTab, true,true );   // incl. column/fow flags
                isFirstTab = false;
            }
            else
                pUndoDoc->AddUndoTab( nTab,nTab, true,true );       // incl. column/fow flags

            rDoc.CopyToDocument(0,0,nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, InsertDeleteFlags::ALL,false, *pUndoDoc );
            rDoc.GetName( nTab, aOldName );
            pUndoDoc->RenameTab( nTab, aOldName );
            if (rDoc.IsLinked(nTab))
            {
                bWasLinked = true;
                pUndoDoc->SetLink( nTab, rDoc.GetLinkMode(nTab), rDoc.GetLinkDoc(nTab),
                                    rDoc.GetLinkFlt(nTab), rDoc.GetLinkOpt(nTab),
                                    rDoc.GetLinkTab(nTab),
                                    rDoc.GetLinkRefreshDelay(nTab) );
            }
            if ( rDoc.IsScenario(nTab) )
            {
                pUndoDoc->SetScenario( nTab, true );
                OUString aComment;
                Color  aColor;
                ScScenarioFlags nScenFlags;
                rDoc.GetScenarioData( nTab, aComment, aColor, nScenFlags );
                pUndoDoc->SetScenarioData( nTab, aComment, aColor, nScenFlags );
                bool bActive = rDoc.IsActiveScenario( nTab );
                pUndoDoc->SetActiveScenario( nTab, bActive );
            }
            pUndoDoc->SetVisible( nTab, rDoc.IsVisible( nTab ) );
            pUndoDoc->SetTabBgColor( nTab, rDoc.GetTabBgColor(nTab) );
            auto pSheetEvents = rDoc.GetSheetEvents( nTab );
            pUndoDoc->SetSheetEvents( nTab, std::unique_ptr<ScSheetEvents>(pSheetEvents ? new ScSheetEvents(*pSheetEvents) : nullptr) );
            pUndoDoc->SetLayoutRTL( nTab, rDoc.IsLayoutRTL( nTab ) );

            if ( rDoc.IsTabProtected( nTab ) )
                pUndoDoc->SetTabProtection(nTab, rDoc.GetTabProtection(nTab));

            //  Drawing-Layer is responsible for its Undo  !!!
            //      pUndoDoc->TransferDrawPage(rDoc, nTab,nTab);
        }

        pUndoDoc->AddUndoTab( 0, nCount-1 );            //  all Tabs for references

        rDoc.BeginDrawUndo();                          //  DeleteTab creates a SdrUndoDelPage

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

    bool bDelDone = false;

    std::vector<ScTable*> aSheetViewHolderTablesToDelete;

    for (sal_Int32 i = rTabs.size() - 1; i >= 0; --i)
    {
        SCTAB nTab = rTabs[i];
        OUString sCodeName;
        bool bHasCodeName = rDoc.GetCodeName(nTab, sCodeName);

        // If we delete the current viewed table, make sure that we exit any sheet views
        if (GetViewData().GetTabNumber() == nTab && GetViewData().GetSheetViewID() != sc::DefaultSheetViewID)
            GetViewData().SetSheetViewID(sc::DefaultSheetViewID);

        if (auto pManager = rDoc.GetSheetViewManager(nTab))
        {
            for (auto const& pSheetView : pManager->getSheetViews())
            {
                aSheetViewHolderTablesToDelete.push_back(pSheetView->getTablePointer());
            }
        }

        if (rDoc.DeleteTab(nTab))
        {
            bDelDone = true;
            if( bVbaEnabled && bHasCodeName )
            {
                VBA_DeleteModule( *pDocSh, sCodeName );
            }
            pDocSh->Broadcast( ScTablesHint(SC_TAB_DELETED, nTab) );
        }
    }

    // Delete sheet view holder tables if it's table has been deleted
    for (auto* pTable : aSheetViewHolderTablesToDelete)
    {
        SCTAB nTab = pTable->GetTab();
        if (rDoc.DeleteTab(nTab))
        {
            pDocSh->Broadcast(ScTablesHint(SC_TAB_DELETED, nTab));
        }
    }

    if (bRecord)
    {
        pDocSh->GetUndoManager()->AddUndoAction(
                    std::make_unique<ScUndoDeleteTab>( *GetViewData().GetDocShell(), rTabs,
                                            std::move(pUndoDoc), std::move(pUndoData) ));
    }

    if (bDelDone)
    {
        if ( nNewTab >= rDoc.GetTableCount() )
            nNewTab = rDoc.GetTableCount() - 1;

        SetTabNo( nNewTab, true );

        if (bWasLinked)
        {
            pDocSh->UpdateLinks();              // update Link-Manager
            GetViewData().GetBindings().Invalidate(SID_LINKS);
        }

        pDocSh->PostPaintExtras();
        pDocSh->SetDocumentModified();

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

bool ScViewFunc::RenameTable( const OUString& rName, SCTAB nTab )
{
    //  order Table/Name is inverted for DocFunc
    bool bSuccess = GetViewData().GetDocShell()->GetDocFunc().
                        RenameTable( nTab, rName, true, false );
    if (bSuccess)
    {
        //  the table name might be part of a formula
        GetViewData().GetViewShell()->UpdateInputHandler();
    }
    return bSuccess;
}

bool ScViewFunc::SetTabBgColor( const Color& rColor, SCTAB nTab )
{
    bool bSuccess = GetViewData().GetDocShell()->GetDocFunc().SetTabBgColor( nTab, rColor, true, false );
    if (bSuccess)
    {
        GetViewData().GetViewShell()->UpdateInputHandler();
    }
    return bSuccess;
}

bool ScViewFunc::SetTabBgColor( ScUndoTabColorInfo::List& rUndoSetTabBgColorInfoList )
{
    bool bSuccess = GetViewData().GetDocShell()->GetDocFunc().SetTabBgColor( rUndoSetTabBgColorInfoList, false );
    if (bSuccess)
    {
        GetViewData().GetViewShell()->UpdateInputHandler();
    }
    return bSuccess;
}

void ScViewFunc::InsertAreaLink( const OUString& rFile,
                                    const OUString& rFilter, const OUString& rOptions,
                                    const OUString& rSource )
{
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    SCCOL nPosX = GetViewData().GetCurX();
    SCROW nPosY = GetViewData().GetCurY();
    SCTAB nTab = GetViewData().CurrentTabForData();
    ScAddress aPos( nPosX, nPosY, nTab );

    pDocSh->GetDocFunc().InsertAreaLink( rFile, rFilter, rOptions, rSource, ScRange(aPos), 0/*nRefresh*/, false, false );
}

void ScViewFunc::InsertTableLink( const OUString& rFile,
                                    const OUString& rFilter, const OUString& rOptions,
                                    std::u16string_view rTabName )
{
    OUString aFilterName = rFilter;
    OUString aOpt = rOptions;
    ScDocumentLoader aLoader( rFile, aFilterName, aOpt );
    if (aLoader.IsError())
        return;

    ScDocShell* pSrcSh = aLoader.GetDocShell();
    ScDocument& rSrcDoc = pSrcSh->GetDocument();
    SCTAB nTab = MAXTAB+1;
    if (rTabName.empty())                // no name given -> first table
        nTab = 0;
    else
    {
        OUString aTemp;
        SCTAB nCount = rSrcDoc.GetTableCount();
        for (SCTAB i=0; i<nCount; i++)
        {
            rSrcDoc.GetName( i, aTemp );
            if ( aTemp == rTabName )
                nTab = i;
        }
    }

    if ( nTab <= MAXTAB )
        ImportTables( pSrcSh, 1, &nTab, true,
                    GetViewData().CurrentTabForData() );
}

//  Copy/link tables from another document

void ScViewFunc::ImportTables( ScDocShell* pSrcShell,
                                SCTAB nCount, const SCTAB* pSrcTabs, bool bLink,SCTAB nTab )
{
    ScDocument& rSrcDoc = pSrcShell->GetDocument();

    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = pDocSh->GetDocument();
    bool bUndo(rDoc.IsUndoEnabled());

    bool bError = false;

    if (rSrcDoc.GetDrawLayer())
        pDocSh->MakeDrawLayer();

    if (bUndo)
        rDoc.BeginDrawUndo();          // drawing layer must do its own undo actions

    SCTAB nInsCount = 0;
    SCTAB i;
    for( i=0; i<nCount; i++ )
    {   // insert sheets first and update all references
        OUString aName;
        rSrcDoc.GetName( pSrcTabs[i], aName );
        rDoc.CreateValidTabName( aName );
        if ( !rDoc.InsertTab( nTab+i, aName ) )
        {
            bError = true;      // total error
            break;  // for
        }
        ++nInsCount;
    }
    for (i=0; i<nCount && !bError; i++)
    {
        SCTAB nSrcTab = pSrcTabs[i];
        SCTAB nDestTab1=nTab+i;
        bool bValid = pDocSh->TransferTab( *pSrcShell, nSrcTab, nDestTab1,
            false, false );     // no insert

        if (!bValid)
        {
            bError = true;
        }

    }

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

        SfxMedium* pMed = pSrcShell->GetMedium();
        OUString aFileName = pMed->GetName();
        OUString aFilterName;
        if (pMed->GetFilter())
            aFilterName = pMed->GetFilter()->GetFilterName();
        OUString aOptions = ScDocumentLoader::GetOptions(*pMed);

        bool bWasThere = rDoc.HasLink( aFileName, aFilterName, aOptions );

        sal_uLong nRefresh = 0;
        OUString aTabStr;
        for (i=0; i<nInsCount; i++)
        {
            rSrcDoc.GetName( pSrcTabs[i], aTabStr );
            rDoc.SetLink( nTab+i, ScLinkMode::NORMAL,
                        aFileName, aFilterName, aOptions, aTabStr, nRefresh );
        }

        if (!bWasThere)         // Insert link only once per source document
        {
            ScTableLink* pLink = new ScTableLink( *pDocSh, aFileName, aFilterName, aOptions, nRefresh );
            pLink->SetInCreate( true );
            pLinkManager->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, aFileName, &aFilterName );
            pLink->Update();
            pLink->SetInCreate( false );

            SfxBindings& rBindings = GetViewData().GetBindings();
            rBindings.Invalidate( SID_LINKS );
        }
    }

    if (bUndo)
    {
        pDocSh->GetUndoManager()->AddUndoAction(
                std::make_unique<ScUndoImportTab>( *pDocSh, nTab, nCount ) );
    }

    for (i=0; i<nInsCount; i++)
        GetViewData().InsertTab(nTab);
    SetTabNo(nTab,true);
    pDocSh->PostPaint( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB,
                                PaintPartFlags::Grid | PaintPartFlags::Top | PaintPartFlags::Left | PaintPartFlags::Extras );

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

    pDocSh->PostPaintExtras();
    pDocSh->PostPaintGridAll();
    pDocSh->SetDocumentModified();
}

//  Move/Copy table to another document

void ScViewFunc::MoveTable(sal_uInt16 nDestDocNo, SCTAB nDestTab, bool bCopy,
                           const OUString* pNewTabName, bool bContextMenu,
                           SCTAB nContextMenuSourceTab)
{
    ScDocument& rDoc       = GetViewData().GetDocument();
    ScDocShell* pDocShell  = GetViewData().GetDocShell();
    ScDocShell* pDestShell = nullptr;
    ScTabViewShell* pDestViewSh = nullptr;
    bool bUndo (rDoc.IsUndoEnabled());
    bool bRename = pNewTabName && !pNewTabName->isEmpty();

    bool bNewDoc = (nDestDocNo == SC_DOC_NEW);
    if ( bNewDoc )
    {
        nDestTab = 0;           // firstly insert

        //  execute without SfxCallMode::RECORD, because already contained in move command

        SfxStringItem aItem( SID_FILE_NAME, "private:factory/" + STRING_SCAPP );
        SfxStringItem aTarget( SID_TARGETNAME, u"_blank"_ustr );

        const SfxPoolItemHolder aResult(GetViewData().GetDispatcher().ExecuteList(
            SID_OPENDOC, SfxCallMode::API|SfxCallMode::SYNCHRON,
            { &aItem, &aTarget }));

        if (aResult)
        {
            if ( auto pObjectItem = dynamic_cast<const SfxObjectItem*>(aResult.getItem()) )
                pDestShell = dynamic_cast<ScDocShell*>( pObjectItem->GetShell()  );
            else if ( auto pViewFrameItem = dynamic_cast<const SfxViewFrameItem*>(aResult.getItem()))
            {
                SfxViewFrame* pFrm = pViewFrameItem->GetFrame();
                if (pFrm)
                    pDestShell = dynamic_cast<ScDocShell*>( pFrm->GetObjectShell()  );
            }
            if (pDestShell)
                pDestViewSh = pDestShell->GetBestViewShell();
        }
    }
    else
        pDestShell = ScDocShell::GetShellByNum( nDestDocNo );

    if (!pDestShell)
    {
        OSL_FAIL("Destination document not found !!!");
        return;
    }

    ScMarkData& rMark = GetViewData().GetMarkData();
    if (bRename && rMark.GetSelectCount() != 1)
    {
        // Custom sheet name is provided, but more than one sheet is selected.
        // We don't support this scenario at the moment.
        return;
    }

    ScDocument& rDestDoc = pDestShell->GetDocument();

    if (&rDestDoc != &rDoc)
    {
        if (bNewDoc)
        {
            while (rDestDoc.GetTableCount() > 1)
                rDestDoc.DeleteTab(0);
            rDestDoc.RenameTab( 0, u"______42_____"_ustr );
        }

        SCTAB       nTabCount   = rDoc.GetTableCount();
        SCTAB       nTabSelCount = rMark.GetSelectCount();

        std::vector<SCTAB> TheTabs;

        for(SCTAB i=0; i<nTabCount; ++i)
        {
            if(rMark.GetTableSelect(i))
            {
                OUString aTabName;
                rDoc.GetName( i, aTabName);
                TheTabs.push_back(i);
                for(SCTAB j=i+1;j<nTabCount;j++)
                {
                    if((!rDoc.IsVisible(j)) && rDoc.IsScenario(j))
                    {
                        rDoc.GetName( j, aTabName);
                        TheTabs.push_back(j);
                        i=j;
                    }
                    else break;
                }
            }
        }

        GetFrameWin()->EnterWait();

        if (rDoc.GetDrawLayer())
            pDestShell->MakeDrawLayer();

        if (!bNewDoc && bUndo)
            rDestDoc.BeginDrawUndo();      // drawing layer must do its own undo actions

        bool bValid = true;
        if(nDestTab==SC_TAB_APPEND)
            nDestTab=rDestDoc.GetTableCount();
        SCTAB nDestTab1=nDestTab;
        ScClipParam aParam;
        for( size_t j=0; j<TheTabs.size(); ++j, ++nDestTab1 )
        {   // insert sheets first and update all references
            OUString aName;
            if (bRename)
                aName = *pNewTabName;
            else
                rDoc.GetName( TheTabs[j], aName );

            rDestDoc.CreateValidTabName( aName );
            if ( !rDestDoc.InsertTab( nDestTab1, aName ) )
            {
                bValid = false;        // total error
                break;  // for
            }
            ScRange aRange( 0, 0, TheTabs[j], rDoc.MaxCol(), rDoc.MaxRow(), TheTabs[j] );
            aParam.maRanges.push_back(aRange);
        }
        rDoc.SetClipParam(aParam);
        if ( bValid )
        {
            nDestTab1 = nDestTab;
            for(SCTAB nTab : TheTabs)
            {
                bValid = pDestShell->TransferTab( *pDocShell, nTab, nDestTab1, false, false );
                nDestTab1++;
            }
        }
        if (!bNewDoc && bUndo)
        {
            OUString sName;
            rDestDoc.GetName(nDestTab, sName);
            pDestShell->GetUndoManager()->AddUndoAction(
                            std::make_unique<ScUndoImportTab>( *pDestShell, nDestTab,
                                static_cast<SCTAB>(TheTabs.size())));

        }
        else
        {
            pDestShell->GetUndoManager()->Clear();
        }

        GetFrameWin()->LeaveWait();

        if (!bValid)
        {
            ErrorMessage(STR_TABINSERT_ERROR);
            return;
        }

        if (!bCopy)
        {
            if(nTabCount!=nTabSelCount)
                DeleteTables(TheTabs); // incl. Paint & Undo
            else
                ErrorMessage(STR_TABREMOVE_ERROR);
        }

        if (bNewDoc)
        {
            //  ChartListenerCollection must be updated before DeleteTab
            if ( rDestDoc.IsChartListenerCollectionNeedsUpdate() )
                rDestDoc.UpdateChartListenerCollection();

            SCTAB nNumTabsInserted = static_cast<SCTAB>(TheTabs.size());
            pDestShell->Broadcast( ScTablesHint( SC_TABS_INSERTED, 0, nNumTabsInserted ) );

            rDestDoc.DeleteTab( nNumTabsInserted );   // old first table
            pDestShell->Broadcast( ScTablesHint( SC_TAB_DELETED, nNumTabsInserted ) );

            if (pDestViewSh)
            {
                // Make sure to clear the cached page view after sheet
                // deletion, which still points to the sdr page belonging to
                // the deleted sheet.
                SdrView* pSdrView = pDestViewSh->GetScDrawView();
                if (pSdrView)
                    pSdrView->ClearPageView();

                pDestViewSh->TabChanged();      // pages on the drawing layer
            }
            pDestShell->PostPaint( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB,
                                    PaintPartFlags::Grid | PaintPartFlags::Top | PaintPartFlags::Left |
                                    PaintPartFlags::Extras | PaintPartFlags::Size );
            //  PaintPartFlags::Size for outline
        }
        else
        {
            pDestShell->Broadcast( ScTablesHint( SC_TAB_INSERTED, nDestTab ) );
            pDestShell->PostPaintExtras();
            pDestShell->PostPaintGridAll();
        }

        TheTabs.clear();

        pDestShell->SetDocumentModified();
        SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) );
    }
    else
    {
        // Move or copy within the same document.
        SCTAB       nTabCount   = rDoc.GetTableCount();

        std::unique_ptr<std::vector<SCTAB>> pSrcTabs(new std::vector<SCTAB>);
        std::unique_ptr<std::vector<SCTAB>> pDestTabs(new std::vector<SCTAB>);
        std::unique_ptr<std::vector<OUString>> pTabNames(new std::vector<OUString>);
        std::unique_ptr<std::vector<OUString>> pDestNames;
        pSrcTabs->reserve(nTabCount);
        pDestTabs->reserve(nTabCount);
        pTabNames->reserve(nTabCount);
        OUString aDestName;

        if (bContextMenu)
        {
            OUString aTabName;
            rDoc.GetName(nContextMenuSourceTab, aTabName);
            pTabNames->push_back(aTabName);
        }
        else
        {
            for(SCTAB i=0;i<nTabCount;i++)
            {
                if(rMark.GetTableSelect(i))
                {
                    OUString aTabName;
                    rDoc.GetName( i, aTabName);
                    pTabNames->push_back(aTabName);

                    for(SCTAB j=i+1;j<nTabCount;j++)
                    {
                        if((!rDoc.IsVisible(j)) && rDoc.IsScenario(j))
                        {
                            rDoc.GetName( j, aTabName);
                            pTabNames->push_back(aTabName);
                            i=j;
                        }
                        else break;
                    }
                }
            }
        }

        if (bCopy && bUndo)
            rDoc.BeginDrawUndo();          // drawing layer must do its own undo actions

        rDoc.GetName( nDestTab, aDestName);
        SCTAB nDestTab1=nDestTab;
        SCTAB nMovTab=0;
        for (size_t j = 0, n = pTabNames->size(); j < n; ++j)
        {
            nTabCount   = rDoc.GetTableCount();
            const OUString& rStr = (*pTabNames)[j];
            if(!rDoc.GetTable(rStr,nMovTab))
            {
                nMovTab=nTabCount;
            }
            if(!rDoc.GetTable(aDestName,nDestTab1))
            {
                nDestTab1=nTabCount;
            }
            pDocShell->MoveTable( nMovTab, nDestTab1, bCopy, false );   // Undo is here

            // tdf#43175 - Adjust chart references on every copied sheet
            if (bCopy)
            {
                // New position of source table after moving
                SCTAB nSrcTab = (nDestTab1 <= nMovTab) ? nMovTab + 1 : nMovTab;
                //#i29848# adjust references to data on the copied sheet
                ScChartHelper::AdjustRangesOfChartsOnDestinationPage(rDoc, rDestDoc, nSrcTab,
                                                                     nDestTab1);
            }

            if(bCopy && rDoc.IsScenario(nMovTab))
            {
                OUString aComment;
                Color  aColor;
                ScScenarioFlags nFlags;

                rDoc.GetScenarioData(nMovTab, aComment,aColor, nFlags);
                rDoc.SetScenario(nDestTab1,true);
                rDoc.SetScenarioData(nDestTab1,aComment,aColor,nFlags);
                bool bActive = rDoc.IsActiveScenario(nMovTab );
                rDoc.SetActiveScenario( nDestTab1, bActive );
                bool bVisible=rDoc.IsVisible(nMovTab);
                rDoc.SetVisible(nDestTab1,bVisible );
            }

            pSrcTabs->push_back(nMovTab);

            if(!bCopy)
            {
                if(!rDoc.GetTable(rStr,nDestTab1))
                {
                    nDestTab1=nTabCount;
                }
            }

            pDestTabs->push_back(nDestTab1);
        }

        // Rename must be done after all sheets have been moved.
        if (bRename)
        {
            pDestNames.reset(new std::vector<OUString>);
            size_t n = pDestTabs->size();
            pDestNames->reserve(n);
            for (size_t j = 0; j < n; ++j)
            {
                SCTAB nRenameTab = (*pDestTabs)[j];
                OUString aTabName = *pNewTabName;
                rDoc.CreateValidTabName( aTabName );
                pDestNames->push_back(aTabName);
                rDoc.RenameTab(nRenameTab, aTabName);
            }
        }
        else
            // No need to keep this around when we are not renaming.
            pTabNames.reset();

        SCTAB nTab = GetViewData().CurrentTabForData();

        if (comphelper::LibreOfficeKit::isActive() && !pSrcTabs->empty())
        {
            ScModelObj* pModel = pDocShell->GetModel();
            SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
        }

        if (bUndo)
        {
            if (bCopy)
            {
                pDocShell->GetUndoManager()->AddUndoAction(
                        std::make_unique<ScUndoCopyTab>(
                            *pDocShell, std::move(pSrcTabs), std::move(pDestTabs), std::move(pDestNames)));
            }
            else
            {
                pDocShell->GetUndoManager()->AddUndoAction(
                        std::make_unique<ScUndoMoveTab>(
                            *pDocShell, std::move(pSrcTabs), std::move(pDestTabs), std::move(pTabNames), std::move(pDestNames)));
            }
        }

        if (bContextMenu)
        {
            for (SCTAB i = 0; i < nTabCount; i++)
            {
                if (rMark.GetTableSelect(i))
                    SetTabNo(i, true);
            }
        }
        else
        {
            SCTAB nNewTab = nDestTab;
            if (nNewTab == SC_TAB_APPEND)
                nNewTab = rDoc.GetTableCount() - 1;
            else if (!bCopy && nTab < nDestTab)
                nNewTab--;

            SetTabNo(nNewTab, true);
        }
    }
}

void ScViewFunc::ShowTable( const std::vector<OUString>& rNames )
{
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = pDocSh->GetDocument();
    bool bUndo(rDoc.IsUndoEnabled());

    std::vector<SCTAB> undoTabs;
    SCTAB nPos = 0;

    bool bFound(false);

    for (const OUString& aName : rNames)
    {
        if (rDoc.GetTable(aName, nPos))
        {
            rDoc.SetVisible( nPos, true );
            SetTabNo( nPos, true );
            SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) );
            if (!bFound)
                bFound = true;
            if (bUndo)
                undoTabs.push_back(nPos);
        }
    }
    if (bFound)
    {
        if (bUndo)
        {
            pDocSh->GetUndoManager()->AddUndoAction( std::make_unique<ScUndoShowHideTab>( *pDocSh, std::move(undoTabs), true ) );
        }
        pDocSh->PostPaint(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB, PaintPartFlags::Extras);
        pDocSh->SetDocumentModified();
    }
}

void ScViewFunc::HideTable( const ScMarkData& rMark, SCTAB nTabToSelect )
{
    ScDocShell* pDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = pDocSh->GetDocument();
    bool bUndo(rDoc.IsUndoEnabled());
    SCTAB nVisible = 0;
    SCTAB nTabCount = rDoc.GetTableCount();

    SCTAB nTabSelCount = rMark.GetSelectCount();

    // check to make sure we won't hide all sheets. we need at least one visible at all times.
    for ( SCTAB i=0; i < nTabCount && nVisible <= nTabSelCount ; i++ )
        if (rDoc.IsVisible(i))
            ++nVisible;

    if (nVisible <= nTabSelCount)
        return;

    std::vector<SCTAB> undoTabs;

    // need to take a copy of selectedtabs since it is modified in the loop
    const ScMarkData::MarkedTabsType selectedTabs = rMark.GetSelectedTabs();
    for (const SCTAB& nTab : selectedTabs)
    {
        if (rDoc.IsVisible( nTab ))
        {
            rDoc.SetVisible( nTab, false );
            // Update views
            pDocSh->Broadcast( ScTablesHint( SC_TAB_HIDDEN, nTab ) );
            SetTabNo( nTab, true );
            // Store for undo
            if (bUndo)
                undoTabs.push_back(nTab);
        }
    }

    if (nTabToSelect != -1)
        SetTabNo(nTabToSelect);

    if (bUndo)
    {
        pDocSh->GetUndoManager()->AddUndoAction( std::make_unique<ScUndoShowHideTab>( *pDocSh, std::move(undoTabs), false ) );
    }

    //  Update views
    SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) );
    pDocSh->PostPaint(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB, PaintPartFlags::Extras);
    pDocSh->SetDocumentModified();
}

void ScViewFunc::InsertSpecialChar( const OUString& rStr, const vcl::Font& rFont )
{
    ScEditableTester aTester = ScEditableTester::CreateAndTestView(this);
    if (!aTester.IsEditable())
    {
        ErrorMessage(aTester.GetMessageId());
        return;
    }

    const sal_Unicode* pChar    = rStr.getStr();
    ScTabViewShell* pViewShell  = GetViewData().GetViewShell();
    SvxFontItem     aFontItem( rFont.GetFamilyType(),
                               rFont.GetFamilyName(),
                               rFont.GetStyleName(),
                               rFont.GetPitch(),
                               rFont.GetCharSet(),
                               ATTR_FONT );

    //  if string contains WEAK characters, set all fonts
    SvtScriptType nScript;
    ScDocument& rDoc = GetViewData().GetDocument();
    if ( rDoc.HasStringWeakCharacters( rStr ) )
        nScript = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX;
    else
        nScript = rDoc.GetStringScriptType( rStr );

    SvxScriptSetItem aSetItem( SID_ATTR_CHAR_FONT, pViewShell->GetPool() );
    aSetItem.PutItemForScriptType( nScript, aFontItem );
    ApplyUserItemSet( aSetItem.GetItemSet() );

    while ( *pChar )
        pViewShell->TabKeyInput( KeyEvent( *(pChar++), vcl::KeyCode() ) );
}

void ScViewFunc::UpdateLineAttrs( SvxBorderLine&       rLine,
                                  const SvxBorderLine* pDestLine,
                                  const SvxBorderLine* pSrcLine,
                                  bool                 bColor )
{
    if ( !(pSrcLine && pDestLine) )
        return;

    if ( bColor )
    {
        rLine.SetColor      ( pSrcLine->GetColor() );
        rLine.SetBorderLineStyle(pDestLine->GetBorderLineStyle());
        rLine.SetWidth      ( pDestLine->GetWidth() );
    }
    else
    {
        rLine.SetColor      ( pDestLine->GetColor() );
        rLine.SetBorderLineStyle(pSrcLine->GetBorderLineStyle());
        rLine.SetWidth      ( pSrcLine->GetWidth() );
    }
}

#define SET_LINE_ATTRIBUTES(LINE,BOXLINE) \
    pBoxLine = aBoxItem.Get##LINE();                                \
    if ( pBoxLine )                                                 \
    {                                                               \
        if ( pLine )                                                \
        {                                                           \
            UpdateLineAttrs( aLine, pBoxLine, pLine, bColorOnly );  \
            aBoxItem.SetLine( &aLine, BOXLINE );                    \
        }                                                           \
        else                                                        \
            aBoxItem.SetLine( nullptr, BOXLINE );                      \
    }

void ScViewFunc::SetSelectionFrameLines( const SvxBorderLine* pLine,
                                         bool bColorOnly )
{
    // Not editable only due to a matrix? Attribute is ok anyhow.
    bool bOnlyNotBecauseOfMatrix;
    if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix )
    {
        ErrorMessage(STR_PROTECTIONERR);
        return;
    }

    ScDocument& rDoc = GetViewData().GetDocument();
    ScMarkData aFuncMark( GetViewData().GetMarkData() );       // local copy for UnmarkFiltered
    ScViewUtil::UnmarkFiltered( aFuncMark, rDoc );
    ScDocShell*             pDocSh = GetViewData().GetDocShell();
    const ScPatternAttr*    pSelAttrs = GetSelectionPattern();
    const SfxItemSet&       rSelItemSet = pSelAttrs->GetItemSet();

    const SfxPoolItem*      pBorderAttr = nullptr;
    SfxItemState            eItemState = rSelItemSet.GetItemState( ATTR_BORDER, true, &pBorderAttr );

    const SfxPoolItem*      pTLBRItem = nullptr;
    SfxItemState            eTLBRState = rSelItemSet.GetItemState( ATTR_BORDER_TLBR, true, &pTLBRItem );

    const SfxPoolItem*      pBLTRItem = nullptr;
    SfxItemState            eBLTRState = rSelItemSet.GetItemState( ATTR_BORDER_BLTR, true, &pBLTRItem );

    // any of the lines visible?
    if( !((eItemState != SfxItemState::DEFAULT) || (eTLBRState != SfxItemState::DEFAULT) || (eBLTRState != SfxItemState::DEFAULT)) )
        return;

    // none of the lines don't care?
    if( (eItemState != SfxItemState::INVALID) && (eTLBRState != SfxItemState::INVALID) && (eBLTRState != SfxItemState::INVALID) )
    {
        SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aOldSet( *rDoc.GetPool() );
        SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aNewSet( *rDoc.GetPool() );

        SvxBorderLine           aLine;

        if( pBorderAttr )
        {
            const SvxBorderLine*    pBoxLine = nullptr;
            SvxBoxItem      aBoxItem( *static_cast<const SvxBoxItem*>(pBorderAttr) );
            SvxBoxInfoItem  aBoxInfoItem( ATTR_BORDER_INNER );

            // here pBoxLine is used
            SET_LINE_ATTRIBUTES(Top,SvxBoxItemLine::TOP)
            SET_LINE_ATTRIBUTES(Bottom,SvxBoxItemLine::BOTTOM)
            SET_LINE_ATTRIBUTES(Left,SvxBoxItemLine::LEFT)
            SET_LINE_ATTRIBUTES(Right,SvxBoxItemLine::RIGHT)

            aBoxInfoItem.SetLine( aBoxItem.GetTop(), SvxBoxInfoItemLine::HORI );
            aBoxInfoItem.SetLine( aBoxItem.GetLeft(), SvxBoxInfoItemLine::VERT );
            aBoxInfoItem.ResetFlags(); // set Lines to Valid

            aOldSet.Put( *pBorderAttr );
            aNewSet.Put( aBoxItem );
            aNewSet.Put( aBoxInfoItem );
        }

        if( pTLBRItem && static_cast<const SvxLineItem*>(pTLBRItem)->GetLine() )
        {
            SvxLineItem aTLBRItem( *static_cast<const SvxLineItem*>(pTLBRItem) );
            UpdateLineAttrs( aLine, aTLBRItem.GetLine(), pLine, bColorOnly );
            aTLBRItem.SetLine( &aLine );
            aOldSet.Put( *pTLBRItem );
            aNewSet.Put( aTLBRItem );
        }

        if( pBLTRItem && static_cast<const SvxLineItem*>(pBLTRItem)->GetLine() )
        {
            SvxLineItem aBLTRItem( *static_cast<const SvxLineItem*>(pBLTRItem) );
            UpdateLineAttrs( aLine, aBLTRItem.GetLine(), pLine, bColorOnly );
            aBLTRItem.SetLine( &aLine );
            aOldSet.Put( *pBLTRItem );
            aNewSet.Put( aBLTRItem );
        }

        ApplyAttributes( aNewSet, aOldSet );
    }
    else // if ( eItemState == SfxItemState::INVALID )
    {
        aFuncMark.MarkToMulti();
        rDoc.ApplySelectionLineStyle( aFuncMark, pLine, bColorOnly );
    }

    const ScRange& aMarkRange = aFuncMark.GetMultiMarkArea();
    SCCOL nStartCol = aMarkRange.aStart.Col();
    SCROW nStartRow = aMarkRange.aStart.Row();
    SCTAB nStartTab = aMarkRange.aStart.Tab();
    SCCOL nEndCol = aMarkRange.aEnd.Col();
    SCROW nEndRow = aMarkRange.aEnd.Row();
    SCTAB nEndTab = aMarkRange.aEnd.Tab();
    pDocSh->PostPaint( nStartCol, nStartRow, nStartTab,
                       nEndCol, nEndRow, nEndTab,
                       PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE );

    pDocSh->UpdateOle(GetViewData());
    pDocSh->SetDocumentModified();
}

#undef SET_LINE_ATTRIBUTES

void ScViewFunc::SetValidation( const ScValidationData& rNew )
{
    ScDocument& rDoc = GetViewData().GetDocument();
    sal_uInt32 nIndex = rDoc.AddValidationEntry(rNew);      // for it there is no Undo
    SfxUInt32Item aItem( ATTR_VALIDDATA, nIndex );

    ApplyAttr( aItem );         // with Paint and Undo...
}

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