summaryrefslogtreecommitdiffstats
path: root/sc/source/core/data/table4.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/data/table4.cxx')
-rw-r--r--sc/source/core/data/table4.cxx2990
1 files changed, 2990 insertions, 0 deletions
diff --git a/sc/source/core/data/table4.cxx b/sc/source/core/data/table4.cxx
new file mode 100644
index 000000000..f6f926c86
--- /dev/null
+++ b/sc/source/core/data/table4.cxx
@@ -0,0 +1,2990 @@
+/* -*- 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 <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <vcl/keycodes.hxx>
+#include <rtl/math.hxx>
+#include <unotools/charclass.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 approxTimeDiff( double a, double b )
+{
+ // Scale to hours, round to "nanohours" (multiple nanoseconds), scale back.
+ // Get back 0.0416666666666667 instead of 0.041666666700621136 or
+ // 0.041666666664241347 (raw a-b) for one hour, or worse the approxDiff()
+ // 0.041666666659999997 value. Though there is no such correct value,
+ // IEEE-754 nearest values are
+ // 0.041666666666666664353702032030923874117434024810791015625
+ // (0x3FA5555555555555) and
+ // 0.04166666666666667129259593593815225176513195037841796875
+ // (0x3FA5555555555556).
+ // This works also for a diff of seconds, unless corner cases would be
+ // discovered, which would make it necessary to ditch the floating point
+ // and convert to/from time structure values instead.
+ return rtl::math::round((a - b) * 24, 9) / 24;
+}
+
+double approxTypedDiff( double a, double b, bool bTime )
+{
+ return bTime ? approxTimeDiff( a, b) : approxDiff( a, b);
+}
+}
+
+void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ FillCmd& rCmd, FillDateCmd& rDateCmd,
+ double& rInc, 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;
+ 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);
+ bool bOverlapped
+ = pPattern->GetItemSet().GetItemState(ATTR_MERGE_FLAG, false) == SfxItemState::SET
+ && pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped();
+
+ if (bOverlapped)
+ bHasOverlappedCells = true;
+
+ if (!bOverlapped || GetCellValue(nColCurr, nRowCurr).meType != 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.meType;
+ 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.mfValue);
+ 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.meType == CELLTYPE_VALUE)
+ {
+ aCurrDate = aNullDate + static_cast<sal_Int32>(aCurrCell.mfValue);
+ 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.mfValue) == 0.0 || fVal == 1.0))
+ {
+ }
+ else if (nValueCount >= 2)
+ {
+ 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.meType == CELLTYPE_VALUE)
+ {
+ double nDiff = approxTypedDiff(aCurrCell.mfValue, aPrevCell.mfValue,
+ (nCurrCellFormatType == SvNumFormatType::TIME ||
+ nCurrCellFormatType == SvNumFormatType::DATETIME));
+ if (i == 1)
+ rInc = nDiff;
+ if (!::rtl::math::approxEqual(nDiff, rInc, 13))
+ bVal = false;
+ else if ((aCurrCell.mfValue == 0.0 || aCurrCell.mfValue == 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.meType;
+ 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.meType;
+
+ 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.mfValue;
+ 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.meType == CELLTYPE_VALUE)
+ {
+ nVal = aCell.mfValue;
+ 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.mfValue) == 0.0 || fVal == 1.0))
+ {
+ // Nothing, rInc stays 0.0, no specific fill mode.
+ }
+ else
+ {
+ if (nCount > 1)
+ {
+ double nVal1 = aFirstCell.mfValue;
+ double nVal2 = GetValue(nCol+nAddX, nRow+nAddY);
+ rInc = approxTypedDiff( nVal2, nVal1, bTime);
+ 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.meType == CELLTYPE_VALUE)
+ {
+ nVal2 = aCell.mfValue;
+ double nDiff = approxTypedDiff( nVal2, nVal1, bTime);
+ 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)
+ 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.meType;
+ 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 != rDocument.GetDefPattern())
+ {
+ // 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 ( 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;
+ 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, nMinDigits, pListData, nListIndex,
+ bHasFiltered, bSkipOverlappedCells, aNonOverlappedCellIdx);
+ else
+ FillAnalyse(nCol1,static_cast<SCROW>(nRow),
+ nCol2,static_cast<SCROW>(nRow), eFillCmd,eDateCmd,
+ nInc, 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;
+ 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, nEndVal, nMinDigits, false,
+ pProgress, bSkipOverlappedCells, &aNonOverlappedCellIdx);
+ else
+ FillSeries( nCol1, static_cast<SCROW>(nRow), nCol2,
+ static_cast<SCROW>(nRow), nFillCount, eFillDir,
+ eFillCmd, eDateCmd, nInc, 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 || bool(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);
+ }
+ }
+ }
+ }
+}
+
+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;
+ 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, 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.meType;
+ 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.mfValue;
+ 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.meType;
+ 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.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ nStart = aCell.mpFormula->GetValue();
+ break;
+ default:
+ nStart = 0.0;
+ }
+ }
+ else
+ nStart = 0.0;
+ if ( eFillCmd == FILL_LINEAR )
+ {
+ 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);
+
+ 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.meType)
+ {
+ case CELLTYPE_FORMULA:
+ {
+ FillFormulaVertical(
+ *rSrcCell.mpFormula, 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.meType)
+ {
+ case CELLTYPE_FORMULA:
+ {
+ for (rInner = nIMin; rInner <= nIMax; ++rInner)
+ {
+ if (rInner > nHiddenLast)
+ bHidden = HiddenRowColumn(this, rInner, bVertical, nHiddenLast);
+
+ if (bHidden)
+ continue;
+
+ FillFormula(rSrcCell.mpFormula, 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.meType == CELLTYPE_FORMULA)
+ {
+ FillFormulaVertical(*aSrcCell.mpFormula, 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.meType)
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ if (aSrcCell.meType == CELLTYPE_STRING)
+ aValue = aSrcCell.mpString->getString();
+ else
+ aValue = ScEditUtil::GetString(*aSrcCell.mpEditText, &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.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ double fVal;
+ if (bBooleanCell && ((fVal = aSrcCell.mfValue) == 0.0 || fVal == 1.0))
+ aCol[rCol].SetValue(rRow, aSrcCell.mfValue);
+ else if(bPercentCell)
+ aCol[rCol].SetValue(rRow, aSrcCell.mfValue + nDelta * 0.01); // tdf#89998 increment by 1% at a time
+ else
+ aCol[rCol].SetValue(rRow, aSrcCell.mfValue + 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.meType, 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.mpFormula, 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.meType == CELLTYPE_FORMULA || aSrcCell.meType == 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, 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.meType == CELLTYPE_VALUE || aSrcCell.meType == CELLTYPE_FORMULA))
+ {
+ double nStartVal;
+ if (aSrcCell.meType == CELLTYPE_VALUE)
+ nStartVal = aSrcCell.mfValue;
+ else
+ nStartVal = aSrcCell.mpFormula->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.meType;
+
+ 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.mfValue :
+ aSrcCell.mpFormula->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;
+ 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.mpString->getString();
+ else
+ aValue = ScEditUtil::GetString(*aSrcCell.mpEditText, &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;
+ 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, 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, 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 )
+{
+ 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.GetPool()));
+ pData->FillToItemSet(i, pPatternAttrs[i]->GetItemSet(), rDocument);
+ }
+
+ SCCOL nCol = nStartCol;
+ SCROW nRow = nStartRow;
+ sal_uInt16 nIndex = 0;
+ // Left top corner
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ // Left column
+ if (pData->IsEqualData(4, 8))
+ AutoFormatArea(nStartCol, nStartRow + 1, nStartCol, nEndRow - 1, *pPatternAttrs[4], nFormatNo);
+ else
+ {
+ nIndex = 4;
+ for (nRow = nStartRow + 1; nRow < nEndRow; nRow++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 4)
+ nIndex = 8;
+ else
+ nIndex = 4;
+ }
+ }
+ // Left bottom corner
+ nRow = nEndRow;
+ nIndex = 12;
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ // Right top corner
+ nCol = nEndCol;
+ nRow = nStartRow;
+ nIndex = 3;
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ // Right column
+ if (pData->IsEqualData(7, 11))
+ AutoFormatArea(nEndCol, nStartRow + 1, nEndCol, nEndRow - 1, *pPatternAttrs[7], nFormatNo);
+ else
+ {
+ nIndex = 7;
+ for (nRow = nStartRow + 1; nRow < nEndRow; nRow++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 7)
+ nIndex = 11;
+ else
+ nIndex = 7;
+ }
+ }
+ // Right bottom corner
+ nRow = nEndRow;
+ nIndex = 15;
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ nRow = nStartRow;
+ nIndex = 1;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 1)
+ nIndex = 2;
+ else
+ nIndex = 1;
+ }
+ // Bottom row
+ nRow = nEndRow;
+ nIndex = 13;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 13)
+ nIndex = 14;
+ else
+ nIndex = 13;
+ }
+ // Body
+ if ((pData->IsEqualData(5, 6)) && (pData->IsEqualData(9, 10)) && (pData->IsEqualData(5, 9)))
+ AutoFormatArea(nStartCol + 1, nStartRow + 1, nEndCol-1, nEndRow - 1, *pPatternAttrs[5], nFormatNo);
+ else
+ {
+ if ((pData->IsEqualData(5, 9)) && (pData->IsEqualData(6, 10)))
+ {
+ nIndex = 5;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ AutoFormatArea(nCol, nStartRow + 1, nCol, nEndRow - 1, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 5)
+ nIndex = 6;
+ else
+ nIndex = 5;
+ }
+ }
+ else
+ {
+ nIndex = 5;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ for (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;
+ } // for nCol
+ } // if not equal Column
+ } // if not all equal
+}
+
+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))
+ aCol[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: */