[Libreoffice-commits] core.git: sc/inc sc/qa sc/source

Libreoffice Gerrit user logerrit at kemper.freedesktop.org
Tue Feb 5 12:56:47 UTC 2019


 sc/inc/column.hxx                   |    2 
 sc/inc/document.hxx                 |   14 +-
 sc/inc/formulacell.hxx              |   14 +-
 sc/inc/table.hxx                    |    2 
 sc/qa/unit/parallelism.cxx          |  134 +++++++++++++++++++++++-
 sc/source/core/data/column2.cxx     |   66 -----------
 sc/source/core/data/column4.cxx     |  200 ++++++++++++++++++++++++++++++++++--
 sc/source/core/data/document10.cxx  |   10 +
 sc/source/core/data/formulacell.cxx |  116 ++++++++++++--------
 sc/source/core/data/table7.cxx      |   13 +-
 sc/source/ui/view/output.cxx        |   53 +++++++--
 11 files changed, 472 insertions(+), 152 deletions(-)

New commits:
commit 3346947b7e102384dfc6cd98dbf7da81936f8fd6
Author:     Dennis Francis <dennis.francis at collabora.com>
AuthorDate: Tue Jan 15 21:34:46 2019 +0530
Commit:     Dennis Francis <dennis.francis at collabora.com>
CommitDate: Tue Feb 5 13:56:22 2019 +0100

    Allow computing spans of formula-groups
    
    Includes unit tests for correctness of the new functionality.
    
    Change-Id: I35f7449006d973de006a756664ae468b9a0dcb31
    Reviewed-on: https://gerrit.libreoffice.org/66841
    Tested-by: Jenkins
    Reviewed-by: Dennis Francis <dennis.francis at collabora.com>

diff --git a/sc/inc/column.hxx b/sc/inc/column.hxx
index d21918648678..a885d0240a2c 100644
--- a/sc/inc/column.hxx
+++ b/sc/inc/column.hxx
@@ -678,7 +678,7 @@ public:
 
     std::unique_ptr<sc::ColumnIterator> GetColumnIterator( SCROW nRow1, SCROW nRow2 ) const;
 
-    void EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2 );
+    bool EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2, bool bSkipRunning = false );
 
     void StoreToCache(SvStream& rStrm) const;
     void RestoreFromCache(SvStream& rStrm);
diff --git a/sc/inc/document.hxx b/sc/inc/document.hxx
index bed8003de16c..ddf92b904a8a 100644
--- a/sc/inc/document.hxx
+++ b/sc/inc/document.hxx
@@ -2430,12 +2430,18 @@ public:
     /**
      * Make sure all of the formula cells in the specified range have been
      * fully calculated.  This method only re-calculates those formula cells
-     * that have been flagged dirty.
+     * that have been flagged dirty. In case of formula-groups, this calculates
+     * only the dirty subspans along with the dependents in the same way
+     * recursively.
      *
-     * @param rRange range in which to potentially calculate the formula
-     *               cells.
+     * @param rRange       range in which to potentially calculate the formula
+     *                     cells.
+     * @param bSkipRunning flag to skip evaluation of formula-cells that are
+     *                     marked as already being evaluated.
+     * @return             true if at least one formula-cell in the specified range was dirty
+     *                     else returns false.
      */
-    void EnsureFormulaCellResults( const ScRange& rRange );
+    SC_DLLPUBLIC bool EnsureFormulaCellResults( const ScRange& rRange, bool bSkipRunning = false );
 
     SvtBroadcaster*         GetBroadcaster( const ScAddress& rPos );
     const SvtBroadcaster*   GetBroadcaster( const ScAddress& rPos ) const;
diff --git a/sc/inc/formulacell.hxx b/sc/inc/formulacell.hxx
index b6959c74d6cd..ec7c61171ae7 100644
--- a/sc/inc/formulacell.hxx
+++ b/sc/inc/formulacell.hxx
@@ -143,10 +143,12 @@ private:
 
     ScFormulaCell( const ScFormulaCell& ) = delete;
 
-    bool CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow = false);
+    bool CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow,
+                                  SCROW nStartOffset, SCROW nEndOffset);
     bool InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope& aScope,
                                         bool& bDependencyComputed,
-                                        bool& bDependencyCheckFailed);
+                                        bool& bDependencyCheckFailed,
+                                        SCROW nStartOffset, SCROW nEndOffset);
     bool InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope& aScope,
                                      bool& bDependencyComputed,
                                      bool& bDependencyCheckFailed);
@@ -248,7 +250,7 @@ public:
     void CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress );        // compile temporary string tokens
     void CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening );
     bool            MarkUsedExternalReferences();
-    void            Interpret();
+    bool Interpret(SCROW nStartOffset = -1, SCROW nEndOffset = -1);
     bool IsIterCell() const { return bIsIterCell; }
     sal_uInt16 GetSeenInIteration() const { return nSeenInIteration; }
 
@@ -433,13 +435,15 @@ public:
         return (pDocument->GetAutoCalc() || (cMatrixFlag != ScMatrixMode::NONE));
     }
 
