[Libreoffice-commits] core.git: Branch 'feature/formula-core-rework' - sc/inc sc/qa sc/source

Kohei Yoshida kohei.yoshida at gmail.com
Wed Jun 26 12:51:48 PDT 2013


 sc/inc/column.hxx                   |    2 
 sc/inc/formulacell.hxx              |    7 +
 sc/inc/mtvelements.hxx              |    2 
 sc/qa/unit/ucalc.cxx                |   77 ++++++++++++++
 sc/source/core/data/column3.cxx     |  191 ++++++++++++++++++++++++++++++++----
 sc/source/core/data/formulacell.cxx |   27 ++++-
 6 files changed, 279 insertions(+), 27 deletions(-)

New commits:
commit 6707424ecd470a98009c00d89751d1b715a7924c
Author: Kohei Yoshida <kohei.yoshida at gmail.com>
Date:   Wed Jun 26 15:53:29 2013 -0400

    Initial version of dynamic grouping of formula cells.
    
    And tests to go along with it.
    
    Change-Id: Idf5ff3b819aa557a1ae31dfb4d0b2c3a8216ed75

diff --git a/sc/inc/column.hxx b/sc/inc/column.hxx
index 974a934..6b46cc8 100644
--- a/sc/inc/column.hxx
+++ b/sc/inc/column.hxx
@@ -487,7 +487,7 @@ private:
 
     sc::CellStoreType::iterator GetPositionToInsert( SCROW nRow );
     sc::CellStoreType::iterator GetPositionToInsert( const sc::CellStoreType::iterator& it, SCROW nRow );
-    void ActivateNewFormulaCell( ScFormulaCell* pCell );
+    void ActivateNewFormulaCell( const sc::CellStoreType::iterator& itPos, SCROW nRow, ScFormulaCell& rCell );
     void BroadcastNewCell( SCROW nRow );
     bool UpdateScriptType( sc::CellTextAttr& rAttr, SCROW nRow );
 
diff --git a/sc/inc/formulacell.hxx b/sc/inc/formulacell.hxx
index 2ac8412..b724d02 100644
--- a/sc/inc/formulacell.hxx
+++ b/sc/inc/formulacell.hxx
@@ -292,7 +292,7 @@ public:
     void                   SetCellGroup( const ScFormulaCellGroupRef &xRef )
         { xGroup = xRef; }
 
-    CompareState CompareByTokenArray( ScFormulaCell *pOther ) const;
+    CompareState CompareByTokenArray( ScFormulaCell& rOther ) const;
 
     bool InterpretFormulaGroup();
     bool InterpretInvariantFormulaGroup();
@@ -303,6 +303,11 @@ public:
     void EndListeningTo(
         ScDocument* pDoc, ScTokenArray* pArr = NULL, ScAddress aPos = ScAddress() );
     void EndListeningTo( sc::EndListeningContext& rCxt );
+
+    bool IsShared() const;
+    bool IsSharedInvariant() const;
+    SCROW GetSharedTopRow() const;
+    SCROW GetSharedLength() const;
 };
 
 #endif
diff --git a/sc/inc/mtvelements.hxx b/sc/inc/mtvelements.hxx
index 0ebf8be..2c58997 100644
--- a/sc/inc/mtvelements.hxx
+++ b/sc/inc/mtvelements.hxx
@@ -15,7 +15,7 @@
 #include "svl/broadcast.hxx"
 #include "editeng/editobj.hxx"
 
-#define DEBUG_COLUMN_STORAGE 0
+#define DEBUG_COLUMN_STORAGE 1
 
 #if DEBUG_COLUMN_STORAGE
 #ifdef NDEBUG
diff --git a/sc/qa/unit/ucalc.cxx b/sc/qa/unit/ucalc.cxx
index 462087a4..5286965 100644
--- a/sc/qa/unit/ucalc.cxx
+++ b/sc/qa/unit/ucalc.cxx
@@ -236,6 +236,7 @@ public:
     void testMergedCells();
     void testUpdateReference();
     void testSearchCells();
