/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <scitems.hxx>
#include <comphelper/string.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/editeng.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/escapementitem.hxx>
#include <editeng/brushitem.hxx>
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <vcl/keycodes.hxx>
#include <rtl/math.hxx>
#include <unotools/charclass.hxx>
#include <tools/duration.hxx>
#include <osl/diagnose.h>
#include <attrib.hxx>
#include <patattr.hxx>
#include <formulacell.hxx>
#include <table.hxx>
#include <global.hxx>
#include <document.hxx>
#include <autoform.hxx>
#include <userlist.hxx>
#include <zforauto.hxx>
#include <subtotal.hxx>
#include <formula/errorcodes.hxx>
#include <docpool.hxx>
#include <progress.hxx>
#include <conditio.hxx>
#include <editutil.hxx>
#include <listenercontext.hxx>
#include <scopetools.hxx>
#include <o3tl/string_view.hxx>
#include <math.h>
#include <memory>
#include <list>
#include <string_view>
#define D_MAX_LONG_
double (0x7fffffff)
namespace {
short lcl_DecompValueString( OUString& rValue, sal_Int32& nVal, sal_uInt16* pMi
nDigits = nullptr )
{
if ( rValue.isEmpty() )
{
nVal = 0;
return 0;
}
const sal_Unicode* p = rValue.getStr();
sal_Int32 nSign = 0;
sal_Int32 nNum = 0;
if ( p[nNum] == '-' || p[nNum] == '+' )
nNum = nSign = 1;
while ( p[nNum] && CharClass::isAsciiNumeric( std::u16string_view(&p[nNum], 1) ) )
nNum++;
sal_Unicode cNext = p[nNum]; // 0 if at the end
sal_Unicode cLast = p[rValue.getLength()-1];
// #i5550# If there are numbers at the beginning and the end,
// prefer the one at the beginning only if it's followed by a space.
// Otherwise, use the number at the end, to enable things like IP addresses.
if ( nNum > nSign && ( cNext == 0 || cNext == ' ' || !CharClass::isAsciiNumeric(std::u16string_view(&cLast, 1)) ) )
{ // number at the beginning
nVal = o3tl::toInt32(rValue.subView( 0, nNum ));
// any number with a leading zero sets the minimum number of digits
if ( p[nSign] == '0' && pMinDigits && ( nNum - nSign > *pMinDigits ) )
*pMinDigits = nNum - nSign;
rValue = rValue.copy(nNum);
return -1;
}
else
{
nSign = 0;
sal_Int32 nEnd = nNum = rValue.getLength() - 1;
while ( nNum && CharClass::isAsciiNumeric( std::u16string_view(&p[nNum], 1) ) )
nNum--;
if ( p[nNum] == '-' || p[nNum] == '+' )
{
nNum--;
nSign = 1;
}
if ( nNum < nEnd - nSign )
{ // number at the end
nVal = o3tl::toInt32(rValue.subView( nNum + 1 ));
// any number with a leading zero sets the minimum number of digits
if ( p[nNum+1+nSign] == '0' && pMinDigits && ( nEnd - nNum - nSign > *pMinDigits ) )
*pMinDigits = nEnd - nNum - nSign;
rValue = rValue.copy(0, nNum + 1);
if (nSign) // use the return value = 2 to put back the '+'
return 2;
else
return 1;
}
}
nVal = 0;
return 0;
}
OUString lcl_ValueString( sal_Int32 nValue, sal_uInt16 nMinDigits )
{
if ( nMinDigits <= 1 )
return OUString::number( nValue ); // simple case...
else
{
OUString aStr = OUString::number( std::abs( nValue ) );
if ( aStr.getLength() < nMinDigits )
{
OUStringBuffer aZero(nMinDigits);
comphelper::string::padToLength(aZero, nMinDigits - aStr.getLength(), '0' );
aStr = aZero.append(aStr).makeStringAndClear();
}
// nMinDigits doesn't include the '-' sign -> add after inserting zeros
if ( nValue < 0 )
aStr = "-" + aStr;
return aStr;
}
}
void setSuffixCell(
ScColumn& rColumn, SCROW nRow, sal_Int32 nValue, sal_uInt16 nDigits,
std::u16string_view rSuffix,
CellType eCellType, bool bIsOrdinalSuffix )
{
ScDocument& rDoc = rColumn.GetDoc();
OUString aValue = lcl_ValueString(nValue, nDigits);
if (!bIsOrdinalSuffix)
{
aValue += rSuffix;
rColumn.SetRawString(nRow, aValue);
return ;
}
OUString aOrdinalSuffix = ScGlobal::GetOrdinalSuffix(nValue);
if (eCellType != CELLTYPE_EDIT)
{
aValue += aOrdinalSuffix;
rColumn.SetRawString(nRow, aValue);
return ;
}
EditEngine aEngine(rDoc.GetEnginePool());
aEngine.SetEditTextObjectPool(rDoc.GetEditPool());
SfxItemSet aAttr = aEngine.GetEmptyItemSet();
aAttr.Put( SvxEscapementItem( SvxEscapement::Superscript, EE_CHAR_ESCAPEMENT));
aEngine.SetText( aValue );
aEngine.QuickInsertText(
aOrdinalSuffix,
ESelection(0, aValue.getLength(), 0, aValue.getLength() + aOrdinalSuffix.getLength()));
aEngine.QuickSetAttribs(
aAttr,
ESelection(0, aValue.getLength(), 0, aValue.getLength() + aOrdinalSuffix.getLength()));
// Text object instance will be owned by the cell.
rColumn.SetEditText(nRow, aEngine.CreateTextObject());
}
}
namespace {
/* TODO: move this to rtl::math::approxDiff() ? Though the name is funny, the
* approx is expected to be more correct than the raw diff. */
/** Calculate a-b trying to diminish precision errors such as for 0.11-0.12
not return -0.009999999999999995 but -0.01 instead.
*/
double approxDiff( double a, double b )
{
if (a == b)
return 0.0;
if (a == 0.0)
return -b;
if (b == 0.0)
return a;
const double c = a - b;
const double aa = fabs(a);
const double ab = fabs(b);
if (aa < 1e-16 || aa > 1e+16 || ab < 1e-16 || ab > 1e+16)
// This is going nowhere, live with the result.
return c;
const double q = aa < ab ? b / a : a / b;
const double d = (a * q - b * q) / q;
if (d == c)
// No differing error, live with the result.
return c;
// We now have two subtractions with a similar but not equal error. Obtain
// the exponent of the error magnitude and round accordingly.
const double e = fabs(d - c);
const int nExp = static_cast <int >(floor(log10(e))) + 1;
// tdf#129606: Limit precision to the 16th significant digit of the least precise argument.
// Cf. mnMaxGeneralPrecision in sc/source/core/data/column3.cxx.
const int nExpArg = static_cast <int >(floor(log10(std::max(aa, ab)))) - 15;
return rtl::math::round(c, -std::max(nExp, nExpArg));
}
double approxTypedDiff( double a, double b, bool bTime, tools::Duration& rDuration )
{
if (bTime)
{
rDuration = tools::Duration(a - b);
return rDuration.GetInDays();
}
return approxDiff( a, b);
}
}
void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
FillCmd& rCmd, FillDateCmd& rDateCmd,
double & rInc, tools::Duration& rDuration, sal_uInt16& rMinDigits,
ScUserListData*& rListData, sal_uInt16& rListIndex,
bool bHasFiltered, bool & rSkipOverlappedCells,
std::vector<sal_Int32>& rNonOverlappedCellIdx)
{
OSL_ENSURE( nCol1==nCol2 || nRow1==nRow2, "FillAnalyse: invalid range" );
rInc = 0.0;
rDuration = tools::Duration();
rMinDigits = 0;
rListData = nullptr;
rCmd = FILL_SIMPLE;
rSkipOverlappedCells = false ;
if ( nScFillModeMouseModifier & KEY_MOD1 )
return ; // Ctrl-key: Copy
SCCOL nAddX;
SCROW nAddY;
SCSIZE nCount;
if (nCol1 == nCol2)
{
nAddX = 0;
nAddY = 1;
nCount = static_cast <SCSIZE>(nRow2 - nRow1 + 1);
}
else
{
nAddX = 1;
nAddY = 0;
nCount = static_cast <SCSIZE>(nCol2 - nCol1 + 1);
}
// Try to analyse the merged cells only if there are no filtered rows in the destination area
// Else fallback to the old way to avoid regression.
// Filling merged cells into an area with filtered (hidden) rows, is a very complex task
// that is not implemented, but not even decided how to do, even excel can't handle that well
if (!bHasFiltered)
{
bool bHasOverlappedCells = false ;
bool bSkipOverlappedCells = true ;
SCCOL nColCurr = nCol1;
SCROW nRowCurr = nRow1;
// collect cells that are not empty or not overlapped
rNonOverlappedCellIdx.resize(nCount);
SCSIZE nValueCount = 0;
for (SCSIZE i = 0; i < nCount; ++i)
{
const ScPatternAttr* pPattern = GetPattern(nColCurr, nRowCurr);
const ScMergeFlagAttr* pMergeFlagItem = nullptr;
bool bOverlapped
= pPattern->GetItemSet().GetItemState(ATTR_MERGE_FLAG, false , &pMergeFlagItem) == SfxItemState::SET
&& pMergeFlagItem->IsOverlapped();
if (bOverlapped)
bHasOverlappedCells = true ;
if (!bOverlapped || GetCellValue(nColCurr, nRowCurr).getType() != CELLTYPE_NONE)
{
rNonOverlappedCellIdx[nValueCount++] = i;
// if there is at least 1 non empty overlapped cell, then no cell should be skipped
if (bOverlapped)
bSkipOverlappedCells = false ;
}
nColCurr += nAddX;
nRowCurr += nAddY;
}
rNonOverlappedCellIdx.resize(nValueCount);
// if all the values are overlapped CELLTYPE_NONE, then there is no need to analyse it.
if (nValueCount == 0)
return ;
// if there is no overlapped cells, there is nothing to skip
if (!bHasOverlappedCells)
bSkipOverlappedCells = false ;
if (bSkipOverlappedCells)
{
nColCurr = nCol1 + rNonOverlappedCellIdx[0] * nAddX;
nRowCurr = nRow1 + rNonOverlappedCellIdx[0] * nAddY;
ScRefCellValue aPrevCell, aCurrCell;
aCurrCell = GetCellValue(nColCurr, nRowCurr);
CellType eCellType = aCurrCell.getType();
if (eCellType == CELLTYPE_VALUE)
{
bool bVal = true ;
double fVal;
SvNumFormatType nCurrCellFormatType
= rDocument.GetFormatTable()->GetType(GetNumberFormat(nColCurr, nRowCurr));
if (nCurrCellFormatType == SvNumFormatType::DATE)
{
if (nValueCount >= 2)
{
tools::Long nCmpInc = 0;
FillDateCmd eType = FILL_YEAR; // just some temporary default values
tools::Long nDDiff = 0, nMDiff = 0, nYDiff = 0; // to avoid warnings
Date aNullDate = rDocument.GetFormatTable()->GetNullDate();
Date aCurrDate = aNullDate, aPrevDate = aNullDate;
aCurrDate.AddDays(aCurrCell.getDouble());
for (SCSIZE i = 1; i < nValueCount && bVal; i++)
{
aPrevCell = aCurrCell;
aPrevDate = aCurrDate;
nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
aCurrCell = GetCellValue(nColCurr, nRowCurr);
if (aCurrCell.getType() == CELLTYPE_VALUE)
{
aCurrDate = aNullDate + static_cast <sal_Int32>(aCurrCell.getDouble());
if (eType != FILL_DAY) {
nDDiff = aCurrDate.GetDay()
- static_cast <tools::Long >(aPrevDate.GetDay());
nMDiff = aCurrDate.GetMonth()
- static_cast <tools::Long >(aPrevDate.GetMonth());
nYDiff = aCurrDate.GetYear()
- static_cast <tools::Long >(aPrevDate.GetYear());
}
if (i == 1)
{
if (nDDiff != 0)
{
eType = FILL_DAY;
nCmpInc = aCurrDate - aPrevDate;
}
else
{
eType = FILL_MONTH;
nCmpInc = nMDiff + 12 * nYDiff;
}
}
else if (eType == FILL_DAY)
{
if (aCurrDate - aPrevDate != nCmpInc)
bVal = false ;
}
else
{
if (nDDiff || (nMDiff + 12 * nYDiff != nCmpInc))
bVal = false ;
}
}
else
bVal = false ; // No date is also not ok
}
if (bVal)
{
if (eType == FILL_MONTH && (nCmpInc % 12 == 0))
{
eType = FILL_YEAR;
nCmpInc /= 12;
}
rCmd = FILL_DATE;
rDateCmd = eType;
rInc = nCmpInc;
rSkipOverlappedCells = true ;
return ;
}
}
else
{
rCmd = FILL_DATE;
rDateCmd = FILL_DAY;
rInc = 1.0;
rSkipOverlappedCells = true ;
return ;
}
}
else if (nCurrCellFormatType == SvNumFormatType::LOGICAL
&& ((fVal = aCurrCell.getDouble()) == 0.0 || fVal == 1.0))
{
}
else if (nValueCount >= 2)
{
tools::Duration aDuration;
for (SCSIZE i = 1; i < nValueCount && bVal; i++)
{
aPrevCell = aCurrCell;
nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
aCurrCell = GetCellValue(nColCurr, nRowCurr);
if (aCurrCell.getType() == CELLTYPE_VALUE)
{
const bool bTime = (nCurrCellFormatType == SvNumFormatType::TIME ||
nCurrCellFormatType == SvNumFormatType::DATETIME);
double nDiff = approxTypedDiff(aCurrCell.getDouble(), aPrevCell.getDouble(),
bTime, aDuration);
if (i == 1)
{
rInc = nDiff;
if (bTime)
rDuration = aDuration;
}
if (!::rtl::math::approxEqual(nDiff, rInc, 13))
bVal = false ;
else if ((aCurrCell.getDouble() == 0.0 || aCurrCell.getDouble() == 1.0)
&& (rDocument.GetFormatTable()->GetType(
GetNumberFormat(nColCurr, nRowCurr))
== SvNumFormatType::LOGICAL))
bVal = false ;
}
else
bVal = false ;
}
if (bVal)
{
rCmd = FILL_LINEAR;
rSkipOverlappedCells = true ;
return ;
}
}
}
else if (eCellType == CELLTYPE_STRING || eCellType == CELLTYPE_EDIT)
{
OUString aStr = GetString(nColCurr, nRowCurr );
OUString aStr2;
rListData = const_cast <ScUserListData*>(ScGlobal::GetUserList().GetData(aStr));
if (rListData)
{
bool bMatchCase = false ;
(void )rListData->GetSubIndex(aStr, rListIndex, bMatchCase);
size_t nListStrCount = rListData->GetSubCount();
sal_uInt16 nPrevListIndex, nInc = 1;
for (SCSIZE i = 1; i < nValueCount && rListData; i++)
{
nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
aStr2 = GetString(nColCurr, nRowCurr);
nPrevListIndex = rListIndex;
if (!rListData->GetSubIndex(aStr2, rListIndex, bMatchCase))
rListData = nullptr;
else
{
sal_Int32 nIncCurr = rListIndex - nPrevListIndex;
if (nIncCurr < 0)
nIncCurr += nListStrCount;
if (i == 1)
nInc = nIncCurr;
else if (nInc != nIncCurr)
rListData = nullptr;
}
}
if (rListData) {
rInc = nInc;
rSkipOverlappedCells = true ;
return ;
}
}
short nFlag1, nFlag2;
sal_Int32 nVal1, nVal2;
nFlag1 = lcl_DecompValueString(aStr, nVal1, &rMinDigits);
if (nFlag1)
{
bool bVal = true ;
rInc = 1;
for (SCSIZE i = 1; i < nValueCount && bVal; i++)
{
nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
ScRefCellValue aCell = GetCellValue(nColCurr, nRowCurr);
CellType eType = aCell.getType();
if (eType == CELLTYPE_STRING || eType == CELLTYPE_EDIT)
{
aStr2 = aCell.getString(rDocument);
nFlag2 = lcl_DecompValueString(aStr2, nVal2, &rMinDigits);
if (nFlag1 == nFlag2 && aStr == aStr2)
{
double nDiff = approxDiff(nVal2, nVal1);
if (i == 1)
rInc = nDiff;
else if (!::rtl::math::approxEqual(nDiff, rInc, 13))
bVal = false ;
nVal1 = nVal2;
}
else
bVal = false ;
}
else
bVal = false ;
}
if (bVal)
{
rCmd = FILL_LINEAR;
rSkipOverlappedCells = true ;
return ;
}
}
}
}
}
//if it is not a FILL_LINEAR - CELLTYPE_VALUE - with merged cells [without hidden values]
//then do it in the old way
SCCOL nCol = nCol1;
SCROW nRow = nRow1;
ScRefCellValue aFirstCell = GetCellValue(nCol, nRow);
CellType eCellType = aFirstCell.getType();
if (eCellType == CELLTYPE_VALUE)
{
double fVal;
sal_uInt32 nFormat = GetAttr(nCol,nRow,ATTR_VALUE_FORMAT)->GetValue();
const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(nFormat);
bool bDate = (nFormatType == SvNumFormatType::DATE); // date without time
bool bTime = (nFormatType == SvNumFormatType::TIME || nFormatType == SvNumFormatType::DATETIME);
bool bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
if (bDate)
{
if (nCount > 1)
{
double nVal;
Date aNullDate = rDocument.GetFormatTable()->GetNullDate();
Date aDate1 = aNullDate;
nVal = aFirstCell.getDouble();
aDate1.AddDays(nVal);
Date aDate2 = aNullDate;
nVal = GetValue(nCol+nAddX, nRow+nAddY);
aDate2.AddDays(nVal);
if ( aDate1 != aDate2 )
{
tools::Long nCmpInc = 0;
FillDateCmd eType;
tools::Long nDDiff = aDate2.GetDay() - static_cast <tools::Long >(aDate1.GetDay());
tools::Long nMDiff = aDate2.GetMonth() - static_cast <tools::Long >(aDate1.GetMonth());
tools::Long nYDiff = aDate2.GetYear() - static_cast <tools::Long >(aDate1.GetYear());
if (nMDiff && aDate1.IsEndOfMonth() && aDate2.IsEndOfMonth())
{
eType = FILL_END_OF_MONTH;
nCmpInc = nMDiff + 12 * nYDiff;
}
else if (nDDiff)
{
eType = FILL_DAY;
nCmpInc = aDate2 - aDate1;
}
else
{
eType = FILL_MONTH;
nCmpInc = nMDiff + 12 * nYDiff;
}
nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
bool bVal = true ;
for (SCSIZE i=1; i<nCount && bVal; i++)
{
ScRefCellValue aCell = GetCellValue(nCol,nRow);
if (aCell.getType() == CELLTYPE_VALUE)
{
nVal = aCell.getDouble();
aDate2 = aNullDate + static_cast <sal_Int32>(nVal);
if ( eType == FILL_DAY )
{
if ( aDate2-aDate1 != nCmpInc )
bVal = false ;
}
else
{
nDDiff = aDate2.GetDay() - static_cast <tools::Long >(aDate1.GetDay());
nMDiff = aDate2.GetMonth() - static_cast <tools::Long >(aDate1.GetMonth());
nYDiff = aDate2.GetYear() - static_cast <tools::Long >(aDate1.GetYear());
if ((nDDiff && !aDate1.IsEndOfMonth() && !aDate2.IsEndOfMonth())
|| (nMDiff + 12 * nYDiff != nCmpInc))
bVal = false ;
}
aDate1 = aDate2;
nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
}
else
bVal = false ; // No date is also not ok
}
if (bVal)
{
if ((eType == FILL_MONTH || eType == FILL_END_OF_MONTH)
&& (nCmpInc % 12 == 0))
{
eType = FILL_YEAR;
nCmpInc /= 12;
}
rCmd = FILL_DATE;
rDateCmd = eType;
rInc = nCmpInc;
}
}
else
{
// tdf#89754 - don't increment non different consecutive date cells
rCmd = FILL_DATE;
rDateCmd = FILL_DAY;
rInc = 0.0;
}
}
else // single date -> increment by days
{
rCmd = FILL_DATE;
rDateCmd = FILL_DAY;
rInc = 1.0;
}
}
else if (bBooleanCell && ((fVal = aFirstCell.getDouble()) == 0.0 || fVal == 1.0))
{
// Nothing, rInc stays 0.0, no specific fill mode.
}
else
{
if (nCount > 1)
{
tools::Duration aDuration;
double nVal1 = aFirstCell.getDouble();
double nVal2 = GetValue(nCol+nAddX, nRow+nAddY);
rInc = approxTypedDiff( nVal2, nVal1, bTime, aDuration);
nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
bool bVal = true ;
for (SCSIZE i=1; i<nCount && bVal; i++)
{
ScRefCellValue aCell = GetCellValue(nCol,nRow);
if (aCell.getType() == CELLTYPE_VALUE)
{
nVal2 = aCell.getDouble();
double nDiff = approxTypedDiff( nVal2, nVal1, bTime, aDuration);
if ( !::rtl::math::approxEqual( nDiff, rInc, 13 ) )
bVal = false ;
else if ((nVal2 == 0.0 || nVal2 == 1.0) &&
(rDocument.GetFormatTable()->GetType(GetNumberFormat(nCol,nRow)) ==
SvNumFormatType::LOGICAL))
bVal = false ;
nVal1 = nVal2;
}
else
bVal = false ;
nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
if (bVal && bTime)
rDuration = aDuration;
}
if (bVal)
rCmd = FILL_LINEAR;
}
else if (nFormatType == SvNumFormatType::PERCENT)
{
rInc = 0.01; // tdf#89998 increment by 1% at a time
}
}
}
else if (eCellType == CELLTYPE_STRING || eCellType == CELLTYPE_EDIT)
{
OUString aStr = GetString(nCol, nRow);
rListData = const_cast <ScUserListData*>(ScGlobal::GetUserList().GetData(aStr));
if (rListData)
{
bool bMatchCase = false ;
(void )rListData->GetSubIndex(aStr, rListIndex, bMatchCase);
size_t nListStrCount = rListData->GetSubCount();
sal_uInt16 nPrevListIndex, nInc = 1;
nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
for (SCSIZE i=1; i<nCount && rListData; i++)
{
nPrevListIndex = rListIndex;
aStr = GetString(nCol, nRow);
if (!rListData->GetSubIndex(aStr, rListIndex, bMatchCase))
rListData = nullptr;
else
{
sal_Int32 nIncCurr = rListIndex - nPrevListIndex;
if (nIncCurr < 0)
nIncCurr += nListStrCount;
if (i == 1)
nInc = nIncCurr;
else if (nInc != nIncCurr)
rListData = nullptr;
}
nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
}
if (rListData)
rInc = nInc;
}
else if ( nCount > 1 )
{
// pass rMinDigits to all DecompValueString calls
// -> longest number defines rMinDigits
sal_Int32 nVal1;
short nFlag1 = lcl_DecompValueString( aStr, nVal1, &rMinDigits );
if ( nFlag1 )
{
sal_Int32 nVal2;
aStr = GetString( nCol+nAddX, nRow+nAddY );
short nFlag2 = lcl_DecompValueString( aStr, nVal2, &rMinDigits );
if ( nFlag1 == nFlag2 )
{
rInc = approxDiff( nVal2, nVal1);
nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
bool bVal = true ;
for (SCSIZE i=1; i<nCount && bVal; i++)
{
ScRefCellValue aCell = GetCellValue(nCol, nRow);
CellType eType = aCell.getType();
if ( eType == CELLTYPE_STRING || eType == CELLTYPE_EDIT )
{
aStr = aCell.getString(rDocument);
nFlag2 = lcl_DecompValueString( aStr, nVal2, &rMinDigits );
if ( nFlag1 == nFlag2 )
{
double nDiff = approxDiff( nVal2, nVal1);
if ( !::rtl::math::approxEqual( nDiff, rInc, 13 ) )
bVal = false ;
nVal1 = nVal2;
}
else
bVal = false ;
}
else
bVal = false ;
nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
}
if (bVal)
rCmd = FILL_LINEAR;
}
}
}
else
{
// call DecompValueString to set rMinDigits
sal_Int32 nDummy;
lcl_DecompValueString( aStr, nDummy, &rMinDigits );
}
}
}
void ScTable::FillFormula(
const ScFormulaCell* pSrcCell, SCCOL nDestCol, SCROW nDestRow, bool bLast )
{
rDocument.SetNoListening( true ); // still the wrong reference
ScAddress aAddr( nDestCol, nDestRow, nTab );
ScFormulaCell* pDestCell = new ScFormulaCell( *pSrcCell, rDocument, aAddr );
aCol[nDestCol].SetFormulaCell(nDestRow, pDestCell);
if ( bLast && pDestCell->GetMatrixFlag() != ScMatrixMode::NONE )
{
ScAddress aOrg;
if ( pDestCell->GetMatrixOrigin( GetDoc(), aOrg ) )
{
if ( nDestCol >= aOrg.Col() && nDestRow >= aOrg.Row() )
{
ScFormulaCell* pOrgCell = rDocument.GetFormulaCell(aOrg);
if (pOrgCell && pOrgCell->GetMatrixFlag() == ScMatrixMode::Formula)
{
pOrgCell->SetMatColsRows(
nDestCol - aOrg.Col() + 1,
nDestRow - aOrg.Row() + 1 );
}
else
{
OSL_FAIL( "FillFormula: MatrixOrigin no formula cell with ScMatrixMode::Formula" );
}
}
else
{
OSL_FAIL( "FillFormula: MatrixOrigin bottom right" );
}
}
else
{
OSL_FAIL( "FillFormula: no MatrixOrigin" );
}
}
rDocument.SetNoListening( false );
pDestCell->StartListeningTo( rDocument );
}
void ScTable::FillAuto( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
sal_uInt64 nFillCount, FillDir eFillDir, ScProgress* pProgress )
{
if ( (nFillCount == 0) || !ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2) )
return ;
// Detect direction
bool bVertical = (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_TOP);
bool bPositive = (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_RIGHT);
SCCOLROW nCol = 0;
SCCOLROW nRow = 0;
SCCOLROW& rInner = bVertical ? nRow : nCol; // loop variables
SCCOLROW& rOuter = bVertical ? nCol : nRow;
SCCOLROW nOStart;
SCCOLROW nOEnd;
SCCOLROW nIStart;
SCCOLROW nIEnd;
SCCOLROW nISrcStart;
SCCOLROW nISrcEnd;
ScRange aFillRange;
if (bVertical)
{
nOStart = nCol1;
nOEnd = nCol2;
if (bPositive)
{
nISrcStart = nRow1;
nISrcEnd = nRow2;
nIStart = nRow2 + 1;
nIEnd = nRow2 + nFillCount;
aFillRange = ScRange(nCol1, nRow2+1, 0, nCol2, nRow2 + nFillCount, 0);
}
else
{
nISrcStart = nRow2;
nISrcEnd = nRow1;
nIStart = nRow1 - 1;
nIEnd = nRow1 - nFillCount;
aFillRange = ScRange(nCol1, nRow1-1, 0, nCol2, nRow2 - nFillCount, 0);
}
}
else
{
nOStart = nRow1;
nOEnd = nRow2;
if (bPositive)
{
nISrcStart = nCol1;
nISrcEnd = nCol2;
nIStart = nCol2 + 1;
nIEnd = nCol2 + nFillCount;
aFillRange = ScRange(nCol2 + 1, nRow1, 0, nCol2 + nFillCount, nRow2, 0);
}
else
{
nISrcStart = nCol2;
nISrcEnd = nCol1;
nIStart = nCol1 - 1;
nIEnd = nCol1 - nFillCount;
aFillRange = ScRange(nCol1 - 1, nRow1, 0, nCol1 - nFillCount, nRow2, 0);
}
}
sal_uInt64 nIMin = nIStart;
sal_uInt64 nIMax = nIEnd;
PutInOrder(nIMin,nIMax);
bool bHasFiltered = IsDataFiltered(aFillRange);
if (!bHasFiltered)
{
if (bVertical)
DeleteArea(nCol1, static_cast <SCROW>(nIMin), nCol2, static_cast <SCROW>(nIMax), InsertDeleteFlags::AUTOFILL);
else
DeleteArea(static_cast <SCCOL>(nIMin), nRow1, static_cast <SCCOL>(nIMax), nRow2, InsertDeleteFlags::AUTOFILL);
}
sal_uInt64 nProgress = 0;
if (pProgress)
nProgress = pProgress->GetState();
// Avoid possible repeated calls to StartListeningFormulaCells() (tdf#132165).
std::list< sc::DelayStartListeningFormulaCells > delayStartListening;
SCCOL delayStartColumn, delayEndColumn;
if (bVertical)
{
delayStartColumn = std::min( nOStart, nOEnd );
delayEndColumn = std::max( nOStart, nOEnd );
}
else
{
delayStartColumn = std::min( nIStart, nIEnd );
delayEndColumn = std::max( nIStart, nIEnd );
}
for ( SCROW col = delayStartColumn; col <= delayEndColumn; ++col )
{
if ( ScColumn* column = FetchColumn( col ))
delayStartListening.emplace_back( *column, true );
}
// execute
sal_uInt64 nActFormCnt = 0;
for (rOuter = nOStart; rOuter <= nOEnd; rOuter++)
{
sal_uInt64 nMaxFormCnt = 0; // for formulas
// transfer attributes
const ScPatternAttr* pSrcPattern = nullptr;
const ScStyleSheet* pStyleSheet = nullptr;
SCCOLROW nAtSrc = nISrcStart;
std::unique_ptr<ScPatternAttr> pNewPattern;
bool bGetPattern = true ;
rInner = nIStart;
while (true ) // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
{
if (!ColHidden(nCol) && !RowHidden(nRow))
{
if ( bGetPattern )
{
if (bVertical) // rInner&:=nRow, rOuter&:=nCol
pSrcPattern = GetColumnData(nCol).GetPattern(static_cast <SCROW>(nAtSrc));
else // rInner&:=nCol, rOuter&:=nRow
pSrcPattern = GetColumnData(nAtSrc).GetPattern(static_cast <SCROW>(nRow));
bGetPattern = false ;
pStyleSheet = pSrcPattern->GetStyleSheet();
// do transfer ATTR_MERGE / ATTR_MERGE_FLAG
//
// Note: ATTR_MERGE is an attribute of the top left cell of a merged area
// containing the size of the area. ATTR_MERGE_FLAGs are attributes of the
// other cells of a merged area, containing the information about also
// overlapping, i.e. visibility of their content.
//
// TODO: extend the similar incomplete selections to a bounding rectangle to
// avoid incomplete fill, where not all AUTO_MERGE_FLAGs are synchronized with
// the copied ATTR_MERGE, resulting broken grid and visibility during run-time.
//
// +--+ +--+--+
// | | | | |
// +--+--+ +--+--+
// | | -> | |
// +--+--+ +--+--+
// | | | | |
// +--+ +--+--+
//
// TODO: protect incompatible merged cells of the destination area, for example
// by skipping the fill operation.
//
// TODO: by dragging the fill handle select only the multiples of the height
// of the originally selected area which is merged vertically to avoid of
// incomplete fill.
//
// +--+ +--+
// |XX| |XX|
// +XX+ +XX+
// |XX| -> |XX|
// +--+ +--+
// | | | |
// +--+ +--+
// | |
// +--+
//
// Other things stored in ATTR_MERGE_FLAG, like autofilter button, will be
// deleted now, but may need to be repaired later, like at ScDocument::Fill.
const SfxItemSet& rSet = pSrcPattern->GetItemSet();
if ( rSet.GetItemState(ATTR_MERGE_FLAG, false ) == SfxItemState::SET )
{
ScMF nOldValue = pSrcPattern->GetItem(ATTR_MERGE_FLAG).GetValue();
ScMF nOldValueMerge = nOldValue & (ScMF::Hor | ScMF::Ver);
// keep only the merge flags
if ( nOldValue != nOldValueMerge )
{
pNewPattern.reset(new ScPatternAttr(*pSrcPattern));
SfxItemSet& rNewSet = pNewPattern->GetItemSet();
if ( nOldValueMerge == ScMF::NONE )
rNewSet.ClearItem(ATTR_MERGE_FLAG);
else
rNewSet.Put(ScMergeFlagAttr(nOldValueMerge));
}
else
pNewPattern.reset();
}
else
pNewPattern.reset();
}
const ScCondFormatItem& rCondFormatItem = pSrcPattern->GetItem(ATTR_CONDITIONAL);
const ScCondFormatIndexes& rCondFormatIndex = rCondFormatItem.GetCondFormatData();
if ( bVertical && nISrcStart == nISrcEnd && !bHasFiltered )
{
// set all attributes at once (en bloc)
if (pNewPattern || !pSrcPattern->isDefault())
{
// Default is already present (DeleteArea)
SCROW nY1 = static_cast <SCROW>(std::min( nIStart, nIEnd ));
SCROW nY2 = static_cast <SCROW>(std::max( nIStart, nIEnd ));
if ( pStyleSheet )
aCol[nCol].ApplyStyleArea( nY1, nY2, *pStyleSheet );
if ( pNewPattern )
aCol[nCol].ApplyPatternArea( nY1, nY2, *pNewPattern );
else
aCol[nCol].ApplyPatternArea( nY1, nY2, *pSrcPattern );
for (const auto & rIndex : rCondFormatIndex)
{
ScConditionalFormat* pCondFormat = mpCondFormatList->GetFormat(rIndex);
if (pCondFormat)
{
ScRangeList aRange = pCondFormat->GetRange();
aRange.Join(ScRange(nCol, nY1, nTab, nCol, nY2, nTab));
pCondFormat->SetRange(aRange);
}
}
}
break ;
}
if ( bHasFiltered )
DeleteArea(static_cast <SCCOL>(nCol), static_cast <SCROW>(nRow),
static_cast <SCCOL>(nCol), static_cast <SCROW>(nRow), InsertDeleteFlags::AUTOFILL);
if ( !ScPatternAttr::areSame(pSrcPattern, aCol[nCol].GetPattern( static_cast <SCROW>(nRow) ) ) )
{
// Transfer template too
//TODO: Merge ApplyPattern to AttrArray ??
if ( pStyleSheet )
aCol[nCol].ApplyStyle( static_cast <SCROW>(nRow), pStyleSheet );
// Use ApplyPattern instead of SetPattern to keep old MergeFlags
if ( pNewPattern )
aCol[nCol].ApplyPattern( static_cast <SCROW>(nRow), *pNewPattern );
else
aCol[nCol].ApplyPattern( static_cast <SCROW>(nRow), *pSrcPattern );
for (const auto & rIndex : rCondFormatIndex)
{
ScConditionalFormat* pCondFormat = mpCondFormatList->GetFormat(rIndex);
if (pCondFormat)
{
ScRangeList aRange = pCondFormat->GetRange();
aRange.Join(ScRange(nCol, nRow, nTab, nCol, nRow, nTab));
pCondFormat->SetRange(aRange);
}
}
}
if (nAtSrc==nISrcEnd)
{
if ( nAtSrc != nISrcStart )
{ // More than one source cell
nAtSrc = nISrcStart;
bGetPattern = true ;
}
}
else if (bPositive)
{
++nAtSrc;
bGetPattern = true ;
}
else
{
--nAtSrc;
bGetPattern = true ;
}
}
if (rInner == nIEnd) break ;
if (bPositive) ++rInner; else --rInner;
}
pNewPattern.reset();
// Analyse
FillCmd eFillCmd;
FillDateCmd eDateCmd = {};
double nInc;
tools::Duration aDurationInc;
sal_uInt16 nMinDigits;
ScUserListData* pListData = nullptr;
sal_uInt16 nListIndex;
bool bSkipOverlappedCells;
std::vector<sal_Int32> aNonOverlappedCellIdx;
if (bVertical)
FillAnalyse(static_cast <SCCOL>(nCol),nRow1,
static_cast <SCCOL>(nCol),nRow2, eFillCmd,eDateCmd,
nInc, aDurationInc, nMinDigits, pListData, nListIndex,
bHasFiltered, bSkipOverlappedCells, aNonOverlappedCellIdx);
else
FillAnalyse(nCol1,static_cast <SCROW>(nRow),
nCol2,static_cast <SCROW>(nRow), eFillCmd,eDateCmd,
nInc, aDurationInc, nMinDigits, pListData, nListIndex,
bHasFiltered, bSkipOverlappedCells, aNonOverlappedCellIdx);
if (pListData)
{
sal_uInt16 nListCount = pListData->GetSubCount();
if (bSkipOverlappedCells)
{
int nFillerCount = 1 + ( nISrcEnd - nISrcStart ) * (bPositive ? 1 : -1);
std::vector<bool > aIsNonEmptyCell(nFillerCount, false );
SCCOLROW nLastValueIdx;
if (bPositive)
{
nLastValueIdx = nISrcEnd - (nFillerCount - 1 - aNonOverlappedCellIdx.back());
for (auto i : aNonOverlappedCellIdx)
aIsNonEmptyCell[i] = true ;
}
else
{
nLastValueIdx = nISrcEnd + aNonOverlappedCellIdx[0];
for (auto i : aNonOverlappedCellIdx)
aIsNonEmptyCell[nFillerCount - 1 - i] = true ;
}
OUString aStr;
if (bVertical)
aStr = GetString(rOuter, nLastValueIdx);
else
aStr = GetString(nLastValueIdx, rOuter);
bool bMatchCase = false ;
(void )pListData->GetSubIndex(aStr, nListIndex, bMatchCase);
sal_Int32 nFillerIdx = 0;
rInner = nIStart;
while (true )
{
if (aIsNonEmptyCell[nFillerIdx])
{
if (bPositive)
{
nListIndex += nInc;
if (nListIndex >= nListCount) nListIndex -= nListCount;
}
else
{
if (nListIndex < nInc) nListIndex += nListCount;
nListIndex -= nInc;
}
aCol[nCol].SetRawString(static_cast <SCROW>(nRow), pListData->GetSubStr(nListIndex));
}
if (rInner == nIEnd) break ;
nFillerIdx = (nFillerIdx + 1) % nFillerCount;
if (bPositive)
++rInner;
else
--rInner;
}
}
else
{
if (!bPositive)
{
// nListIndex of FillAnalyse points to the last entry -> adjust
sal_Int64 nAdjust = nListIndex - (nISrcStart - nISrcEnd) * nInc;
nAdjust = nAdjust % nListCount;
if (nAdjust < 0)
nAdjust += nListCount;
nListIndex = nAdjust;
}
rInner = nIStart;
while (true ) // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
{
if (!ColHidden(nCol) && !RowHidden(nRow))
{
if (bPositive)
{
nListIndex += nInc;
if (nListIndex >= nListCount) nListIndex -= nListCount;
}
else
{
if (nListIndex < nInc) nListIndex += nListCount;
nListIndex -= nInc;
}
aCol[nCol].SetRawString(static_cast <SCROW>(nRow), pListData->GetSubStr(nListIndex));
}
if (rInner == nIEnd) break ;
if (bPositive) ++rInner; else --rInner;
}
}
if (pProgress)
{
nProgress += nIMax - nIMin + 1;
pProgress->SetStateOnPercent( nProgress );
}
}
else if (eFillCmd == FILL_SIMPLE) // fill with pattern/sample
{
FillAutoSimple(
nISrcStart, nISrcEnd, nIStart, nIEnd, rInner, nCol, nRow,
nActFormCnt, nMaxFormCnt, bHasFiltered, bVertical, bPositive, pProgress, nProgress);
}
else
{
if (!bPositive)
{
nInc = -nInc;
aDurationInc = -aDurationInc;
}
double nEndVal = (nInc>=0.0) ? MAXDOUBLE : -MAXDOUBLE;
if (bVertical)
FillSeries( static_cast <SCCOL>(nCol), nRow1,
static_cast <SCCOL>(nCol), nRow2, nFillCount, eFillDir,
eFillCmd, eDateCmd, nInc, aDurationInc, nEndVal, nMinDigits, false ,
pProgress, bSkipOverlappedCells, &aNonOverlappedCellIdx);
else
FillSeries( nCol1, static_cast <SCROW>(nRow), nCol2,
static_cast <SCROW>(nRow), nFillCount, eFillDir,
eFillCmd, eDateCmd, nInc, aDurationInc, nEndVal, nMinDigits, false ,
pProgress, bSkipOverlappedCells, &aNonOverlappedCellIdx);
if (pProgress)
nProgress = pProgress->GetState();
}
if (bVertical)
FillSparkline(bVertical, nCol, nRow1, nRow2, nIStart, nIEnd);
else
FillSparkline(bVertical, nRow, nCol1, nCol2, nIStart, nIEnd);
nActFormCnt += nMaxFormCnt;
}
}
void ScTable::FillSparkline(bool bVertical, SCCOLROW nFixed,
SCCOLROW nStart, SCCOLROW nEnd,
SCCOLROW nFillStart, SCCOLROW nFillEnd)
{
bool bHasSparklines = false ;
std::vector<std::shared_ptr<sc::Sparkline>> aSparklineSeries;
for (SCROW nCurrent = nStart; nCurrent <= nEnd; nCurrent++)
{
auto pSparkline = bVertical ? GetSparkline(nFixed, nCurrent) : GetSparkline(nCurrent, nFixed);
bHasSparklines = bHasSparklines || pSparkline;
aSparklineSeries.push_back(pSparkline);
}
if (bHasSparklines)
{
for (SCCOLROW nCurrent = nFillStart; nCurrent <= nFillEnd; nCurrent++)
{
size_t nIndex = size_t(nFillStart - nCurrent) % aSparklineSeries.size();
if (auto & rpSparkline = aSparklineSeries[nIndex])
{
auto pGroup = rpSparkline->getSparklineGroup();
auto * pNewSparkline = bVertical ? CreateSparkline(nFixed, nCurrent, pGroup)
: CreateSparkline(nCurrent, nFixed, pGroup);
if (pNewSparkline)
{
SCCOLROW nPosition = bVertical ? rpSparkline->getRow()
: rpSparkline->getColumn();
SCCOLROW nDelta = nCurrent - nPosition;
ScRangeList aRangeList(rpSparkline->getInputRange());
for (ScRange& rRange : aRangeList)
{
if (bVertical)
{
rRange.aStart.IncRow(nDelta);
rRange.aEnd.IncRow(nDelta);
}
else
{
rRange.aStart.IncCol(nDelta);
rRange.aEnd.IncCol(nDelta);
}
}
pNewSparkline->setInputRange(aRangeList);
}
}
}
}
}
void ScTable::GetBackColorArea(SCCOL& rStartCol, SCROW& /*rStartRow*/,
SCCOL& rEndCol, SCROW& rEndRow ) const
{
bool bExtend;
const SvxBrushItem* pDefBackground = &rDocument.GetPool()->GetUserOrPoolDefaultItem(ATTR_BACKGROUND);
rStartCol = std::min<SCCOL>(rStartCol, aCol.size() - 1);
rEndCol = std::min<SCCOL>(rEndCol, aCol.size() - 1);
do
{
bExtend = false ;
if (rEndRow < rDocument.MaxRow())
{
for (SCCOL nCol = rStartCol; nCol <= rEndCol; ++nCol)
{
const ScPatternAttr* pPattern = GetColumnData(nCol).GetPattern(rEndRow + 1);
const SvxBrushItem* pBackground = &pPattern->GetItem(ATTR_BACKGROUND);
if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty() ||
(pBackground->GetColor() != COL_TRANSPARENT && pBackground != pDefBackground))
{
bExtend = true ;
break ;
}
}
if (bExtend)
++rEndRow;
}
} while (bExtend);
}
OUString ScTable::GetAutoFillPreview( const ScRange& rSource, SCCOL nEndX, SCROW nEndY )
{
OUString aValue;
SCCOL nCol1 = rSource.aStart.Col();
SCROW nRow1 = rSource.aStart.Row();
SCCOL nCol2 = rSource.aEnd.Col();
SCROW nRow2 = rSource.aEnd.Row();
bool bOk = true ;
tools::Long nIndex = 0;
sal_uInt64 nSrcCount = 0;
FillDir eFillDir = FILL_TO_BOTTOM;
if ( nEndX == nCol2 && nEndY == nRow2 ) // empty
bOk = false ;
else if ( nEndX == nCol2 ) // to up / down
{
nCol2 = nCol1; // use only first column
nSrcCount = nRow2 - nRow1 + 1;
nIndex = static_cast <tools::Long >(nEndY) - nRow1; // can be negative
if ( nEndY >= nRow1 )
eFillDir = FILL_TO_BOTTOM;
else
eFillDir = FILL_TO_TOP;
}
else if ( nEndY == nRow2 ) // to left / right
{
nEndY = nRow2 = nRow1; // use only first row
nSrcCount = nCol2 - nCol1 + 1;
nIndex = static_cast <tools::Long >(nEndX) - nCol1; // can be negative
if ( nEndX >= nCol1 )
eFillDir = FILL_TO_RIGHT;
else
eFillDir = FILL_TO_LEFT;
}
else // direction not clear
bOk = false ;
if ( bOk )
{
tools::Long nBegin = 0;
tools::Long nEnd = 0;
tools::Long nHidden = 0;
if (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_TOP)
{
if (nEndY > nRow1)
{
nBegin = nRow2+1;
nEnd = nEndY;
}
else
{
nBegin = nEndY;
nEnd = nRow1 -1;
}
tools::Long nVisible = CountVisibleRows(nBegin, nEnd);
nHidden = nEnd + 1 - nBegin - nVisible;
}
else
{
if (nEndX > nCol1)
{
nBegin = nCol2+1;
nEnd = nEndX;
}
else
{
nBegin = nEndX;
nEnd = nCol1 -1;
}
tools::Long nVisible = CountVisibleCols(nBegin, nEnd);
nHidden = nEnd + 1 - nBegin - nVisible;
}
if (nHidden)
{
if (nIndex > 0)
nIndex = nIndex - nHidden;
else
nIndex = nIndex + nHidden;
}
FillCmd eFillCmd;
FillDateCmd eDateCmd;
double nInc;
tools::Duration aDurationInc;
sal_uInt16 nMinDigits;
ScUserListData* pListData = nullptr;
sal_uInt16 nListIndex;
bool bSkipOverlappedCells;
std::vector<sal_Int32> aNonOverlappedCellIdx;
// Todo: update this function to calculate with merged cell fills,
// after FillAnalyse / FillSeries fully handle them.
// Now FillAnalyse called as if there are filtered rows, so it will work in the old way.
FillAnalyse(nCol1, nRow1, nCol2, nRow2, eFillCmd, eDateCmd,
nInc, aDurationInc, nMinDigits, pListData, nListIndex,
true , bSkipOverlappedCells, aNonOverlappedCellIdx);
if ( pListData ) // user defined list
{
sal_uInt16 nListCount = pListData->GetSubCount();
if ( nListCount )
{
sal_uInt64 nSub = nSrcCount - 1; // nListIndex is from last source entry
while ( nIndex < sal::static_int_cast<tools::Long >(nSub) )
nIndex += nListCount;
sal_uInt64 nPos = ( nListIndex + nIndex - nSub ) % nListCount;
aValue = pListData->GetSubStr(sal::static_int_cast<sal_uInt16>(nPos));
}
}
else if ( eFillCmd == FILL_SIMPLE ) // fill with pattern/sample
{
tools::Long nPosIndex = nIndex;
while ( nPosIndex < 0 )
nPosIndex += nSrcCount;
sal_uInt64 nPos = nPosIndex % nSrcCount;
SCCOL nSrcX = nCol1;
SCROW nSrcY = nRow1;
if ( eFillDir == FILL_TO_TOP || eFillDir == FILL_TO_BOTTOM )
nSrcY = sal::static_int_cast<SCROW>( nSrcY + static_cast <SCROW>(nPos) );
else
nSrcX = sal::static_int_cast<SCCOL>( nSrcX + static_cast <SCCOL>(nPos) );
ScRefCellValue aCell = GetCellValue(nSrcX, nSrcY);
if (!aCell.isEmpty())
{
sal_Int32 nDelta;
if (nIndex >= 0)
nDelta = nIndex / nSrcCount;
else
nDelta = ( nIndex - nSrcCount + 1 ) / nSrcCount; // -1 -> -1
CellType eType = aCell.getType();
switch ( eType )
{
case CELLTYPE_STRING:
case CELLTYPE_EDIT:
{
aValue = aCell.getString(rDocument);
if ( !(nScFillModeMouseModifier & KEY_MOD1) )
{
sal_Int32 nVal;
sal_uInt16 nCellDigits = 0; // look at each source cell individually
short nFlag = lcl_DecompValueString( aValue, nVal, &nCellDigits );
if ( nFlag < 0 )
{
if (aValue == ScGlobal::GetOrdinalSuffix( nVal))
aValue = ScGlobal::GetOrdinalSuffix( nVal + nDelta);
aValue = lcl_ValueString( nVal + nDelta, nCellDigits ) + aValue;
}
else if ( nFlag > 0 )
{
sal_Int32 nNextValue;
if ( nVal < 0 )
nNextValue = nVal - nDelta;
else
nNextValue = nVal + nDelta;
if ( nFlag == 2 && nNextValue >= 0 ) // Put back the '+'
aValue += "+" ;
aValue += lcl_ValueString( nNextValue, nCellDigits );
}
}
}
break ;
case CELLTYPE_VALUE:
{
sal_uInt32 nNumFmt = GetNumberFormat( nSrcX, nSrcY );
// overflow is possible...
double nVal = aCell.getDouble();
if ( !(nScFillModeMouseModifier & KEY_MOD1) )
{
const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(nNumFmt);
bool bPercentCell = (nFormatType == SvNumFormatType::PERCENT);
if (bPercentCell)
{
// tdf#89998 increment by 1% at a time
nVal += static_cast <double >(nDelta) * 0.01;
}
else if (nVal == 0.0 || nVal == 1.0)
{
bool bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
if (!bBooleanCell)
nVal += static_cast <double >(nDelta);
}
else
{
nVal += static_cast <double >(nDelta);
}
}
const Color* pColor;
rDocument.GetFormatTable()->GetOutputString( nVal, nNumFmt, aValue, &pColor );
}
break ;
// not for formulas
default :
{
// added to avoid warnings
}
}
}
}
else if ( eFillCmd == FILL_LINEAR || eFillCmd == FILL_DATE ) // values
{
bool bValueOk;
double nStart;
sal_Int32 nVal = 0;
short nHeadNoneTail = 0;
ScRefCellValue aCell = GetCellValue(nCol1, nRow1);
if (!aCell.isEmpty())
{
CellType eType = aCell.getType();
switch ( eType )
{
case CELLTYPE_STRING:
case CELLTYPE_EDIT:
{
aValue = aCell.getString(rDocument);
nHeadNoneTail = lcl_DecompValueString( aValue, nVal );
if ( nHeadNoneTail )
nStart = static_cast <double >(nVal);
else
nStart = 0.0;
}
break ;
case CELLTYPE_VALUE:
nStart = aCell.getDouble();
break ;
case CELLTYPE_FORMULA:
nStart = aCell.getFormula()->GetValue();
break ;
default :
nStart = 0.0;
}
}
else
nStart = 0.0;
if ( eFillCmd == FILL_LINEAR )
{
if (aDurationInc)
{
bool bOverflow;
tools::Duration aDuration( aDurationInc.Mult( nIndex, bOverflow));
bValueOk = SubTotal::SafePlus( nStart, aDuration.GetInDays()) && !bOverflow;
}
else
{
double nAdd = nInc;
bValueOk = ( SubTotal::SafeMult( nAdd, static_cast <double >(nIndex) ) &&
SubTotal::SafePlus( nStart, nAdd ) );
}
}
else // date
{
bValueOk = true ;
sal_uInt16 nDayOfMonth = 0;
if ( nIndex < 0 )
{
nIndex = -nIndex;
nInc = -nInc;
}
for (tools::Long i=0; i<nIndex; i++)
IncDate( nStart, nDayOfMonth, nInc, eDateCmd );
}
if (bValueOk)
{
if ( nHeadNoneTail )
{
if ( nHeadNoneTail < 0 )
{
if (aValue == ScGlobal::GetOrdinalSuffix( nVal))
aValue = ScGlobal::GetOrdinalSuffix( static_cast <sal_Int32>(nStart) );
aValue = lcl_ValueString( static_cast <sal_Int32>(nStart), nMinDigits ) + aValue;
}
else
{
if ( nHeadNoneTail == 2 && nStart >= 0 ) // Put back the '+'
aValue += "+" ;
aValue += lcl_ValueString( static_cast <sal_Int32>(nStart), nMinDigits );
}
}
else
{
//TODO: get number format according to Index?
const Color* pColor;
sal_uInt32 nNumFmt = GetNumberFormat( nCol1, nRow1 );
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=93 H=92 G=92
¤ Dauer der Verarbeitung: 0.24 Sekunden
¤
*© Formatika GbR, Deutschland