-    void MaybeInterpret()
+    bool MaybeInterpret()
     {
         if (NeedsInterpret())
         {
             assert(!pDocument->IsThreadedGroupCalcInProgress());
             Interpret();
+            return true;
         }
+        return false;
     }
 
     /**
@@ -451,7 +455,7 @@ public:
 
     CompareState CompareByTokenArray( const ScFormulaCell& rOther ) const;
 
-    bool InterpretFormulaGroup();
+    bool InterpretFormulaGroup(SCROW nStartOffset = -1, SCROW nEndOffset = -1);
 
     // nOnlyNames may be one or more of SC_LISTENING_NAMES_*
     void StartListeningTo( ScDocument* pDoc );
diff --git a/sc/inc/table.hxx b/sc/inc/table.hxx
index 38ea573ba120..ee217ba50c82 100644
--- a/sc/inc/table.hxx
+++ b/sc/inc/table.hxx
@@ -1050,7 +1050,7 @@ public:
 
     std::unique_ptr<sc::ColumnIterator> GetColumnIterator( SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const;
 
-    void EnsureFormulaCellResults( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2 );
+    bool EnsureFormulaCellResults( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2, bool bSkipRunning = false );
 
     void ConvertFormulaToValue(
         sc::EndListeningContext& rCxt,
diff --git a/sc/qa/unit/parallelism.cxx b/sc/qa/unit/parallelism.cxx
index 670e7242645d..058cbfad9728 100644
--- a/sc/qa/unit/parallelism.cxx
+++ b/sc/qa/unit/parallelism.cxx
@@ -38,6 +38,8 @@ public:
     void testFGCycleWithPlainFormulaCell1();
     void testFGCycleWithPlainFormulaCell2();
     void testMultipleFGColumn();
+    void testFormulaGroupSpanEval();
+    void testFormulaGroupSpanEvalNonGroup();
 
     CPPUNIT_TEST_SUITE(ScParallelismTest);
     CPPUNIT_TEST(testSUMIFS);
@@ -49,6 +51,8 @@ public:
     CPPUNIT_TEST(testFGCycleWithPlainFormulaCell1);
     CPPUNIT_TEST(testFGCycleWithPlainFormulaCell2);
     CPPUNIT_TEST(testMultipleFGColumn);
+    CPPUNIT_TEST(testFormulaGroupSpanEval);
+    CPPUNIT_TEST(testFormulaGroupSpanEvalNonGroup);
     CPPUNIT_TEST_SUITE_END();
 
 private:
@@ -510,11 +514,11 @@ void ScParallelismTest::testMultipleFGColumn()
     sc::AutoCalcSwitch aACSwitch(*m_pDoc, false);
     m_pDoc->InsertTab(0, "1");
 
-    size_t nNumRowsInBlock = 200;
-    size_t nNumFG = 50;
-    size_t nNumRowsInRef = nNumRowsInBlock*2;
-    size_t nColAFGLen = 2*nNumRowsInBlock*nNumFG - nNumRowsInRef + 1;
-    size_t nColAStartOffset = nNumRowsInBlock/2;
+    constexpr size_t nNumRowsInBlock = 200;
+    constexpr size_t nNumFG = 50;
+    constexpr size_t nNumRowsInRef = nNumRowsInBlock*2;
+    constexpr size_t nColAFGLen = 2*nNumRowsInBlock*nNumFG - nNumRowsInRef + 1;
+    constexpr size_t nColAStartOffset = nNumRowsInBlock/2;
     lcl_setupMultipleFGColumn(m_pDoc, nNumRowsInBlock, nNumFG, nColAStartOffset);
 
     m_xDocShell->DoHardRecalc();
@@ -535,6 +539,126 @@ void ScParallelismTest::testMultipleFGColumn()
     m_pDoc->DeleteTab(0);
 }
 
+void ScParallelismTest::testFormulaGroupSpanEval()
+{
+    sc::AutoCalcSwitch aACSwitch(*m_pDoc, false);
+    m_pDoc->InsertTab(0, "1");
+
+    constexpr size_t nFGLen = 2048;
+    OUString aFormula;
+
+    for (size_t nRow = 0; nRow < nFGLen; ++nRow)
+    {
+        aFormula = "=$C" + OUString::number(nRow+1) + " + 0";
+        m_pDoc->SetFormula(ScAddress(1, nRow, 0), aFormula,
+                           formula::FormulaGrammar::GRAM_NATIVE_UI);
+        aFormula = "=SUM($B" + OUString::number(nRow+1) + ":$B" + OUString::number(nRow+2) + ")";
+        m_pDoc->SetFormula(ScAddress(0, nRow, 0), aFormula,
+                           formula::FormulaGrammar::GRAM_NATIVE_UI);
+    }
+
+    m_xDocShell->DoHardRecalc();
+
+    for (size_t nRow = 0; nRow < nFGLen; ++nRow)
+    {
+        m_pDoc->SetValue(2, nRow, 0, 1.0);
+        ScFormulaCell* pFCell = m_pDoc->GetFormulaCell(ScAddress(1, nRow, 0));
+        pFCell->SetDirtyVar();
+        pFCell = m_pDoc->GetFormulaCell(ScAddress(0, nRow, 0));
+        pFCell->SetDirtyVar();
+    }
+
+    constexpr size_t nSpanStart = 100;
+    constexpr size_t nSpanLen = 1024;
+    constexpr size_t nSpanEnd = nSpanStart + nSpanLen - 1;
+
+    m_pDoc->SetAutoCalc(true);
+
+    // EnsureFormulaCellResults should only calculate the sepecified range along with the dependent spans recursively and nothing more.
+    // The specified range is A99:A1124, and the dependent range is B99:B1125 (since A99 = SUM(B99:B100) and A1124 = SUM(B1124:B1125) )
+    bool bAnyDirty = m_pDoc->EnsureFormulaCellResults(ScRange(0, nSpanStart, 0, 0, nSpanEnd, 0));
+    CPPUNIT_ASSERT(bAnyDirty);
+    m_pDoc->SetAutoCalc(false);
+
+    OString aMsg;
+    for (size_t nRow = 0; nRow < nFGLen; ++nRow)
+    {
+        size_t nExpectedA = 0, nExpectedB = 0;
+        // For nRow from 100(nSpanStart) to 1123(nSpanEnd) column A must have the value of 2 and
+        // column B should have value 1.
+
+        // For nRow == 1124, column A should have value 0 and column B should have value 1.
+
+        // For all other rows both column A and B must have value 0.
+        if (nRow >= nSpanStart)
+        {
+            if (nRow <= nSpanEnd)
+            {
+                nExpectedA = 2;
+                nExpectedB = 1;
+            }
+            else if (nRow == nSpanEnd + 1)
+                nExpectedB = 1;
+        }
+
+        aMsg = "Value at Cell A" + OString::number(nRow+1);
+        CPPUNIT_ASSERT_EQUAL_MESSAGE(aMsg.getStr(), nExpectedA, static_cast<size_t>(m_pDoc->GetValue(0, nRow, 0)));
+        aMsg = "Value at Cell B" + OString::number(nRow+1);
+        CPPUNIT_ASSERT_EQUAL_MESSAGE(aMsg.getStr(), nExpectedB, static_cast<size_t>(m_pDoc->GetValue(1, nRow, 0)));
+    }
+
+    m_pDoc->DeleteTab(0);
+}
+
+void ScParallelismTest::testFormulaGroupSpanEvalNonGroup()
+{
+    sc::AutoCalcSwitch aACSwitch(*m_pDoc, false);
+    m_pDoc->InsertTab(0, "1");
+
+    constexpr size_t nFGLen = 2048;
+    OUString aFormula;
+
+    for (size_t nRow = 0; nRow < nFGLen; ++nRow)
+    {
+        aFormula = "=$B" + OUString::number(nRow+1) + " + 0";
+        m_pDoc->SetFormula(ScAddress(0, nRow, 0), aFormula,
+                           formula::FormulaGrammar::GRAM_NATIVE_UI);
+    }
+
+    m_xDocShell->DoHardRecalc();
+
+    constexpr size_t nNumChanges = 12;
+    constexpr size_t nChangeRows[nNumChanges] = {10, 11, 12, 101, 102, 103, 251, 252, 253, 503, 671, 1029};
+    for (size_t nIdx = 0; nIdx < nNumChanges; ++nIdx)
+    {
+        size_t nRow = nChangeRows[nIdx];
+        m_pDoc->SetValue(1, nRow, 0, 1.0);
+        ScFormulaCell* pFCell = m_pDoc->GetFormulaCell(ScAddress(0, nRow, 0));
+        pFCell->SetDirtyVar();
+    }
+
+    m_pDoc->SetAutoCalc(true);
+    bool bAnyDirty = m_pDoc->EnsureFormulaCellResults(ScRange(0, 9, 0, 0, 1030, 0));
+    CPPUNIT_ASSERT(bAnyDirty);
+    m_pDoc->SetAutoCalc(false);
+
+    OString aMsg;
+    for (size_t nRow = 0, nIdx = 0; nRow < nFGLen; ++nRow)
+    {
+        size_t nExpected = 0;
+        if (nIdx < nNumChanges && nRow == nChangeRows[nIdx])
+        {
+            nExpected = 1;
+            ++nIdx;
+        }
+
+        aMsg = "Value at Cell A" + OString::number(nRow+1);
+        CPPUNIT_ASSERT_EQUAL_MESSAGE(aMsg.getStr(), nExpected, static_cast<size_t>(m_pDoc->GetValue(0, nRow, 0)));
+    }
+
+    m_pDoc->DeleteTab(0);
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(ScParallelismTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sc/source/core/data/column2.cxx b/sc/source/core/data/column2.cxx
index 5f093d4c660e..8d2702980fba 100644
--- a/sc/source/core/data/column2.cxx
+++ b/sc/source/core/data/column2.cxx
@@ -2906,70 +2906,6 @@ void ScColumn::AssertNoInterpretNeeded( SCROW nRow1, SCROW nRow2 )
 }
 #endif
 
-
-bool ScColumn::HandleRefArrayForParallelism( SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup )
-{
-    if (nRow1 > nRow2)
-        return false;
-
-    std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow1);
-    sc::CellStoreType::const_iterator it = aPos.first;
-    size_t nOffset = aPos.second;
-    SCROW nRow = nRow1;
-    for (;it != maCells.end() && nRow <= nRow2; ++it, nOffset = 0)
-    {
-        switch( it->type )
-        {
-            case sc::element_type_edittext:
-                // These require EditEngine (in ScEditUtils::GetString()), which is probably
-                // too complex for use in threads.
-                return false;
-            case sc::element_type_formula:
-            {
-                size_t nRowsToRead = nRow2 - nRow + 1;
-                size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1
-                sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data);
-                std::advance(itCell, nOffset);
-                // Loop inside the formula block.
-                for (size_t i = nOffset; i < nEnd; ++itCell, ++i)
-                {
-                    // Check if itCell is already in path.
-                    // If yes use a cycle guard to mark all elements of the cycle
-                    // and return false
-                    const ScFormulaCellGroupRef& mxGroupChild = (*itCell)->GetCellGroup();
-                    ScFormulaCell* pChildTopCell = mxGroupChild ? mxGroupChild->mpTopCell : *itCell;
-                    if (pChildTopCell->GetSeenInPath())
-                    {
-                        ScRecursionHelper& rRecursionHelper = GetDoc()->GetRecursionHelper();
-                        ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pChildTopCell);
-                        return false;
-                    }
-
-                    (*itCell)->MaybeInterpret();
-
-                    // child cell's Interpret could result in calling dependency calc
-                    // and that could detect a cycle involving mxGroup
-                    // and do early exit in that case.
-                    if (mxGroup->mbPartOfCycle)
-                    {
-                        // Set itCell as dirty as itCell may be interpreted in InterpretTail()
-                        (*itCell)->SetDirtyVar();
-                        return false;
-                    }
-                }
-                nRow += nEnd - nOffset;
-                break;
-            }
-            default:
-                // Skip this block.
-                nRow += it->size - nOffset;
-                continue;
-        }
-    }
-
-    return true;
-}
-
 void ScColumn::SetFormulaResults( SCROW nRow, const double* pResults, size_t nLen )
 {
     sc::CellStoreType::position_type aPos = maCells.position(nRow);
@@ -3030,6 +2966,8 @@ void ScColumn::CalculateInThread( ScInterpreterContext& rContext, SCROW nRow, si
             continue;
 
         ScFormulaCell& rCell = **itCell;
+        if (!rCell.NeedsInterpret())
+            continue;
         // Here we don't call IncInterpretLevel() and DecInterpretLevel() as this call site is
         // always in a threaded calculation.
         rCell.InterpretTail(rContext, ScFormulaCell::SCITP_NORMAL);
diff --git a/sc/source/core/data/column4.cxx b/sc/source/core/data/column4.cxx
index 7448ef02dbab..4e04c3cca55b 100644
--- a/sc/source/core/data/column4.cxx
+++ b/sc/source/core/data/column4.cxx
@@ -28,6 +28,7 @@
 #include <sharedformula.hxx>
 #include <drwlayer.hxx>
 #include <compiler.hxx>
+#include <recursionhelper.hxx>
 
 #include <svl/sharedstringpool.hxx>
 #include <sal/log.hxx>
@@ -1642,20 +1643,201 @@ std::unique_ptr<sc::ColumnIterator> ScColumn::GetColumnIterator( SCROW nRow1, SC
     return std::make_unique<sc::ColumnIterator>(maCells, nRow1, nRow2);
 }
 
-void ScColumn::EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2 )
+static bool lcl_InterpretSpan(sc::formula_block::const_iterator& rSpanIter, SCROW nStartOffset, SCROW nEndOffset,
+                              const ScFormulaCellGroupRef& mxParentGroup, bool& bAllowThreading)
 {
-    if (!ValidRow(nRow1) || !ValidRow(nRow2) || nRow1 > nRow2)
-        return;
+    bAllowThreading = true;
+    ScFormulaCell* pCellStart = nullptr;
+    SCROW nSpanStart = -1;
+    SCROW nSpanEnd = -1;
+    sc::formula_block::const_iterator itSpanStart;
+    bool bAnyDirty = false;
+    for (SCROW nFGOffset = nStartOffset; nFGOffset <= nEndOffset; ++rSpanIter, ++nFGOffset)
+    {
+        bool bThisDirty = (*rSpanIter)->NeedsInterpret();
+        if (!pCellStart && bThisDirty)
+        {
+            pCellStart = *rSpanIter;
+            itSpanStart = rSpanIter;
+            nSpanStart = nFGOffset;
+            bAnyDirty = true;
+        }
 
-    if (!HasFormulaCell(nRow1, nRow2))
-        return;
+        if (pCellStart && (!bThisDirty || nFGOffset == nEndOffset))
+        {
+            nSpanEnd = bThisDirty ? nFGOffset : nFGOffset - 1;
+            assert(nSpanStart >= nStartOffset && nSpanStart <= nSpanEnd && nSpanEnd <= nEndOffset);
+
+            // Found a completely dirty sub span [nSpanStart, nSpanEnd] inside the required span [nStartOffset, nEndOffset]
+            bool bGroupInterpreted = pCellStart->Interpret(nSpanStart, nSpanEnd);
 
-    sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2,
-        []( size_t /*nRow*/, ScFormulaCell* pCell )
+            // child cell's Interpret could result in calling dependency calc
+            // and that could detect a cycle involving mxGroup
+            // and do early exit in that case.
+            if (mxParentGroup && mxParentGroup->mbPartOfCycle)
+            {
+                // Set pCellStart as dirty as pCellStart may be interpreted in InterpretTail()
+                pCellStart->SetDirtyVar();
+                bAllowThreading = false;
+                return bAnyDirty;
+            }
+
+            if (!bGroupInterpreted)
+            {
+                // Evaluate from second cell in non-grouped style (no point in trying group-interpret again).
+                ++itSpanStart;
+                for (SCROW nIdx = nSpanStart+1; nIdx <= nSpanEnd; ++nIdx, ++itSpanStart)
+                    (*itSpanStart)->Interpret(); // We know for sure that this cell is dirty so directly call Interpret().
+            }
+
+            pCellStart = nullptr; // For next sub span start detection.
+        }
+    }
+
+    return bAnyDirty;
+}
+
+static void lcl_EvalDirty(sc::CellStoreType& rCells, SCROW nRow1, SCROW nRow2, ScDocument& rDoc,
+                          const ScFormulaCellGroupRef& mxGroup, bool bThreadingDepEval, bool bSkipRunning,
+                          bool& bIsDirty, bool& bAllowThreading)
+{
+    std::pair<sc::CellStoreType::const_iterator,size_t> aPos = rCells.position(nRow1);
+    sc::CellStoreType::const_iterator it = aPos.first;
+    size_t nOffset = aPos.second;
+    SCROW nRow = nRow1;
+
+    bIsDirty = false;
+
+    for (;it != rCells.end() && nRow <= nRow2; ++it, nOffset = 0)
+    {
+        switch( it->type )
         {
-            pCell->MaybeInterpret();
+            case sc::element_type_edittext:
+                // These require EditEngine (in ScEditUtils::GetString()), which is probably
+                // too complex for use in threads.
+                if (bThreadingDepEval)
+                {
+                    bAllowThreading = false;
+                    return;
+                }
+                break;
+            case sc::element_type_formula:
+            {
+                size_t nRowsToRead = nRow2 - nRow + 1;
+                const size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1
+                sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data);
+                std::advance(itCell, nOffset);
+
+                // Loop inside the formula block.
+                size_t nCellIdx = nOffset;
+                while (nCellIdx < nEnd)
+                {
+                    const ScFormulaCellGroupRef& mxGroupChild = (*itCell)->GetCellGroup();
+                    ScFormulaCell* pChildTopCell = mxGroupChild ? mxGroupChild->mpTopCell : *itCell;
+
+                    // Check if itCell is already in path.
+                    // If yes use a cycle guard to mark all elements of the cycle
+                    // and return false
+                    if (bThreadingDepEval && pChildTopCell->GetSeenInPath())
+                    {
+                        ScRecursionHelper& rRecursionHelper = rDoc.GetRecursionHelper();
+                        ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pChildTopCell);
+                        bAllowThreading = false;
+                        return;
+                    }
+
+                    if (bSkipRunning && (*itCell)->IsRunning())
+                    {
+                        ++itCell;
+                        nCellIdx += 1;
+                        nRow += 1;
+                        nRowsToRead -= 1;
+                        continue;
+                    }
+
+                    if (mxGroupChild)
+                    {
+                        // It is a Formula-group, evaluate the necessary parts of it (spans).
+                        const SCROW nFGStartOffset = (*itCell)->aPos.Row() - pChildTopCell->aPos.Row();
+                        const SCROW nFGEndOffset = std::min(nFGStartOffset + static_cast<SCROW>(nRowsToRead) - 1, mxGroupChild->mnLength - 1);
+                        assert(nFGEndOffset >= nFGStartOffset);
+                        const SCROW nSpanLen = nFGEndOffset - nFGStartOffset + 1;
+                        // The (main) span required to be evaluated is [nFGStartOffset, nFGEndOffset], but this span may contain
+                        // non-dirty cells, so split this into sets of completely-dirty spans and try evaluate each of them in grouped-style.
+
+                        bool bAnyDirtyInSpan = lcl_InterpretSpan(itCell, nFGStartOffset, nFGEndOffset, mxGroup, bAllowThreading);
+                        if (!bAllowThreading)
+                            return;
+                        // itCell will now point to cell just after the end of span [nFGStartOffset, nFGEndOffset].
+                        bIsDirty = bIsDirty || bAnyDirtyInSpan;
+
+                        // update the counters by nSpanLen.
+                        // itCell already got updated.
+                        nCellIdx += nSpanLen;
+                        nRow += nSpanLen;
+                        nRowsToRead -= nSpanLen;
+                    }
+                    else
+                    {
+                        // No formula-group here.
+                        bool bDirtyFlag = (*itCell)->MaybeInterpret();
+                        bIsDirty = bIsDirty || bDirtyFlag;
+
+                        // child cell's Interpret could result in calling dependency calc
+                        // and that could detect a cycle involving mxGroup
+                        // and do early exit in that case.
+                        if (bThreadingDepEval && mxGroup && mxGroup->mbPartOfCycle)
+                        {
+                            // Set itCell as dirty as itCell may be interpreted in InterpretTail()
+                            (*itCell)->SetDirtyVar();
+                            bAllowThreading = false;
+                            return;
+                        }
+
+                        // update the counters by 1.
+                        nCellIdx += 1;
+                        nRow += 1;
+                        nRowsToRead -= 1;
+                        ++itCell;
+                    }
+                }
+                break;
+            }
+            default:
+                // Skip this block.
+                nRow += it->size - nOffset;
+                continue;
         }
