Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/LibreOffice/sc/source/ui/view/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 118 kB image not shown  

Quelle  viewfun2.cxx   Sprache: C

 
/* -*- 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 <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);
}
}

using ::std::vector;
using ::std::unique_ptr;

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

    ScDocument& rDoc = rDocSh.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(rDocSh);
    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 )
            rDocSh.PostPaint( 0, nPaintY, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab,
                                                PaintPartFlags::Grid | PaintPartFlags::Left );
    }

    if ( bPaint && bAnyChanged )
        rDocSh.UpdateOle(GetViewData());

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

    return bAnyChanged;
}

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

    ScDocShell& rDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = rDocSh.GetDocument();
    SCTAB nTab = GetViewData().GetTabNo();
    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(rDocSh);
    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 )
        rDocSh.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 */, nTab);
        ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), ROW_HEADER, GetViewData().GetTabNo());
    }

    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().GetTabNo();

    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().GetTabNo();
    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.GetEnginePool(), 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.GetEditPool());
    }
    else
        pInsDoc->SetString( nCol, nRow, nTab, aNewStr );

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

    const SfxUInt32Item* pItem = pInsDoc->GetAttr(
        nCol, nRow, nTab, ATTR_VALUE_FORMAT );
    if ( pItem )
    {   // set number format if incompatible
        // MarkData was already MarkToSimple'ed in PasteFromClip
        const ScRange& aRange = rMark.GetMarkArea();
        ScPatternAttr aPattern(rDoc.getCellAttributeHelper());
        aPattern.GetItemSet().Put( *pItem );
        SvNumFormatType nNewType = rDoc.GetFormatTable()->GetType( pItem->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().GetTabNo();
    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().GetTabNo();
    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& rDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = rDocSh.GetDocument();
    SCTAB nTab = GetViewData().GetTabNo();
    bool bUndo(rDoc.IsUndoEnabled());

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

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

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

void ScViewFunc::SetPrintZoom(sal_uInt16 nScale)
{
    ScDocShell& rDocSh = GetViewData().GetDocShell();
    SCTAB nTab = GetViewData().GetTabNo();
    rDocSh.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& rDocSh  = GetViewData().GetDocShell();
    ScDocument& rDoc    = rDocSh.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().GetTabNo();
        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);
        }

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

    //  update page breaks

    for (const auto& rTab : rMark)
        ScPrintFunc( rDocSh, rDocSh.GetPrinter(), rTab ).UpdatePages();

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

    rDocSh.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( this );
    if (!aTester.IsEditable())
    {
        ErrorMessage(aTester.GetMessageId());
        return;
    }

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

    ScDocShell& rDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = rDocSh.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, &rDocSh, aMergeOption=std::move(aMergeOption),
                    bApi, nStartCol, nStartRow, aMarkRange]
        (bool bNowDoContents, bool bNowEmptyMergedCells)
    {
        if (rDocSh.GetDocFunc().MergeCells(aMergeOption, bNowDoContents, true/*bRecord*/,
                                             bApi, bNowEmptyMergedCells))
        {
            SetCursor( nStartCol, nStartRow );
            // DoneBlockMode( sal_False);
            Unmark();

            rDocSh.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( 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& rDocSh = 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 = rDocSh.GetDocFunc().UnmergeCells(aOption, true/*bRecord*/, nullptr);
        aExtended = aOption.getFirstSingleRange();
        MarkRange( aExtended );

        if (bOk)
            rDocSh.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& rDocSh = GetViewData().GetDocShell();
        const ScMarkData& rMark = GetViewData().GetMarkData();
        bool bSuccess = rDocSh.GetDocFunc().FillSimple( aRange, &rMark, eDir, false );
        if (bSuccess)
        {
            rDocSh.UpdateOle(GetViewData());
            UpdateScrollBars();

            auto& rDoc = rDocSh.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& rDocSh = GetViewData().GetDocShell();
        const ScMarkData& rMark = GetViewData().GetMarkData();
        bool bSuccess = rDocSh.GetDocFunc().
                        FillSeries( aRange, &rMark, eDir, eCmd, eDateCmd,
                                    fStart, fStep, fMax, false );
        if (bSuccess)
        {
            rDocSh.UpdateOle(GetViewData());
            UpdateScrollBars();

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

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

    MarkRange( aRange, false );         // aRange was modified in FillAuto
    rDocSh.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 = rDocSh.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().GetTabNo();
    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( this );
    if (!aTester.IsEditable())
    {
        ErrorMessage(aTester.GetMessageId());
        return;
    }

    ScDocShell& rDocSh = GetViewData().GetDocShell();
    ScDocument& rDoc = rDocSh.GetDocument();
    ScMarkData& rMark = GetViewData().GetMarkData();
    SCTAB nTab = GetViewData().GetTabNo();
    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
        rDocSh.GetUndoManager()->AddUndoAction(
            std::make_unique<ScUndoFillTable>( rDocSh, rMark,
                                aMarkRange.aStart.Col(), aMarkRange.aStart.Row(), nTab,
                                aMarkRange.aEnd.Col(), aMarkRange.aEnd.Row(), nTab,
                                std::move(pUndoDoc), bMulti, nTab, nFlags, nFunction, bSkipEmpty, bAsLink ) );
    }

    rDocSh.PostPaintGridAll();
    rDocSh.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& rDocSh = GetViewData().GetDocShell();
    rDocSh.GetDocFunc().ConvertFormulaToValue(aRange, true);
    // tdf#131326 - invalidate cell slots and update input line with new content
    CellContentChanged();
    rDocSh.PostPaint(aRange, PaintPartFlags::Grid);
}

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

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=89 G=90

¤ Dauer der Verarbeitung: 0.20 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.