summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Francis <dennis.francis@collabora.com>2019-01-15 21:34:46 +0530
committerDennis Francis <dennis.francis@collabora.com>2019-02-05 13:56:22 +0100
commit3346947b7e102384dfc6cd98dbf7da81936f8fd6 (patch)
tree01db16dcb48779866e2f611a57e6836c4e9111c7
parentba1e745b3d022856080c25167226e8a9eeadc911 (diff)
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@collabora.com>
-rw-r--r--sc/inc/column.hxx2
-rw-r--r--sc/inc/document.hxx14
-rw-r--r--sc/inc/formulacell.hxx14
-rw-r--r--sc/inc/table.hxx2
-rw-r--r--sc/qa/unit/parallelism.cxx134
-rw-r--r--sc/source/core/data/column2.cxx66
-rw-r--r--sc/source/core/data/column4.cxx200
-rw-r--r--sc/source/core/data/document10.cxx10
-rw-r--r--sc/source/core/data/formulacell.cxx116
-rw-r--r--sc/source/core/data/table7.cxx13
-rw-r--r--sc/source/ui/view/output.cxx53
11 files changed, 472 insertions, 152 deletions
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);
}