-    );
+    }
+
+    if (bThreadingDepEval)
+        bAllowThreading = true;
+
+}
+
+// Returns true if at least one FC is dirty.
+bool ScColumn::EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2, bool bSkipRunning )
+{
+    if (!ValidRow(nRow1) || !ValidRow(nRow2) || nRow1 > nRow2)
+        return false;
+
+    if (!HasFormulaCell(nRow1, nRow2))
+        return false;
+
+    bool bAnyDirty = false, bTmp = false;
+    lcl_EvalDirty(maCells, nRow1, nRow2, *GetDoc(), nullptr, false, bSkipRunning, bAnyDirty, bTmp);
+    return bAnyDirty;
+}
+
+bool ScColumn::HandleRefArrayForParallelism( SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup )
+{
+    if (nRow1 > nRow2)
+        return false;
+
+    bool bAllowThreading = true, bTmp = false;
+    lcl_EvalDirty(maCells, nRow1, nRow2, *GetDoc(), mxGroup, true, false, bTmp, bAllowThreading);
+
+    return bAllowThreading;
 }
 
 namespace {
diff --git a/sc/source/core/data/document10.cxx b/sc/source/core/data/document10.cxx
index c495ff133db6..938d096c071d 100644
--- a/sc/source/core/data/document10.cxx
+++ b/sc/source/core/data/document10.cxx
@@ -944,17 +944,21 @@ std::unique_ptr<sc::ColumnIterator> ScDocument::GetColumnIterator( SCTAB nTab, S
     return pTab->GetColumnIterator(nCol, nRow1, nRow2);
 }
 
-void ScDocument::EnsureFormulaCellResults( const ScRange& rRange )
+bool ScDocument::EnsureFormulaCellResults( const ScRange& rRange, bool bSkipRunning )
 {
+    bool bAnyDirty = false;
     for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
     {
         ScTable* pTab = FetchTable(nTab);
         if (!pTab)
             continue;
 
-        pTab->EnsureFormulaCellResults(
-            rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
+        bool bRet = pTab->EnsureFormulaCellResults(
+            rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), bSkipRunning);
+        bAnyDirty = bAnyDirty || bRet;
     }
+
+    return bAnyDirty;
 }
 
 sc::ExternalDataMapper& ScDocument::GetExternalDataMapper()
diff --git a/sc/source/core/data/formulacell.cxx b/sc/source/core/data/formulacell.cxx
index eac866003765..94d6ad81da8b 100644
--- a/sc/source/core/data/formulacell.cxx
+++ b/sc/source/core/data/formulacell.cxx
@@ -1509,9 +1509,10 @@ struct TemporaryCellGroupMaker
 
 } // namespace
 
