/* -*- 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, false , false ,
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, true , true );
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