sc/inc/column.hxx | 2 sc/inc/document.hxx | 4 sc/inc/global.hxx | 9 + sc/inc/table.hxx | 3 sc/inc/tokenarray.hxx | 7 + sc/qa/unit/ucalc.cxx | 199 +++++++++++++++++++++++++++++++----- sc/source/core/data/column.cxx | 8 - sc/source/core/data/documen2.cxx | 155 +++++++++++++++++++--------- sc/source/core/data/document.cxx | 10 - sc/source/core/data/formulacell.cxx | 6 + sc/source/core/data/table2.cxx | 4 sc/source/core/tool/token.cxx | 38 ++++++ 12 files changed, 357 insertions(+), 88 deletions(-)
New commits: commit 54d971bc0b527a3f2679f558fecb9f253a50e387 Author: Tomaž Vajngerl <[email protected]> AuthorDate: Fri Feb 27 18:09:18 2026 +0900 Commit: Miklos Vajna <[email protected]> CommitDate: Mon Mar 2 09:17:22 2026 +0100 sc: Fix cross-tab references when using OverwriteContent When we overwrite the content of a tab, we need to handle the cross-tab references in the document, or the formulas will refer to the wrong tab. Introduce TableContentCopier, which handles copying content for CopyTab and OverwriteContent. The class is needed so we don't duplicate almost the same algorithm and still keep the code readable. Add a new AdjustCrossSheetRefs flag to ScCloneFlags to handle the cross-tab references. Expose ScCloneFlags as a parameter in ScTable::CopyToTable, which makes it possible to set the new flag from the outside. Previously a bool was used, which was converted to a ScCloneFlags value. Change-Id: I8d5886b03c68421f78fc57c42af6ead493eab5b9 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/200619 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/sc/inc/column.hxx b/sc/inc/column.hxx index 22e3ad1e284c..a6eb2015e77c 100644 --- a/sc/inc/column.hxx +++ b/sc/inc/column.hxx @@ -378,7 +378,7 @@ public: void CopyToColumn( sc::CopyToDocContext& rCxt, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked, ScColumn& rColumn, const ScMarkData* pMarkData = nullptr, bool bAsLink = false, - bool bGlobalNamesToLocal = false ) const; + ScCloneFlags nCloneFlags = ScCloneFlags::Default ) const; void UndoToColumn( sc::CopyToDocContext& rCxt, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked, diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx index c68d550fddfd..81244baa0536 100644 --- a/sc/inc/document.hxx +++ b/sc/inc/document.hxx @@ -234,6 +234,8 @@ namespace sc { typedef std::map<OUString, BitmapEx> IconSetBitmapMap; +class TableContentCopier; + } namespace com::sun::star { @@ -368,6 +370,7 @@ friend class ScColumn; friend struct ScRefCellValue; friend class ScDocumentImport; friend class sc::EditTextIterator; +friend class sc::TableContentCopier; friend struct ScMutationGuard; friend struct ScMutationDisable; @@ -1071,7 +1074,6 @@ public: bool MoveTab( SCTAB nOldPos, SCTAB nNewPos, ScProgress* pProgress = nullptr ); SC_DLLPUBLIC bool CopyTab( SCTAB nOldPos, SCTAB nNewPos, const ScMarkData* pOnlyMarked = nullptr ); - void CopyContent(SCTAB nSourceTabNo, SCTAB nTargetTabNo, const ScMarkData* pOnlyMarked, sc::RefUpdateInsertTabContext& rContext); bool OverwriteContent(SCTAB nSourceTabNo, SCTAB nTargetTabNo); SC_DLLPUBLIC bool TransferTab(ScDocument& rSrcDoc, SCTAB nSrcPos, SCTAB nDestPos, diff --git a/sc/inc/global.hxx b/sc/inc/global.hxx index 6e986267c8a6..4692dc791220 100644 --- a/sc/inc/global.hxx +++ b/sc/inc/global.hxx @@ -263,11 +263,16 @@ enum class ScCloneFlags{ /** If set, global named expressions will be converted to sheet-local named expressions. */ - NamesToLocal = 0x0004 + NamesToLocal = 0x0004, + + /** If set, cross-sheet references will be adjusted during copying to + compensate for the difference of the tab index between source and + destination cells. */ + AdjustCrossSheetRefs = 0x0008 }; namespace o3tl { - template<> struct typed_flags<ScCloneFlags> : is_typed_flags<ScCloneFlags, 0x0007> {}; + template<> struct typed_flags<ScCloneFlags> : is_typed_flags<ScCloneFlags, 0x000f> {}; } enum CellType diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx index 92a6e76d4bbd..9a8d5cf37390 100644 --- a/sc/inc/table.hxx +++ b/sc/inc/table.hxx @@ -288,6 +288,7 @@ friend class sc::ColumnSpanSet; friend class sc::RangeColumnSpanSet; friend class sc::EditTextIterator; friend class sc::FormulaGroupAreaListener; +friend class sc::TableContentCopier; public: ScTable( ScDocument& rDoc, SCTAB nNewTab, const OUString& rNewName, @@ -584,7 +585,7 @@ public: sc::CopyToDocContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked, ScTable* pDestTab, const ScMarkData* pMarkData, bool bAsLink, bool bColRowFlags, - bool bGlobalNamesToLocal, bool bCopyCaptions ); + ScCloneFlags nCloneFlags, bool bCopyCaptions ); void CopyCaptionsToTable( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScTable* pDestTab, bool bCloneCaption ); diff --git a/sc/inc/tokenarray.hxx b/sc/inc/tokenarray.hxx index 08938553ea4f..13d3230a9700 100644 --- a/sc/inc/tokenarray.hxx +++ b/sc/inc/tokenarray.hxx @@ -151,6 +151,13 @@ public: */ void AdjustAbsoluteRefs( const ScDocument& rOldDoc, const ScAddress& rOldPos, const ScAddress& rNewPos, bool bCheckCopyArea ); + /** Adjust relative tab references when copying a formula between tabs when + overwriting a tab. Non-zero relative tab offsets are decremented by + input delta value so they still resolve to the original target sheet + from the new position. Same-sheet relative refs with offset 0 are left + unchanged. */ + void AdjustRelativeTabRefs(SCTAB nDelta); + /** When copying a sheet-local named expression, move sheet references that point to the originating sheet to point to the new sheet instead. */ diff --git a/sc/qa/unit/ucalc.cxx b/sc/qa/unit/ucalc.cxx index c793d093b5a5..70227e300eb9 100644 --- a/sc/qa/unit/ucalc.cxx +++ b/sc/qa/unit/ucalc.cxx @@ -7151,56 +7151,203 @@ CPPUNIT_TEST_FIXTURE(Test, testDocumentModelAccessor_getDocumentCurrencies) CPPUNIT_TEST_FIXTURE(Test, testOverwriteContent) { - m_pDoc->InsertTab(0, u"Tab1"_ustr); - m_pDoc->InsertTab(1, u"Tab2"_ustr); - m_pDoc->InsertTab(2, u"Tab3"_ustr); + m_pDoc->InsertTab(0, u"Tab0"_ustr); + m_pDoc->InsertTab(1, u"Tab1"_ustr); + m_pDoc->InsertTab(2, u"Tab2"_ustr); + m_pDoc->InsertTab(3, u"Tab3"_ustr); + + // For cross table formula checking (forward and backward) + m_pDoc->SetValue(ScAddress(0, 0, 0), 99.0); // Tab0.A1 + m_pDoc->SetValue(ScAddress(0, 0, 3), 66.0); // Tab3.A1 std::vector<std::vector<OUString>> aData = { - { u"C1"_ustr, u"C2"_ustr, u"C3"_ustr }, - { u"7"_ustr, u"1"_ustr, u"A"_ustr }, - { u"3"_ustr, u"2"_ustr, u""_ustr }, - { u"2"_ustr, u"4"_ustr, u"B"_ustr }, - { u"4"_ustr, u"3"_ustr, u"B"_ustr } + { u"Column 1"_ustr, u"Column 2"_ustr, u"Column 3"_ustr, }, + { u"7"_ustr, u"A"_ustr, u"=A2+5"_ustr }, + { u"=2+2"_ustr, u"C"_ustr, u"=Tab3.A1"_ustr }, // forward cross-tab reference + { u"=A3-2"_ustr, u"D"_ustr, u"=SUM(A2:A6)"_ustr }, + { u"3"_ustr, u"B"_ustr, u"=C3-60"_ustr }, + { u"5"_ustr, u"E"_ustr, u"=Tab0.A1"_ustr }, // backward cross-tab reference }; - // Populate cells. for (size_t i = 0; i < aData.size(); ++i) { for (size_t j = 0; j < aData[0].size(); ++j) { if (!aData[i][j].isEmpty()) { - m_pDoc->SetString(j, i, 0, aData[i][j]); + m_pDoc->SetString(j, i, 1, aData[i][j]); } } } + m_pDoc->CalcAll(); - CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); - CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(0, 1, 1))); + // Verify Tab1 values and formulas before overwrite + { + CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(0, 1, 1))); + CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(0, 2, 1))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 3, 1))); // A3-2=2 + CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0, 4, 1))); + CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(0, 5, 1))); + + CPPUNIT_ASSERT_EQUAL(u"A"_ustr, m_pDoc->GetString(ScAddress(1, 1, 1))); + CPPUNIT_ASSERT_EQUAL(u"C"_ustr, m_pDoc->GetString(ScAddress(1, 2, 1))); + CPPUNIT_ASSERT_EQUAL(u"D"_ustr, m_pDoc->GetString(ScAddress(1, 3, 1))); + CPPUNIT_ASSERT_EQUAL(u"B"_ustr, m_pDoc->GetString(ScAddress(1, 4, 1))); + CPPUNIT_ASSERT_EQUAL(u"E"_ustr, m_pDoc->GetString(ScAddress(1, 5, 1))); + + CPPUNIT_ASSERT_EQUAL(12.0, m_pDoc->GetValue(ScAddress(2, 1, 1))); // =A2+5=12 + CPPUNIT_ASSERT_EQUAL(66.0, m_pDoc->GetValue(ScAddress(2, 2, 1))); // =Tab3.A1 + CPPUNIT_ASSERT_EQUAL(21.0, m_pDoc->GetValue(ScAddress(2, 3, 1))); // =SUM(A2:A6) + CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(2, 4, 1))); // =C3-60 + CPPUNIT_ASSERT_EQUAL(99.0, m_pDoc->GetValue(ScAddress(2, 5, 1))); // =Tab0.A1 + } + + // Tab2 should be empty CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(0, 1, 2))); + CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(1, 2, 2))); + CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(2, 3, 2))); - m_pDoc->OverwriteContent(0, 1); + // Overwrite Tab2 with Tab1 content + m_pDoc->OverwriteContent(1, 2); - CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); - CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(0, 1, 1))); - CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(0, 1, 2))); + // Tab1 should be unchanged + { + CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(0, 1, 1))); + CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(0, 2, 1))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 3, 1))); + CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0, 4, 1))); + CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(0, 5, 1))); + + CPPUNIT_ASSERT_EQUAL(u"A"_ustr, m_pDoc->GetString(ScAddress(1, 1, 1))); + CPPUNIT_ASSERT_EQUAL(u"C"_ustr, m_pDoc->GetString(ScAddress(1, 2, 1))); + CPPUNIT_ASSERT_EQUAL(u"D"_ustr, m_pDoc->GetString(ScAddress(1, 3, 1))); + CPPUNIT_ASSERT_EQUAL(u"B"_ustr, m_pDoc->GetString(ScAddress(1, 4, 1))); + CPPUNIT_ASSERT_EQUAL(u"E"_ustr, m_pDoc->GetString(ScAddress(1, 5, 1))); + + CPPUNIT_ASSERT_EQUAL(12.0, m_pDoc->GetValue(ScAddress(2, 1, 1))); + CPPUNIT_ASSERT_EQUAL(66.0, m_pDoc->GetValue(ScAddress(2, 2, 1))); + CPPUNIT_ASSERT_EQUAL(21.0, m_pDoc->GetValue(ScAddress(2, 3, 1))); + CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(2, 4, 1))); + CPPUNIT_ASSERT_EQUAL(99.0, m_pDoc->GetValue(ScAddress(2, 5, 1))); + } + + // Tab2 values should match Tab1 and the formulas adjusted if needed + { + CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(0, 1, 2))); + CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(0, 2, 2))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 3, 2))); + CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0, 4, 2))); + CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(0, 5, 2))); + + CPPUNIT_ASSERT_EQUAL(u"A"_ustr, m_pDoc->GetString(ScAddress(1, 1, 2))); + CPPUNIT_ASSERT_EQUAL(u"C"_ustr, m_pDoc->GetString(ScAddress(1, 2, 2))); + CPPUNIT_ASSERT_EQUAL(u"D"_ustr, m_pDoc->GetString(ScAddress(1, 3, 2))); + CPPUNIT_ASSERT_EQUAL(u"B"_ustr, m_pDoc->GetString(ScAddress(1, 4, 2))); + CPPUNIT_ASSERT_EQUAL(u"E"_ustr, m_pDoc->GetString(ScAddress(1, 5, 2))); + + CPPUNIT_ASSERT_EQUAL(12.0, m_pDoc->GetValue(ScAddress(2, 1, 2))); + CPPUNIT_ASSERT_EQUAL(66.0, m_pDoc->GetValue(ScAddress(2, 2, 2))); + CPPUNIT_ASSERT_EQUAL(21.0, m_pDoc->GetValue(ScAddress(2, 3, 2))); + CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(2, 4, 2))); + CPPUNIT_ASSERT_EQUAL(99.0, m_pDoc->GetValue(ScAddress(2, 5, 2))); + } - m_pDoc->SetString(0, 1, 0, u"9.0"_ustr); + // Check Tab2 formulas were copied correctly + { + CPPUNIT_ASSERT_EQUAL(u"=2+2"_ustr, m_pDoc->GetFormula(0, 2, 2)); + CPPUNIT_ASSERT_EQUAL(u"=A3-2"_ustr, m_pDoc->GetFormula(0, 3, 2)); + CPPUNIT_ASSERT_EQUAL(u"=A2+5"_ustr, m_pDoc->GetFormula(2, 1, 2)); + CPPUNIT_ASSERT_EQUAL(u"='Tab3'.A1"_ustr, m_pDoc->GetFormula(2, 2, 2)); + CPPUNIT_ASSERT_EQUAL(u"=SUM(A2:A6)"_ustr, m_pDoc->GetFormula(2, 3, 2)); + CPPUNIT_ASSERT_EQUAL(u"=C3-60"_ustr, m_pDoc->GetFormula(2, 4, 2)); + CPPUNIT_ASSERT_EQUAL(u"=Tab0.A1"_ustr, m_pDoc->GetFormula(2, 5, 2)); + } - CPPUNIT_ASSERT_EQUAL(9.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); - CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(0, 1, 1))); - CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(0, 1, 2))); + // Change Tab1 and see if it affects Tab2 + m_pDoc->SetValue(ScAddress(0, 1, 1), 10.0); // 7 -> 10 + m_pDoc->CalcAll(); + // Check Tab 1 + { + CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(0, 1, 1))); + CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(0, 2, 1))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 3, 1))); + CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0, 4, 1))); + CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(0, 5, 1))); + + CPPUNIT_ASSERT_EQUAL(u"A"_ustr, m_pDoc->GetString(ScAddress(1, 1, 1))); + CPPUNIT_ASSERT_EQUAL(u"C"_ustr, m_pDoc->GetString(ScAddress(1, 2, 1))); + CPPUNIT_ASSERT_EQUAL(u"D"_ustr, m_pDoc->GetString(ScAddress(1, 3, 1))); + CPPUNIT_ASSERT_EQUAL(u"B"_ustr, m_pDoc->GetString(ScAddress(1, 4, 1))); + CPPUNIT_ASSERT_EQUAL(u"E"_ustr, m_pDoc->GetString(ScAddress(1, 5, 1))); + + CPPUNIT_ASSERT_EQUAL(15.0, m_pDoc->GetValue(ScAddress(2, 1, 1))); + CPPUNIT_ASSERT_EQUAL(66.0, m_pDoc->GetValue(ScAddress(2, 2, 1))); + CPPUNIT_ASSERT_EQUAL(24.0, m_pDoc->GetValue(ScAddress(2, 3, 1))); // =SUM(A2:A6)=10+4+2+3+5 + CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(2, 4, 1))); + CPPUNIT_ASSERT_EQUAL(99.0, m_pDoc->GetValue(ScAddress(2, 5, 1))); + } - m_pDoc->OverwriteContent(0, 1); + // Check Tab 2 + { + CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(0, 1, 2))); + CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(0, 2, 2))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 3, 2))); + CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0, 4, 2))); + CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(0, 5, 2))); + + CPPUNIT_ASSERT_EQUAL(u"A"_ustr, m_pDoc->GetString(ScAddress(1, 1, 2))); + CPPUNIT_ASSERT_EQUAL(u"C"_ustr, m_pDoc->GetString(ScAddress(1, 2, 2))); + CPPUNIT_ASSERT_EQUAL(u"D"_ustr, m_pDoc->GetString(ScAddress(1, 3, 2))); + CPPUNIT_ASSERT_EQUAL(u"B"_ustr, m_pDoc->GetString(ScAddress(1, 4, 2))); + CPPUNIT_ASSERT_EQUAL(u"E"_ustr, m_pDoc->GetString(ScAddress(1, 5, 2))); + + CPPUNIT_ASSERT_EQUAL(12.0, m_pDoc->GetValue(ScAddress(2, 1, 2))); + CPPUNIT_ASSERT_EQUAL(66.0, m_pDoc->GetValue(ScAddress(2, 2, 2))); + CPPUNIT_ASSERT_EQUAL(21.0, m_pDoc->GetValue(ScAddress(2, 3, 2))); + CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(2, 4, 2))); + CPPUNIT_ASSERT_EQUAL(99.0, m_pDoc->GetValue(ScAddress(2, 5, 2))); + } - CPPUNIT_ASSERT_EQUAL(9.0, m_pDoc->GetValue(ScAddress(0, 1, 0))); - CPPUNIT_ASSERT_EQUAL(9.0, m_pDoc->GetValue(ScAddress(0, 1, 1))); - CPPUNIT_ASSERT_EQUAL(0.0, m_pDoc->GetValue(ScAddress(0, 1, 2))); + // Overwrite Tab2 again with updated Tab1 + m_pDoc->OverwriteContent(1, 2); - m_pDoc->DeleteTab(0); - m_pDoc->DeleteTab(1); + // Check Tab 2 + { + CPPUNIT_ASSERT_EQUAL(10.0, m_pDoc->GetValue(ScAddress(0, 1, 2))); + CPPUNIT_ASSERT_EQUAL(4.0, m_pDoc->GetValue(ScAddress(0, 2, 2))); + CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(0, 3, 2))); + CPPUNIT_ASSERT_EQUAL(3.0, m_pDoc->GetValue(ScAddress(0, 4, 2))); + CPPUNIT_ASSERT_EQUAL(5.0, m_pDoc->GetValue(ScAddress(0, 5, 2))); + + CPPUNIT_ASSERT_EQUAL(u"A"_ustr, m_pDoc->GetString(ScAddress(1, 1, 2))); + CPPUNIT_ASSERT_EQUAL(u"C"_ustr, m_pDoc->GetString(ScAddress(1, 2, 2))); + CPPUNIT_ASSERT_EQUAL(u"D"_ustr, m_pDoc->GetString(ScAddress(1, 3, 2))); + CPPUNIT_ASSERT_EQUAL(u"B"_ustr, m_pDoc->GetString(ScAddress(1, 4, 2))); + CPPUNIT_ASSERT_EQUAL(u"E"_ustr, m_pDoc->GetString(ScAddress(1, 5, 2))); + + CPPUNIT_ASSERT_EQUAL(15.0, m_pDoc->GetValue(ScAddress(2, 1, 2))); + CPPUNIT_ASSERT_EQUAL(66.0, m_pDoc->GetValue(ScAddress(2, 2, 2))); + CPPUNIT_ASSERT_EQUAL(24.0, m_pDoc->GetValue(ScAddress(2, 3, 2))); + CPPUNIT_ASSERT_EQUAL(6.0, m_pDoc->GetValue(ScAddress(2, 4, 2))); + CPPUNIT_ASSERT_EQUAL(99.0, m_pDoc->GetValue(ScAddress(2, 5, 2))); + } + + // Check formulas are still the same + { + CPPUNIT_ASSERT_EQUAL(u"=2+2"_ustr, m_pDoc->GetFormula(0, 2, 2)); + CPPUNIT_ASSERT_EQUAL(u"=A3-2"_ustr, m_pDoc->GetFormula(0, 3, 2)); + CPPUNIT_ASSERT_EQUAL(u"=A2+5"_ustr, m_pDoc->GetFormula(2, 1, 2)); + CPPUNIT_ASSERT_EQUAL(u"='Tab3'.A1"_ustr, m_pDoc->GetFormula(2, 2, 2)); + CPPUNIT_ASSERT_EQUAL(u"=SUM(A2:A6)"_ustr, m_pDoc->GetFormula(2, 3, 2)); + CPPUNIT_ASSERT_EQUAL(u"=C3-60"_ustr, m_pDoc->GetFormula(2, 4, 2)); + CPPUNIT_ASSERT_EQUAL(u"=Tab0.A1"_ustr, m_pDoc->GetFormula(2, 5, 2)); + } + + m_pDoc->DeleteTab(3); m_pDoc->DeleteTab(2); + m_pDoc->DeleteTab(1); + m_pDoc->DeleteTab(0); } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sc/source/core/data/column.cxx b/sc/source/core/data/column.cxx index 15060d98c1b3..1b6db61f6afa 100644 --- a/sc/source/core/data/column.cxx +++ b/sc/source/core/data/column.cxx @@ -1349,14 +1349,14 @@ class CopyByCloneHandler public: CopyByCloneHandler(const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos, - InsertDeleteFlags nCopyFlags, svl::SharedStringPool* pSharedStringPool, bool bGlobalNamesToLocal) : + InsertDeleteFlags nCopyFlags, svl::SharedStringPool* pSharedStringPool, ScCloneFlags nCloneFlags) : mrSrcCol(rSrcCol), mrDestCol(rDestCol), mpDestPos(pDestPos), mpSharedStringPool(pSharedStringPool), mnCopyFlags(nCopyFlags), meListenType(sc::SingleCellListening), - mnFormulaCellCloneFlags(bGlobalNamesToLocal ? ScCloneFlags::NamesToLocal : ScCloneFlags::Default) + mnFormulaCellCloneFlags(nCloneFlags) { if (mpDestPos) maDestPos = *mpDestPos; @@ -1522,7 +1522,7 @@ public: void ScColumn::CopyToColumn( sc::CopyToDocContext& rCxt, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked, ScColumn& rColumn, - const ScMarkData* pMarkData, bool bAsLink, bool bGlobalNamesToLocal) const + const ScMarkData* pMarkData, bool bAsLink, ScCloneFlags nCloneFlags) const { if (bMarked) { @@ -1579,7 +1579,7 @@ void ScColumn::CopyToColumn( (GetDoc().GetPool() != rColumn.GetDoc().GetPool()) ? &rColumn.GetDoc().GetSharedStringPool() : nullptr; CopyByCloneHandler aFunc(*this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol), nFlags, - pSharedStringPool, bGlobalNamesToLocal); + pSharedStringPool, nCloneFlags); aFunc.setStartListening(rCxt.isStartListening()); sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2); } diff --git a/sc/source/core/data/documen2.cxx b/sc/source/core/data/documen2.cxx index a3d3d5939cb3..0cb0ec300fc6 100644 --- a/sc/source/core/data/documen2.cxx +++ b/sc/source/core/data/documen2.cxx @@ -820,66 +820,110 @@ bool ScDocument::MoveTab( SCTAB nOldPos, SCTAB nNewPos, ScProgress* pProgress ) return bValid; } -void ScDocument::CopyContent(SCTAB nSourceTabNo, SCTAB nTargetTabNo, const ScMarkData* pOnlyMarked, sc::RefUpdateInsertTabContext& rContext) +namespace sc { - SetNoListening( true ); // not yet at CopyToTable/Insert + +class TableContentCopier +{ + ScDocument& mrDoc; + const SCTAB mnSourceTabNo; + const SCTAB mnTargetTabNo; + ScTable* const mpSourceTab; + ScTable* const mpTargetTab; +public: + TableContentCopier(ScDocument& rDoc, + SCTAB nSourceTabNo, SCTAB nTargetTabNo); + + ~TableContentCopier(); + + void performCopy(const ScMarkData* pOnlyMarked, ScCloneFlags nCloneFlags, + SCTAB nPreviousSourceTabNo = -1); + void updateReferencesAfterTabInsertion(RefUpdateInsertTabContext& rContext); + void recompileTargetFormulas(); +}; + +} // namespace sc + +sc::TableContentCopier::TableContentCopier(ScDocument& rDoc, + SCTAB nSourceTabNo, SCTAB nTargetTabNo) + : mrDoc(rDoc) + , mnSourceTabNo(nSourceTabNo) + , mnTargetTabNo(nTargetTabNo) + , mpSourceTab(mrDoc.FetchTable(nSourceTabNo)) + , mpTargetTab(mrDoc.FetchTable(nTargetTabNo)) +{ +} + +void sc::TableContentCopier::performCopy(const ScMarkData* pOnlyMarked, + ScCloneFlags nCloneFlags, SCTAB nPreviousSourceTabNo) +{ + if (nPreviousSourceTabNo < 0) + nPreviousSourceTabNo = mnSourceTabNo; const bool bGlobalNamesToLocal = true; - const SCTAB nRealOldPos = (nTargetTabNo < nSourceTabNo) ? nSourceTabNo - 1 : nSourceTabNo; - const ScRangeName* pNames = GetRangeName( nSourceTabNo); + const ScRangeName* pNames = mrDoc.GetRangeName(mnSourceTabNo); if (pNames) - pNames->CopyUsedNames( nSourceTabNo, nRealOldPos, nTargetTabNo, *this, *this, bGlobalNamesToLocal); - GetRangeName()->CopyUsedNames( -1, nRealOldPos, nTargetTabNo, *this, *this, bGlobalNamesToLocal); - - sc::CopyToDocContext aCopyDocCxt(*this); - pDBCollection->CopyToTable(nSourceTabNo, nTargetTabNo); - maTabs[nSourceTabNo]->CopyToTable(aCopyDocCxt, 0, 0, MaxCol(), MaxRow(), InsertDeleteFlags::ALL, - (pOnlyMarked != nullptr), maTabs[nTargetTabNo].get(), pOnlyMarked, - false /*bAsLink*/, true /*bColRowFlags*/, bGlobalNamesToLocal, false /*bCopyCaptions*/ ); - maTabs[nTargetTabNo]->SetTabBgColor(maTabs[nSourceTabNo]->GetTabBgColor()); - - SCTAB nDz = nTargetTabNo - nSourceTabNo; - sc::RefUpdateContext aRefContext(*this); + pNames->CopyUsedNames(mnSourceTabNo, nPreviousSourceTabNo, mnTargetTabNo, mrDoc, mrDoc, bGlobalNamesToLocal); + mrDoc.GetRangeName()->CopyUsedNames(-1, nPreviousSourceTabNo, mnTargetTabNo, mrDoc, mrDoc, bGlobalNamesToLocal); + + sc::CopyToDocContext aCopyDocCxt(mrDoc); + if (mrDoc.pDBCollection) + mrDoc.pDBCollection->CopyToTable(mnSourceTabNo, mnTargetTabNo); + mpSourceTab->CopyToTable(aCopyDocCxt, 0, 0, mrDoc.MaxCol(), mrDoc.MaxRow(), InsertDeleteFlags::ALL, + (pOnlyMarked != nullptr), mpTargetTab, pOnlyMarked, + false /*bAsLink*/, true /*bColRowFlags*/, nCloneFlags, false /*bCopyCaptions*/); + mpTargetTab->SetTabBgColor(mpSourceTab->GetTabBgColor()); + + SCTAB nDz = mnTargetTabNo - mnSourceTabNo; + sc::RefUpdateContext aRefContext(mrDoc); aRefContext.meMode = URM_COPY; - aRefContext.maRange = ScRange(0, 0, nTargetTabNo, MaxCol(), MaxRow(), nTargetTabNo); + aRefContext.maRange = ScRange(0, 0, mnTargetTabNo, mrDoc.MaxCol(), mrDoc.MaxRow(), mnTargetTabNo); aRefContext.mnTabDelta = nDz; - maTabs[nTargetTabNo]->UpdateReference(aRefContext); - - maTabs[nTargetTabNo]->UpdateInsertTabAbs(nTargetTabNo); // move all paragraphs up by one!! - maTabs[nSourceTabNo]->UpdateInsertTab(rContext); - - maTabs[nSourceTabNo]->UpdateCompile(); - maTabs[nTargetTabNo]->UpdateCompile( true ); // maybe already compiled in Clone, but used names need recompilation - SetNoListening( false ); - sc::StartListeningContext aSLCxt(*this); - maTabs[nSourceTabNo]->StartListeners(aSLCxt, true); - maTabs[nTargetTabNo]->StartListeners(aSLCxt, true); + mpTargetTab->UpdateReference(aRefContext); sc::SetFormulaDirtyContext aFormulaDirtyCxt; - SetAllFormulasDirty(aFormulaDirtyCxt); + mrDoc.SetAllFormulasDirty(aFormulaDirtyCxt); - if (mpDrawLayer) // Skip cloning Note caption object + if (mrDoc.mpDrawLayer) // Skip cloning Note caption object // page is already created in ScTable ctor - mpDrawLayer->ScCopyPage( static_cast<sal_uInt16>(nSourceTabNo), static_cast<sal_uInt16>(nTargetTabNo) ); + mrDoc.mpDrawLayer->ScCopyPage( static_cast<sal_uInt16>(mnSourceTabNo), static_cast<sal_uInt16>(mnTargetTabNo) ); - if (pDPCollection) - pDPCollection->CopyToTab(nSourceTabNo, nTargetTabNo); + if (mrDoc.pDPCollection) + mrDoc.pDPCollection->CopyToTab(mnSourceTabNo, mnTargetTabNo); - maTabs[nTargetTabNo]->SetPageStyle( maTabs[nSourceTabNo]->GetPageStyle() ); - maTabs[nTargetTabNo]->SetPendingRowHeights( maTabs[nSourceTabNo]->IsPendingRowHeights() ); + mpTargetTab->SetPageStyle( mpSourceTab->GetPageStyle() ); + mpTargetTab->SetPendingRowHeights( mpSourceTab->IsPendingRowHeights() ); // Copy the custom print range if exists. - maTabs[nTargetTabNo]->CopyPrintRange(*maTabs[nSourceTabNo]); + mpTargetTab->CopyPrintRange(*mpSourceTab); // Copy the RTL settings - maTabs[nTargetTabNo]->SetLayoutRTL(maTabs[nSourceTabNo]->IsLayoutRTL()); - maTabs[nTargetTabNo]->SetLoadingRTL(maTabs[nSourceTabNo]->IsLoadingRTL()); + mpTargetTab->SetLayoutRTL(mpSourceTab->IsLayoutRTL()); + mpTargetTab->SetLoadingRTL(mpSourceTab->IsLoadingRTL()); // Finally copy the note captions, which need - // 1. the updated source ScColumn::nTab members if nTargetTabNo <= nSourceTabNo + // 1. the updated source ScColumn::nTab members if the target tab is before the source tab // 2. row heights and column widths of the destination // 3. RTL settings of the destination - maTabs[nSourceTabNo]->CopyCaptionsToTable( 0, 0, MaxCol(), MaxRow(), maTabs[nTargetTabNo].get(), true /*bCloneCaption*/); + mpSourceTab->CopyCaptionsToTable( 0, 0, mrDoc.MaxCol(), mrDoc.MaxRow(), mpTargetTab, true /*bCloneCaption*/); +} + +void sc::TableContentCopier::updateReferencesAfterTabInsertion(sc::RefUpdateInsertTabContext& rContext) +{ + mpTargetTab->UpdateInsertTabAbs(mnTargetTabNo); + mpSourceTab->UpdateInsertTab(rContext); + mpSourceTab->UpdateCompile(); +} + +void sc::TableContentCopier::recompileTargetFormulas() +{ + // maybe already compiled in Clone, but used names need recompilation + mpTargetTab->UpdateCompile(true); +} + +sc::TableContentCopier::~TableContentCopier() +{ + recompileTargetFormulas(); } bool ScDocument::OverwriteContent(SCTAB nSourceTabNo, SCTAB nTargetTabNo) @@ -889,14 +933,21 @@ bool ScDocument::OverwriteContent(SCTAB nSourceTabNo, SCTAB nTargetTabNo) if (pSourceTable && pTargetTable) { + sc::AutoCalcSwitch aACSwitch(*this, false); + SetNoListening(true); pTargetTable->DeleteArea(0, 0, MaxCol(), MaxRow(), InsertDeleteFlags::ALL); - sc::AutoCalcSwitch aACSwitch(*this, false); - sc::RefUpdateInsertTabContext aContext(*this, nTargetTabNo, 1); - CopyContent(nSourceTabNo, nTargetTabNo, nullptr, aContext); + { + sc::TableContentCopier aHandler(*this, nSourceTabNo, nTargetTabNo); + aHandler.performCopy(nullptr, ScCloneFlags::NamesToLocal | ScCloneFlags::AdjustCrossSheetRefs); + } + + SetNoListening(false); + sc::StartListeningContext aSLCxt(*this); + maTabs[nTargetTabNo]->StartListeners(aSLCxt, true); return true; } - return false; + return true; } @@ -983,7 +1034,19 @@ bool ScDocument::CopyTab( SCTAB nOldPos, SCTAB nNewPos, const ScMarkData* pOnlyM if (bValid) { - CopyContent(nOldPos, nNewPos, pOnlyMarked, aCxt); + SetNoListening(true); + + SCTAB nPreviousSourceTabNo = (nNewPos < nOldPos) ? nOldPos - 1 : nOldPos; + { + sc::TableContentCopier aHandler(*this, nOldPos, nNewPos); + aHandler.performCopy(pOnlyMarked, ScCloneFlags::NamesToLocal, nPreviousSourceTabNo); + aHandler.updateReferencesAfterTabInsertion(aCxt); + } + + SetNoListening(false); + sc::StartListeningContext aSLCxt(*this); + maTabs[nOldPos]->StartListeners(aSLCxt, true); + maTabs[nNewPos]->StartListeners(aSLCxt, true); } return bValid; @@ -1059,7 +1122,7 @@ bool ScDocument::TransferTab( ScDocument& rSrcDoc, SCTAB nSrcPos, rSrcDoc.maTabs[nSrcPos]->CopyToTable(aCxt, 0, 0, MaxCol(), MaxRow(), ( bResultsOnly ? InsertDeleteFlags::ALL & ~InsertDeleteFlags::FORMULA : InsertDeleteFlags::ALL), false, maTabs[nDestPos].get(), /*pMarkData*/nullptr, /*bAsLink*/false, /*bColRowFlags*/true, - /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); + ScCloneFlags::Default, /*bCopyCaptions*/true ); } } maTabs[nDestPos]->SetTabNo(nDestPos); diff --git a/sc/source/core/data/document.cxx b/sc/source/core/data/document.cxx index ebd41484f18a..a81bc70fa83a 100644 --- a/sc/source/core/data/document.cxx +++ b/sc/source/core/data/document.cxx @@ -2173,7 +2173,7 @@ void ScDocument::CopyToDocument(const ScRange& rRange, pTab->CopyToTable( aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(), aNewRange.aEnd.Col(), aNewRange.aEnd.Row(), nFlags, bOnlyMarked, pDestTab, pMarks, false, bColRowFlags, - /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true); + ScCloneFlags::Default, /*bCopyCaptions*/true); } rDestDoc.StartAllListeners(aNewRange); @@ -3380,12 +3380,12 @@ void ScDocument::FillTab( const ScRange& rSrcArea, const ScMarkData& rMark, maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow, InsertDeleteFlags::CONTENTS, false, pMixDoc->maTabs[i].get(), /*pMarkData*/nullptr, /*bAsLink*/false, /*bColRowFlags*/true, - /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); + ScCloneFlags::Default, /*bCopyCaptions*/true ); } maTabs[i]->DeleteArea( nStartCol,nStartRow, nEndCol,nEndRow, nDelFlags); pSourceTable->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow, nFlags, false, maTabs[i].get(), nullptr, bAsLink, - /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); + /*bColRowFlags*/true, ScCloneFlags::Default, /*bCopyCaptions*/true ); if (bDoMix) maTabs[i]->MixData(aMixDocCxt, nStartCol,nStartRow, nEndCol,nEndRow, @@ -3445,14 +3445,14 @@ void ScDocument::FillTabMarked( SCTAB nSrcTab, const ScMarkData& rMark, sc::CopyToDocContext aMixCxt(*pMixDoc); maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow, InsertDeleteFlags::CONTENTS, true, pMixDoc->maTabs[i].get(), &rMark, - /*bAsLink*/false, /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, + /*bAsLink*/false, /*bColRowFlags*/true, ScCloneFlags::Default, /*bCopyCaptions*/true ); } maTabs[i]->DeleteSelection( nDelFlags, rMark ); pSourceTable->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow, nFlags, true, maTabs[i].get(), &rMark, bAsLink, - /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true ); + /*bColRowFlags*/true, ScCloneFlags::Default, /*bCopyCaptions*/true ); if (bDoMix) maTabs[i]->MixMarked(aMixDocCxt, rMark, nFunction, bSkipEmpty, pMixDoc->maTabs[i].get()); diff --git a/sc/source/core/data/formulacell.cxx b/sc/source/core/data/formulacell.cxx index 16c585fe5364..2bed434ae623 100644 --- a/sc/source/core/data/formulacell.cxx +++ b/sc/source/core/data/formulacell.cxx @@ -848,6 +848,12 @@ ScFormulaCell::ScFormulaCell(const ScFormulaCell& rCell, ScDocument& rDoc, const } pCode->AdjustAbsoluteRefs( rCell.rDocument, rCell.aPos, aPos, bCopyBetweenDocs ); + + if ((nCloneFlags & ScCloneFlags::AdjustCrossSheetRefs) != ScCloneFlags::Default) + { + SCTAB nDelta = aPos.Tab() - rCell.aPos.Tab(); + pCode->AdjustRelativeTabRefs(nDelta); + } } if (!rDocument.IsClipOrUndo()) diff --git a/sc/source/core/data/table2.cxx b/sc/source/core/data/table2.cxx index 57550e926c4e..00e898afda35 100644 --- a/sc/source/core/data/table2.cxx +++ b/sc/source/core/data/table2.cxx @@ -1337,7 +1337,7 @@ void ScTable::StartListeningFormulaCells( void ScTable::CopyToTable( sc::CopyToDocContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked, ScTable* pDestTab, const ScMarkData* pMarkData, - bool bAsLink, bool bColRowFlags, bool bGlobalNamesToLocal, bool bCopyCaptions ) + bool bAsLink, bool bColRowFlags, ScCloneFlags nCloneFlags, bool bCopyCaptions ) { if (!ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2)) return; @@ -1377,7 +1377,7 @@ void ScTable::CopyToTable( pDestTab->CreateColumnIfNotExists(ClampToAllocatedColumns(nCol2)); // avoid repeated resizing for (SCCOL i = nCol1; i <= ClampToAllocatedColumns(nCol2); i++) aCol[i].CopyToColumn(rCxt, nRow1, nRow2, bToUndoDoc ? nFlags : nTempFlags, bMarked, - pDestTab->CreateColumnIfNotExists(i), pMarkData, bAsLink, bGlobalNamesToLocal); + pDestTab->CreateColumnIfNotExists(i), pMarkData, bAsLink, nCloneFlags); // tdf#154044: Restore from the default column data if (bFromUndoDoc && (nFlags & InsertDeleteFlags::ATTRIB) && nCol2 >= aCol.size()) { diff --git a/sc/source/core/tool/token.cxx b/sc/source/core/tool/token.cxx index eb9ba6c1db50..3d17fee21ffa 100644 --- a/sc/source/core/tool/token.cxx +++ b/sc/source/core/tool/token.cxx @@ -2703,6 +2703,44 @@ void ScTokenArray::AdjustAbsoluteRefs( const ScDocument& rOldDoc, const ScAddres } } +void ScTokenArray::AdjustRelativeTabRefs( SCTAB nDelta ) +{ + TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN, true); + for (size_t j=0; j<2; ++j) + { + FormulaToken** pp = aPtrs.maPointerRange[j].mpStart; + FormulaToken** pEnd = aPtrs.maPointerRange[j].mpStop; + for (; pp != pEnd; ++pp) + { + FormulaToken* p = aPtrs.getHandledToken(j,pp); + if (!p) + continue; + + switch ( p->GetType() ) + { + case svDoubleRef: + { + ScComplexRefData& rRef = *p->GetDoubleRef(); + if (rRef.Ref1.IsTabRel() && rRef.Ref1.Tab() != 0) + rRef.Ref1.IncTab(-nDelta); + if (rRef.Ref2.IsTabRel() && rRef.Ref2.Tab() != 0) + rRef.Ref2.IncTab(-nDelta); + break; + } + case svSingleRef: + { + ScSingleRefData& rRef = *p->GetSingleRef(); + if (rRef.IsTabRel() && rRef.Tab() != 0) + rRef.IncTab(-nDelta); + break; + } + default: + break; + } + } + } +} + void ScTokenArray::AdjustSheetLocalNameReferences( SCTAB nOldTab, SCTAB nNewTab ) { TokenPointers aPtrs( pCode.get(), nLen, pRPN, nRPN, false);