-void ScFormulaCell::Interpret()
+bool ScFormulaCell::Interpret(SCROW nStartOffset, SCROW nEndOffset)
 {
     ScRecursionHelper& rRecursionHelper = pDocument->GetRecursionHelper();
+    bool bGroupInterpreted = false;
 
     static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType();
     TemporaryCellGroupMaker cellGroupMaker( this, forceType != ForceCalculationNone && forceType != ForceCalculationCore );
@@ -1524,7 +1525,7 @@ void ScFormulaCell::Interpret()
         aResult.SetResultError( FormulaError::CircularReference );
         // This will mark all elements in the cycle as parts-of-cycle.
         ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pTopCell);
-        return;
+        return bGroupInterpreted;
     }
 
 #if DEBUG_CALCULATION
@@ -1539,20 +1540,20 @@ void ScFormulaCell::Interpret()
 #endif
 
     if (!IsDirtyOrInTableOpDirty() || rRecursionHelper.IsInReturn())
-        return;     // no double/triple processing
+        return bGroupInterpreted;     // no double/triple processing
 
     //FIXME:
     //  If the call originates from a Reschedule in DdeLink update, leave dirty
     //  Better: Do a Dde Link Update without Reschedule or do it completely asynchronously!
     if ( pDocument->IsInDdeLinkUpdate() )
