diff options
Diffstat (limited to 'sc')
-rw-r--r-- | sc/inc/document.hxx | 2 | ||||
-rw-r--r-- | sc/inc/table.hxx | 8 | ||||
-rw-r--r-- | sc/qa/unit/copy_paste_test.cxx | 60 | ||||
-rw-r--r-- | sc/qa/unit/data/ods/tdf88782_AutofillLinearNumbersInMergedCells.ods | bin | 0 -> 24452 bytes | |||
-rw-r--r-- | sc/source/core/data/table4.cxx | 171 | ||||
-rw-r--r-- | sc/source/ui/inc/cellmergeoption.hxx | 6 | ||||
-rw-r--r-- | sc/source/ui/inc/docfunc.hxx | 3 |
7 files changed, 234 insertions, 16 deletions
diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx index 62315b51429f..7fcb3af03fc6 100644 --- a/sc/inc/document.hxx +++ b/sc/inc/document.hxx @@ -1178,7 +1178,7 @@ public: * @return pointer to the double value stored in a numeric cell, or NULL * if the cell at specified position is not a numeric cell. */ - double* GetValueCell( const ScAddress& rPos ); + SC_DLLPUBLIC double* GetValueCell( const ScAddress& rPos ); SC_DLLPUBLIC svl::SharedStringPool& GetSharedStringPool(); const svl::SharedStringPool& GetSharedStringPool() const; diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx index 15a74d3fbaf8..3c2ab08e2b9c 100644 --- a/sc/inc/table.hxx +++ b/sc/inc/table.hxx @@ -1115,11 +1115,15 @@ private: sal_uLong nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd, double nStepValue, double nMaxValue, sal_uInt16 nMinDigits, - bool bAttribs, ScProgress* pProgress ); + bool bAttribs, ScProgress* pProgress, + bool bSkipOverlappedCells = false, + std::vector<sal_Int32>* pNonOverlappedCellIdx = nullptr); void FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, FillCmd& rCmd, FillDateCmd& rDateCmd, double& rInc, sal_uInt16& rMinDigits, - ScUserListData*& rListData, sal_uInt16& rListIndex); + ScUserListData*& rListData, sal_uInt16& rListIndex, + bool bHasFiltered, bool& rSkipOverlappedCells, + std::vector<sal_Int32>& rNonOverlappedCellIdx ); void FillAuto( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, sal_uLong nFillCount, FillDir eFillDir, ScProgress* pProgress ); diff --git a/sc/qa/unit/copy_paste_test.cxx b/sc/qa/unit/copy_paste_test.cxx index caeb05263a9c..325a883d212d 100644 --- a/sc/qa/unit/copy_paste_test.cxx +++ b/sc/qa/unit/copy_paste_test.cxx @@ -12,6 +12,8 @@ #include <comphelper/processfactory.hxx> #include <docsh.hxx> +#include <docfunc.hxx> +#include <cellmergeoption.hxx> #include <tabvwsh.hxx> #include <impex.hxx> #include <viewfunc.hxx> @@ -42,6 +44,7 @@ public: void testTdf53431_fillOnAutofilter(); void testTdf40993_fillMergedCells(); void testTdf43958_clickSelectOnMergedCells(); + void testTdf88782_autofillLinearNumbersInMergedCells(); CPPUNIT_TEST_SUITE(ScCopyPasteTest); CPPUNIT_TEST(testCopyPasteXLS); @@ -52,6 +55,7 @@ public: CPPUNIT_TEST(testTdf53431_fillOnAutofilter); CPPUNIT_TEST(testTdf40993_fillMergedCells); CPPUNIT_TEST(testTdf43958_clickSelectOnMergedCells); + CPPUNIT_TEST(testTdf88782_autofillLinearNumbersInMergedCells); CPPUNIT_TEST_SUITE_END(); private: @@ -620,6 +624,62 @@ void ScCopyPasteTest::testTdf43958_clickSelectOnMergedCells() lcl_clickAndCheckCurrentArea(2, 8, 2, 8); // C9 } +void ScCopyPasteTest::testTdf88782_autofillLinearNumbersInMergedCells() +{ + ScDocShellRef xDocSh = loadDocAndSetupModelViewController("tdf88782_AutofillLinearNumbersInMergedCells.", FORMAT_ODS, true); + ScDocument& rDoc = xDocSh->GetDocument(); + + // Get the document controller + ScTabViewShell* pView = xDocSh->GetBestViewShell(false); + CPPUNIT_ASSERT(pView != nullptr); + + // merge the yellow cells + ScCellMergeOption aMergeOptions(9, 11, 10, 13); //J12:K14 + aMergeOptions.maTabs.insert(0); + xDocSh->GetDocFunc().MergeCells(aMergeOptions, false, true, true, false); + + // fillauto numbers, these areas contains mostly merged cells + pView->FillAuto(FILL_TO_BOTTOM, 1, 8, 3, 14, 7); // B9:D15 -> B9:D22 + pView->FillAuto(FILL_TO_BOTTOM, 5, 8, 7, 17, 10); // F9:H18 -> F9:H28 + pView->FillAuto(FILL_TO_BOTTOM, 9, 8, 10, 13, 6); // J9:K14 -> J9:K20 + pView->FillAuto(FILL_TO_RIGHT, 9, 30, 16, 35, 8); //J31:Q36 -> J31:Y36 + pView->FillAuto(FILL_TO_LEFT, 9, 30, 16, 35, 8); //J31:Q36 -> B31:Q36 + + // compare the results of fill-down with the reference stored in the test file + // this compare the whole area blindly, for concrete test cases, check the test file + // the test file have instructions / explanations, so that is easy to understand + for (int nCol = 1; nCol <= 10; nCol++) { + for (int nRow = 8; nRow <= 27; nRow++) { + CellType nType1 = rDoc.GetCellType(ScAddress(nCol, nRow, 0)); + CellType nType2 = rDoc.GetCellType(ScAddress(nCol + 22, nRow, 0)); + double* pValue1 = rDoc.GetValueCell(ScAddress(nCol, nRow, 0)); + double* pValue2 = rDoc.GetValueCell(ScAddress(nCol + 22, nRow, 0)); + + CPPUNIT_ASSERT_EQUAL(nType1, nType2); + if (pValue2 != nullptr) + CPPUNIT_ASSERT_EQUAL(*pValue1, *pValue2); //cells with number value + else + CPPUNIT_ASSERT_EQUAL(pValue1, pValue2); //empty cells + } + } + + // compare the results of fill-right and left with the reference stored in the test file + for (int nCol = 1; nCol <= 24; nCol++) { + for (int nRow = 30; nRow <= 35; nRow++) { + CellType nType1 = rDoc.GetCellType(ScAddress(nCol, nRow, 0)); + CellType nType2 = rDoc.GetCellType(ScAddress(nCol, nRow + 16, 0)); + double* pValue1 = rDoc.GetValueCell(ScAddress(nCol, nRow, 0)); + double* pValue2 = rDoc.GetValueCell(ScAddress(nCol, nRow + 16, 0)); + + CPPUNIT_ASSERT_EQUAL(nType1, nType2); + if (pValue2 != nullptr) + CPPUNIT_ASSERT_EQUAL(*pValue1, *pValue2); + else + CPPUNIT_ASSERT_EQUAL(pValue1, pValue2); + } + } +} + ScCopyPasteTest::ScCopyPasteTest() : ScBootstrapFixture( "sc/qa/unit/data" ) { diff --git a/sc/qa/unit/data/ods/tdf88782_AutofillLinearNumbersInMergedCells.ods b/sc/qa/unit/data/ods/tdf88782_AutofillLinearNumbersInMergedCells.ods Binary files differnew file mode 100644 index 000000000000..99fea7087010 --- /dev/null +++ b/sc/qa/unit/data/ods/tdf88782_AutofillLinearNumbersInMergedCells.ods diff --git a/sc/source/core/data/table4.cxx b/sc/source/core/data/table4.cxx index f6058d51c921..e2d86480cc72 100644 --- a/sc/source/core/data/table4.cxx +++ b/sc/source/core/data/table4.cxx @@ -216,7 +216,9 @@ double approxDiff( double a, double 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) + ScUserListData*& rListData, sal_uInt16& rListIndex, + bool bHasFiltered, bool& rSkipOverlappedCells, + std::vector<sal_Int32>& rNonOverlappedCellIdx) { OSL_ENSURE( nCol1==nCol2 || nRow1==nRow2, "FillAnalyse: invalid range" ); @@ -224,6 +226,7 @@ void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, rMinDigits = 0; rListData = nullptr; rCmd = FILL_SIMPLE; + rSkipOverlappedCells = false; if ( nScFillModeMouseModifier & KEY_MOD1 ) return ; // Ctrl-key: Copy @@ -243,6 +246,100 @@ void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, 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 nColAkt = nCol1; + SCROW nRowAkt = 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(nColAkt, nRowAkt); + bool bOverlapped + = pPattern->GetItemSet().GetItemState(ATTR_MERGE_FLAG, false) == SfxItemState::SET + && pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped(); + + if (bOverlapped) + bHasOverlappedCells = true; + + if (!bOverlapped || GetCellValue(nColAkt, nRowAkt).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; + } + + nColAkt += nAddX; + nRowAkt += 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) + { + nColAkt = nCol1 + rNonOverlappedCellIdx[0] * nAddX; + nRowAkt = nRow1 + rNonOverlappedCellIdx[0] * nAddY; + ScRefCellValue aPrevCell, aAktCell; + aAktCell = GetCellValue(nColAkt, nRowAkt); + CellType eCellType = aAktCell.meType; + if (eCellType == CELLTYPE_VALUE) + { + // TODO: Check / handle special cases of number formats: like date, boolean + bool bVal = true; + if (nValueCount >= 2) + { + for (SCSIZE i = 1; i < nValueCount && bVal; i++) + { + aPrevCell = aAktCell; + nColAkt = nCol1 + rNonOverlappedCellIdx[i] * nAddX; + nRowAkt = nRow1 + rNonOverlappedCellIdx[i] * nAddY; + aAktCell = GetCellValue(nColAkt, nRowAkt); + if (aAktCell.meType == CELLTYPE_VALUE) + { + double nDiff = approxDiff(aAktCell.mfValue, aPrevCell.mfValue); + if (i == 1) + rInc = nDiff; + if (!::rtl::math::approxEqual(nDiff, rInc, 13)) + bVal = false; + } + else + bVal = false; + } + if (bVal) + { + rCmd = FILL_LINEAR; + rSkipOverlappedCells = true; + return; + } + } + } + else if (eCellType == CELLTYPE_STRING || eCellType == CELLTYPE_EDIT) + { + // TODO: check / handle if it is a sequence of userlist string + // or if the strings are composition of a number sequence and a constant string + } + } + } + + //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; @@ -795,14 +892,18 @@ void ScTable::FillAuto( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, sal_uInt16 nMinDigits; ScUserListData* pListData = nullptr; sal_uInt16 nListIndex; + bool nSkipOverlappedCells; + std::vector<sal_Int32> aNonOverlappedCellIdx; if (bVertical) FillAnalyse(static_cast<SCCOL>(nCol),nRow1, static_cast<SCCOL>(nCol),nRow2, eFillCmd,eDateCmd, - nInc,nMinDigits, pListData,nListIndex); + nInc, nMinDigits, pListData, nListIndex, + bHasFiltered, nSkipOverlappedCells, aNonOverlappedCellIdx); else FillAnalyse(nCol1,static_cast<SCROW>(nRow), nCol2,static_cast<SCROW>(nRow), eFillCmd,eDateCmd, - nInc,nMinDigits, pListData,nListIndex); + nInc, nMinDigits, pListData, nListIndex, + bHasFiltered, nSkipOverlappedCells, aNonOverlappedCellIdx); if (pListData) { @@ -860,12 +961,12 @@ void ScTable::FillAuto( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, FillSeries( static_cast<SCCOL>(nCol), nRow1, static_cast<SCCOL>(nCol), nRow2, nFillCount, eFillDir, eFillCmd, eDateCmd, nInc, nEndVal, nMinDigits, false, - pProgress ); + pProgress, nSkipOverlappedCells, &aNonOverlappedCellIdx); else FillSeries( nCol1, static_cast<SCROW>(nRow), nCol2, static_cast<SCROW>(nRow), nFillCount, eFillDir, eFillCmd, eDateCmd, nInc, nEndVal, nMinDigits, false, - pProgress ); + pProgress, nSkipOverlappedCells, &aNonOverlappedCellIdx); if (pProgress) nProgress = pProgress->GetState(); } @@ -919,8 +1020,15 @@ OUString ScTable::GetAutoFillPreview( const ScRange& rSource, SCCOL nEndX, SCROW sal_uInt16 nMinDigits; ScUserListData* pListData = nullptr; sal_uInt16 nListIndex; + bool nSkipOverlappedCells; + std::vector<sal_Int32> aNonOverlappedCellIdx; - FillAnalyse(nCol1,nRow1, nCol2,nRow2, eFillCmd,eDateCmd, nInc,nMinDigits, pListData,nListIndex); + // 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, nSkipOverlappedCells, aNonOverlappedCellIdx); if ( pListData ) // user defined list { @@ -1693,7 +1801,8 @@ inline bool isOverflow( const double& rVal, const double& rMax, const double& rS 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 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 @@ -1718,9 +1827,12 @@ void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, SCCOLROW nIEnd; SCCOLROW nISource; ScRange aFillRange; + sal_uLong nFillerCount; + std::vector<bool> aIsNonEmptyCell; if (bVertical) { + nFillerCount = (nRow2 - nRow1) + 1; nFillCount += (nRow2 - nRow1); if (nFillCount == 0) return; @@ -1745,6 +1857,7 @@ void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, } else { + nFillerCount = (nCol2 - nCol1) + 1; nFillCount += (nCol2 - nCol1); if (nFillCount == 0) return; @@ -1781,6 +1894,8 @@ void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, // 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<SCCOL>(nOStart)].GetCellValue(static_cast<SCROW>(nISource)); @@ -1841,6 +1956,34 @@ void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, // Source cell value. We need to clone the value since it may be inserted repeatedly. ScCellValue aSrcCell = aCol[nCol].GetCellValue(static_cast<SCROW>(nRow)); + // Maybe another source cell need to be searched, if the fill is going trough 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<SCROW>(nfirstValueIdx)); + else + aSrcCell = aCol[nfirstValueIdx].GetCellValue(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(); @@ -1899,14 +2042,24 @@ void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, 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) + if (!bError && bNonEmpty) { switch (eFillCmd) { @@ -1943,7 +2096,7 @@ void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, if (bError) aCol[nCol].SetError(static_cast<SCROW>(nRow), FormulaError::NoValue); - else if (!bOverflow) + else if (!bOverflow && bNonEmpty) aCol[nCol].SetValue(static_cast<SCROW>(nRow), nVal); if (bAttribs && !bEntireArea && !bOverflow) diff --git a/sc/source/ui/inc/cellmergeoption.hxx b/sc/source/ui/inc/cellmergeoption.hxx index ff4a7cbebcc9..937b9079960d 100644 --- a/sc/source/ui/inc/cellmergeoption.hxx +++ b/sc/source/ui/inc/cellmergeoption.hxx @@ -24,9 +24,9 @@ struct ScCellMergeOption bool mbCenter; explicit ScCellMergeOption(const ScRange& rRange); - explicit ScCellMergeOption(SCCOL nStartCol, SCROW nStartRow, - SCCOL nEndCol, SCROW nEndRow, - bool bCenter = false); + SC_DLLPUBLIC explicit ScCellMergeOption(SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, + bool bCenter = false); ScRange getSingleRange(SCTAB nTab) const; ScRange getFirstSingleRange() const; diff --git a/sc/source/ui/inc/docfunc.hxx b/sc/source/ui/inc/docfunc.hxx index e3a8117a5493..1984c0da440a 100644 --- a/sc/source/ui/inc/docfunc.hxx +++ b/sc/source/ui/inc/docfunc.hxx @@ -191,7 +191,8 @@ public: void ResizeMatrix( const ScRange& rOldRange, const ScAddress& rNewEnd ); - bool MergeCells( const ScCellMergeOption& rOption, bool bContents, + SC_DLLPUBLIC bool + MergeCells( const ScCellMergeOption& rOption, bool bContents, bool bRecord, bool bApi, bool bEmptyMergedCells = false ); bool UnmergeCells( const ScRange& rRange, bool bRecord, ScUndoRemoveMerge* pUndoRemoveMerge ); bool UnmergeCells( const ScCellMergeOption& rOption, bool bRecord, ScUndoRemoveMerge* pUndoRemoveMerge ); |