+    void testSharedFormulas();
 
     /**
      * Make sure the sheet streams are invalidated properly.
@@ -352,6 +353,7 @@ public:
     CPPUNIT_TEST(testMergedCells);
     CPPUNIT_TEST(testUpdateReference);
     CPPUNIT_TEST(testSearchCells);
+    CPPUNIT_TEST(testSharedFormulas);
     CPPUNIT_TEST(testJumpToPrecedentsDependents);
     CPPUNIT_TEST(testSetBackgroundColor);
     CPPUNIT_TEST(testRenameTable);
@@ -6295,6 +6297,81 @@ void Test::testSearchCells()
     m_pDoc->DeleteTab(0);
 }
 
+void Test::testSharedFormulas()
+{
+    m_pDoc->InsertTab(0, "Test");
+
+    ScAddress aPos(1, 9, 0); // B10
+    m_pDoc->SetString(aPos, "=A10*2"); // Insert into B10.
+    const ScFormulaCell* pFC = m_pDoc->GetFormulaCell(aPos);
+    CPPUNIT_ASSERT_MESSAGE("Expected to be a non-shared cell.", pFC && !pFC->IsShared());
+
+    aPos.SetRow(10); // B11
+    m_pDoc->SetString(aPos, "=A11*2");
+    pFC = m_pDoc->GetFormulaCell(aPos);
+    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC && pFC->IsShared());
+    CPPUNIT_ASSERT_EQUAL(9, pFC->GetSharedTopRow());
+    CPPUNIT_ASSERT_EQUAL(2, pFC->GetSharedLength());
+
+    aPos.SetRow(8); // B9
+    m_pDoc->SetString(aPos, "=A9*2");
+    pFC = m_pDoc->GetFormulaCell(aPos);
+    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC && pFC->IsShared());
+    CPPUNIT_ASSERT_EQUAL(8, pFC->GetSharedTopRow());
+    CPPUNIT_ASSERT_EQUAL(3, pFC->GetSharedLength());
+
+    aPos.SetRow(12); // B13
+    m_pDoc->SetString(aPos, "=A13*2");
+    pFC = m_pDoc->GetFormulaCell(aPos);
+    CPPUNIT_ASSERT_MESSAGE("This formula cell shouldn't be shared yet.", pFC && !pFC->IsShared());
+
+    // Insert a formula to B12, and B9:B13 should be shared.
+    aPos.SetRow(11); // B12
+    m_pDoc->SetString(aPos, "=A12*2");
+    pFC = m_pDoc->GetFormulaCell(aPos);
+    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC && pFC->IsShared());
+    CPPUNIT_ASSERT_EQUAL(8, pFC->GetSharedTopRow());
+    CPPUNIT_ASSERT_EQUAL(5, pFC->GetSharedLength());
+
+    // Insert formulas to B15:B16.
+    aPos.SetRow(14); // B15
+    m_pDoc->SetString(aPos, "=A15*2");
+    aPos.SetRow(15); // B16
+    m_pDoc->SetString(aPos, "=A16*2");
+    pFC = m_pDoc->GetFormulaCell(aPos);
+    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC && pFC->IsShared());
+    CPPUNIT_ASSERT_EQUAL(14, pFC->GetSharedTopRow());
+    CPPUNIT_ASSERT_EQUAL(2, pFC->GetSharedLength());
+
+    // Insert a formula to B14, and B9:B16 should be shared.
+    aPos.SetRow(13); // B14
+    m_pDoc->SetString(aPos, "=A14*2");
+    pFC = m_pDoc->GetFormulaCell(aPos);
+    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC && pFC->IsShared());
+    CPPUNIT_ASSERT_EQUAL(8, pFC->GetSharedTopRow());
+    CPPUNIT_ASSERT_EQUAL(8, pFC->GetSharedLength());
+
+    // Insert an incompatible formula to B12, to split the shared range to B9:B11 and B13:B16.
+    aPos.SetRow(11); // B12
+    m_pDoc->SetString(aPos, "=$A$1*4");
+    pFC = m_pDoc->GetFormulaCell(aPos);
+    CPPUNIT_ASSERT_MESSAGE("This cell shouldn't be shared.", pFC && !pFC->IsShared());
+
+    aPos.SetRow(8); // B9
+    pFC = m_pDoc->GetFormulaCell(aPos);
+    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC && pFC->IsShared());
+    CPPUNIT_ASSERT_EQUAL(8, pFC->GetSharedTopRow());
+    CPPUNIT_ASSERT_EQUAL(3, pFC->GetSharedLength());
+
+    aPos.SetRow(12); // B13
+    pFC = m_pDoc->GetFormulaCell(aPos);
+    CPPUNIT_ASSERT_MESSAGE("This cell is expected to be a shared formula cell.", pFC && pFC->IsShared());
+    CPPUNIT_ASSERT_EQUAL(12, pFC->GetSharedTopRow());
+    CPPUNIT_ASSERT_EQUAL(4, pFC->GetSharedLength());
+
+    m_pDoc->DeleteTab(0);
+}
+
 namespace {
 
 bool hasRange(const std::vector<ScTokenRef>& rRefTokens, const ScRange& rRange)
diff --git a/sc/source/core/data/column3.cxx b/sc/source/core/data/column3.cxx
index 227a69b..6cac968 100644
--- a/sc/source/core/data/column3.cxx
+++ b/sc/source/core/data/column3.cxx
@@ -332,17 +332,172 @@ sc::CellStoreType::iterator ScColumn::GetPositionToInsert( const sc::CellStoreTy
     // See if we are overwriting an existing formula cell.
     sc::CellStoreType::position_type aRet = maCells.position(it, nRow);
     sc::CellStoreType::iterator itRet = aRet.first;
-    if (itRet->type == sc::element_type_formula && !pDocument->IsClipOrUndo())
+    if (itRet->type == sc::element_type_formula)
     {
-        ScFormulaCell* pCell = sc::formula_block::at(*itRet->data, aRet.second);
-        pCell->EndListeningTo(pDocument);
+        ScFormulaCell& rCell = *sc::formula_block::at(*itRet->data, aRet.second);
+        if (!pDocument->IsClipOrUndo())
+            // Have the dying formula cell stop listening.
+            rCell.EndListeningTo(pDocument);
+
+        if (rCell.IsShared())
+        {
+            // This formula cell is shared. Adjust the shared group.
+            if (rCell.aPos.Row() == rCell.GetSharedTopRow())
+            {
+                // Top of the shared range.
+                ScFormulaCellGroupRef xGroup = rCell.GetCellGroup();
+                if (xGroup->mnLength == 2)
+                {
+                    // Group consists only only two cells. Mark the second one non-shared.
+#if DEBUG_COLUMN_STORAGE
+                    if (aRet.second+1 >= aRet.first->size)
+                    {
+                        cerr << "ScColumn::GetPositionToInsert: There is no next formula cell but there should be!" << endl;
+                        cerr.flush();
+                        abort();
+                    }
+#endif
+                    ScFormulaCellGroupRef xNone;
+                    ScFormulaCell& rNext = *sc::formula_block::at(*itRet->data, aRet.second+1);
+                    rNext.SetCellGroup(xNone);
+                }
+                else
+                {
+                    // Move the top cell to the next formula cell down.
+                    --xGroup->mnLength;
+                    ++xGroup->mnStart;
+                }
+            }
+            else if (rCell.aPos.Row() == rCell.GetSharedTopRow() + rCell.GetSharedLength() - 1)
+            {
+                // Bottom of the shared range.
+                ScFormulaCellGroupRef xGroup = rCell.GetCellGroup();
+                if (xGroup->mnLength == 2)
+                {
+                    // Mark the top cell non-shared.
+#if DEBUG_COLUMN_STORAGE
+                    if (aRet.second == 0)
+                    {
+                        cerr << "ScColumn::GetPositionToInsert: There is no previous formula cell but there should be!" << endl;
+                        cerr.flush();
+                        abort();
+                    }
+#endif
+                    ScFormulaCellGroupRef xNone;
+                    ScFormulaCell& rPrev = *sc::formula_block::at(*itRet->data, aRet.second-1);
+                    rPrev.SetCellGroup(xNone);
+                }
+                else
+                {
+                    // Just shortern the shared range length by one.
+                    --xGroup->mnLength;
+                }
+            }
+            else
+            {
+                // In the middle of the shared range. Split it into two groups.
+                ScFormulaCellGroupRef xGroup = rCell.GetCellGroup();
+                SCROW nEndRow = xGroup->mnStart + xGroup->mnLength - 1;
+                xGroup->mnLength = rCell.aPos.Row() - xGroup->mnStart; // Shorten the top group.
+
+                ScFormulaCellGroupRef xGroup2(new ScFormulaCellGroup);
+                xGroup2->mnStart = rCell.aPos.Row() + 1;
+                xGroup2->mnLength = nEndRow - rCell.aPos.Row();
+#if DEBUG_COLUMN_STORAGE
+                if (xGroup2->mnStart + xGroup2->mnLength > itRet->position + itRet->size)
+                {
+                    cerr << "ScColumn::GetPositionToInsert: Shared formula region goes beyond the formula block. Not good." << endl;
+                    cerr.flush();
+                    abort();
+                }
+#endif
+                sc::formula_block::iterator itCell = sc::formula_block::begin(*itRet->data);
+                std::advance(itCell, aRet.second+1);
+                sc::formula_block::iterator itCellEnd = itCell;
+                std::advance(itCellEnd, xGroup2->mnLength);
+                for (; itCell != itCellEnd; ++itCell)
+                {
+                    ScFormulaCell& rCell2 = **itCell;
+                    rCell2.SetCellGroup(xGroup2);
+                }
+            }
+        }
     }
 
     return itRet;
 }
 
-void ScColumn::ActivateNewFormulaCell( ScFormulaCell* pCell )
+namespace {
+
+void joinFormulaCells(SCROW nRow, ScFormulaCell& rCell1, ScFormulaCell& rCell2)
 {
+    ScFormulaCell::CompareState eState = rCell1.CompareByTokenArray(rCell2);
+    if (eState == ScFormulaCell::NotEqual)
+        return;
+
+    // Formula tokens equal those of the previous formula cell.
+    ScFormulaCellGroupRef xGroup1 = rCell1.GetCellGroup();
+    ScFormulaCellGroupRef xGroup2 = rCell2.GetCellGroup();
+    if (xGroup1)
+    {
+        if (xGroup2)
+        {
+            // Both cell1 and cell2 are shared. Merge them together.
+            xGroup1->mnLength += xGroup2->mnLength;
+            rCell2.SetCellGroup(xGroup1);
+        }
+        else
+        {
+            // cell1 is shared but cell2 is not.
+            rCell2.SetCellGroup(xGroup1);
+            ++xGroup1->mnLength;
+        }
+    }
+    else
+    {
+        if (xGroup2)
+        {
+            // cell1 is not shared, but cell2 is already shared.
+            rCell1.SetCellGroup(xGroup2);
+            xGroup2->mnStart = nRow;
+            ++xGroup2->mnLength;
+        }
+        else
+        {
+            // neither cells are shared.
+            xGroup1.reset(new ScFormulaCellGroup);
+            xGroup1->mnStart = nRow;
+            xGroup1->mbInvariant = (eState == ScFormulaCell::EqualInvariant);
+            xGroup1->mnLength = 2;
+
+            rCell1.SetCellGroup(xGroup1);
+            rCell2.SetCellGroup(xGroup1);
+        }
+    }
+}
+
+}
+
+void ScColumn::ActivateNewFormulaCell(
+    const sc::CellStoreType::iterator& itPos, SCROW nRow, ScFormulaCell& rCell )
+{
+    // See if this new formula cell can join an existing shared formula group.
+    sc::CellStoreType::position_type aPos = maCells.position(itPos, nRow);
+
+    // Check the previous row position for possible grouping.
+    if (aPos.first->type == sc::element_type_formula && aPos.second > 0)
+    {
+        ScFormulaCell& rPrev = *sc::formula_block::at(*aPos.first->data, aPos.second-1);
+        joinFormulaCells(nRow-1, rPrev, rCell);
+    }
+
+    // Check the next row position for possible grouping.
+    if (aPos.first->type == sc::element_type_formula && aPos.second+1 < aPos.first->size)
+    {
+        ScFormulaCell& rNext = *sc::formula_block::at(*aPos.first->data, aPos.second+1);
+        joinFormulaCells(nRow, rCell, rNext);
+    }
+
     // When we insert from the Clipboard we still have wrong (old) References!
     // First they are rewired in CopyBlockFromClip via UpdateReference and the
     // we call StartListeningFromClip and BroadcastFromClip.
@@ -350,9 +505,9 @@ void ScColumn::ActivateNewFormulaCell( ScFormulaCell* pCell )
     // After Import we call CalcAfterLoad and in there Listening.
     if (!pDocument->IsClipOrUndo() && !pDocument->IsInsertingFromOtherDoc())
     {
-        pCell->StartListeningTo(pDocument);
+        rCell.StartListeningTo(pDocument);
         if (!pDocument->IsCalcingAfterLoad())
-            pCell->SetDirty();
+            rCell.SetDirty();
     }
 }
 
@@ -1598,12 +1753,11 @@ void ScColumn::SetFormula( SCROW nRow, const ScTokenArray& rArray, formula::Form
     sal_uInt32 nCellFormat = GetNumberFormat(nRow);
     if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
         pCell->SetNeedNumberFormat(true);
-    maCells.set(it, nRow, pCell);
+    it = maCells.set(it, nRow, pCell);
     maCellTextAttrs.set(nRow, sc::CellTextAttr());
-    RegroupFormulaCells(nRow);
     CellStorageModified();
 
-    ActivateNewFormulaCell(pCell);
+    ActivateNewFormulaCell(it, nRow, *pCell);
 }
 
 void ScColumn::SetFormula( SCROW nRow, const OUString& rFormula, formula::FormulaGrammar::Grammar eGram )
@@ -1615,23 +1769,21 @@ void ScColumn::SetFormula( SCROW nRow, const OUString& rFormula, formula::Formul
     sal_uInt32 nCellFormat = GetNumberFormat(nRow);
     if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
         pCell->SetNeedNumberFormat(true);
-    maCells.set(it, nRow, pCell);
+    it = maCells.set(it, nRow, pCell);
     maCellTextAttrs.set(nRow, sc::CellTextAttr());
-    RegroupFormulaCells(nRow);
     CellStorageModified();
 
-    ActivateNewFormulaCell(pCell);
+    ActivateNewFormulaCell(it, nRow, *pCell);
 }
 
 void ScColumn::SetFormulaCell( SCROW nRow, ScFormulaCell* pCell )
 {
     sc::CellStoreType::iterator it = GetPositionToInsert(nRow);
-    maCells.set(it, nRow, pCell);
+    it = maCells.set(it, nRow, pCell);
     maCellTextAttrs.set(nRow, sc::CellTextAttr());
-    RegroupFormulaCells(nRow);
     CellStorageModified();
 
-    ActivateNewFormulaCell(pCell);
+    ActivateNewFormulaCell(it, nRow, *pCell);
 }
 
 void ScColumn::SetFormulaCell( sc::ColumnBlockPosition& rBlockPos, SCROW nRow, ScFormulaCell* pCell )
@@ -1640,10 +1792,9 @@ void ScColumn::SetFormulaCell( sc::ColumnBlockPosition& rBlockPos, SCROW nRow, S
     rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, pCell);
     rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
         rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
-    RegroupFormulaCells(nRow);
     CellStorageModified();
 
-    ActivateNewFormulaCell(pCell);
+    ActivateNewFormulaCell(rBlockPos.miCellPos, nRow, *pCell);
 }
 
 namespace {
@@ -2030,12 +2181,12 @@ void ScColumn::SetError( SCROW nRow, const sal_uInt16 nError)
     pCell->SetErrCode(nError);
 
     sc::CellStoreType::iterator it = GetPositionToInsert(nRow);
-    maCells.set(it, nRow, pCell);
+    it = maCells.set(it, nRow, pCell);
     maCellTextAttrs.set(nRow, sc::CellTextAttr());
     RegroupFormulaCells(nRow);
     CellStorageModified();
 
-    ActivateNewFormulaCell(pCell);
+    ActivateNewFormulaCell(it, nRow, *pCell);
 }
 
 void ScColumn::SetRawString( SCROW nRow, const OUString& rStr, bool bBroadcast )
@@ -2573,7 +2724,7 @@ public:
             if (!pPrev)
                 continue;
 
-            ScFormulaCell::CompareState eCompState = pPrev->CompareByTokenArray(pCur);
+            ScFormulaCell::CompareState eCompState = pPrev->CompareByTokenArray(*pCur);
             if (eCompState == ScFormulaCell::NotEqual)
             {
                 // different formula tokens.
diff --git a/sc/source/core/data/formulacell.cxx b/sc/source/core/data/formulacell.cxx
index e554df4..9fc515d 100644
--- a/sc/source/core/data/formulacell.cxx
+++ b/sc/source/core/data/formulacell.cxx
@@ -2844,20 +2844,20 @@ void ScFormulaCell::CompileColRowNameFormula()
     }
 }
 
-ScFormulaCell::CompareState ScFormulaCell::CompareByTokenArray( ScFormulaCell *pOtherCell ) const
+ScFormulaCell::CompareState ScFormulaCell::CompareByTokenArray( ScFormulaCell& rOther ) const
 {
     // no Matrix formulae yet.
     if ( GetMatrixFlag() != MM_NONE )
         return NotEqual;
 
     // are these formule at all similar ?
-    if ( GetHash() != pOtherCell->GetHash() )
+    if ( GetHash() != rOther.GetHash() )
         return NotEqual;
 
     FormulaToken **pThis = pCode->GetCode();
     sal_uInt16     nThisLen = pCode->GetCodeLen();
-    FormulaToken **pOther = pOtherCell->pCode->GetCode();
-    sal_uInt16     nOtherLen = pOtherCell->pCode->GetCodeLen();
+    FormulaToken **pOther = rOther.pCode->GetCode();
+    sal_uInt16     nOtherLen = rOther.pCode->GetCodeLen();
 
     if ( !pThis || !pOther )
     {
@@ -3402,4 +3402,23 @@ void ScFormulaCell::EndListeningTo( sc::EndListeningContext& rCxt )
     }
 }
 
+bool ScFormulaCell::IsShared() const
+{
+    return xGroup.get() != NULL;
+}
+
+bool ScFormulaCell::IsSharedInvariant() const
+{
+    return xGroup ? xGroup->mbInvariant : false;
+}
+
+SCROW ScFormulaCell::GetSharedTopRow() const
+{
+    return xGroup ? xGroup->mnStart : -1;
+}
+SCROW ScFormulaCell::GetSharedLength() const
+{
+    return xGroup ? xGroup->mnLength : 0;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */


More information about the Libreoffice-commits mailing list