-        return;
+        return bGroupInterpreted;
 
     if (bRunning)
     {
         if (!pDocument->GetDocOptions().IsIter())
         {
             aResult.SetResultError( FormulaError::CircularReference );
-            return;
+            return bGroupInterpreted;
         }
 
         if (aResult.GetResultError() == FormulaError::CircularReference)
@@ -1563,14 +1564,14 @@ void ScFormulaCell::Interpret()
                 !rRecursionHelper.GetRecursionInIterationStack().top()->bIsIterCell)
             rRecursionHelper.SetInIterationReturn( true);
 
-        return;
+        return bGroupInterpreted;
     }
     // no multiple interprets for GetErrCode, IsValue, GetValue and
     // different entry point recursions. Would also lead to premature
     // convergence in iterations.
     if (rRecursionHelper.GetIteration() && nSeenInIteration ==
             rRecursionHelper.GetIteration())
-        return ;
+        return bGroupInterpreted;
 
     bool bOldRunning = bRunning;
     if (rRecursionHelper.GetRecursionCount() > MAXRECURSION)
@@ -1586,7 +1587,7 @@ void ScFormulaCell::Interpret()
         aDC.enterGroup();
 #endif
         bool bPartOfCycleBefore = mxGroup && mxGroup->mbPartOfCycle;
