/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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( OUString(p[nNum]) ) ) 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(OUString(cLast)) ) ) { // number at the beginning nVal = rValue.copy( 0, nNum ).toInt32(); // 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( OUString(p[nNum]) ) ) nNum--; if ( p[nNum] == '-' || p[nNum] == '+' ) { nNum--; nSign = 1; } if ( nNum < nEnd - nSign ) { // number at the end nVal = rValue.copy( nNum + 1 ).toInt32(); // 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; comphelper::string::padToLength(aZero, nMinDigits - aStr.getLength(), '0'); aStr = aZero.makeStringAndClear() + aStr; } // 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(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(floor(log10(std::max(aa, ab)))) - 15; // Round the mean of the two subtractions return rtl::math::round((c + d) / 2, -std::max(nExp, nExpArg)); } } 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& 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(nRow2 - nRow1 + 1); } else { nAddX = 1; nAddY = 0; nCount = static_cast(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(aCurrCell.mfValue); if (eType != FILL_DAY) { nDDiff = aCurrDate.GetDay() - static_cast(aPrevDate.GetDay()); nMDiff = aCurrDate.GetMonth() - static_cast(aPrevDate.GetMonth()); nYDiff = aCurrDate.GetYear() - static_cast(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 = approxDiff(aCurrCell.mfValue, aPrevCell.mfValue); 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,aStr2; GetString(nColCurr, nRowCurr, aStr); rListData = const_cast(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; GetString(nColCurr, nRowCurr, aStr2); 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 ); bool bBooleanCell = (!bDate && 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(aDate1.GetDay()); tools::Long nMDiff = aDate2.GetMonth() - static_cast(aDate1.GetMonth()); tools::Long nYDiff = aDate2.GetYear() - static_cast(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( nCol + nAddX ); nRow = sal::static_int_cast( nRow + nAddY ); bool bVal = true; for (SCSIZE i=1; i(nVal); if ( eType == FILL_DAY ) { if ( aDate2-aDate1 != nCmpInc ) bVal = false; } else { nDDiff = aDate2.GetDay() - static_cast(aDate1.GetDay()); nMDiff = aDate2.GetMonth() - static_cast(aDate1.GetMonth()); nYDiff = aDate2.GetYear() - static_cast(aDate1.GetYear()); if ((nDDiff && !aDate1.IsEndOfMonth() && !aDate2.IsEndOfMonth()) || (nMDiff + 12 * nYDiff != nCmpInc)) bVal = false; } aDate1 = aDate2; nCol = sal::static_int_cast( nCol + nAddX ); nRow = sal::static_int_cast( 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 = approxDiff( nVal2, nVal1); nCol = sal::static_int_cast( nCol + nAddX ); nRow = sal::static_int_cast( nRow + nAddY ); bool bVal = true; for (SCSIZE i=1; iGetType(GetNumberFormat(nCol,nRow)) == SvNumFormatType::LOGICAL)) bVal = false; nVal1 = nVal2; } else bVal = false; nCol = sal::static_int_cast( nCol + nAddX ); nRow = sal::static_int_cast( 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, aStr); rListData = const_cast(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( nCol + nAddX ); nRow = sal::static_int_cast( nRow + nAddY ); for (SCSIZE i=1; iGetSubIndex(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( nCol + nAddX ); nRow = sal::static_int_cast( 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; GetString( nCol+nAddX, nRow+nAddY, aStr ); short nFlag2 = lcl_DecompValueString( aStr, nVal2, &rMinDigits ); if ( nFlag1 == nFlag2 ) { rInc = approxDiff( nVal2, nVal1); nCol = sal::static_int_cast( nCol + nAddX ); nRow = sal::static_int_cast( nRow + nAddY ); bool bVal = true; for (SCSIZE i=1; i( nCol + nAddX ); nRow = sal::static_int_cast( 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_uLong 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_uLong nIMin = nIStart; sal_uLong nIMax = nIEnd; PutInOrder(nIMin,nIMax); bool bHasFiltered = IsDataFiltered(aFillRange); if (!bHasFiltered) { if (bVertical) DeleteArea(nCol1, static_cast(nIMin), nCol2, static_cast(nIMax), InsertDeleteFlags::AUTOFILL); else DeleteArea(static_cast(nIMin), nRow1, static_cast(nIMax), nRow2, InsertDeleteFlags::AUTOFILL); } sal_uLong 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_uLong nActFormCnt = 0; for (rOuter = nOStart; rOuter <= nOEnd; rOuter++) { sal_uLong nMaxFormCnt = 0; // for formulas // transfer attributes const ScPatternAttr* pSrcPattern = nullptr; const ScStyleSheet* pStyleSheet = nullptr; SCCOLROW nAtSrc = nISrcStart; std::unique_ptr 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 = aCol[nCol].GetPattern(static_cast(nAtSrc)); else // rInner&:=nCol, rOuter&:=nRow pSrcPattern = aCol[nAtSrc].GetPattern(static_cast(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(std::min( nIStart, nIEnd )); SCROW nY2 = static_cast(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(nCol), static_cast(nRow), static_cast(nCol), static_cast(nRow), InsertDeleteFlags::AUTOFILL); if ( pSrcPattern != aCol[nCol].GetPattern( static_cast(nRow) ) ) { // Transfer template too //TODO: Merge ApplyPattern to AttrArray ?? if ( pStyleSheet ) aCol[nCol].ApplyStyle( static_cast(nRow), pStyleSheet ); // Use ApplyPattern instead of SetPattern to keep old MergeFlags if ( pNewPattern ) aCol[nCol].ApplyPattern( static_cast(nRow), *pNewPattern ); else aCol[nCol].ApplyPattern( static_cast(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 aNonOverlappedCellIdx; if (bVertical) FillAnalyse(static_cast(nCol),nRow1, static_cast(nCol),nRow2, eFillCmd,eDateCmd, nInc, nMinDigits, pListData, nListIndex, bHasFiltered, bSkipOverlappedCells, aNonOverlappedCellIdx); else FillAnalyse(nCol1,static_cast(nRow), nCol2,static_cast(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 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) GetString(rOuter, nLastValueIdx, aStr); else GetString(nLastValueIdx, rOuter, aStr); 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(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(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(nCol), nRow1, static_cast(nCol), nRow2, nFillCount, eFillDir, eFillCmd, eDateCmd, nInc, nEndVal, nMinDigits, false, pProgress, bSkipOverlappedCells, &aNonOverlappedCellIdx); else FillSeries( nCol1, static_cast(nRow), nCol2, static_cast(nRow), nFillCount, eFillDir, eFillCmd, eDateCmd, nInc, nEndVal, nMinDigits, false, pProgress, bSkipOverlappedCells, &aNonOverlappedCellIdx); if (pProgress) nProgress = pProgress->GetState(); } nActFormCnt += nMaxFormCnt; } } 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_uLong 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(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(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 ) { FillCmd eFillCmd; FillDateCmd eDateCmd; double nInc; sal_uInt16 nMinDigits; ScUserListData* pListData = nullptr; sal_uInt16 nListIndex; bool bSkipOverlappedCells; std::vector 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_uLong nSub = nSrcCount - 1; // nListIndex is from last source entry while ( nIndex < sal::static_int_cast(nSub) ) nIndex += nListCount; sal_uLong nPos = ( nListIndex + nIndex - nSub ) % nListCount; aValue = pListData->GetSubStr(sal::static_int_cast(nPos)); } } else if ( eFillCmd == FILL_SIMPLE ) // fill with pattern/sample { if ((eFillDir == FILL_TO_BOTTOM)||(eFillDir == FILL_TO_TOP)) { tools::Long nBegin = 0; tools::Long nEnd = 0; if (nEndY > nRow1) { nBegin = nRow2+1; nEnd = nEndY; } else { nBegin = nEndY; nEnd = nRow1 -1; } tools::Long nNonFiltered = CountNonFilteredRows(nBegin, nEnd); tools::Long nFiltered = nEnd + 1 - nBegin - nNonFiltered; if (nIndex > 0) nIndex = nIndex - nFiltered; else nIndex = nIndex + nFiltered; } tools::Long nPosIndex = nIndex; while ( nPosIndex < 0 ) nPosIndex += nSrcCount; sal_uLong nPos = nPosIndex % nSrcCount; SCCOL nSrcX = nCol1; SCROW nSrcY = nRow1; if ( eFillDir == FILL_TO_TOP || eFillDir == FILL_TO_BOTTOM ) nSrcY = sal::static_int_cast( nSrcY + static_cast(nPos) ); else nSrcX = sal::static_int_cast( nSrcX + static_cast(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(nDelta) * 0.01; } else if (nVal == 0.0 || nVal == 1.0) { bool bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL); if (!bBooleanCell) nVal += static_cast(nDelta); } else { nVal += static_cast(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(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(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(nStart) ); aValue = lcl_ValueString( static_cast(nStart), nMinDigits ) + aValue; } else { if ( nHeadNoneTail == 2 && nStart >= 0 ) // Put back the '+' aValue += "+"; aValue += lcl_ValueString( static_cast(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(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(nMonth)); aDate.SetYear(static_cast(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(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(nRowColumn), nullptr, &nLast); rLastPos = nLast; } return bHidden; } } void ScTable::FillFormulaVertical( const ScFormulaCell& rSrcCell, SCCOLROW& rInner, SCCOL nCol, SCROW nRow1, SCROW nRow2, ScProgress* pProgress, sal_uLong& 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 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(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_uLong& 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_uLong nActFormCnt, sal_uLong nMaxFormCnt, bool bHasFiltered, bool bVertical, bool bPositive, ScProgress* pProgress, sal_uLong& rProgress ) { SCCOLROW nSource = nISrcStart; double nDelta; if ( nScFillModeMouseModifier & KEY_MOD1 ) nDelta = 0.0; else if ( bPositive ) nDelta = 1.0; else nDelta = -1.0; sal_uLong 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 nColHiddenLast = -1; SCROW nRowHiddenLast = -1; rInner = nIStart; while (true) // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes { if (rCol > nColHiddenLast) bColHidden = ColHidden(rCol, nullptr, &nColHiddenLast); if (rRow > nRowHiddenLast) bRowHidden = RowHidden(rRow, nullptr, &nRowHiddenLast); if (!bColHidden && !bRowHidden) { if ( bGetCell ) { if (bVertical) // rInner&:=nRow, rOuter&:=nCol { aSrcCell = aCol[rCol].GetCellValue(nSource); if (nISrcStart == nISrcEnd && aSrcCell.meType == CELLTYPE_FORMULA) { FillFormulaVertical(*aSrcCell.mpFormula, rInner, rCol, nIStart, nIEnd, pProgress, rProgress); return; } const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType( aCol[rCol].GetNumberFormat( rDocument.GetNonThreadedContext(), nSource)); bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL); bPercentCell = (nFormatType == SvNumFormatType::PERCENT); } else // rInner&:=nCol, rOuter&:=nRow { aSrcCell = aCol[nSource].GetCellValue(rRow); const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType( aCol[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(nDelta); else nNextValue = nStringValue + static_cast(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; break; 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_uLong nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd, double nStepValue, double nMaxValue, sal_uInt16 nArgMinDigits, bool bAttribs, ScProgress* pProgress, bool bSkipOverlappedCells, std::vector* 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_uLong nFillerCount; std::vector 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 = aCol[static_cast(nOStart)].GetCellValue(static_cast(nISource)); else aSrcCell = aCol[static_cast(nISource)].GetCellValue(static_cast(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(nIMin), nCol2, static_cast(nIMax), nDel); else DeleteArea(static_cast(nIMin), nRow1, static_cast(nIMax), nRow2, nDel); } sal_uLong nProgress = 0; if (pProgress) nProgress = pProgress->GetState(); // Perform the fill once per each 'outer' position i.e. one per column // when filling vertically. sal_uLong nActFormCnt = 0; 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 = aCol[nCol].GetCellValue(static_cast(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 = aCol[nOStart].GetCellValue(static_cast(nFirstValueIdx)); else aSrcCell = aCol[nFirstValueIdx].GetCellValue(static_cast(nOStart)); } const ScPatternAttr* pSrcPattern = aCol[nCol].GetPattern(static_cast(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(nIMin), static_cast(nIMax), *pSrcPattern, rCondFormatIndex); } else if (eFillCmd == FILL_SIMPLE) { assert(bIsFiltered); for(SCROW nAtRow = static_cast(nIMin); nAtRow <= static_cast(nIMax); ++nAtRow) { if(!RowHidden(nAtRow)) { SetPatternAreaCondFormat( nCol, nAtRow, nAtRow, *pSrcPattern, rCondFormatIndex); } } } } else if (bEntireArea || eFillCmd == FILL_SIMPLE) { for (SCCOL nAtCol = static_cast(nIMin); nAtCol <= sal::static_int_cast(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(++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); } if (bError) aCol[nCol].SetError(static_cast(nRow), FormulaError::NoValue); else if (!bOverflow && bNonEmpty) aCol[nCol].SetValue(static_cast(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(nStringValue); double nVal = nStartVal; tools::Long nIndex = 0; bool bError = false; bool bOverflow = false; bool bNonEmpty = true; bool bIsOrdinalSuffix = aValue == ScGlobal::GetOrdinalSuffix( static_cast(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(++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(nRow), FormulaError::NoValue); else if (!bOverflow && bNonEmpty) { nStringValue = static_cast(nVal); if ( nHeadNoneTail < 0 ) { setSuffixCell( aCol[nCol], static_cast(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(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 ); } ++nActFormCnt; } } void ScTable::Fill( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, sal_uLong 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 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: */