3152 lines
121 KiB
C++
3152 lines
121 KiB
C++
/* -*- 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* pMinDigits = 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 );
|
|
rDocument.GetFormatTable()->GetOutputString( nStart, nNumFmt, aValue, &pColor );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("GetAutoFillPreview: invalid mode");
|
|
}
|
|
}
|
|
|
|
return aValue;
|
|
}
|
|
|
|
void ScTable::IncDate(double& rVal, sal_uInt16& nDayOfMonth, double nStep, FillDateCmd eCmd)
|
|
{
|
|
if (eCmd == FILL_DAY)
|
|
{
|
|
rVal += nStep;
|
|
return;
|
|
}
|
|
|
|
// class Date limits
|
|
const sal_uInt16 nMinYear = 1583;
|
|
const sal_uInt16 nMaxYear = 9956;
|
|
|
|
tools::Long nInc = static_cast<tools::Long>(nStep); // upper/lower limits ?
|
|
Date aNullDate = rDocument.GetFormatTable()->GetNullDate();
|
|
Date aDate = aNullDate;
|
|
aDate.AddDays(rVal);
|
|
switch (eCmd)
|
|
{
|
|
case FILL_WEEKDAY:
|
|
{
|
|
aDate.AddDays(nInc);
|
|
DayOfWeek eWeekDay = aDate.GetDayOfWeek();
|
|
if (nInc >= 0)
|
|
{
|
|
if (eWeekDay == SATURDAY)
|
|
aDate.AddDays(2);
|
|
else if (eWeekDay == SUNDAY)
|
|
aDate.AddDays(1);
|
|
}
|
|
else
|
|
{
|
|
if (eWeekDay == SATURDAY)
|
|
aDate.AddDays(-1);
|
|
else if (eWeekDay == SUNDAY)
|
|
aDate.AddDays(-2);
|
|
}
|
|
}
|
|
break;
|
|
case FILL_MONTH:
|
|
case FILL_END_OF_MONTH:
|
|
{
|
|
if ( nDayOfMonth == 0 )
|
|
nDayOfMonth = aDate.GetDay(); // init
|
|
tools::Long nMonth = aDate.GetMonth();
|
|
tools::Long nYear = aDate.GetYear();
|
|
|
|
nMonth += nInc;
|
|
|
|
if (nInc >= 0)
|
|
{
|
|
if (nMonth > 12)
|
|
{
|
|
tools::Long nYAdd = (nMonth-1) / 12;
|
|
nMonth -= nYAdd * 12;
|
|
nYear += nYAdd;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (nMonth < 1)
|
|
{
|
|
tools::Long nYAdd = 1 - nMonth / 12; // positive
|
|
nMonth += nYAdd * 12;
|
|
nYear -= nYAdd;
|
|
}
|
|
}
|
|
|
|
if ( nYear < nMinYear )
|
|
aDate = Date( 1,1, nMinYear );
|
|
else if ( nYear > nMaxYear )
|
|
aDate = Date( 31,12, nMaxYear );
|
|
else
|
|
{
|
|
aDate.SetMonth(static_cast<sal_uInt16>(nMonth));
|
|
aDate.SetYear(static_cast<sal_uInt16>(nYear));
|
|
if (eCmd == FILL_END_OF_MONTH)
|
|
{
|
|
aDate.SetDay(Date::GetDaysInMonth(nMonth, nYear));
|
|
}
|
|
else
|
|
{
|
|
aDate.SetDay(std::min(Date::GetDaysInMonth(nMonth, nYear), nDayOfMonth));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case FILL_YEAR:
|
|
{
|
|
tools::Long nYear = aDate.GetYear();
|
|
nYear += nInc;
|
|
if ( nYear < nMinYear )
|
|
aDate = Date( 1,1, nMinYear );
|
|
else if ( nYear > nMaxYear )
|
|
aDate = Date( 31,12, nMaxYear );
|
|
else
|
|
aDate.SetYear(static_cast<sal_uInt16>(nYear));
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
// added to avoid warnings
|
|
}
|
|
}
|
|
|
|
rVal = aDate - aNullDate;
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool HiddenRowColumn(const ScTable* pTable, SCCOLROW nRowColumn, bool bVertical, SCCOLROW& rLastPos)
|
|
{
|
|
bool bHidden = false;
|
|
if(bVertical)
|
|
{
|
|
SCROW nLast;
|
|
bHidden = pTable->RowHidden(nRowColumn, nullptr, &nLast);
|
|
rLastPos = nLast;
|
|
}
|
|
else
|
|
{
|
|
SCCOL nLast;
|
|
bHidden = pTable->ColHidden(static_cast<SCCOL>(nRowColumn), nullptr, &nLast);
|
|
rLastPos = nLast;
|
|
}
|
|
return bHidden;
|
|
}
|
|
|
|
}
|
|
|
|
void ScTable::FillFormulaVertical(
|
|
const ScFormulaCell& rSrcCell,
|
|
SCCOLROW& rInner, SCCOL nCol, SCROW nRow1, SCROW nRow2,
|
|
ScProgress* pProgress, sal_uInt64& rProgress )
|
|
{
|
|
// rInner is the row position when filling vertically. Also, when filling
|
|
// across hidden regions, it may create multiple dis-jointed spans of
|
|
// formula cells.
|
|
|
|
bool bHidden = false;
|
|
SCCOLROW nHiddenLast = -1;
|
|
|
|
SCCOLROW nRowStart = -1, nRowEnd = -1;
|
|
std::vector<sc::RowSpan> aSpans;
|
|
PutInOrder(nRow1, nRow2);
|
|
for (rInner = nRow1; rInner <= nRow2; ++rInner)
|
|
{
|
|
if (rInner > nHiddenLast)
|
|
bHidden = HiddenRowColumn(this, rInner, true, nHiddenLast);
|
|
|
|
if (bHidden)
|
|
{
|
|
if (nRowStart >= 0)
|
|
{
|
|
nRowEnd = rInner - 1;
|
|
aSpans.emplace_back(nRowStart, nRowEnd);
|
|
nRowStart = -1;
|
|
}
|
|
rInner = nHiddenLast;
|
|
continue;
|
|
}
|
|
|
|
if (nRowStart < 0)
|
|
nRowStart = rInner;
|
|
}
|
|
|
|
if (nRowStart >= 0)
|
|
{
|
|
nRowEnd = rInner - 1;
|
|
aSpans.emplace_back(nRowStart, nRowEnd);
|
|
}
|
|
|
|
if (aSpans.empty())
|
|
return;
|
|
|
|
aCol[nCol].DeleteRanges(aSpans, InsertDeleteFlags::VALUE | InsertDeleteFlags::DATETIME | InsertDeleteFlags::STRING | InsertDeleteFlags::FORMULA | InsertDeleteFlags::OUTLINE);
|
|
aCol[nCol].CloneFormulaCell(rSrcCell, sc::CellTextAttr(), aSpans);
|
|
|
|
const auto pSet = std::make_shared<sc::ColumnBlockPositionSet>(rDocument);
|
|
sc::StartListeningContext aStartCxt(rDocument, pSet);
|
|
sc::EndListeningContext aEndCxt(rDocument, pSet);
|
|
|
|
SCROW nStartRow = aSpans.front().mnRow1;
|
|
SCROW nEndRow = aSpans.back().mnRow2;
|
|
aCol[nCol].EndListeningFormulaCells(aEndCxt, nStartRow, nEndRow, &nStartRow, &nEndRow);
|
|
aCol[nCol].StartListeningFormulaCells(aStartCxt, aEndCxt, nStartRow, nEndRow);
|
|
|
|
for (const auto& rSpan : aSpans)
|
|
aCol[nCol].SetDirty(rSpan.mnRow1, rSpan.mnRow2, ScColumn::BROADCAST_NONE);
|
|
|
|
rProgress += nRow2 - nRow1 + 1;
|
|
if (pProgress)
|
|
pProgress->SetStateOnPercent(rProgress);
|
|
}
|
|
|
|
void ScTable::FillSeriesSimple(
|
|
const ScCellValue& rSrcCell, SCCOLROW& rInner, SCCOLROW nIMin, SCCOLROW nIMax,
|
|
const SCCOLROW& rCol, const SCCOLROW& rRow, bool bVertical, ScProgress* pProgress, sal_uInt64& rProgress )
|
|
{
|
|
bool bHidden = false;
|
|
SCCOLROW nHiddenLast = -1;
|
|
|
|
if (bVertical)
|
|
{
|
|
switch (rSrcCell.getType())
|
|
{
|
|
case CELLTYPE_FORMULA:
|
|
{
|
|
FillFormulaVertical(
|
|
*rSrcCell.getFormula(), rInner, rCol, nIMin, nIMax, pProgress, rProgress);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
for (rInner = nIMin; rInner <= nIMax; ++rInner)
|
|
{
|
|
if (rInner > nHiddenLast)
|
|
bHidden = HiddenRowColumn(this, rInner, bVertical, nHiddenLast);
|
|
|
|
if (bHidden)
|
|
{
|
|
rInner = nHiddenLast;
|
|
continue;
|
|
}
|
|
|
|
ScAddress aDestPos(rCol, rRow, nTab);
|
|
rSrcCell.commit(aCol[rCol], aDestPos.Row());
|
|
}
|
|
rProgress += nIMax - nIMin + 1;
|
|
if (pProgress)
|
|
pProgress->SetStateOnPercent(rProgress);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (rSrcCell.getType())
|
|
{
|
|
case CELLTYPE_FORMULA:
|
|
{
|
|
for (rInner = nIMin; rInner <= nIMax; ++rInner)
|
|
{
|
|
if (rInner > nHiddenLast)
|
|
bHidden = HiddenRowColumn(this, rInner, bVertical, nHiddenLast);
|
|
|
|
if (bHidden)
|
|
continue;
|
|
|
|
FillFormula(rSrcCell.getFormula(), rCol, rRow, (rInner == nIMax));
|
|
if (pProgress)
|
|
pProgress->SetStateOnPercent(++rProgress);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
for (rInner = nIMin; rInner <= nIMax; ++rInner)
|
|
{
|
|
if (rInner > nHiddenLast)
|
|
bHidden = HiddenRowColumn(this, rInner, bVertical, nHiddenLast);
|
|
|
|
if (bHidden)
|
|
continue;
|
|
|
|
ScAddress aDestPos(rCol, rRow, nTab);
|
|
rSrcCell.commit(aCol[rCol], aDestPos.Row());
|
|
}
|
|
rProgress += nIMax - nIMin + 1;
|
|
if (pProgress)
|
|
pProgress->SetStateOnPercent(rProgress);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScTable::FillAutoSimple(
|
|
SCCOLROW nISrcStart, SCCOLROW nISrcEnd, SCCOLROW nIStart, SCCOLROW nIEnd,
|
|
SCCOLROW& rInner, const SCCOLROW& rCol, const SCCOLROW& rRow, sal_uInt64 nActFormCnt,
|
|
sal_uInt64 nMaxFormCnt, bool bHasFiltered, bool bVertical, bool bPositive,
|
|
ScProgress* pProgress, sal_uInt64& rProgress )
|
|
{
|
|
SCCOLROW nSource = nISrcStart;
|
|
double nDelta;
|
|
if ( nScFillModeMouseModifier & KEY_MOD1 )
|
|
nDelta = 0.0;
|
|
else if ( bPositive )
|
|
nDelta = 1.0;
|
|
else
|
|
nDelta = -1.0;
|
|
sal_uInt64 nFormulaCounter = nActFormCnt;
|
|
bool bGetCell = true;
|
|
bool bBooleanCell = false;
|
|
bool bPercentCell = false;
|
|
sal_uInt16 nCellDigits = 0;
|
|
short nHeadNoneTail = 0;
|
|
sal_Int32 nStringValue = 0;
|
|
OUString aValue;
|
|
ScCellValue aSrcCell;
|
|
bool bIsOrdinalSuffix = false;
|
|
|
|
bool bColHidden = false, bRowHidden = false;
|
|
SCCOL nColHiddenFirst = rDocument.MaxCol();
|
|
SCCOL nColHiddenLast = -1;
|
|
SCROW nRowHiddenFirst = rDocument.MaxRow();
|
|
SCROW nRowHiddenLast = -1;
|
|
|
|
rInner = nIStart;
|
|
while (true) // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
|
|
{
|
|
if (bPositive)
|
|
{
|
|
if (rCol > nColHiddenLast)
|
|
bColHidden = ColHidden(rCol, nullptr, &nColHiddenLast);
|
|
if (rRow > nRowHiddenLast)
|
|
bRowHidden = RowHidden(rRow, nullptr, &nRowHiddenLast);
|
|
}
|
|
else
|
|
{
|
|
if (rCol < nColHiddenFirst)
|
|
bColHidden = ColHidden(rCol, &nColHiddenFirst);
|
|
if (rRow < nRowHiddenFirst)
|
|
bRowHidden = RowHidden(rRow, &nRowHiddenFirst);
|
|
}
|
|
|
|
if (!bColHidden && !bRowHidden)
|
|
{
|
|
if ( bGetCell )
|
|
{
|
|
if (bVertical) // rInner&:=nRow, rOuter&:=nCol
|
|
{
|
|
aSrcCell = GetCellValue(rCol, nSource);
|
|
if (nISrcStart == nISrcEnd && aSrcCell.getType() == CELLTYPE_FORMULA)
|
|
{
|
|
FillFormulaVertical(*aSrcCell.getFormula(), rInner, rCol, nIStart, nIEnd, pProgress, rProgress);
|
|
return;
|
|
}
|
|
const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(
|
|
GetColumnData(rCol).GetNumberFormat( rDocument.GetNonThreadedContext(), nSource));
|
|
bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
|
|
bPercentCell = (nFormatType == SvNumFormatType::PERCENT);
|
|
|
|
}
|
|
else // rInner&:=nCol, rOuter&:=nRow
|
|
{
|
|
aSrcCell = GetCellValue(nSource, rRow);
|
|
const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(
|
|
GetColumnData(nSource).GetNumberFormat( rDocument.GetNonThreadedContext(), rRow));
|
|
bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
|
|
bPercentCell = (nFormatType == SvNumFormatType::PERCENT);
|
|
}
|
|
|
|
bGetCell = false;
|
|
if (!aSrcCell.isEmpty())
|
|
{
|
|
switch (aSrcCell.getType())
|
|
{
|
|
case CELLTYPE_STRING:
|
|
case CELLTYPE_EDIT:
|
|
if (aSrcCell.getType() == CELLTYPE_STRING)
|
|
aValue = aSrcCell.getSharedString()->getString();
|
|
else
|
|
aValue = ScEditUtil::GetString(*aSrcCell.getEditText(), &rDocument);
|
|
if ( !(nScFillModeMouseModifier & KEY_MOD1) && !bHasFiltered )
|
|
{
|
|
nCellDigits = 0; // look at each source cell individually
|
|
nHeadNoneTail = lcl_DecompValueString(
|
|
aValue, nStringValue, &nCellDigits );
|
|
|
|
bIsOrdinalSuffix = aValue ==
|
|
ScGlobal::GetOrdinalSuffix(nStringValue);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
// added to avoid warnings
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (aSrcCell.getType())
|
|
{
|
|
case CELLTYPE_VALUE:
|
|
{
|
|
double fVal;
|
|
if (bBooleanCell && ((fVal = aSrcCell.getDouble()) == 0.0 || fVal == 1.0))
|
|
aCol[rCol].SetValue(rRow, aSrcCell.getDouble());
|
|
else if(bPercentCell)
|
|
aCol[rCol].SetValue(rRow, aSrcCell.getDouble() + nDelta * 0.01); // tdf#89998 increment by 1% at a time
|
|
else
|
|
aCol[rCol].SetValue(rRow, aSrcCell.getDouble() + nDelta);
|
|
}
|
|
break;
|
|
case CELLTYPE_STRING:
|
|
case CELLTYPE_EDIT:
|
|
if ( nHeadNoneTail )
|
|
{
|
|
sal_Int32 nNextValue;
|
|
if (nStringValue < 0)
|
|
nNextValue = nStringValue - static_cast<sal_Int32>(nDelta);
|
|
else
|
|
nNextValue = nStringValue + static_cast<sal_Int32>(nDelta);
|
|
|
|
if ( nHeadNoneTail < 0 )
|
|
{
|
|
setSuffixCell(
|
|
aCol[rCol], rRow,
|
|
nNextValue, nCellDigits, aValue,
|
|
aSrcCell.getType(), bIsOrdinalSuffix);
|
|
}
|
|
else
|
|
{
|
|
OUString aStr;
|
|
if (nHeadNoneTail == 2 && nNextValue >= 0) // Put back the '+'
|
|
aStr = aValue + "+" + lcl_ValueString(nNextValue, nCellDigits);
|
|
else
|
|
aStr = aValue + lcl_ValueString(nNextValue, nCellDigits);
|
|
|
|
aCol[rCol].SetRawString(rRow, aStr);
|
|
}
|
|
}
|
|
else
|
|
aSrcCell.commit(aCol[rCol], rRow);
|
|
|
|
break;
|
|
case CELLTYPE_FORMULA :
|
|
FillFormula(
|
|
aSrcCell.getFormula(), rCol, rRow, (rInner == nIEnd));
|
|
if (nFormulaCounter - nActFormCnt > nMaxFormCnt)
|
|
nMaxFormCnt = nFormulaCounter - nActFormCnt;
|
|
break;
|
|
default:
|
|
{
|
|
// added to avoid warnings
|
|
}
|
|
}
|
|
|
|
if (nSource == nISrcEnd)
|
|
{
|
|
if ( nSource != nISrcStart )
|
|
{ // More than one source cell
|
|
nSource = nISrcStart;
|
|
bGetCell = true;
|
|
}
|
|
if ( !(nScFillModeMouseModifier & KEY_MOD1) )
|
|
{
|
|
if ( bPositive )
|
|
nDelta += 1.0;
|
|
else
|
|
nDelta -= 1.0;
|
|
}
|
|
nFormulaCounter = nActFormCnt;
|
|
}
|
|
else if (bPositive)
|
|
{
|
|
++nSource;
|
|
bGetCell = true;
|
|
}
|
|
else
|
|
{
|
|
--nSource;
|
|
bGetCell = true;
|
|
}
|
|
}
|
|
|
|
if (rInner == nIEnd)
|
|
break;
|
|
if (bPositive)
|
|
++rInner;
|
|
else
|
|
--rInner;
|
|
|
|
// Progress in inner loop only for expensive cells,
|
|
// and even then not individually for each one
|
|
|
|
++rProgress;
|
|
if ( pProgress && (aSrcCell.getType() == CELLTYPE_FORMULA || aSrcCell.getType() == CELLTYPE_EDIT) )
|
|
pProgress->SetStateOnPercent( rProgress );
|
|
|
|
}
|
|
if (pProgress)
|
|
pProgress->SetStateOnPercent( rProgress );
|
|
}
|
|
|
|
namespace
|
|
{
|
|
// Target value exceeded?
|
|
inline bool isOverflow( const double& rVal, const double& rMax, const double& rStep,
|
|
const double& rStartVal, FillCmd eFillCmd )
|
|
{
|
|
switch (eFillCmd)
|
|
{
|
|
case FILL_LINEAR:
|
|
case FILL_DATE:
|
|
if (rStep >= 0.0)
|
|
return rVal > rMax;
|
|
else
|
|
return rVal < rMax;
|
|
case FILL_GROWTH:
|
|
if (rStep > 0.0)
|
|
{
|
|
if (rStep >= 1.0)
|
|
{
|
|
// Growing away from zero, including zero growth (1.0).
|
|
if (rVal >= 0.0)
|
|
return rVal > rMax;
|
|
else
|
|
return rVal < rMax;
|
|
}
|
|
else
|
|
{
|
|
// Shrinking towards zero.
|
|
if (rVal >= 0.0)
|
|
return rVal < rMax;
|
|
else
|
|
return rVal > rMax;
|
|
}
|
|
}
|
|
else if (rStep < 0.0)
|
|
{
|
|
// Alternating positive and negative values.
|
|
if (rStep <= -1.0)
|
|
{
|
|
// Growing away from zero, including zero growth (-1.0).
|
|
if (rVal >= 0.0)
|
|
{
|
|
if (rMax >= 0.0)
|
|
return rVal > rMax;
|
|
else
|
|
// Regard negative rMax as lower limit, which will
|
|
// be reached only by a negative rVal.
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (rMax <= 0.0)
|
|
return rVal < rMax;
|
|
else
|
|
// Regard positive rMax as upper limit, which will
|
|
// be reached only by a positive rVal.
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Shrinking towards zero.
|
|
if (rVal >= 0.0)
|
|
return rVal < rMax;
|
|
else
|
|
return rVal > rMax;
|
|
}
|
|
}
|
|
else // if (rStep == 0.0)
|
|
{
|
|
// All values become zero.
|
|
// Corresponds with bEntireArea in FillSeries().
|
|
if (rMax > 0.0)
|
|
return rMax < rStartVal;
|
|
else if (rMax < 0.0)
|
|
return rStartVal < rMax;
|
|
}
|
|
break;
|
|
default:
|
|
assert(!"eFillCmd");
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
|
|
sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd,
|
|
double nStepValue, const tools::Duration& rDurationStep,
|
|
double nMaxValue, sal_uInt16 nArgMinDigits,
|
|
bool bAttribs, ScProgress* pProgress,
|
|
bool bSkipOverlappedCells, std::vector<sal_Int32>* pNonOverlappedCellIdx )
|
|
{
|
|
// The term 'inner' here refers to the loop in the filling direction i.e.
|
|
// when filling vertically, the inner position is the row position whereas
|
|
// when filling horizontally the column position becomes the inner
|
|
// position. The term 'outer' refers to the column position when filling
|
|
// vertically, or the row position when filling horizontally. The fill is
|
|
// performed once in each 'outer' position e.g. when filling vertically,
|
|
// we perform the fill once in each column.
|
|
|
|
// 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 nISource;
|
|
ScRange aFillRange;
|
|
sal_uInt64 nFillerCount;
|
|
std::vector<bool> aIsNonEmptyCell;
|
|
|
|
if (bVertical)
|
|
{
|
|
nFillerCount = (nRow2 - nRow1) + 1;
|
|
nFillCount += (nRow2 - nRow1);
|
|
if (nFillCount == 0)
|
|
return;
|
|
nOStart = nCol1;
|
|
nOEnd = nCol2;
|
|
if (bPositive)
|
|
{
|
|
// downward fill
|
|
nISource = nRow1; // top row of the source range.
|
|
nIStart = nRow1 + 1; // first row where we start filling.
|
|
nIEnd = nRow1 + nFillCount;
|
|
aFillRange = ScRange(nCol1, nRow1 + 1, nTab, nCol2, nRow1 + nFillCount, nTab);
|
|
}
|
|
else
|
|
{
|
|
// upward fill
|
|
nISource = nRow2;
|
|
nIStart = nRow2 - 1;
|
|
nIEnd = nRow2 - nFillCount;
|
|
aFillRange = ScRange(nCol1, nRow2 -1, nTab, nCol2, nRow2 - nFillCount, nTab);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nFillerCount = (nCol2 - nCol1) + 1;
|
|
nFillCount += (nCol2 - nCol1);
|
|
if (nFillCount == 0)
|
|
return;
|
|
nOStart = nRow1;
|
|
nOEnd = nRow2;
|
|
if (bPositive)
|
|
{
|
|
// to the right
|
|
nISource = nCol1;
|
|
nIStart = nCol1 + 1;
|
|
nIEnd = nCol1 + nFillCount;
|
|
aFillRange = ScRange(nCol1 + 1, nRow1, nTab, nCol1 + nFillCount, nRow2, nTab);
|
|
}
|
|
else
|
|
{
|
|
// to the left
|
|
nISource = nCol2;
|
|
nIStart = nCol2 - 1;
|
|
nIEnd = nCol2 - nFillCount;
|
|
aFillRange = ScRange(nCol2 - 1, nRow1, nTab, nCol2 - nFillCount, nRow2, nTab);
|
|
}
|
|
}
|
|
|
|
SCCOLROW nIMin = nIStart;
|
|
SCCOLROW nIMax = nIEnd;
|
|
PutInOrder(nIMin,nIMax);
|
|
|
|
const bool bIsFiltered = IsDataFiltered(aFillRange);
|
|
bool bEntireArea = (!bIsFiltered && eFillCmd == FILL_SIMPLE);
|
|
if (!bIsFiltered && !bEntireArea && (eFillCmd == FILL_LINEAR || eFillCmd == FILL_GROWTH)
|
|
&& (nOEnd - nOStart == 0))
|
|
{
|
|
// For the usual case of one col/row determine if a numeric series is
|
|
// at least as long as the area to be filled and does not end earlier,
|
|
// so we can treat it as entire area for performance reasons at least
|
|
// in the vertical case.
|
|
// This is not exact in case of merged cell fills with skipping overlapped parts, but
|
|
// it is still a good upper estimation.
|
|
ScCellValue aSrcCell;
|
|
if (bVertical)
|
|
aSrcCell = GetCellValue(static_cast<SCCOL>(nOStart), static_cast<SCROW>(nISource));
|
|
else
|
|
aSrcCell = GetCellValue(static_cast<SCCOL>(nISource), static_cast<SCROW>(nOStart));
|
|
// Same logic as for the actual series.
|
|
if (!aSrcCell.isEmpty() && (aSrcCell.getType() == CELLTYPE_VALUE || aSrcCell.getType() == CELLTYPE_FORMULA))
|
|
{
|
|
double nStartVal;
|
|
if (aSrcCell.getType() == CELLTYPE_VALUE)
|
|
nStartVal = aSrcCell.getDouble();
|
|
else
|
|
nStartVal = aSrcCell.getFormula()->GetValue();
|
|
if (eFillCmd == FILL_LINEAR)
|
|
{
|
|
if (nStepValue == 0.0)
|
|
bEntireArea = (nStartVal <= nMaxValue); // fill with same value
|
|
else if (((nMaxValue - nStartVal) / nStepValue) >= nFillCount)
|
|
bEntireArea = true;
|
|
}
|
|
else if (eFillCmd == FILL_GROWTH)
|
|
{
|
|
if (nStepValue == 1.0)
|
|
bEntireArea = (nStartVal <= nMaxValue); // fill with same value
|
|
else if (nStepValue == -1.0)
|
|
bEntireArea = (fabs(nStartVal) <= fabs(nMaxValue)); // fill with alternating value
|
|
else if (nStepValue == 0.0)
|
|
bEntireArea = (nStartVal == 0.0
|
|
|| (nStartVal < 0.0 && nMaxValue >= 0.0)
|
|
|| (nStartVal > 0.0 && nMaxValue <= 0.0)); // fill with 0.0
|
|
}
|
|
}
|
|
}
|
|
if (bEntireArea)
|
|
{
|
|
InsertDeleteFlags nDel = (bAttribs ? InsertDeleteFlags::AUTOFILL :
|
|
(InsertDeleteFlags::AUTOFILL & InsertDeleteFlags::CONTENTS));
|
|
if (bVertical)
|
|
DeleteArea(nCol1, static_cast<SCROW>(nIMin), nCol2, static_cast<SCROW>(nIMax), nDel);
|
|
else
|
|
DeleteArea(static_cast<SCCOL>(nIMin), nRow1, static_cast<SCCOL>(nIMax), nRow2, nDel);
|
|
}
|
|
|
|
sal_uInt64 nProgress = 0;
|
|
if (pProgress)
|
|
nProgress = pProgress->GetState();
|
|
|
|
// Perform the fill once per each 'outer' position i.e. one per column
|
|
// when filling vertically.
|
|
|
|
for (rOuter = nOStart; rOuter <= nOEnd; rOuter++)
|
|
{
|
|
rInner = nISource;
|
|
|
|
CreateColumnIfNotExists(nCol);
|
|
|
|
// Source cell value. We need to clone the value since it may be inserted repeatedly.
|
|
ScCellValue aSrcCell = GetCellValue(nCol, static_cast<SCROW>(nRow));
|
|
|
|
// Maybe another source cell need to be searched, if the fill is going through merged cells,
|
|
// where overlapped parts does not contain any information, so they can be skipped.
|
|
if (bSkipOverlappedCells)
|
|
{
|
|
// create a vector to make it easier to decide if a cell need to be filled, or skipped.
|
|
aIsNonEmptyCell.resize(nFillerCount, false);
|
|
|
|
SCCOLROW nFirstValueIdx;
|
|
if (bPositive)
|
|
{
|
|
nFirstValueIdx = nISource + (*pNonOverlappedCellIdx)[0];
|
|
for (auto i : (*pNonOverlappedCellIdx))
|
|
aIsNonEmptyCell[i] = true;
|
|
}
|
|
else
|
|
{
|
|
nFirstValueIdx = nISource - (nFillerCount - 1 - (*pNonOverlappedCellIdx).back());
|
|
for (auto i : (*pNonOverlappedCellIdx))
|
|
aIsNonEmptyCell[nFillerCount - 1 - i] = true;
|
|
}
|
|
|
|
//Set the real source cell
|
|
if (bVertical)
|
|
aSrcCell = GetCellValue(nOStart, static_cast<SCROW>(nFirstValueIdx));
|
|
else
|
|
aSrcCell = GetCellValue(nFirstValueIdx, static_cast<SCROW>(nOStart));
|
|
}
|
|
|
|
const ScPatternAttr* pSrcPattern = aCol[nCol].GetPattern(static_cast<SCROW>(nRow));
|
|
const ScCondFormatItem& rCondFormatItem = pSrcPattern->GetItem(ATTR_CONDITIONAL);
|
|
const ScCondFormatIndexes& rCondFormatIndex = rCondFormatItem.GetCondFormatData();
|
|
|
|
if (bAttribs)
|
|
{
|
|
if (bVertical)
|
|
{
|
|
// If entire area (not filtered and simple fill) use the faster
|
|
// method, else hidden cols/rows should be skipped and series
|
|
// fill needs to determine the end row dynamically.
|
|
if (bEntireArea)
|
|
{
|
|
SetPatternAreaCondFormat( nCol, static_cast<SCROW>(nIMin),
|
|
static_cast<SCROW>(nIMax), *pSrcPattern, rCondFormatIndex);
|
|
}
|
|
else if (eFillCmd == FILL_SIMPLE)
|
|
{
|
|
assert(bIsFiltered);
|
|
for(SCROW nAtRow = static_cast<SCROW>(nIMin); nAtRow <= static_cast<SCROW>(nIMax); ++nAtRow)
|
|
{
|
|
if(!RowHidden(nAtRow))
|
|
{
|
|
SetPatternAreaCondFormat( nCol, nAtRow, nAtRow, *pSrcPattern, rCondFormatIndex);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
else if (bEntireArea || eFillCmd == FILL_SIMPLE)
|
|
{
|
|
for (SCCOL nAtCol = static_cast<SCCOL>(nIMin); nAtCol <= sal::static_int_cast<SCCOL>(nIMax); nAtCol++)
|
|
{
|
|
if(!ColHidden(nAtCol))
|
|
{
|
|
SetPatternAreaCondFormat( nAtCol, nRow, nRow, *pSrcPattern, rCondFormatIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!aSrcCell.isEmpty())
|
|
{
|
|
CellType eCellType = aSrcCell.getType();
|
|
|
|
if (eFillCmd == FILL_SIMPLE) // copy
|
|
{
|
|
FillSeriesSimple(aSrcCell, rInner, nIMin, nIMax, nCol, nRow, bVertical, pProgress, nProgress);
|
|
}
|
|
else if (eCellType == CELLTYPE_VALUE || eCellType == CELLTYPE_FORMULA)
|
|
{
|
|
const double nStartVal = (eCellType == CELLTYPE_VALUE ? aSrcCell.getDouble() :
|
|
aSrcCell.getFormula()->GetValue());
|
|
double nVal = nStartVal;
|
|
tools::Long nIndex = 0;
|
|
|
|
bool bError = false;
|
|
bool bOverflow = false;
|
|
bool bNonEmpty = true;
|
|
|
|
sal_uInt16 nDayOfMonth = 0;
|
|
sal_Int32 nFillerIdx = 0;
|
|
if (bSkipOverlappedCells && !aIsNonEmptyCell[0])
|
|
--nIndex;
|
|
rInner = nIStart;
|
|
while (true)
|
|
{
|
|
if (bSkipOverlappedCells)
|
|
{
|
|
nFillerIdx = (nFillerIdx + 1) % nFillerCount;
|
|
bNonEmpty = aIsNonEmptyCell[nFillerIdx];
|
|
}
|
|
|
|
if(!ColHidden(nCol) && !RowHidden(nRow))
|
|
{
|
|
if (!bError && bNonEmpty)
|
|
{
|
|
switch (eFillCmd)
|
|
{
|
|
case FILL_LINEAR:
|
|
{
|
|
// use multiplication instead of repeated addition
|
|
// to avoid accumulating rounding errors
|
|
nVal = nStartVal;
|
|
if (rDurationStep)
|
|
{
|
|
tools::Duration aDuration( rDurationStep.Mult( ++nIndex, bError));
|
|
bError |= !SubTotal::SafePlus( nVal, aDuration.GetInDays());
|
|
}
|
|
else
|
|
{
|
|
double nAdd = nStepValue;
|
|
if ( !SubTotal::SafeMult( nAdd, static_cast<double>(++nIndex) ) ||
|
|
!SubTotal::SafePlus( nVal, nAdd ) )
|
|
bError = true;
|
|
}
|
|
}
|
|
break;
|
|
case FILL_GROWTH:
|
|
if (!SubTotal::SafeMult(nVal, nStepValue))
|
|
bError = true;
|
|
break;
|
|
case FILL_DATE:
|
|
if (fabs(nVal) > D_MAX_LONG_)
|
|
bError = true;
|
|
else
|
|
IncDate(nVal, nDayOfMonth, nStepValue, eFillDateCmd);
|
|
break;
|
|
default:
|
|
{
|
|
// added to avoid warnings
|
|
}
|
|
}
|
|
|
|
if (!bError)
|
|
bOverflow = isOverflow( nVal, nMaxValue, nStepValue, nStartVal, eFillCmd);
|
|
}
|
|
|
|
CreateColumnIfNotExists(nCol);
|
|
if (bError)
|
|
aCol[nCol].SetError(static_cast<SCROW>(nRow), FormulaError::NoValue);
|
|
else if (!bOverflow && bNonEmpty)
|
|
aCol[nCol].SetValue(static_cast<SCROW>(nRow), nVal);
|
|
|
|
if (bAttribs && !bEntireArea && !bOverflow)
|
|
SetPatternAreaCondFormat( nCol, nRow, nRow, *pSrcPattern, rCondFormatIndex);
|
|
}
|
|
|
|
if (rInner == nIEnd || bOverflow)
|
|
break;
|
|
if (bPositive)
|
|
{
|
|
++rInner;
|
|
}
|
|
else
|
|
{
|
|
--rInner;
|
|
}
|
|
}
|
|
nProgress += nIMax - nIMin + 1;
|
|
if(pProgress)
|
|
pProgress->SetStateOnPercent( nProgress );
|
|
}
|
|
else if (eCellType == CELLTYPE_STRING || eCellType == CELLTYPE_EDIT)
|
|
{
|
|
if ( nStepValue >= 0 )
|
|
{
|
|
if ( nMaxValue >= double(LONG_MAX) )
|
|
nMaxValue = double(LONG_MAX) - 1;
|
|
}
|
|
else
|
|
{
|
|
if ( nMaxValue <= double(LONG_MIN) )
|
|
nMaxValue = double(LONG_MIN) + 1;
|
|
}
|
|
OUString aValue;
|
|
if (eCellType == CELLTYPE_STRING)
|
|
aValue = aSrcCell.getSharedString()->getString();
|
|
else
|
|
aValue = ScEditUtil::GetString(*aSrcCell.getEditText(), &rDocument);
|
|
sal_Int32 nStringValue;
|
|
sal_uInt16 nMinDigits = nArgMinDigits;
|
|
short nHeadNoneTail = lcl_DecompValueString( aValue, nStringValue, &nMinDigits );
|
|
if ( nHeadNoneTail )
|
|
{
|
|
const double nStartVal = static_cast<double>(nStringValue);
|
|
double nVal = nStartVal;
|
|
tools::Long nIndex = 0;
|
|
bool bError = false;
|
|
bool bOverflow = false;
|
|
bool bNonEmpty = true;
|
|
|
|
bool bIsOrdinalSuffix = aValue == ScGlobal::GetOrdinalSuffix(
|
|
static_cast<sal_Int32>(nStartVal));
|
|
|
|
sal_Int32 nFillerIdx = 0;
|
|
if (bSkipOverlappedCells && !aIsNonEmptyCell[0])
|
|
--nIndex;
|
|
rInner = nIStart;
|
|
while (true)
|
|
{
|
|
if (bSkipOverlappedCells)
|
|
{
|
|
nFillerIdx = (nFillerIdx + 1) % nFillerCount;
|
|
bNonEmpty = aIsNonEmptyCell[nFillerIdx];
|
|
}
|
|
if(!ColHidden(nCol) && !RowHidden(nRow))
|
|
{
|
|
if (!bError && bNonEmpty)
|
|
{
|
|
switch (eFillCmd)
|
|
{
|
|
case FILL_LINEAR:
|
|
{
|
|
// use multiplication instead of repeated addition
|
|
// to avoid accumulating rounding errors
|
|
nVal = nStartVal;
|
|
if (rDurationStep)
|
|
{
|
|
tools::Duration aDuration( rDurationStep.Mult( ++nIndex, bError));
|
|
bError |= !SubTotal::SafePlus( nVal, aDuration.GetInDays());
|
|
}
|
|
else
|
|
{
|
|
double nAdd = nStepValue;
|
|
if ( !SubTotal::SafeMult( nAdd, static_cast<double>(++nIndex) ) ||
|
|
!SubTotal::SafePlus( nVal, nAdd ) )
|
|
bError = true;
|
|
}
|
|
}
|
|
break;
|
|
case FILL_GROWTH:
|
|
if (!SubTotal::SafeMult(nVal, nStepValue))
|
|
bError = true;
|
|
break;
|
|
default:
|
|
{
|
|
// added to avoid warnings
|
|
}
|
|
}
|
|
|
|
if (!bError)
|
|
bOverflow = isOverflow( nVal, nMaxValue, nStepValue, nStartVal, eFillCmd);
|
|
}
|
|
|
|
if (bError)
|
|
aCol[nCol].SetError(static_cast<SCROW>(nRow), FormulaError::NoValue);
|
|
else if (!bOverflow && bNonEmpty)
|
|
{
|
|
nStringValue = static_cast<sal_Int32>(nVal);
|
|
if ( nHeadNoneTail < 0 )
|
|
{
|
|
setSuffixCell(
|
|
aCol[nCol], static_cast<SCROW>(nRow),
|
|
nStringValue, nMinDigits, aValue,
|
|
eCellType, bIsOrdinalSuffix);
|
|
}
|
|
else
|
|
{
|
|
OUString aStr;
|
|
if (nHeadNoneTail == 2 && nStringValue >= 0) // Put back the '+'
|
|
aStr = aValue + "+";
|
|
else
|
|
aStr = aValue;
|
|
aStr += lcl_ValueString( nStringValue, nMinDigits );
|
|
aCol[nCol].SetRawString(static_cast<SCROW>(nRow), aStr);
|
|
}
|
|
}
|
|
|
|
if (bAttribs && !bEntireArea && !bOverflow)
|
|
SetPatternAreaCondFormat( nCol, nRow, nRow, *pSrcPattern, rCondFormatIndex);
|
|
}
|
|
|
|
if (rInner == nIEnd || bOverflow)
|
|
break;
|
|
if (bPositive)
|
|
++rInner;
|
|
else
|
|
--rInner;
|
|
}
|
|
}
|
|
if(pProgress)
|
|
{
|
|
nProgress += nIMax - nIMin + 1;
|
|
pProgress->SetStateOnPercent( nProgress );
|
|
}
|
|
}
|
|
}
|
|
else if(pProgress)
|
|
{
|
|
nProgress += nIMax - nIMin + 1;
|
|
pProgress->SetStateOnPercent( nProgress );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScTable::Fill( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
|
|
sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd,
|
|
double nStepValue, const tools::Duration& rDurationStep,
|
|
double nMaxValue, ScProgress* pProgress)
|
|
{
|
|
if (eFillCmd == FILL_AUTO)
|
|
FillAuto(nCol1, nRow1, nCol2, nRow2, nFillCount, eFillDir, pProgress);
|
|
else
|
|
FillSeries(nCol1, nRow1, nCol2, nRow2, nFillCount, eFillDir,
|
|
eFillCmd, eFillDateCmd, nStepValue, rDurationStep, nMaxValue, 0, true, pProgress);
|
|
}
|
|
|
|
void ScTable::AutoFormatArea(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
|
|
const ScPatternAttr& rAttr, sal_uInt16 nFormatNo)
|
|
{
|
|
ScAutoFormat& rFormat = *ScGlobal::GetOrCreateAutoFormat();
|
|
ScAutoFormatData* pData = rFormat.findByIndex(nFormatNo);
|
|
if (pData)
|
|
{
|
|
ApplyPatternArea(nStartCol, nStartRow, nEndCol, nEndRow, rAttr);
|
|
}
|
|
}
|
|
|
|
void ScTable::AutoFormat( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
|
|
sal_uInt16 nFormatNo, ScProgress* pProgress )
|
|
{
|
|
if (!(ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow)))
|
|
return;
|
|
|
|
ScAutoFormat& rFormat = *ScGlobal::GetOrCreateAutoFormat();
|
|
ScAutoFormatData* pData = rFormat.findByIndex(nFormatNo);
|
|
if (!pData)
|
|
return;
|
|
|
|
std::unique_ptr<ScPatternAttr> pPatternAttrs[16];
|
|
for (sal_uInt8 i = 0; i < 16; ++i)
|
|
{
|
|
pPatternAttrs[i].reset(new ScPatternAttr(rDocument.getCellAttributeHelper()));
|
|
pData->FillToItemSet(i, pPatternAttrs[i]->GetItemSet(), rDocument);
|
|
}
|
|
|
|
// Important special case: when the whole rows are selected. Then applying autoformat to right
|
|
// column individually would create all columns. In this case, assume that right column isn't
|
|
// needed, to allow "to the end of row" format optimization (which doesn't create columns).
|
|
// Keep left column in this case, because it may be pre-formatted for categories. To enable the
|
|
// optimization, apply uniform format row by row where possible, not column by column.
|
|
|
|
const bool isWholeRows = nStartCol == 0 && nEndCol == rDocument.MaxCol();
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Top row data indexes:
|
|
// 0 - left corner style
|
|
// 1 - odd columns style
|
|
// 2 - even column style
|
|
// 3 - right corner style
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
SCCOL startOffset = 1;
|
|
// Left top corner
|
|
if (pData->HasSameData(0, 1) && pData->HasSameData(0, 2))
|
|
startOffset = 0; // Left corner is same as the rest of the row
|
|
else
|
|
AutoFormatArea(nStartCol, nStartRow, nStartCol, nStartRow, *pPatternAttrs[0], nFormatNo);
|
|
|
|
SCCOL endOffset = 1;
|
|
// Right top corner: ignore when whole rows selected
|
|
if (isWholeRows || (pData->HasSameData(3, 1) && pData->HasSameData(3, 2)))
|
|
endOffset = 0; // Right corner is same as the rest of the row (most important case)
|
|
else
|
|
AutoFormatArea(nEndCol, nStartRow, nEndCol, nStartRow, *pPatternAttrs[3], nFormatNo);
|
|
|
|
// Top row
|
|
if (pData->HasSameData(1, 2))
|
|
AutoFormatArea(nStartCol + startOffset, nStartRow, nEndCol - endOffset, nStartRow, *pPatternAttrs[1], nFormatNo);
|
|
else
|
|
{
|
|
sal_uInt16 nIndex = 1;
|
|
for (SCCOL nCol = nStartCol + startOffset; nCol <= nEndCol - endOffset; nCol++)
|
|
{
|
|
AutoFormatArea(nCol, nStartRow, nCol, nStartRow, *pPatternAttrs[nIndex], nFormatNo);
|
|
if (nIndex == 1)
|
|
nIndex = 2;
|
|
else
|
|
nIndex = 1;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Body data indexes:
|
|
// 4 - left column odd row style
|
|
// 8 - left column even row style
|
|
// 7 - right column odd row style
|
|
// 11 - right column even row style
|
|
// 5 - body odd column odd row style
|
|
// 6 - body even column odd row style
|
|
// 9 - body odd column even row style
|
|
// 10 - body even column even row style
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
startOffset = 1;
|
|
// Left column
|
|
if (pData->HasSameData(4, 5) && pData->HasSameData(4, 6) && pData->HasSameData(8, 9) && pData->HasSameData(8, 10))
|
|
startOffset = 0; // Left column is same as the lines of the body
|
|
else
|
|
{
|
|
if (pData->HasSameData(4, 8)) // even and odd rows are same
|
|
AutoFormatArea(nStartCol, nStartRow + 1, nStartCol, nEndRow - 1, *pPatternAttrs[4], nFormatNo);
|
|
else
|
|
{
|
|
sal_uInt16 nIndex = 4;
|
|
for (SCROW nRow = nStartRow + 1; nRow < nEndRow; nRow++)
|
|
{
|
|
AutoFormatArea(nStartCol, nRow, nStartCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
|
|
if (nIndex == 4)
|
|
nIndex = 8;
|
|
else
|
|
nIndex = 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
endOffset = 1;
|
|
// Right column: ignore when whole rows selected
|
|
if (isWholeRows || (pData->HasSameData(7, 5) && pData->HasSameData(7, 6) && pData->HasSameData(11, 9) && pData->HasSameData(11, 10)))
|
|
endOffset = 0; // Right column is same as the lines of the body (most important case)
|
|
else
|
|
{
|
|
if (pData->HasSameData(7, 11)) // even and odd rows are same
|
|
AutoFormatArea(nEndCol, nStartRow + 1, nEndCol, nEndRow - 1, *pPatternAttrs[7], nFormatNo);
|
|
else
|
|
{
|
|
sal_uInt16 nIndex = 7;
|
|
for (SCROW nRow = nStartRow + 1; nRow < nEndRow; nRow++)
|
|
{
|
|
AutoFormatArea(nEndCol, nRow, nEndCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
|
|
if (nIndex == 7)
|
|
nIndex = 11;
|
|
else
|
|
nIndex = 7;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Body
|
|
if (pData->HasSameData(5, 6) && pData->HasSameData(9, 10)) // Odd and even columns are same (most important case)
|
|
{
|
|
if (pData->HasSameData(5, 9)) // Everything is the same
|
|
AutoFormatArea(nStartCol + startOffset, nStartRow + 1, nEndCol - endOffset, nEndRow - 1, *pPatternAttrs[5], nFormatNo);
|
|
else // Odd and even rows differ
|
|
{
|
|
if (pProgress)
|
|
pProgress->SetState(1, nEndRow - nStartRow + 3); // account for elements outside the "Body" block
|
|
sal_uInt16 nIndex = 5;
|
|
for (SCROW nRow = nStartRow + 1; nRow < nEndRow; nRow++)
|
|
{
|
|
AutoFormatArea(nStartCol + startOffset, nRow, nEndCol - endOffset, nRow, *pPatternAttrs[nIndex], nFormatNo);
|
|
if (nIndex == 5)
|
|
nIndex = 9;
|
|
else
|
|
nIndex = 5;
|
|
if (pProgress)
|
|
pProgress->SetStateOnPercent(nRow - nStartRow + 1);
|
|
}
|
|
}
|
|
}
|
|
else if (pData->HasSameData(5, 9) && pData->HasSameData(6, 10)) // odd and even rows are same
|
|
{
|
|
if (pProgress)
|
|
pProgress->SetState(1, nEndCol - nStartCol + 3); // account for elements outside the "Body" block
|
|
sal_uInt16 nIndex = 5;
|
|
for (SCCOL nCol = nStartCol + startOffset; nCol <= nEndCol - endOffset; nCol++)
|
|
{
|
|
AutoFormatArea(nCol, nStartRow + 1, nCol, nEndRow - 1, *pPatternAttrs[nIndex], nFormatNo);
|
|
if (nIndex == 5)
|
|
nIndex = 6;
|
|
else
|
|
nIndex = 5;
|
|
if (pProgress)
|
|
pProgress->SetStateOnPercent(nCol - nStartCol + 1);
|
|
}
|
|
}
|
|
else // Everything is different
|
|
{
|
|
if (pProgress)
|
|
pProgress->SetState(1, nEndCol - nStartCol + 3); // account for elements outside the "Body" block
|
|
sal_uInt16 nIndex = 5;
|
|
for (SCCOL nCol = nStartCol + startOffset; nCol <= nEndCol - endOffset; nCol++)
|
|
{
|
|
for (SCROW nRow = nStartRow + 1; nRow < nEndRow; nRow++)
|
|
{
|
|
AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
|
|
if ((nIndex == 5) || (nIndex == 9))
|
|
{
|
|
if (nIndex == 5)
|
|
nIndex = 9;
|
|
else
|
|
nIndex = 5;
|
|
}
|
|
else
|
|
{
|
|
if (nIndex == 6)
|
|
nIndex = 10;
|
|
else
|
|
nIndex = 6;
|
|
}
|
|
} // for nRow
|
|
if ((nIndex == 5) || (nIndex == 9))
|
|
nIndex = 6;
|
|
else
|
|
nIndex = 5;
|
|
if (pProgress)
|
|
pProgress->SetStateOnPercent(nCol - nStartCol + 1);
|
|
|
|
} // for nCol
|
|
} // if not all equal
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Bottom row data indexes:
|
|
// 12 - left corner style
|
|
// 13 - odd columns style
|
|
// 14 - even column style
|
|
// 15 - right corner style
|
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
startOffset = 1;
|
|
// Left bottom corner
|
|
if (pData->HasSameData(12, 13) && pData->HasSameData(12, 14))
|
|
startOffset = 0;
|
|
else
|
|
AutoFormatArea(nStartCol, nEndRow, nStartCol, nEndRow, *pPatternAttrs[12], nFormatNo);
|
|
|
|
endOffset = 1;
|
|
// Right bottom corner: ignore when whole rows selected
|
|
if (isWholeRows || (pData->HasSameData(15, 13) && pData->HasSameData(15, 14)))
|
|
endOffset = 0;
|
|
else
|
|
AutoFormatArea(nEndCol, nEndRow, nEndCol, nEndRow, *pPatternAttrs[15], nFormatNo);
|
|
|
|
// Bottom row
|
|
if (pData->HasSameData(13, 14))
|
|
AutoFormatArea(nStartCol + startOffset, nEndRow, nEndCol - endOffset, nEndRow, *pPatternAttrs[13], nFormatNo);
|
|
else
|
|
{
|
|
sal_uInt16 nIndex = 13;
|
|
for (SCCOL nCol = nStartCol + startOffset; nCol <= nEndCol - endOffset; nCol++)
|
|
{
|
|
AutoFormatArea(nCol, nEndRow, nCol, nEndRow, *pPatternAttrs[nIndex], nFormatNo);
|
|
if (nIndex == 13)
|
|
nIndex = 14;
|
|
else
|
|
nIndex = 13;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScTable::GetAutoFormatAttr(SCCOL nCol, SCROW nRow, sal_uInt16 nIndex, ScAutoFormatData& rData)
|
|
{
|
|
sal_uInt32 nFormatIndex = GetNumberFormat( nCol, nRow );
|
|
ScNumFormatAbbrev aNumFormat( nFormatIndex, *rDocument.GetFormatTable() );
|
|
rData.GetFromItemSet( nIndex, GetPattern( nCol, nRow )->GetItemSet(), aNumFormat );
|
|
}
|
|
|
|
#define LF_LEFT 1
|
|
#define LF_TOP 2
|
|
#define LF_RIGHT 4
|
|
#define LF_BOTTOM 8
|
|
#define LF_ALL (LF_LEFT | LF_TOP | LF_RIGHT | LF_BOTTOM)
|
|
|
|
void ScTable::GetAutoFormatFrame(SCCOL nCol, SCROW nRow, sal_uInt16 nFlags, sal_uInt16 nIndex, ScAutoFormatData& rData)
|
|
{
|
|
const SvxBoxItem* pTheBox = GetAttr(nCol, nRow, ATTR_BORDER);
|
|
const SvxBoxItem* pLeftBox = GetAttr(nCol - 1, nRow, ATTR_BORDER);
|
|
const SvxBoxItem* pTopBox = GetAttr(nCol, nRow - 1, ATTR_BORDER);
|
|
const SvxBoxItem* pRightBox = GetAttr(nCol + 1, nRow, ATTR_BORDER);
|
|
const SvxBoxItem* pBottomBox = GetAttr(nCol, nRow + 1, ATTR_BORDER);
|
|
|
|
SvxBoxItem aBox( ATTR_BORDER );
|
|
if (nFlags & LF_LEFT)
|
|
{
|
|
if (pLeftBox)
|
|
{
|
|
if (ScHasPriority(pTheBox->GetLeft(), pLeftBox->GetRight()))
|
|
aBox.SetLine(pTheBox->GetLeft(), SvxBoxItemLine::LEFT);
|
|
else
|
|
aBox.SetLine(pLeftBox->GetRight(), SvxBoxItemLine::LEFT);
|
|
}
|
|
else
|
|
aBox.SetLine(pTheBox->GetLeft(), SvxBoxItemLine::LEFT);
|
|
}
|
|
if (nFlags & LF_TOP)
|
|
{
|
|
if (pTopBox)
|
|
{
|
|
if (ScHasPriority(pTheBox->GetTop(), pTopBox->GetBottom()))
|
|
aBox.SetLine(pTheBox->GetTop(), SvxBoxItemLine::TOP);
|
|
else
|
|
aBox.SetLine(pTopBox->GetBottom(), SvxBoxItemLine::TOP);
|
|
}
|
|
else
|
|
aBox.SetLine(pTheBox->GetTop(), SvxBoxItemLine::TOP);
|
|
}
|
|
if (nFlags & LF_RIGHT)
|
|
{
|
|
if (pRightBox)
|
|
{
|
|
if (ScHasPriority(pTheBox->GetRight(), pRightBox->GetLeft()))
|
|
aBox.SetLine(pTheBox->GetRight(), SvxBoxItemLine::RIGHT);
|
|
else
|
|
aBox.SetLine(pRightBox->GetLeft(), SvxBoxItemLine::RIGHT);
|
|
}
|
|
else
|
|
aBox.SetLine(pTheBox->GetRight(), SvxBoxItemLine::RIGHT);
|
|
}
|
|
if (nFlags & LF_BOTTOM)
|
|
{
|
|
if (pBottomBox)
|
|
{
|
|
if (ScHasPriority(pTheBox->GetBottom(), pBottomBox->GetTop()))
|
|
aBox.SetLine(pTheBox->GetBottom(), SvxBoxItemLine::BOTTOM);
|
|
else
|
|
aBox.SetLine(pBottomBox->GetTop(), SvxBoxItemLine::BOTTOM);
|
|
}
|
|
else
|
|
aBox.SetLine(pTheBox->GetBottom(), SvxBoxItemLine::BOTTOM);
|
|
}
|
|
rData.PutItem( nIndex, aBox );
|
|
}
|
|
|
|
void ScTable::GetAutoFormatData(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, ScAutoFormatData& rData)
|
|
{
|
|
if (!(ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow)))
|
|
return;
|
|
|
|
if ((nEndCol - nStartCol < 3) || (nEndRow - nStartRow < 3))
|
|
return;
|
|
|
|
// Left top corner
|
|
GetAutoFormatAttr(nStartCol, nStartRow, 0, rData);
|
|
GetAutoFormatFrame(nStartCol, nStartRow, LF_ALL, 0, rData);
|
|
// Left column
|
|
GetAutoFormatAttr(nStartCol, nStartRow + 1, 4, rData);
|
|
GetAutoFormatAttr(nStartCol, nStartRow + 2, 8, rData);
|
|
GetAutoFormatFrame(nStartCol, nStartRow + 1, LF_LEFT | LF_RIGHT | LF_BOTTOM, 4, rData);
|
|
if (nEndRow - nStartRow >= 4)
|
|
GetAutoFormatFrame(nStartCol, nStartRow + 2, LF_LEFT | LF_RIGHT | LF_BOTTOM, 8, rData);
|
|
else
|
|
rData.CopyItem( 8, 4, ATTR_BORDER );
|
|
// Left bottom corner
|
|
GetAutoFormatAttr(nStartCol, nEndRow, 12, rData);
|
|
GetAutoFormatFrame(nStartCol, nEndRow, LF_ALL, 12, rData);
|
|
// Right top corner
|
|
GetAutoFormatAttr(nEndCol, nStartRow, 3, rData);
|
|
GetAutoFormatFrame(nEndCol, nStartRow, LF_ALL, 3, rData);
|
|
// Right column
|
|
GetAutoFormatAttr(nEndCol, nStartRow + 1, 7, rData);
|
|
GetAutoFormatAttr(nEndCol, nStartRow + 2, 11, rData);
|
|
GetAutoFormatFrame(nEndCol, nStartRow + 1, LF_LEFT | LF_RIGHT | LF_BOTTOM, 7, rData);
|
|
if (nEndRow - nStartRow >= 4)
|
|
GetAutoFormatFrame(nEndCol, nStartRow + 2, LF_LEFT | LF_RIGHT | LF_BOTTOM, 11, rData);
|
|
else
|
|
rData.CopyItem( 11, 7, ATTR_BORDER );
|
|
// Right bottom corner
|
|
GetAutoFormatAttr(nEndCol, nEndRow, 15, rData);
|
|
GetAutoFormatFrame(nEndCol, nEndRow, LF_ALL, 15, rData);
|
|
// Top row
|
|
GetAutoFormatAttr(nStartCol + 1, nStartRow, 1, rData);
|
|
GetAutoFormatAttr(nStartCol + 2, nStartRow, 2, rData);
|
|
GetAutoFormatFrame(nStartCol + 1, nStartRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 1, rData);
|
|
if (nEndCol - nStartCol >= 4)
|
|
GetAutoFormatFrame(nStartCol + 2, nStartRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 2, rData);
|
|
else
|
|
rData.CopyItem( 2, 1, ATTR_BORDER );
|
|
// Bottom row
|
|
GetAutoFormatAttr(nStartCol + 1, nEndRow, 13, rData);
|
|
GetAutoFormatAttr(nStartCol + 2, nEndRow, 14, rData);
|
|
GetAutoFormatFrame(nStartCol + 1, nEndRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 13, rData);
|
|
if (nEndCol - nStartCol >= 4)
|
|
GetAutoFormatFrame(nStartCol + 2, nEndRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 14, rData);
|
|
else
|
|
rData.CopyItem( 14, 13, ATTR_BORDER );
|
|
// Body
|
|
GetAutoFormatAttr(nStartCol + 1, nStartRow + 1, 5, rData);
|
|
GetAutoFormatAttr(nStartCol + 2, nStartRow + 1, 6, rData);
|
|
GetAutoFormatAttr(nStartCol + 1, nStartRow + 2, 9, rData);
|
|
GetAutoFormatAttr(nStartCol + 2, nStartRow + 2, 10, rData);
|
|
GetAutoFormatFrame(nStartCol + 1, nStartRow + 1, LF_RIGHT | LF_BOTTOM, 5, rData);
|
|
if ((nEndCol - nStartCol >= 4) && (nEndRow - nStartRow >= 4))
|
|
{
|
|
GetAutoFormatFrame(nStartCol + 2, nStartRow + 1, LF_RIGHT | LF_BOTTOM, 6, rData);
|
|
GetAutoFormatFrame(nStartCol + 1, nStartRow + 2, LF_RIGHT | LF_BOTTOM, 9, rData);
|
|
GetAutoFormatFrame(nStartCol + 2, nStartRow + 2, LF_RIGHT | LF_BOTTOM, 10, rData);
|
|
}
|
|
else
|
|
{
|
|
rData.CopyItem( 6, 5, ATTR_BORDER );
|
|
rData.CopyItem( 9, 5, ATTR_BORDER );
|
|
rData.CopyItem( 10, 5, ATTR_BORDER );
|
|
}
|
|
}
|
|
|
|
void ScTable::SetError( SCCOL nCol, SCROW nRow, FormulaError nError)
|
|
{
|
|
if (ValidColRow(nCol, nRow))
|
|
CreateColumnIfNotExists(nCol).SetError(nRow, nError);
|
|
}
|
|
|
|
void ScTable::UpdateInsertTabAbs(SCTAB nTable)
|
|
{
|
|
for (SCCOL i=0; i < aCol.size(); i++)
|
|
aCol[i].UpdateInsertTabAbs(nTable);
|
|
}
|
|
|
|
bool ScTable::GetNextSpellingCell(SCCOL& rCol, SCROW& rRow, bool bInSel,
|
|
const ScMarkData& rMark) const
|
|
{
|
|
if (rRow == rDocument.MaxRow()+2) // end of table
|
|
{
|
|
rRow = 0;
|
|
rCol = 0;
|
|
}
|
|
else
|
|
{
|
|
rRow++;
|
|
if (rRow == rDocument.MaxRow()+1)
|
|
{
|
|
rCol++;
|
|
rRow = 0;
|
|
}
|
|
}
|
|
if (rCol == rDocument.MaxCol()+1)
|
|
return true;
|
|
for (;;)
|
|
{
|
|
if (!ValidCol(rCol))
|
|
return true;
|
|
if (rCol >= GetAllocatedColumnsCount())
|
|
return true;
|
|
if (aCol[rCol].GetNextSpellingCell(rRow, bInSel, rMark))
|
|
return true;
|
|
/*else (rRow == rDocument.MaxRow()+1) */
|
|
rCol++;
|
|
rRow = 0;
|
|
}
|
|
}
|
|
|
|
void ScTable::TestTabRefAbs(SCTAB nTable) const
|
|
{
|
|
for (SCCOL i=0; i < aCol.size(); i++)
|
|
if (aCol[i].TestTabRefAbs(nTable))
|
|
return;
|
|
}
|
|
|
|
void ScTable::CompileDBFormula( sc::CompileFormulaContext& rCxt )
|
|
{
|
|
for (SCCOL i = 0; i < aCol.size(); ++i)
|
|
aCol[i].CompileDBFormula(rCxt);
|
|
}
|
|
|
|
void ScTable::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt )
|
|
{
|
|
for (SCCOL i = 0; i < aCol.size(); ++i)
|
|
aCol[i].CompileColRowNameFormula(rCxt);
|
|
}
|
|
|
|
SCSIZE ScTable::GetPatternCount( SCCOL nCol ) const
|
|
{
|
|
if( ValidCol( nCol ) )
|
|
return aCol[nCol].GetPatternCount();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
SCSIZE ScTable::GetPatternCount( SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const
|
|
{
|
|
if( ValidCol( nCol ) && ValidRow( nRow1 ) && ValidRow( nRow2 ) )
|
|
return aCol[nCol].GetPatternCount( nRow1, nRow2 );
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
bool ScTable::ReservePatternCount( SCCOL nCol, SCSIZE nReserve )
|
|
{
|
|
if( ValidCol( nCol ) )
|
|
return aCol[nCol].ReservePatternCount( nReserve );
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|