-        bool bGroupInterpreted = InterpretFormulaGroup();
+        bGroupInterpreted = InterpretFormulaGroup(nStartOffset, nEndOffset);
         bool bPartOfCycleAfter = mxGroup && mxGroup->mbPartOfCycle;
 
 #if DEBUG_CALCULATION
@@ -1600,7 +1601,7 @@ void ScFormulaCell::Interpret()
             if (!bPartOfCycleBefore && bPartOfCycleAfter && rRecursionHelper.AnyParentFGInCycle())
             {
                 pDocument->DecInterpretLevel();
-                return;
+                return bGroupInterpreted;
             }
 
             ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this);
@@ -1846,6 +1847,8 @@ void ScFormulaCell::Interpret()
     else
         aDC.storeResult( aResult.GetString());
 #endif
+
+    return bGroupInterpreted;
 }
 
 void ScFormulaCell::InterpretTail( ScInterpreterContext& rContext, ScInterpretTailParameter eTailParam )
@@ -4232,9 +4235,12 @@ struct ScDependantsCalculator
     const SCROW mnLen;
     const ScAddress& mrPos;
     const bool mFromFirstRow;
+    const SCROW mnStartOffset;
+    const SCROW mnEndOffset;
+    const SCROW mnSpanLen;
 
     ScDependantsCalculator(ScDocument& rDoc, const ScTokenArray& rCode, const ScFormulaCell& rCell,
-            const ScAddress& rPos, bool fromFirstRow) :
+            const ScAddress& rPos, bool fromFirstRow, SCROW nStartOffset, SCROW nEndOffset) :
         mrDoc(rDoc),
         mrCode(rCode),
         mxGroup(rCell.GetCellGroup()),
@@ -4243,7 +4249,10 @@ struct ScDependantsCalculator
         // ScColumn::FetchVectorRefArray() always fetches data from row 0, even if the data is used
         // only from further rows. This data fetching could also lead to Interpret() calls, so
         // in OpenCL mode the formula in practice depends on those cells too.
-        mFromFirstRow(fromFirstRow)
+        mFromFirstRow(fromFirstRow),
+        mnStartOffset(nStartOffset),
+        mnEndOffset(nEndOffset),
+        mnSpanLen(nEndOffset - nStartOffset + 1)
     {
     }
 
@@ -4369,7 +4378,10 @@ struct ScDependantsCalculator
         // Partially from ScGroupTokenConverter::convert in sc/source/core/data/grouptokenconverter.cxx
 
         ScRangeList aRangeList;
+
+        // Self references should be checked by considering the entire formula-group not just the provided span.
         bool bHasSelfReferences = false;
+
         for (auto p: mrCode.RPNTokens())
         {
             switch (p->GetType())
@@ -4393,10 +4405,10 @@ struct ScDependantsCalculator
                         }
 
                         // Trim data array length to actual data range.
-                        SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row(), mnLen);
+                        SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row() + mnStartOffset, mnSpanLen);
 
-                        aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row(), aRefPos.Tab(),
-                                                aRefPos.Col(), aRefPos.Row() + nTrimLen - 1, aRefPos.Tab()));
+                        aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row() + mnStartOffset, aRefPos.Tab(),
+                                                aRefPos.Col(), aRefPos.Row() + mnStartOffset + nTrimLen - 1, aRefPos.Tab()));
                     }
                     else
                     {
@@ -4458,26 +4470,19 @@ struct ScDependantsCalculator
                         continue;
                     }
 
-                    // Row reference is relative.
-                    bool bAbsLast = !aRef.Ref2.IsRowRel();
-                    ScAddress aRefPos = aAbs.aStart;
-                    SCROW nRefRowSize = aAbs.aEnd.Row() - aAbs.aStart.Row() + 1;
-                    SCROW nArrayLength = nRefRowSize;
-                    if (!bAbsLast)
-                    {
-                        // range end position is relative. Extend the array length.
-                        SCROW nLastRefRowOffset = aAbs.aEnd.Row() - mrPos.Row();
-                        SCROW nLastRefRow = mrPos.Row() + mnLen - 1 + nLastRefRowOffset;
-                        SCROW nNewLength = nLastRefRow - aAbs.aStart.Row() + 1;
-                        if (nNewLength > nArrayLength)
-                            nArrayLength = nNewLength;
-                    }
+                    // The first row that will be referenced through the doubleref.
+                    SCROW nFirstRefRow = bIsRef1RowRel ? aAbs.aStart.Row() + mnStartOffset : aAbs.aStart.Row();
+                    // The last row that will be referenced through the doubleref.
+                    SCROW nLastRefRow =  bIsRef2RowRel ? aAbs.aEnd.Row() + mnEndOffset : aAbs.aEnd.Row();
+                    // Number of rows to be evaluated from nFirstRefRow.
+                    SCROW nArrayLength = nLastRefRow - nFirstRefRow + 1;
+                    assert(nArrayLength > 0);
 
                     // Trim trailing empty rows.
