sc/inc/dbdata.hxx | 1 sc/qa/unit/ucalc_formula.cxx | 223 +++++++++++++++++++++++++++++++++++++++++ sc/source/core/tool/dbdata.cxx | 53 ++++----- 3 files changed, 250 insertions(+), 27 deletions(-)
New commits: commit 470848093a2687a6d8056629da6b43773ff4cb0c Author: Eike Rathke <er...@redhat.com> Date: Tue Sep 8 23:30:27 2015 +0200 factor out duplicated code into NamedDBs::initInserted() Change-Id: Idc95d4b8acae9dcd4ed0092c43986e6687f06c11 diff --git a/sc/inc/dbdata.hxx b/sc/inc/dbdata.hxx index 85ace9f..1f825e7 100644 --- a/sc/inc/dbdata.hxx +++ b/sc/inc/dbdata.hxx @@ -243,6 +243,7 @@ public: NamedDBs(const NamedDBs& r); virtual ~NamedDBs(); NamedDBs & operator=(NamedDBs const&) = delete; + void initInserted( ScDBData* p ); public: typedef DBsType::iterator iterator; diff --git a/sc/source/core/tool/dbdata.cxx b/sc/source/core/tool/dbdata.cxx index 19c460b..9edd5d0 100644 --- a/sc/source/core/tool/dbdata.cxx +++ b/sc/source/core/tool/dbdata.cxx @@ -1058,27 +1058,7 @@ ScDBCollection::NamedDBs::NamedDBs(const NamedDBs& r) ScDBData* p = new ScDBData(*it); std::unique_ptr<ScDBData> pData(p); if (m_DBs.insert( std::move(pData)).second) - { - p->SetContainer( this); - if (!mrDoc.IsClipOrUndo()) - { - p->StartTableColumnNamesListener(); // needs the container be set already - if (p->AreTableColumnNamesDirty()) - { - if (p->HasHeader()) - { - // Refresh table column names in next round. - maDirtyTableColumnNames.Join( p->GetHeaderArea()); - } - else - { - // Header-less table can generate its column names - // already without accessing the document. - p->RefreshTableColumnNames( nullptr); - } - } - } - } + initInserted(p); } } @@ -1086,6 +1066,29 @@ ScDBCollection::NamedDBs::~NamedDBs() { } +void ScDBCollection::NamedDBs::initInserted( ScDBData* p ) +{ + p->SetContainer( this); + if (!mrDoc.IsClipOrUndo()) + { + p->StartTableColumnNamesListener(); // needs the container be set already + if (p->AreTableColumnNamesDirty()) + { + if (p->HasHeader()) + { + // Refresh table column names in next round. + maDirtyTableColumnNames.Join( p->GetHeaderArea()); + } + else + { + // Header-less table can generate its column names + // already without accessing the document. + p->RefreshTableColumnNames( nullptr); + } + } + } +} + ScDBCollection::NamedDBs::iterator ScDBCollection::NamedDBs::begin() { return m_DBs.begin(); @@ -1136,25 +1139,7 @@ bool ScDBCollection::NamedDBs::insert(ScDBData* p) if (r.second) { - p->SetContainer( this); - if (!mrDoc.IsClipOrUndo()) - { - p->StartTableColumnNamesListener(); // needs the container be set already - if (p->AreTableColumnNamesDirty()) - { - if (p->HasHeader()) - { - // Refresh table column names in next round. - maDirtyTableColumnNames.Join( p->GetHeaderArea()); - } - else - { - // Header-less table can generate its column names - // already without accessing the document. - p->RefreshTableColumnNames( nullptr); - } - } - } + initInserted(p); /* TODO: shouldn't the import refresh not be setup for * clipboard/undo documents? It was already like this before.. */ commit 70a1e658c0d1f111c8c0991fb87561e3be5683f0 Author: Eike Rathke <er...@redhat.com> Date: Tue Sep 8 23:03:09 2015 +0200 TableRef: add unit tests for column names updates and header-less Change-Id: I244866c91a4f98a55e7ba5f6f87a820cb3de0ead diff --git a/sc/qa/unit/ucalc_formula.cxx b/sc/qa/unit/ucalc_formula.cxx index 4aa08b7..675e517 100644 --- a/sc/qa/unit/ucalc_formula.cxx +++ b/sc/qa/unit/ucalc_formula.cxx @@ -5434,6 +5434,9 @@ void Test::testFuncTableRef() sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn on auto calc. m_pDoc->InsertTab(0, "Sheet1"); + ScMarkData aMark; + aMark.SelectOneTable(0); + ScDocFunc& rDocFunc = getDocShell().GetDocFunc(); { ScDBCollection* pDBs = m_pDoc->GetDBCollection(); @@ -5552,6 +5555,226 @@ void Test::testFuncTableRef() aPrefix + m_pDoc->GetString( aPos)); } + // Insert a column at column B to extend database range from column A,B to + // A,B,C. Use ScDocFunc so RefreshDirtyTableColumnNames() is called. + rDocFunc.InsertCells(ScRange(1,0,0,1,MAXROW,0), &aMark, INS_INSCOLS_BEFORE, false, true); + + // Re-verify the named expression in SUM() formula, on row 4 that + // intersects, now starting at column E, still works. + m_pDoc->CalcAll(); + for (size_t i = 0, n = SAL_N_ELEMENTS(aNames); i < n; ++i) + { + OUString aFormula( "=SUM(" + OUString::createFromAscii( aNames[i].pName) + ")"); + ScAddress aPos(4+i,3,0); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix( aPos.Format(SCA_VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL( aPrefix + OUString::createFromAscii( aNames[i].pSum4), + aPrefix + m_pDoc->GetString( aPos)); + } + + const char* pColumn2Formula = "=SUM(table[[#Data];[Column2]])"; + { + // Populate "table" database range with empty header and data in newly + // inserted column, B1:B4 plus a table formula in B6. The empty header + // should result in the internal table column name "Column2" that is + // used in the formula. + const char* aData[][1] = { + { "" }, + { "64" }, + { "128" }, + { "256" }, + { "" }, + { pColumn2Formula } + }; + ScAddress aPos(1,0,0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData, SAL_N_ELEMENTS(aData)); + CPPUNIT_ASSERT(aRange.aStart == aPos); + } + + // Verify the formula result in B6 (64+128+256=448). + { + OUString aFormula( OUString::createFromAscii( pColumn2Formula)); + ScAddress aPos(1,5,0); + OUString aPrefix( aPos.Format(SCA_VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL( aPrefix + OUString("448"), aPrefix + m_pDoc->GetString( aPos)); + } + + // Set header in column B. Use ScDocFunc to have table column names refreshed. + rDocFunc.SetStringCell(ScAddress(1,0,0), "NewHeader",true); + // Verify that formula adapted using the updated table column names. + if (!checkFormula(*m_pDoc, ScAddress(1,5,0), "SUM(table[[#Data];[NewHeader]])")) + CPPUNIT_FAIL("Wrong formula"); + + // Set header in column A to identical string. Internal table column name + // for B should get a "2" appended. + rDocFunc.SetStringCell(ScAddress(0,0,0), "NewHeader",true); + // Verify that formula adapted using the updated table column names. + if (!checkFormula(*m_pDoc, ScAddress(1,5,0), "SUM(table[[#Data];[NewHeader2]])")) + CPPUNIT_FAIL("Wrong formula"); + + // Set header in column B to empty string, effectively clearing the cell. + rDocFunc.SetStringCell(ScAddress(1,0,0), "",true); + // Verify that formula is still using the previous table column name. + if (!checkFormula(*m_pDoc, ScAddress(1,5,0), "SUM(table[[#Data];[NewHeader2]])")) + CPPUNIT_FAIL("Wrong formula"); + + // === header-less === + + { + ScDBCollection* pDBs = m_pDoc->GetDBCollection(); + CPPUNIT_ASSERT_MESSAGE("Failed to fetch DB collection object.", pDBs); + + // Insert "headerless" database range definition for E10:F12, without headers. + ScDBData* pData = new ScDBData( "hltable", 0, 4,9, 5,11, true, false); + bool bInserted = pDBs->getNamedDBs().insert(pData); + CPPUNIT_ASSERT_MESSAGE( "Failed to insert \"hltable\" database range.", bInserted); + } + + { + // Populate "hltable" database range with data in E10:F12 + const char* aData[][2] = { + { "1", "2" }, + { "4", "8" }, + { "16", "32" } + }; + ScAddress aPos(4,9,0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData, SAL_N_ELEMENTS(aData)); + CPPUNIT_ASSERT(aRange.aStart == aPos); + } + + // Named expressions that use header-less Table structured references. + struct { + const char* pName; + const char* pExpr; + const char* pCounta; // expected result when used in row 10 (first data row) as argument to COUNTA() + const char* pSum3; // expected result when used in row 11 (second data row) as argument to SUM(). + const char* pSum4; // expected result when used in row 12 (third data row) as argument to SUM(). + const char* pSumX; // expected result when used in row 13 (non-intersecting) as argument to SUM(). + } aHlNames[] = { + { "hl_all", "hltable[[#All]]", "6", "63", "63", "63" }, + { "hl_data_implicit", "hltable[]", "6", "63", "63", "63" }, + { "hl_data", "hltable[[#Data]]", "6", "63", "63", "63" }, + { "hl_headers", "hltable[[#Headers]]", "1", "#REF!", "#REF!", "#REF!" }, + { "hl_column1", "hltable[[Column1]]", "3", "21", "21", "21" }, + { "hl_column2", "hltable[[Column2]]", "3", "42", "42", "42" }, + { "hl_data_column1", "hltable[[#Data];[Column1]]", "3", "21", "21", "21" }, + { "hl_data_column2", "hltable[[#Data];[Column2]]", "3", "42", "42", "42" }, + { "hl_this_row", "hltable[[#This Row]]", "2", "12", "48", "#VALUE!" }, + { "hl_this_row_column1", "hltable[[#This Row];[Column1]]", "1", "4", "16", "#VALUE!" }, + { "hl_this_row_column2", "hltable[[#This Row];[Column2]]", "1", "8", "32", "#VALUE!" }, + { "hl_this_row_range_column_1_to_2", "hltable[[#This Row];[Column1]:[Column2]]", "2", "12", "48", "#VALUE!" } + }; + + { + // Insert named expressions. + ScRangeName* pGlobalNames = m_pDoc->GetRangeName(); + CPPUNIT_ASSERT_MESSAGE("Failed to obtain global named expression object.", pGlobalNames); + + for (size_t i = 0, n = SAL_N_ELEMENTS(aHlNames); i < n; ++i) + { + // Choose base position that does not intersect with the database + // range definition to test later use of [#This Row] results in + // proper rows. + ScRangeData* pName = new ScRangeData( + m_pDoc, OUString::createFromAscii(aHlNames[i].pName), OUString::createFromAscii(aHlNames[i].pExpr), + ScAddress(6,12,0), RT_NAME, formula::FormulaGrammar::GRAM_NATIVE); + bool bInserted = pGlobalNames->insert(pName); + CPPUNIT_ASSERT_MESSAGE( + OString("Failed to insert named expression "+ OString(aHlNames[i].pName) +".").getStr(), bInserted); + } + } + + // Use the named expressions in COUNTA() formulas, on row 10 that intersects. + for (size_t i = 0, n = SAL_N_ELEMENTS(aHlNames); i < n; ++i) + { + OUString aFormula( "=COUNTA(" + OUString::createFromAscii( aHlNames[i].pName) + ")"); + ScAddress aPos(7+i,9,0); + m_pDoc->SetString( aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix( aPos.Format(SCA_VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL( aPrefix + OUString::createFromAscii( aHlNames[i].pCounta), + aPrefix + m_pDoc->GetString( aPos)); + } + + // Use the named expressions in SUM() formulas, on row 11 that intersects. + for (size_t i = 0, n = SAL_N_ELEMENTS(aHlNames); i < n; ++i) + { + OUString aFormula( "=SUM(" + OUString::createFromAscii( aHlNames[i].pName) + ")"); + ScAddress aPos(7+i,10,0); + m_pDoc->SetString( aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix( aPos.Format(SCA_VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL( aPrefix + OUString::createFromAscii( aHlNames[i].pSum3), + aPrefix + m_pDoc->GetString( aPos)); + } + + // Use the named expressions in SUM() formulas, on row 12 that intersects. + for (size_t i = 0, n = SAL_N_ELEMENTS(aHlNames); i < n; ++i) + { + OUString aFormula( "=SUM(" + OUString::createFromAscii( aHlNames[i].pName) + ")"); + ScAddress aPos(7+i,11,0); + m_pDoc->SetString( aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix( aPos.Format(SCA_VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL( aPrefix + OUString::createFromAscii( aHlNames[i].pSum4), + aPrefix + m_pDoc->GetString( aPos)); + } + + // Use the named expressions in SUM() formulas, on row 13 that does not intersect. + for (size_t i = 0, n = SAL_N_ELEMENTS(aHlNames); i < n; ++i) + { + OUString aFormula( "=SUM(" + OUString::createFromAscii( aHlNames[i].pName) + ")"); + ScAddress aPos(7+i,12,0); + m_pDoc->SetString( aPos, aFormula); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix( aPos.Format(SCA_VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL( aPrefix + OUString::createFromAscii( aHlNames[i].pSumX), + aPrefix + m_pDoc->GetString( aPos)); + } + + // Insert a column at column F to extend database range from column E,F to + // E,F,G. Use ScDocFunc so RefreshDirtyTableColumnNames() is called. + rDocFunc.InsertCells(ScRange(5,0,0,5,MAXROW,0), &aMark, INS_INSCOLS_BEFORE, false, true); + + // Re-verify the named expression in SUM() formula, on row 12 that + // intersects, now starting at column I, still works. + m_pDoc->CalcAll(); + for (size_t i = 0, n = SAL_N_ELEMENTS(aHlNames); i < n; ++i) + { + OUString aFormula( "=SUM(" + OUString::createFromAscii( aHlNames[i].pName) + ")"); + ScAddress aPos(8+i,11,0); + // For easier "debugability" have position and formula in assertion. + OUString aPrefix( aPos.Format(SCA_VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL( aPrefix + OUString::createFromAscii( aHlNames[i].pSum4), + aPrefix + m_pDoc->GetString( aPos)); + } + + const char* pColumn3Formula = "=SUM(hltable[[#Data];[Column3]])"; + { + // Populate "hltable" database range with data in newly inserted + // column, F10:F12 plus a table formula in F14. The new header should + // result in the internal table column name "Column3" that is used in + // the formula. + const char* aData[][1] = { + { "64" }, + { "128" }, + { "256" }, + { "" }, + { pColumn3Formula } + }; + ScAddress aPos(5,9,0); + ScRange aRange = insertRangeData(m_pDoc, aPos, aData, SAL_N_ELEMENTS(aData)); + CPPUNIT_ASSERT(aRange.aStart == aPos); + } + + // Verify the formula result in F14 (64+128+256=448). + { + OUString aFormula( OUString::createFromAscii( pColumn3Formula)); + ScAddress aPos(5,13,0); + OUString aPrefix( aPos.Format(SCA_VALID) + " " + aFormula + " : "); + CPPUNIT_ASSERT_EQUAL( aPrefix + OUString("448"), aPrefix + m_pDoc->GetString( aPos)); + } + m_pDoc->DeleteTab(0); } commit 8721b1c02fbbc36ca0848ca36a5094d7fbcebc96 Author: Eike Rathke <er...@redhat.com> Date: Tue Sep 8 20:43:01 2015 +0200 TableRef: for header-less ScDBData generate table column names already ... during insertion as NamedDB, as they don't need to access header cells and thus don't have to add a (fake) dirty range. Change-Id: Iab3f1406e53ca39ad0820a86def70323a41ce241 diff --git a/sc/source/core/tool/dbdata.cxx b/sc/source/core/tool/dbdata.cxx index c5b14a0..19c460b 100644 --- a/sc/source/core/tool/dbdata.cxx +++ b/sc/source/core/tool/dbdata.cxx @@ -771,7 +771,7 @@ void ScDBData::RefreshTableColumnNames( ScDocument* pDoc ) ::std::vector<OUString> aNewNames; aNewNames.resize( nEndCol - nStartCol + 1); bool bHaveEmpty = false; - if (!HasHeader()) + if (!HasHeader() || !pDoc) bHaveEmpty = true; // Assume we have empty ones and fill below. else { @@ -1065,10 +1065,17 @@ ScDBCollection::NamedDBs::NamedDBs(const NamedDBs& r) p->StartTableColumnNamesListener(); // needs the container be set already if (p->AreTableColumnNamesDirty()) { - // Refresh table column names in next round. - ScRange aHeader( p->GetHeaderArea()); - if (aHeader.IsValid()) - maDirtyTableColumnNames.Join( aHeader); + if (p->HasHeader()) + { + // Refresh table column names in next round. + maDirtyTableColumnNames.Join( p->GetHeaderArea()); + } + else + { + // Header-less table can generate its column names + // already without accessing the document. + p->RefreshTableColumnNames( nullptr); + } } } } @@ -1135,10 +1142,17 @@ bool ScDBCollection::NamedDBs::insert(ScDBData* p) p->StartTableColumnNamesListener(); // needs the container be set already if (p->AreTableColumnNamesDirty()) { - // Refresh table column names in next round. - ScRange aHeader( p->GetHeaderArea()); - if (aHeader.IsValid()) - maDirtyTableColumnNames.Join( aHeader); + if (p->HasHeader()) + { + // Refresh table column names in next round. + maDirtyTableColumnNames.Join( p->GetHeaderArea()); + } + else + { + // Header-less table can generate its column names + // already without accessing the document. + p->RefreshTableColumnNames( nullptr); + } } } _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/libreoffice-commits