-                    nArrayLength = trimLength(aRefPos.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), aRefPos.Row(), nArrayLength);
+                    nArrayLength = trimLength(aAbs.aStart.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), nFirstRefRow, nArrayLength);
 
-                    aRangeList.Join(ScRange(aAbs.aStart.Col(), aRefPos.Row(), aRefPos.Tab(),
-                               aAbs.aEnd.Col(), aRefPos.Row() + nArrayLength - 1, aRefPos.Tab()));
+                    aRangeList.Join(ScRange(aAbs.aStart.Col(), nFirstRefRow, aAbs.aStart.Tab(),
+                               aAbs.aEnd.Col(), nFirstRefRow + nArrayLength - 1, aAbs.aEnd.Tab()));
                 }
                 break;
             default:
@@ -4510,7 +4515,7 @@ struct ScDependantsCalculator
     }
 };
 
-bool ScFormulaCell::InterpretFormulaGroup()
+bool ScFormulaCell::InterpretFormulaGroup(SCROW nStartOffset, SCROW nEndOffset)
 {
     if (!mxGroup || !pCode)
         return false;
@@ -4573,17 +4578,29 @@ bool ScFormulaCell::InterpretFormulaGroup()
     bool bDependencyComputed = false;
     bool bDependencyCheckFailed = false;
 
+    // Get rid of -1's in offsets (defaults) or any invalid offsets.
+    SCROW nMaxOffset = mxGroup->mnLength - 1;
+    nStartOffset = nStartOffset < 0 ? 0 : std::min(nStartOffset, nMaxOffset);
+    nEndOffset = nEndOffset < 0 ? nMaxOffset : std::min(nEndOffset, nMaxOffset);
+
+    if (nEndOffset < nStartOffset)
+    {
+        nStartOffset = 0;
+        nEndOffset = nMaxOffset;
+    }
+
     // Preference order: First try OpenCL, then threading.
+    // TODO: Do formula-group span computation for OCL too if nStartOffset/nEndOffset are non default.
     if( InterpretFormulaGroupOpenCL(aScope, bDependencyComputed, bDependencyCheckFailed))
         return true;
 
-    if( InterpretFormulaGroupThreading(aScope, bDependencyComputed, bDependencyCheckFailed))
+    if( InterpretFormulaGroupThreading(aScope, bDependencyComputed, bDependencyCheckFailed, nStartOffset, nEndOffset))
         return true;
 
     return false;
 }
 
-bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow)
+bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow, SCROW nStartOffset, SCROW nEndOffset)
 {
     ScRecursionHelper& rRecursionHelper = pDocument->GetRecursionHelper();
     // iterate over code in the formula ...
@@ -4601,7 +4618,7 @@ bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rSco
         }
 
         ScFormulaGroupDependencyComputeGuard aDepComputeGuard(rRecursionHelper);
-        ScDependantsCalculator aCalculator(*pDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow);
+        ScDependantsCalculator aCalculator(*pDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset);
         bOKToParallelize = aCalculator.DoIt();
     }
 
@@ -4632,14 +4649,16 @@ bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rSco
 // To be called only from InterpretFormulaGroup().
 bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope& aScope,
                                                    bool& bDependencyComputed,
-                                                   bool& bDependencyCheckFailed)
+                                                   bool& bDependencyCheckFailed,
+                                                   SCROW nStartOffset,
+                                                   SCROW nEndOffset)
 {
     static const bool bThreadingProhibited = std::getenv("SC_NO_THREADED_CALCULATION");
     if (!bDependencyCheckFailed && !bThreadingProhibited &&
         pCode->IsEnabledForThreading() &&
         ScCalcConfig::isThreadingEnabled())
     {
-        if(!bDependencyComputed && !CheckComputeDependencies(aScope))
+        if(!bDependencyComputed && !CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset))
         {
             bDependencyComputed = true;
             bDependencyCheckFailed = true;
@@ -4660,7 +4679,8 @@ bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope
             ScDocument* mpDocument;
             ScInterpreterContext* mpContext;
             const ScAddress& mrTopPos;
-            SCROW const mnLength;
+            SCROW const mnStartOffset;
+            SCROW const mnEndOffset;
 
         public:
             Executor(const std::shared_ptr<comphelper::ThreadTaskTag>& rTag,
@@ -4669,20 +4689,23 @@ bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope
                      ScDocument* pDocument2,
                      ScInterpreterContext* pContext,
                      const ScAddress& rTopPos,
-                     SCROW nLength) :
+                     SCROW nStartOffset,
+                     SCROW nEndOffset) :
                 comphelper::ThreadTask(rTag),
                 mnThisThread(nThisThread),
                 mnThreadsTotal(nThreadsTotal),
                 mpDocument(pDocument2),
                 mpContext(pContext),
                 mrTopPos(rTopPos),
-                mnLength(nLength)
+                mnStartOffset(nStartOffset),
+                mnEndOffset(nEndOffset)
             {
             }
 
             virtual void doWork() override
             {
-                mpDocument->CalculateInColumnInThread(*mpContext, mrTopPos, mnLength, mnThisThread, mnThreadsTotal);
+                ScAddress aStartPos(mrTopPos.Col(), mrTopPos.Row() + mnStartOffset, mrTopPos.Tab());
+                mpDocument->CalculateInColumnInThread(*mpContext, aStartPos, mnEndOffset - mnStartOffset + 1, mnThisThread, mnThreadsTotal);
                 ScDocumentThreadSpecific::MergeBackIntoNonThreadedData(mpDocument->maNonThreaded);
             }
 
@@ -4707,11 +4730,13 @@ bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope
             std::shared_ptr<comphelper::ThreadTaskTag> aTag = comphelper::ThreadPool::createThreadTaskTag();
             ScThreadedInterpreterContextGetterGuard aContextGetterGuard(nThreadCount, *pDocument, pNonThreadedFormatter);
             ScInterpreterContext* context = nullptr;
+
             for (int i = 0; i < nThreadCount; ++i)
             {
                 context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i);
                 ScDocument::SetupFromNonThreadedContext(*context, i);
-                rThreadPool.pushTask(std::make_unique<Executor>(aTag, i, nThreadCount, pDocument, context, mxGroup->mpTopCell->aPos, mxGroup->mnLength));
+                rThreadPool.pushTask(std::make_unique<Executor>(aTag, i, nThreadCount, pDocument, context, mxGroup->mpTopCell->aPos,
+                                                                nStartOffset, nEndOffset));
             }
 
             SAL_INFO("sc.threaded", "Joining threads");
@@ -4729,7 +4754,10 @@ bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope
             SAL_INFO("sc.threaded", "Done");
         }
 
-        pDocument->HandleStuffAfterParallelCalculation(mxGroup->mpTopCell->aPos, mxGroup->mnLength);
+        ScAddress aStartPos(mxGroup->mpTopCell->aPos);
+        SCROW nSpanLen = nEndOffset - nStartOffset + 1;
+        aStartPos.SetRow(aStartPos.Row() + nStartOffset);
+        pDocument->HandleStuffAfterParallelCalculation(aStartPos, nSpanLen);
 
         return true;
     }
@@ -4782,7 +4810,7 @@ bool ScFormulaCell::InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope& a
     if (bDependencyCheckFailed)
         return false;
 
-    if(!bDependencyComputed && !CheckComputeDependencies(aScope, true))
+    if(!bDependencyComputed && !CheckComputeDependencies(aScope, true, 0, mxGroup->mnLength - 1))
     {
         bDependencyComputed = true;
         bDependencyCheckFailed = true;
diff --git a/sc/source/core/data/table7.cxx b/sc/source/core/data/table7.cxx
index cbc3949d47b6..30f869a58733 100644
--- a/sc/source/core/data/table7.cxx
+++ b/sc/source/core/data/table7.cxx
@@ -429,15 +429,22 @@ std::unique_ptr<sc::ColumnIterator> ScTable::GetColumnIterator( SCCOL nCol, SCRO
     return aCol[nCol].GetColumnIterator(nRow1, nRow2);
 }
 
-void ScTable::EnsureFormulaCellResults( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2 )
+bool ScTable::EnsureFormulaCellResults( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2, bool bSkipRunning )
 {
     if (nCol2 < nCol1 || !IsColValid(nCol1) || !ValidCol(nCol2))
-        return;
+        return false;
 
     const SCCOL nMaxCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );
 
+    bool bAnyDirty = false;
+
     for (SCCOL nCol = nCol1; nCol <= nMaxCol2; ++nCol)
-        aCol[nCol].EnsureFormulaCellResults(nRow1, nRow2);
+    {
+        bool bRet = aCol[nCol].EnsureFormulaCellResults(nRow1, nRow2, bSkipRunning);
+        bAnyDirty = bAnyDirty || bRet;
+    }
+
+    return bAnyDirty;
 }
 
 void ScTable::finalizeOutlineImport()
diff --git a/sc/source/ui/view/output.cxx b/sc/source/ui/view/output.cxx
index 1cf2a1620d06..616e6d2073d7 100644
--- a/sc/source/ui/view/output.cxx
+++ b/sc/source/ui/view/output.cxx
@@ -1804,6 +1804,8 @@ void ScOutputData::FindChanged()
         pRowInfo[nArrY].bChanged = false;
 
     bool bProgress = false;
+    SCCOL nCol1 = MAXCOL, nCol2 = 0;
+    SCROW nRow1 = MAXROW, nRow2 = 0;
     for (nArrY=0; nArrY<nArrCount; nArrY++)
     {
         RowInfo* pThisRowInfo = &pRowInfo[nArrY];
@@ -1824,26 +1826,51 @@ void ScOutputData::FindChanged()
                 // still being interpreted. Skip it.
                 continue;
 
-            (void)pFCell->GetValue();
-            if (!pFCell->IsChanged())
-                // the result hasn't changed. Skip it.
-                continue;
+            ScAddress& rPos(pFCell->aPos);
+            nCol1 = std::min(rPos.Col(), nCol1);
+            nCol2 = std::max(rPos.Col(), nCol2);
+            nRow1 = std::min(rPos.Row(), nRow1);
+            nRow2 = std::max(rPos.Row(), nRow2);
+        }
+    }
+
+    if (bProgress)
+    {
+        mpDoc->EnsureFormulaCellResults(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab), true);
 
-            pThisRowInfo->bChanged = true;
-            if ( pThisRowInfo->pCellInfo[nX+1].bMerged )
+        for (nArrY=0; nArrY<nArrCount; nArrY++)
+        {
+            RowInfo* pThisRowInfo = &pRowInfo[nArrY];
+            for (nX=nX1; nX<=nX2; nX++)
             {
-                SCSIZE nOverY = nArrY + 1;
-                while ( nOverY<nArrCount &&
-                        pRowInfo[nOverY].pCellInfo[nX+1].bVOverlapped )
+                const ScRefCellValue& rCell = pThisRowInfo->pCellInfo[nX+1].maCell;
+
+                if (rCell.meType != CELLTYPE_FORMULA)
+                    continue;
+
+                ScFormulaCell* pFCell = rCell.mpFormula;
+
+                if (!pFCell->IsChanged())
+                    // the result hasn't changed. Skip it.
+                    continue;
+
+                pThisRowInfo->bChanged = true;
+                if ( pThisRowInfo->pCellInfo[nX+1].bMerged )
                 {
-                    pRowInfo[nOverY].bChanged = true;
-                    ++nOverY;
+                    SCSIZE nOverY = nArrY + 1;
+                    while ( nOverY<nArrCount &&
+                            pRowInfo[nOverY].pCellInfo[nX+1].bVOverlapped )
+                    {
+                        pRowInfo[nOverY].bChanged = true;
+                        ++nOverY;
+                    }
                 }
             }
         }
-    }
-    if ( bProgress )
+
         ScProgress::DeleteInterpretProgress();
+    }
+
     mpDoc->EnableIdle(bWasIdleEnabled);
 }
 


More information about the Libreoffice-commits mailing list