1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_feature_opencl.h>
22 #include <sal/config.h>
23 #include <sal/log.hxx>
24 #include <osl/diagnose.h>
29 #include <formulacell.hxx>
30 #include <grouptokenconverter.hxx>
32 #include <compiler.hxx>
33 #include <document.hxx>
34 #include <cellvalue.hxx>
35 #include <interpre.hxx>
36 #include <macromgr.hxx>
37 #include <refupdat.hxx>
38 #include <recursionhelper.hxx>
39 #include <docoptio.hxx>
40 #include <rangenam.hxx>
41 #include <rangelst.hxx>
43 #include <progress.hxx>
44 #include <scmatrix.hxx>
45 #include <rechead.hxx>
46 #include <scitems.hxx>
47 #include <validat.hxx>
48 #include <editutil.hxx>
49 #include <chgtrack.hxx>
50 #include <tokenarray.hxx>
52 #include <comphelper/threadpool.hxx>
53 #include <editeng/editobj.hxx>
54 #include <formula/errorcodes.hxx>
55 #include <svl/intitem.hxx>
56 #include <svl/numformat.hxx>
57 #include <formulagroup.hxx>
58 #include <listenercontext.hxx>
60 #include <scopetools.hxx>
61 #include <refupdatecontext.hxx>
62 #include <tokenstringcontext.hxx>
63 #include <refhint.hxx>
64 #include <listenerquery.hxx>
65 #include <listenerqueryids.hxx>
66 #include <grouparealistener.hxx>
67 #include <formulalogger.hxx>
68 #include <com/sun/star/sheet/FormulaLanguage.hpp>
70 #if HAVE_FEATURE_OPENCL
71 #include <opencl/openclwrapper.hxx>
77 using namespace formula
;
79 #define DEBUG_CALCULATION 0
81 static bool bDebugCalculationActive
= false; // Set to true for global active init,
82 static ScAddress
aDebugCalculationTriggerAddress(1,2,0); // or on cell Sheet1.B3, whatever you like
84 struct DebugCalculationEntry
88 const ScDocument
& mrDoc
;
90 sal_uInt16 mnRecursion
;
92 DebugCalculationEntry( const ScAddress
& rPos
, ScDocument
& rDoc
, sal_uInt32 nGroup
) :
96 mnRecursion(rDoc
.GetRecursionHelper().GetRecursionCount())
101 /** Debug/dump formula cell calculation chain.
102 Either, somewhere set aDC.mbActive=true, or
103 aDC.maTrigger=ScAddress(col,row,tab) of interest from where to start.
104 This does not work for deep recursion > MAXRECURSION, the results are
105 somewhat... funny... ;)
107 static struct DebugCalculation
109 std::vector
< DebugCalculationEntry
> mvPos
;
110 std::vector
< DebugCalculationEntry
> mvResults
;
118 DebugCalculation() : mnGroup(0), mbActive(bDebugCalculationActive
), mbSwitchOff(false),
119 mbPrint(true), mbPrintResults(false) {}
121 /** Print chain in encountered dependency order. */
124 for (auto const& it
: mvPos
)
126 OUString
aStr( it
.maPos
.Format( ScRefFlags::VALID
| ScRefFlags::TAB_3D
, &it
.mrDoc
) +
127 " [" + OUString::number( it
.mnRecursion
) + "," + OUString::number( it
.mnGroup
) + "]");
128 fprintf( stderr
, "%s -> ", aStr
.toUtf8().getStr());
130 fprintf( stderr
, "%s", "END\n");
133 /** Print chain results. */
134 void printResults() const
136 for (auto const& it
: mvResults
)
138 OUString
aStr( it
.maPos
.Format( ScRefFlags::VALID
| ScRefFlags::TAB_3D
, &it
.mrDoc
));
139 aStr
+= " (" + it
.maResult
+ ")";
140 fprintf( stderr
, "%s, ", aStr
.toUtf8().getStr());
142 fprintf( stderr
, "%s", "END\n");
145 void storeResult( const svl::SharedString
& rStr
)
147 if (mbActive
&& !mvPos
.empty())
148 mvPos
.back().maResult
= "\"" + rStr
.getString() + "\"";
151 void storeResult( const double& fVal
)
153 if (mbActive
&& !mvPos
.empty())
154 mvPos
.back().maResult
= rtl::math::doubleToUString( fVal
, rtl_math_StringFormat_G
, 2, '.', true);
157 void storeResultError( FormulaError nErr
)
159 if (mbActive
&& !mvPos
.empty())
160 mvPos
.back().maResult
= "Err:" + OUString::number( int( nErr
));
175 struct DebugCalculationStacker
177 DebugCalculationStacker( const ScAddress
& rPos
, ScDocument
& rDoc
)
179 if (!aDC
.mbActive
&& rPos
== aDC
.maTrigger
)
180 aDC
.mbActive
= aDC
.mbSwitchOff
= true;
183 aDC
.mvPos
.push_back( DebugCalculationEntry( rPos
, rDoc
, aDC
.mnGroup
));
188 ~DebugCalculationStacker()
192 if (!aDC
.mvPos
.empty())
199 if (aDC
.mbPrintResults
)
201 // Store results until final result is available, reversing order.
202 aDC
.mvResults
.push_back( aDC
.mvPos
.back());
204 aDC
.mvPos
.pop_back();
205 if (aDC
.mbPrintResults
&& aDC
.mvPos
.empty())
208 std::vector
< DebugCalculationEntry
>().swap( aDC
.mvResults
);
210 if (aDC
.mbSwitchOff
&& aDC
.mvPos
.empty())
211 aDC
.mbActive
= false;
220 // More or less arbitrary, of course all recursions must fit into available
221 // stack space (which is what on all systems we don't know yet?). Choosing a
222 // lower value may be better than trying a much higher value that also isn't
223 // sufficient but temporarily leads to high memory consumption. On the other
224 // hand, if the value fits all recursions, execution is quicker as no resumes
225 // are necessary. Could be made a configurable option.
226 // Allow for a year's calendar (366).
227 const sal_uInt16 MAXRECURSION
= 400;
229 typedef SCCOLROW(*DimensionSelector
)(const ScDocument
&, const ScAddress
&, const ScSingleRefData
&);
231 SCCOLROW
lcl_GetCol(const ScDocument
& rDoc
, const ScAddress
& rPos
, const ScSingleRefData
& rData
)
233 return rData
.toAbs(rDoc
, rPos
).Col();
236 SCCOLROW
lcl_GetRow(const ScDocument
& rDoc
, const ScAddress
& rPos
, const ScSingleRefData
& rData
)
238 return rData
.toAbs(rDoc
, rPos
).Row();
241 SCCOLROW
lcl_GetTab(const ScDocument
& rDoc
, const ScAddress
& rPos
, const ScSingleRefData
& rData
)
243 return rData
.toAbs(rDoc
, rPos
).Tab();
246 /** Check if both references span the same range in selected dimension.
249 lcl_checkRangeDimension(
250 const ScDocument
& rDoc
,
251 const ScAddress
& rPos
, const SingleDoubleRefProvider
& rRef1
, const SingleDoubleRefProvider
& rRef2
,
252 const DimensionSelector aWhich
)
254 return aWhich(rDoc
, rPos
, rRef1
.Ref1
) == aWhich(rDoc
, rPos
, rRef2
.Ref1
) &&
255 aWhich(rDoc
, rPos
, rRef1
.Ref2
) == aWhich(rDoc
, rPos
, rRef2
.Ref2
);
259 lcl_checkRangeDimensions(
260 const ScDocument
& rDoc
,
261 const ScAddress
& rPos
, const SingleDoubleRefProvider
& rRef1
, const SingleDoubleRefProvider
& rRef2
,
262 bool& bCol
, bool& bRow
, bool& bTab
)
264 const bool bSameCols(lcl_checkRangeDimension(rDoc
, rPos
, rRef1
, rRef2
, lcl_GetCol
));
265 const bool bSameRows(lcl_checkRangeDimension(rDoc
, rPos
, rRef1
, rRef2
, lcl_GetRow
));
266 const bool bSameTabs(lcl_checkRangeDimension(rDoc
, rPos
, rRef1
, rRef2
, lcl_GetTab
));
268 // Test if exactly two dimensions are equal
269 if (int(bSameCols
) + int(bSameRows
) + int(bSameTabs
) == 2)
279 /** Check if references in given reference list can possibly
280 form a range. To do that, two of their dimensions must be the same.
283 lcl_checkRangeDimensions(
284 const ScDocument
& rDoc
, const ScAddress
& rPos
,
285 const std::vector
<formula::FormulaToken
*>::const_iterator
& rBegin
,
286 const std::vector
<formula::FormulaToken
*>::const_iterator
& rEnd
,
287 bool& bCol
, bool& bRow
, bool& bTab
)
289 std::vector
<formula::FormulaToken
*>::const_iterator
aCur(rBegin
);
291 const SingleDoubleRefProvider
aRef(**rBegin
);
294 const SingleDoubleRefProvider
aRefCur(**aCur
);
295 bOk
= lcl_checkRangeDimensions(rDoc
, rPos
, aRef
, aRefCur
, bCol
, bRow
, bTab
);
297 while (bOk
&& aCur
!= rEnd
)
299 const SingleDoubleRefProvider
aRefCur(**aCur
);
303 bOk
= lcl_checkRangeDimensions(rDoc
, rPos
, aRef
, aRefCur
, bColTmp
, bRowTmp
, bTabTmp
);
304 bOk
= bOk
&& (bCol
== bColTmp
&& bRow
== bRowTmp
&& bTab
== bTabTmp
);
308 return bOk
&& aCur
== rEnd
;
311 class LessByReference
313 const ScDocument
& mrDoc
;
315 DimensionSelector maFunc
;
317 LessByReference(const ScDocument
& rDoc
, const ScAddress
& rPos
, const DimensionSelector
& rFunc
) :
318 mrDoc(rDoc
), maPos(rPos
), maFunc(rFunc
) {}
320 bool operator() (const formula::FormulaToken
* pRef1
, const formula::FormulaToken
* pRef2
)
322 const SingleDoubleRefProvider
aRef1(*pRef1
);
323 const SingleDoubleRefProvider
aRef2(*pRef2
);
324 return maFunc(mrDoc
, maPos
, aRef1
.Ref1
) < maFunc(mrDoc
, maPos
, aRef2
.Ref1
);
329 * Returns true if range denoted by token p2 starts immediately after range
330 * denoted by token p1. Dimension, in which the comparison takes place, is
333 class AdjacentByReference
335 const ScDocument
& mrDoc
;
337 DimensionSelector maFunc
;
339 AdjacentByReference(const ScDocument
& rDoc
, const ScAddress
& rPos
, DimensionSelector aFunc
) :
340 mrDoc(rDoc
), maPos(rPos
), maFunc(aFunc
) {}
342 bool operator() (const formula::FormulaToken
* p1
, const formula::FormulaToken
* p2
)
344 const SingleDoubleRefProvider
aRef1(*p1
);
345 const SingleDoubleRefProvider
aRef2(*p2
);
346 return maFunc(mrDoc
, maPos
, aRef2
.Ref1
) - maFunc(mrDoc
, maPos
, aRef1
.Ref2
) == 1;
352 const ScDocument
& rDoc
,
353 const ScAddress
& rPos
, const std::vector
<formula::FormulaToken
*>& rReferences
, const DimensionSelector aWhich
)
355 auto aBegin(rReferences
.cbegin());
356 auto aEnd(rReferences
.cend());
357 auto aBegin1(aBegin
);
360 return std::equal(aBegin
, aEnd
, aBegin1
, AdjacentByReference(rDoc
, rPos
, aWhich
));
364 lcl_fillRangeFromRefList(
365 const ScDocument
& rDoc
,
366 const ScAddress
& aPos
, const std::vector
<formula::FormulaToken
*>& rReferences
, ScRange
& rRange
)
368 const ScSingleRefData
aStart(
369 SingleDoubleRefProvider(*rReferences
.front()).Ref1
);
370 rRange
.aStart
= aStart
.toAbs(rDoc
, aPos
);
371 const ScSingleRefData
aEnd(
372 SingleDoubleRefProvider(*rReferences
.back()).Ref2
);
373 rRange
.aEnd
= aEnd
.toAbs(rDoc
, aPos
);
377 lcl_refListFormsOneRange(
378 const ScDocument
& rDoc
,
379 const ScAddress
& rPos
, std::vector
<formula::FormulaToken
*>& rReferences
,
382 if (rReferences
.size() == 1)
384 lcl_fillRangeFromRefList(rDoc
, rPos
, rReferences
, rRange
);
391 if (lcl_checkRangeDimensions(rDoc
, rPos
, rReferences
.begin(), rReferences
.end(), bCell
, bRow
, bTab
))
393 DimensionSelector aWhich
;
408 OSL_FAIL( "lcl_checkRangeDimensions shouldn't allow that!");
409 aWhich
= lcl_GetRow
; // initialize to avoid warning
412 // Sort the references by start of range
413 std::sort(rReferences
.begin(), rReferences
.end(), LessByReference(rDoc
, rPos
, aWhich
));
414 if (lcl_checkIfAdjacent(rDoc
, rPos
, rReferences
, aWhich
))
416 lcl_fillRangeFromRefList(rDoc
, rPos
, rReferences
, rRange
);
423 bool lcl_isReference(const FormulaToken
& rToken
)
426 rToken
.GetType() == svSingleRef
||
427 rToken
.GetType() == svDoubleRef
;
430 void adjustRangeName(formula::FormulaToken
* pToken
, ScDocument
& rNewDoc
, const ScDocument
& rOldDoc
,
431 const ScAddress
& rNewPos
, const ScAddress
& rOldPos
, bool bGlobalNamesToLocal
)
433 ScRangeData
* pRangeData
= nullptr;
434 SCTAB nSheet
= pToken
->GetSheet();
435 sal_uInt16 nIndex
= pToken
->GetIndex();
436 if (!rOldDoc
.CopyAdjustRangeName( nSheet
, nIndex
, pRangeData
, rNewDoc
, rNewPos
, rOldPos
, bGlobalNamesToLocal
, true))
437 return; // nothing to do
441 // If this happened we have a real problem.
443 assert(!"inserting the range name should not fail");
447 pToken
->SetIndex(nIndex
);
448 pToken
->SetSheet(nSheet
);
451 void adjustDBRange(formula::FormulaToken
* pToken
, ScDocument
& rNewDoc
, const ScDocument
& rOldDoc
)
453 ScDBCollection
* pOldDBCollection
= rOldDoc
.GetDBCollection();
454 if (!pOldDBCollection
)
455 return;//strange error case, don't do anything
456 ScDBCollection::NamedDBs
& aOldNamedDBs
= pOldDBCollection
->getNamedDBs();
457 ScDBData
* pDBData
= aOldNamedDBs
.findByIndex(pToken
->GetIndex());
459 return; //invalid index
460 OUString aDBName
= pDBData
->GetUpperName();
462 //search in new document
463 ScDBCollection
* pNewDBCollection
= rNewDoc
.GetDBCollection();
464 if (!pNewDBCollection
)
466 rNewDoc
.SetDBCollection(std::unique_ptr
<ScDBCollection
>(new ScDBCollection(rNewDoc
)));
467 pNewDBCollection
= rNewDoc
.GetDBCollection();
469 ScDBCollection::NamedDBs
& aNewNamedDBs
= pNewDBCollection
->getNamedDBs();
470 ScDBData
* pNewDBData
= aNewNamedDBs
.findByUpperName(aDBName
);
473 pNewDBData
= new ScDBData(*pDBData
);
474 bool ins
= aNewNamedDBs
.insert(std::unique_ptr
<ScDBData
>(pNewDBData
));
475 assert(ins
); (void)ins
;
477 pToken
->SetIndex(pNewDBData
->GetIndex());
482 bool AreaListenerKey::operator < ( const AreaListenerKey
& r
) const
484 if (maRange
.aStart
.Tab() != r
.maRange
.aStart
.Tab())
485 return maRange
.aStart
.Tab() < r
.maRange
.aStart
.Tab();
486 if (maRange
.aStart
.Col() != r
.maRange
.aStart
.Col())
487 return maRange
.aStart
.Col() < r
.maRange
.aStart
.Col();
488 if (maRange
.aStart
.Row() != r
.maRange
.aStart
.Row())
489 return maRange
.aStart
.Row() < r
.maRange
.aStart
.Row();
490 if (maRange
.aEnd
.Tab() != r
.maRange
.aEnd
.Tab())
491 return maRange
.aEnd
.Tab() < r
.maRange
.aEnd
.Tab();
492 if (maRange
.aEnd
.Col() != r
.maRange
.aEnd
.Col())
493 return maRange
.aEnd
.Col() < r
.maRange
.aEnd
.Col();
494 if (maRange
.aEnd
.Row() != r
.maRange
.aEnd
.Row())
495 return maRange
.aEnd
.Row() < r
.maRange
.aEnd
.Row();
496 if (mbStartFixed
!= r
.mbStartFixed
)
497 return r
.mbStartFixed
;
498 if (mbEndFixed
!= r
.mbEndFixed
)
504 ScFormulaCellGroup::ScFormulaCellGroup() :
509 mnFormatType(SvNumFormatType::NUMBER
),
512 mbPartOfCycle(false),
513 meCalcState(sc::GroupCalcEnabled
)
517 ScFormulaCellGroup::~ScFormulaCellGroup()
521 void ScFormulaCellGroup::setCode( const ScTokenArray
& rCode
)
523 mpCode
= rCode
.CloneValue();
524 mbInvariant
= mpCode
->IsInvariant();
528 void ScFormulaCellGroup::compileCode(
529 ScDocument
& rDoc
, const ScAddress
& rPos
, FormulaGrammar::Grammar eGram
)
534 if (mpCode
->GetLen() && mpCode
->GetCodeError() == FormulaError::NONE
&& !mpCode
->GetCodeLen())
536 bool bMatrixFormula
= mpTopCell
->GetMatrixFlag() != ScMatrixMode::NONE
;
537 ScCompiler
aComp(rDoc
, rPos
, *mpCode
, eGram
, true, bMatrixFormula
);
538 mbSubTotal
= aComp
.CompileTokenArray();
539 mnFormatType
= aComp
.GetNumFormatType();
543 mbSubTotal
= mpCode
->HasOpCodeRPN( ocSubTotal
) || mpCode
->HasOpCodeRPN( ocAggregate
);
547 sc::FormulaGroupAreaListener
* ScFormulaCellGroup::getAreaListener(
548 ScFormulaCell
** ppTopCell
, const ScRange
& rRange
, bool bStartFixed
, bool bEndFixed
)
550 AreaListenerKey
aKey(rRange
, bStartFixed
, bEndFixed
);
552 AreaListenersType::iterator it
= m_AreaListeners
.lower_bound(aKey
);
553 if (it
== m_AreaListeners
.end() || m_AreaListeners
.key_comp()(aKey
, it
->first
))
556 it
= m_AreaListeners
.emplace_hint(
557 it
, std::piecewise_construct
,
558 std::forward_as_tuple(aKey
),
559 std::forward_as_tuple(
560 rRange
, (*ppTopCell
)->GetDocument(), (*ppTopCell
)->aPos
, mnLength
, bStartFixed
, bEndFixed
));
566 void ScFormulaCellGroup::endAllGroupListening( ScDocument
& rDoc
)
568 for (auto& rEntry
: m_AreaListeners
)
570 sc::FormulaGroupAreaListener
& rListener
= rEntry
.second
;
571 ScRange aListenRange
= rListener
.getListeningRange();
572 // This "always listen" special range is never grouped.
573 bool bGroupListening
= (aListenRange
!= BCA_LISTEN_ALWAYS
);
574 rDoc
.EndListeningArea(aListenRange
, bGroupListening
, &rListener
);
577 m_AreaListeners
.clear();
580 ScFormulaCell::ScFormulaCell( ScDocument
& rDoc
, const ScAddress
& rPos
) :
582 bTableOpDirty(false),
588 bInChangeTrack(false),
589 bNeedListening(false),
590 mbNeedsNumberFormat(false),
591 mbAllowNumberFormatChange(false),
592 mbPostponedDirty(false),
596 cMatrixFlag(ScMatrixMode::NONE
),
598 nFormatType(SvNumFormatType::NUMBER
),
599 eTempGrammar(formula::FormulaGrammar::GRAM_DEFAULT
),
600 pCode(new ScTokenArray(rDoc
)),
604 pPreviousTrack(nullptr),
610 ScFormulaCell::ScFormulaCell( ScDocument
& rDoc
, const ScAddress
& rPos
,
611 const OUString
& rFormula
,
612 const FormulaGrammar::Grammar eGrammar
,
613 ScMatrixMode cMatInd
) :
614 bDirty( true ), // -> Because of the use of the Auto Pilot Function was: cMatInd != 0
615 bTableOpDirty( false ),
620 bIsIterCell( false ),
621 bInChangeTrack( false ),
622 bNeedListening( false ),
623 mbNeedsNumberFormat( false ),
624 mbAllowNumberFormatChange(false),
625 mbPostponedDirty(false),
629 cMatrixFlag ( cMatInd
),
631 nFormatType ( SvNumFormatType::NUMBER
),
632 eTempGrammar( eGrammar
),
637 pPreviousTrack(nullptr),
641 Compile( rFormula
, true, eGrammar
); // bNoListening, Insert does that
643 // We need to have a non-NULL token array instance at all times.
644 pCode
= new ScTokenArray(rDoc
);
647 ScFormulaCell::ScFormulaCell(
648 ScDocument
& rDoc
, const ScAddress
& rPos
, std::unique_ptr
<ScTokenArray
> pArray
,
649 const FormulaGrammar::Grammar eGrammar
, ScMatrixMode cMatInd
) :
651 bTableOpDirty( false ),
656 bIsIterCell( false ),
657 bInChangeTrack( false ),
658 bNeedListening( false ),
659 mbNeedsNumberFormat( false ),
660 mbAllowNumberFormatChange(false),
661 mbPostponedDirty(false),
665 cMatrixFlag ( cMatInd
),
667 nFormatType ( SvNumFormatType::NUMBER
),
668 eTempGrammar( eGrammar
),
669 pCode(pArray
.release()),
673 pPreviousTrack(nullptr),
677 assert(pCode
); // Never pass a NULL pointer here.
679 pCode
->Finalize(); // Reduce memory usage if needed.
681 // Generate RPN token array.
682 if (pCode
->GetLen() && pCode
->GetCodeError() == FormulaError::NONE
&& !pCode
->GetCodeLen())
684 ScCompiler
aComp(rDocument
, aPos
, *pCode
, eTempGrammar
, true, cMatrixFlag
!= ScMatrixMode::NONE
);
685 bSubTotal
= aComp
.CompileTokenArray();
686 nFormatType
= aComp
.GetNumFormatType();
690 if ( pCode
->HasOpCodeRPN( ocSubTotal
) || pCode
->HasOpCodeRPN( ocAggregate
) )
695 rDocument
.AddSubTotalCell(this);
700 ScFormulaCell::ScFormulaCell(
701 ScDocument
& rDoc
, const ScAddress
& rPos
, const ScTokenArray
& rArray
,
702 const FormulaGrammar::Grammar eGrammar
, ScMatrixMode cMatInd
) :
704 bTableOpDirty( false ),
709 bIsIterCell( false ),
710 bInChangeTrack( false ),
711 bNeedListening( false ),
712 mbNeedsNumberFormat( false ),
713 mbAllowNumberFormatChange(false),
714 mbPostponedDirty(false),
718 cMatrixFlag ( cMatInd
),
720 nFormatType ( SvNumFormatType::NUMBER
),
721 eTempGrammar( eGrammar
),
722 pCode(new ScTokenArray(rArray
)), // also implicitly does Finalize() on the array
726 pPreviousTrack(nullptr),
730 // RPN array generation
731 if( pCode
->GetLen() && pCode
->GetCodeError() == FormulaError::NONE
&& !pCode
->GetCodeLen() )
733 ScCompiler
aComp( rDocument
, aPos
, *pCode
, eTempGrammar
, true, cMatrixFlag
!= ScMatrixMode::NONE
);
734 bSubTotal
= aComp
.CompileTokenArray();
735 nFormatType
= aComp
.GetNumFormatType();
739 if ( pCode
->HasOpCodeRPN( ocSubTotal
) || pCode
->HasOpCodeRPN( ocAggregate
) )
744 rDocument
.AddSubTotalCell(this);
749 ScFormulaCell::ScFormulaCell(
750 ScDocument
& rDoc
, const ScAddress
& rPos
, const ScFormulaCellGroupRef
& xGroup
,
751 const FormulaGrammar::Grammar eGrammar
, ScMatrixMode cInd
) :
754 bTableOpDirty( false ),
758 bSubTotal(xGroup
->mbSubTotal
),
759 bIsIterCell( false ),
760 bInChangeTrack( false ),
761 bNeedListening( false ),
762 mbNeedsNumberFormat( false ),
763 mbAllowNumberFormatChange(false),
764 mbPostponedDirty(false),
768 cMatrixFlag ( cInd
),
770 nFormatType(xGroup
->mnFormatType
),
771 eTempGrammar( eGrammar
),
772 pCode(xGroup
->mpCode
? &*xGroup
->mpCode
: new ScTokenArray(rDoc
)),
776 pPreviousTrack(nullptr),
781 rDocument
.AddSubTotalCell(this);
784 ScFormulaCell::ScFormulaCell(const ScFormulaCell
& rCell
, ScDocument
& rDoc
, const ScAddress
& rPos
, ScCloneFlags nCloneFlags
) :
785 bDirty( rCell
.bDirty
),
786 bTableOpDirty( false ),
787 bChanged( rCell
.bChanged
),
789 bCompile( rCell
.bCompile
),
790 bSubTotal( rCell
.bSubTotal
),
791 bIsIterCell( false ),
792 bInChangeTrack( false ),
793 bNeedListening( false ),
794 mbNeedsNumberFormat( rCell
.mbNeedsNumberFormat
),
795 mbAllowNumberFormatChange(false),
796 mbPostponedDirty(false),
800 cMatrixFlag ( rCell
.cMatrixFlag
),
802 nFormatType( rCell
.nFormatType
),
803 aResult( rCell
.aResult
),
804 eTempGrammar( rCell
.eTempGrammar
),
808 pPreviousTrack(nullptr),
812 pCode
= rCell
.pCode
->Clone().release();
814 // set back any errors and recompile
815 // not in the Clipboard - it must keep the received error flag
816 // Special Length=0: as bad cells are generated, then they are also retained
817 if ( pCode
->GetCodeError() != FormulaError::NONE
&& !rDocument
.IsClipboard() && pCode
->GetLen() )
819 pCode
->SetCodeError( FormulaError::NONE
);
822 // Compile ColRowNames on URM_MOVE/URM_COPY _after_ UpdateReference !
823 bool bCompileLater
= false;
824 bool bClipMode
= rCell
.rDocument
.IsClipboard();
826 //update ScNameTokens
827 if (!rDocument
.IsClipOrUndo() || rDoc
.IsUndo())
829 if (!rDocument
.IsClipboardSource() || aPos
.Tab() != rCell
.aPos
.Tab())
831 bool bGlobalNamesToLocal
= ((nCloneFlags
& ScCloneFlags::NamesToLocal
) != ScCloneFlags::Default
);
832 formula::FormulaToken
* pToken
= nullptr;
833 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
834 while((pToken
= aIter
.GetNextName())!= nullptr)
836 OpCode eOpCode
= pToken
->GetOpCode();
837 if (eOpCode
== ocName
)
838 adjustRangeName(pToken
, rDoc
, rCell
.rDocument
, aPos
, rCell
.aPos
, bGlobalNamesToLocal
);
839 else if (eOpCode
== ocDBArea
|| eOpCode
== ocTableRef
)
840 adjustDBRange(pToken
, rDoc
, rCell
.rDocument
);
844 bool bCopyBetweenDocs
= rDocument
.GetPool() != rCell
.rDocument
.GetPool();
845 if (bCopyBetweenDocs
&& !(nCloneFlags
& ScCloneFlags::NoMakeAbsExternal
))
847 pCode
->ReadjustAbsolute3DReferences(rCell
.rDocument
, rDoc
, rCell
.aPos
);
850 pCode
->AdjustAbsoluteRefs( rCell
.rDocument
, rCell
.aPos
, aPos
, bCopyBetweenDocs
);
853 if (!rDocument
.IsClipOrUndo())
855 if (&rDocument
.GetSharedStringPool() != &rCell
.rDocument
.GetSharedStringPool())
856 pCode
->ReinternStrings( rDocument
.GetSharedStringPool());
857 pCode
->AdjustReferenceOnCopy( aPos
);
861 { // Name references with references and ColRowNames
862 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
865 formula::FormulaToken
* t
= aIter
.GetNextReferenceOrName();
868 if ( t
->IsExternalRef() )
870 // External name, cell, and area references.
873 else if ( t
->GetType() == svIndex
)
875 const ScRangeData
* pRangeData
= rDoc
.FindRangeNameBySheetAndIndex( t
->GetSheet(), t
->GetIndex());
878 if( pRangeData
->HasReferences() )
882 bCompile
= true; // invalid reference!
884 else if ( t
->GetOpCode() == ocColRowName
)
886 bCompile
= true; // new lookup needed
887 bCompileLater
= bClipMode
;
893 if ( !bCompileLater
&& bClipMode
)
895 // Merging ranges needs the actual positions after UpdateReference.
896 // ColRowNames and TableRefs need new lookup after positions are
898 bCompileLater
= pCode
->HasOpCode( ocRange
) || pCode
->HasOpCode( ocColRowName
) ||
899 pCode
->HasOpCode( ocTableRef
);
901 if ( !bCompileLater
)
903 // bNoListening, not at all if in Clipboard/Undo,
904 // and not from Clipboard either, instead after Insert(Clone) and UpdateReference.
905 CompileTokenArray( true );
909 if( nCloneFlags
& ScCloneFlags::StartListening
)
910 StartListeningTo( rDoc
);
913 rDocument
.AddSubTotalCell(this);
916 ScFormulaCell::~ScFormulaCell()
918 rDocument
.RemoveFromFormulaTrack( this );
919 rDocument
.RemoveFromFormulaTree( this );
920 rDocument
.RemoveSubTotalCell(this);
921 if (pCode
->HasOpCode(ocMacro
))
922 rDocument
.GetMacroManager()->RemoveDependentCell(this);
924 if (rDocument
.HasExternalRefManager())
925 rDocument
.GetExternalRefManager()->removeRefCell(this);
927 if (!mxGroup
|| !mxGroup
->mpCode
)
928 // Formula token is not shared.
931 if (mxGroup
&& mxGroup
->mpTopCell
== this)
932 mxGroup
->mpTopCell
= nullptr;
935 ScFormulaCell
* ScFormulaCell::Clone() const
937 return new ScFormulaCell(*this, rDocument
, aPos
);
940 ScFormulaCell
* ScFormulaCell::Clone( const ScAddress
& rPos
) const
942 return new ScFormulaCell(*this, rDocument
, rPos
, ScCloneFlags::Default
);
945 size_t ScFormulaCell::GetHash() const
947 return pCode
->GetHash();
950 OUString
ScFormulaCell::GetFormula( const FormulaGrammar::Grammar eGrammar
, ScInterpreterContext
* pContext
) const
952 if( pCode
->GetCodeError() != FormulaError::NONE
&& !pCode
->GetLen() )
954 return ScGlobal::GetErrorString(pCode
->GetCodeError());
956 OUStringBuffer buffer
;
957 if( cMatrixFlag
== ScMatrixMode::Reference
)
959 // Reference to another cell that contains a matrix formula.
960 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
961 formula::FormulaToken
* p
= aIter
.GetNextReferenceRPN();
964 /* FIXME: original GetFormula() code obtained
965 * pCell only if (!IsInChangeTrack()),
966 * GetEnglishFormula() omitted that test.
967 * Can we live without in all cases? */
968 ScFormulaCell
* pCell
= nullptr;
969 ScSingleRefData
& rRef
= *p
->GetSingleRef();
970 ScAddress aAbs
= rRef
.toAbs(rDocument
, aPos
);
971 if (rDocument
.ValidAddress(aAbs
))
972 pCell
= rDocument
.GetFormulaCell(aAbs
);
976 return pCell
->GetFormula( eGrammar
, pContext
);
980 ScCompiler
aComp( rDocument
, aPos
, *pCode
, eGrammar
, false, false, pContext
);
981 aComp
.CreateStringFromTokenArray( buffer
);
986 OSL_FAIL("ScFormulaCell::GetFormula: not a matrix");
991 ScCompiler
aComp( rDocument
, aPos
, *pCode
, eGrammar
, false, false, pContext
);
992 aComp
.CreateStringFromTokenArray( buffer
);
995 buffer
.insert( 0, '=');
996 if( cMatrixFlag
!= ScMatrixMode::NONE
)
998 buffer
.insert( 0, '{');
1001 return buffer
.makeStringAndClear();
1004 OUString
ScFormulaCell::GetFormula( sc::CompileFormulaContext
& rCxt
, ScInterpreterContext
* pContext
) const
1006 OUStringBuffer aBuf
;
1007 if (pCode
->GetCodeError() != FormulaError::NONE
&& !pCode
->GetLen())
1009 ScTokenArray
aCode(rCxt
.getDoc());
1010 aCode
.AddToken( FormulaErrorToken( pCode
->GetCodeError()));
1011 ScCompiler
aComp(rCxt
, aPos
, aCode
, false, false, pContext
);
1012 aComp
.CreateStringFromTokenArray(aBuf
);
1013 return aBuf
.makeStringAndClear();
1015 else if( cMatrixFlag
== ScMatrixMode::Reference
)
1017 // Reference to another cell that contains a matrix formula.
1018 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
1019 formula::FormulaToken
* p
= aIter
.GetNextReferenceRPN();
1022 /* FIXME: original GetFormula() code obtained
1023 * pCell only if (!IsInChangeTrack()),
1024 * GetEnglishFormula() omitted that test.
1025 * Can we live without in all cases? */
1026 ScFormulaCell
* pCell
= nullptr;
1027 ScSingleRefData
& rRef
= *p
->GetSingleRef();
1028 ScAddress aAbs
= rRef
.toAbs(rDocument
, aPos
);
1029 if (rDocument
.ValidAddress(aAbs
))
1030 pCell
= rDocument
.GetFormulaCell(aAbs
);
1034 return pCell
->GetFormula(rCxt
);
1038 ScCompiler
aComp(rCxt
, aPos
, *pCode
, false, false, pContext
);
1039 aComp
.CreateStringFromTokenArray(aBuf
);
1044 OSL_FAIL("ScFormulaCell::GetFormula: not a matrix");
1049 ScCompiler
aComp(rCxt
, aPos
, *pCode
, false, false, pContext
);
1050 aComp
.CreateStringFromTokenArray(aBuf
);
1053 aBuf
.insert( 0, '=');
1054 if( cMatrixFlag
!= ScMatrixMode::NONE
)
1056 aBuf
.insert( 0, '{');
1060 return aBuf
.makeStringAndClear();
1063 void ScFormulaCell::GetResultDimensions( SCSIZE
& rCols
, SCSIZE
& rRows
)
1067 if (pCode
->GetCodeError() == FormulaError::NONE
&& aResult
.GetType() == svMatrixCell
)
1069 const ScMatrix
* pMat
= aResult
.GetToken()->GetMatrix();
1072 pMat
->GetDimensions( rCols
, rRows
);
1073 if (pCode
->IsHyperLink())
1075 // Row 2 element is the URL that is not to be displayed and the
1076 // result dimension not to be extended.
1087 void ScFormulaCell::ResetDirty() { bDirty
= bTableOpDirty
= mbPostponedDirty
= false; }
1088 void ScFormulaCell::SetNeedsListening( bool bVar
) { bNeedListening
= bVar
; }
1090 void ScFormulaCell::SetNeedsDirty( bool bVar
)
1092 mbPostponedDirty
= bVar
;
1095 void ScFormulaCell::SetNeedNumberFormat( bool bVal
)
1097 mbNeedsNumberFormat
= mbAllowNumberFormatChange
= bVal
;
1100 void ScFormulaCell::Compile( const OUString
& rFormula
, bool bNoListening
,
1101 const FormulaGrammar::Grammar eGrammar
)
1103 if ( rDocument
.IsClipOrUndo() )
1105 bool bWasInFormulaTree
= rDocument
.IsInFormulaTree( this );
1106 if ( bWasInFormulaTree
)
1107 rDocument
.RemoveFromFormulaTree( this );
1108 // pCode may not deleted for queries, but must be empty
1111 ScTokenArray
* pCodeOld
= pCode
;
1112 ScCompiler
aComp( rDocument
, aPos
, eGrammar
);
1113 pCode
= aComp
.CompileString( rFormula
).release();
1116 if( pCode
->GetCodeError() == FormulaError::NONE
)
1118 if ( !pCode
->GetLen() && !aResult
.GetHybridFormula().isEmpty() && rFormula
== aResult
.GetHybridFormula() )
1119 { // not recursive CompileTokenArray/Compile/CompileTokenArray
1120 if ( rFormula
[0] == '=' )
1121 pCode
->AddBad( rFormula
.copy(1) );
1123 pCode
->AddBad( rFormula
);
1126 CompileTokenArray( bNoListening
);
1131 if ( bWasInFormulaTree
)
1132 rDocument
.PutInFormulaTree( this );
1135 void ScFormulaCell::Compile(
1136 sc::CompileFormulaContext
& rCxt
, const OUString
& rFormula
, bool bNoListening
)
1138 if ( rDocument
.IsClipOrUndo() )
1140 bool bWasInFormulaTree
= rDocument
.IsInFormulaTree( this );
1141 if ( bWasInFormulaTree
)
1142 rDocument
.RemoveFromFormulaTree( this );
1143 // pCode may not deleted for queries, but must be empty
1146 ScTokenArray
* pCodeOld
= pCode
;
1147 ScCompiler
aComp(rCxt
, aPos
);
1148 pCode
= aComp
.CompileString( rFormula
).release();
1151 if( pCode
->GetCodeError() == FormulaError::NONE
)
1153 if ( !pCode
->GetLen() && !aResult
.GetHybridFormula().isEmpty() && rFormula
== aResult
.GetHybridFormula() )
1154 { // not recursive CompileTokenArray/Compile/CompileTokenArray
1155 if ( rFormula
[0] == '=' )
1156 pCode
->AddBad( rFormula
.copy(1) );
1158 pCode
->AddBad( rFormula
);
1161 CompileTokenArray(rCxt
, bNoListening
);
1166 if ( bWasInFormulaTree
)
1167 rDocument
.PutInFormulaTree( this );
1170 void ScFormulaCell::CompileTokenArray( bool bNoListening
)
1172 // Not already compiled?
1173 if( !pCode
->GetLen() && !aResult
.GetHybridFormula().isEmpty() )
1175 Compile( aResult
.GetHybridFormula(), bNoListening
, eTempGrammar
);
1177 else if( bCompile
&& !rDocument
.IsClipOrUndo() && pCode
->GetCodeError() == FormulaError::NONE
)
1179 // RPN length may get changed
1180 bool bWasInFormulaTree
= rDocument
.IsInFormulaTree( this );
1181 if ( bWasInFormulaTree
)
1182 rDocument
.RemoveFromFormulaTree( this );
1184 // Loading from within filter? No listening yet!
1185 if( rDocument
.IsInsertingFromOtherDoc() )
1186 bNoListening
= true;
1188 if( !bNoListening
&& pCode
->GetCodeLen() )
1189 EndListeningTo( rDocument
);
1190 ScCompiler
aComp(rDocument
, aPos
, *pCode
, rDocument
.GetGrammar(), true, cMatrixFlag
!= ScMatrixMode::NONE
);
1191 bSubTotal
= aComp
.CompileTokenArray();
1192 if( pCode
->GetCodeError() == FormulaError::NONE
)
1194 nFormatType
= aComp
.GetNumFormatType();
1196 aResult
.SetToken( nullptr);
1198 if ( !bNoListening
)
1199 StartListeningTo( rDocument
);
1201 if ( bWasInFormulaTree
)
1202 rDocument
.PutInFormulaTree( this );
1205 rDocument
.AddSubTotalCell(this);
1209 void ScFormulaCell::CompileTokenArray( sc::CompileFormulaContext
& rCxt
, bool bNoListening
)
1211 // Not already compiled?
1212 if( !pCode
->GetLen() && !aResult
.GetHybridFormula().isEmpty() )
1214 rCxt
.setGrammar(eTempGrammar
);
1215 Compile(rCxt
, aResult
.GetHybridFormula(), bNoListening
);
1217 else if( bCompile
&& !rDocument
.IsClipOrUndo() && pCode
->GetCodeError() == FormulaError::NONE
)
1219 // RPN length may get changed
1220 bool bWasInFormulaTree
= rDocument
.IsInFormulaTree( this );
1221 if ( bWasInFormulaTree
)
1222 rDocument
.RemoveFromFormulaTree( this );
1224 // Loading from within filter? No listening yet!
1225 if( rDocument
.IsInsertingFromOtherDoc() )
1226 bNoListening
= true;
1228 if( !bNoListening
&& pCode
->GetCodeLen() )
1229 EndListeningTo( rDocument
);
1230 ScCompiler
aComp(rCxt
, aPos
, *pCode
, true, cMatrixFlag
!= ScMatrixMode::NONE
);
1231 bSubTotal
= aComp
.CompileTokenArray();
1232 if( pCode
->GetCodeError() == FormulaError::NONE
)
1234 nFormatType
= aComp
.GetNumFormatType();
1236 aResult
.SetToken( nullptr);
1238 if ( !bNoListening
)
1239 StartListeningTo( rDocument
);
1241 if ( bWasInFormulaTree
)
1242 rDocument
.PutInFormulaTree( this );
1245 rDocument
.AddSubTotalCell(this);
1249 void ScFormulaCell::CompileXML( sc::CompileFormulaContext
& rCxt
, ScProgress
& rProgress
)
1251 if ( cMatrixFlag
== ScMatrixMode::Reference
)
1252 { // is already token code via ScDocFunc::EnterMatrix, ScDocument::InsertMatrixFormula
1253 // just establish listeners
1254 StartListeningTo( rDocument
);
1258 // Error constant formula cell stays as is.
1259 if (!pCode
->GetLen() && pCode
->GetCodeError() != FormulaError::NONE
)
1262 // Compilation changes RPN count, remove and reinsert to FormulaTree if it
1263 // was in to update its count.
1264 bool bWasInFormulaTree
= rDocument
.IsInFormulaTree( this);
1265 if (bWasInFormulaTree
)
1266 rDocument
.RemoveFromFormulaTree( this);
1267 rCxt
.setGrammar(eTempGrammar
);
1268 ScCompiler
aComp(rCxt
, aPos
, *pCode
, true, cMatrixFlag
!= ScMatrixMode::NONE
);
1269 OUString aFormula
, aFormulaNmsp
;
1270 aComp
.CreateStringFromXMLTokenArray( aFormula
, aFormulaNmsp
);
1271 rDocument
.DecXMLImportedFormulaCount( aFormula
.getLength() );
1272 rProgress
.SetStateCountDownOnPercent( rDocument
.GetXMLImportedFormulaCount() );
1273 // pCode may not deleted for queries, but must be empty
1276 bool bDoCompile
= true;
1278 if ( !mxGroup
&& aFormulaNmsp
.isEmpty() ) // optimization
1280 ScAddress
aPreviousCell( aPos
);
1281 aPreviousCell
.IncRow( -1 );
1282 ScFormulaCell
*pPreviousCell
= rDocument
.GetFormulaCell( aPreviousCell
);
1283 if (pPreviousCell
&& pPreviousCell
->GetCode()->IsShareable())
1285 // Build formula string using the tokens from the previous cell,
1286 // but use the current cell position.
1287 ScCompiler
aBackComp( rCxt
, aPos
, *(pPreviousCell
->pCode
) );
1288 OUStringBuffer aShouldBeBuf
;
1289 aBackComp
.CreateStringFromTokenArray( aShouldBeBuf
);
1291 // The initial '=' is optional in ODFF.
1292 const sal_Int32 nLeadingEqual
= (aFormula
.getLength() > 0 && aFormula
[0] == '=') ? 1 : 0;
1293 if (aFormula
.getLength() == aShouldBeBuf
.getLength() + nLeadingEqual
&&
1294 aFormula
.match( aShouldBeBuf
, nLeadingEqual
))
1296 // Put them in the same formula group.
1297 ScFormulaCellGroupRef xGroup
= pPreviousCell
->GetCellGroup();
1298 if (!xGroup
) // Last cell is not grouped yet. Start a new group.
1299 xGroup
= pPreviousCell
->CreateCellGroup(1, false);
1301 SetCellGroup( xGroup
);
1303 // Do setup here based on previous cell.
1305 nFormatType
= pPreviousCell
->nFormatType
;
1306 bSubTotal
= pPreviousCell
->bSubTotal
;
1311 rDocument
.AddSubTotalCell(this);
1314 pCode
= pPreviousCell
->pCode
;
1315 if (pPreviousCell
->mbIsExtRef
)
1316 rDocument
.GetExternalRefManager()->insertRefCellFromTemplate( pPreviousCell
, this );
1323 ScTokenArray
* pCodeOld
= pCode
;
1324 pCode
= aComp
.CompileString( aFormula
, aFormulaNmsp
).release();
1328 if( pCode
->GetCodeError() == FormulaError::NONE
)
1330 if ( !pCode
->GetLen() )
1332 if ( !aFormula
.isEmpty() && aFormula
[0] == '=' )
1333 pCode
->AddBad( aFormula
.copy( 1 ) );
1335 pCode
->AddBad( aFormula
);
1337 bSubTotal
= aComp
.CompileTokenArray();
1338 if( pCode
->GetCodeError() == FormulaError::NONE
)
1340 nFormatType
= aComp
.GetNumFormatType();
1346 rDocument
.AddSubTotalCell(this);
1352 // After loading, it must be known if ocDde/ocWebservice is in any formula
1353 // (for external links warning, CompileXML is called at the end of loading XML file)
1354 rDocument
.CheckLinkFormulaNeedingCheck(*pCode
);
1356 //volatile cells must be added here for import
1357 if( !pCode
->IsRecalcModeNormal() || pCode
->IsRecalcModeForced())
1359 // During load, only those cells that are marked explicitly dirty get
1360 // recalculated. So we need to set it dirty here.
1362 rDocument
.AppendToFormulaTrack(this);
1363 // Do not call TrackFormulas() here, not all listeners may have been
1364 // established, postponed until ScDocument::CompileXML() finishes.
1366 else if (bWasInFormulaTree
)
1367 rDocument
.PutInFormulaTree(this);
1370 void ScFormulaCell::CalcAfterLoad( sc::CompileFormulaContext
& rCxt
, bool bStartListening
)
1372 bool bNewCompiled
= false;
1373 // If a Calc 1.0-doc is read, we have a result, but no token array
1374 if( !pCode
->GetLen() && !aResult
.GetHybridFormula().isEmpty() )
1376 rCxt
.setGrammar(eTempGrammar
);
1377 Compile(rCxt
, aResult
.GetHybridFormula(), true);
1378 aResult
.SetToken( nullptr);
1380 bNewCompiled
= true;
1382 // The RPN array is not created when a Calc 3.0-Doc has been read as the Range Names exist until now.
1383 if( pCode
->GetLen() && !pCode
->GetCodeLen() && pCode
->GetCodeError() == FormulaError::NONE
)
1385 ScCompiler
aComp(rCxt
, aPos
, *pCode
, true, cMatrixFlag
!= ScMatrixMode::NONE
);
1386 bSubTotal
= aComp
.CompileTokenArray();
1387 nFormatType
= aComp
.GetNumFormatType();
1390 bNewCompiled
= true;
1393 rDocument
.AddSubTotalCell(this);
1396 // On OS/2 with broken FPU exception, we can somehow store /0 without Err503. Later on in
1397 // the BLC Lib NumberFormatter crashes when doing a fabs (NAN) (# 32739 #).
1398 // We iron this out here for all systems, such that we also have an Err503 here.
1399 if ( aResult
.IsValue() && !std::isfinite( aResult
.GetDouble() ) )
1401 OSL_FAIL("Formula cell INFINITY!!! Where does this document come from?");
1402 aResult
.SetResultError( FormulaError::IllegalFPOperation
);
1406 // DoubleRefs for binary operators were always a Matrix before version v5.0.
1407 // Now this is only the case when in an array formula, otherwise it's an implicit intersection
1408 if ( ScDocument::GetSrcVersion() < SC_MATRIX_DOUBLEREF
&&
1409 GetMatrixFlag() == ScMatrixMode::NONE
&& pCode
->HasMatrixDoubleRefOps() )
1411 cMatrixFlag
= ScMatrixMode::Formula
;
1412 SetMatColsRows( 1, 1);
1415 // Do the cells need to be calculated? After Load cells can contain an error code, and then start
1416 // the listener and Recalculate (if needed) if not ScRecalcMode::NORMAL
1417 if( !bNewCompiled
|| pCode
->GetCodeError() == FormulaError::NONE
)
1419 if (bStartListening
)
1420 StartListeningTo(rDocument
);
1422 if( !pCode
->IsRecalcModeNormal() )
1425 if ( pCode
->IsRecalcModeAlways() )
1426 { // random(), today(), now() always stay in the FormulaTree, so that they are calculated
1430 // No SetDirty yet, as no all Listeners are known yet (only in SetDirtyAfterLoad)
1433 bool ScFormulaCell::MarkUsedExternalReferences()
1435 return pCode
&& rDocument
.MarkUsedExternalReferences(*pCode
, aPos
);
1439 class RecursionCounter
1441 ScRecursionHelper
& rRec
;
1442 bool bStackedInIteration
;
1443 #if defined DBG_UTIL && !defined NDEBUG
1444 const ScFormulaCell
* cell
;
1447 RecursionCounter( ScRecursionHelper
& r
, ScFormulaCell
* p
)
1449 #if defined DBG_UTIL && !defined NDEBUG
1453 bStackedInIteration
= rRec
.IsDoingIteration();
1454 if (bStackedInIteration
)
1455 rRec
.GetRecursionInIterationStack().push( p
);
1456 rRec
.IncRecursionCount();
1460 rRec
.DecRecursionCount();
1461 if (bStackedInIteration
)
1463 #if defined DBG_UTIL && !defined NDEBUG
1464 assert(rRec
.GetRecursionInIterationStack().top() == cell
);
1466 rRec
.GetRecursionInIterationStack().pop();
1471 // Forced calculation: OpenCL and threads require formula groups, so force even single cells to be a "group".
1472 // Remove the group again at the end, since there are some places throughout the code
1473 // that do not handle well groups with just 1 cell. Remove the groups only when the recursion level
1474 // reaches 0 again (groups contain some info such as disabling threading because of cycles, so removing
1475 // a group immediately would remove the info), for this reason affected cells are stored in the recursion
1477 struct TemporaryCellGroupMaker
1479 TemporaryCellGroupMaker( ScFormulaCell
* cell
, bool enable
)
1481 , mEnabled( enable
)
1483 if( mEnabled
&& mCell
->GetCellGroup() == nullptr )
1485 mCell
->CreateCellGroup( 1, false );
1486 mCell
->GetDocument().GetRecursionHelper().AddTemporaryGroupCell( mCell
);
1489 ~TemporaryCellGroupMaker() COVERITY_NOEXCEPT_FALSE
1492 mCell
->GetDocument().GetRecursionHelper().CleanTemporaryGroupCells();
1494 ScFormulaCell
* mCell
;
1495 const bool mEnabled
;
1500 bool ScFormulaCell::Interpret(SCROW nStartOffset
, SCROW nEndOffset
)
1502 ScRecursionHelper
& rRecursionHelper
= rDocument
.GetRecursionHelper();
1503 bool bGroupInterpreted
= false;
1505 // The result would possibly depend on a cell without a valid value, bail out
1506 // the entire dependency computation.
1507 if (rRecursionHelper
.IsAbortingDependencyComputation())
1510 if ((mxGroup
&& !rRecursionHelper
.CheckFGIndependence(mxGroup
.get())) || !rRecursionHelper
.AreGroupsIndependent())
1511 return bGroupInterpreted
;
1513 static ForceCalculationType forceType
= ScCalcConfig::getForceCalculationType();
1514 TemporaryCellGroupMaker
cellGroupMaker( this, forceType
!= ForceCalculationNone
&& forceType
!= ForceCalculationCore
);
1516 ScFormulaCell
* pTopCell
= mxGroup
? mxGroup
->mpTopCell
: this;
1518 if (pTopCell
->mbSeenInPath
&& rRecursionHelper
.GetDepComputeLevel() &&
1519 rRecursionHelper
.AnyCycleMemberInDependencyEvalMode(pTopCell
))
1521 // This call arose from a dependency calculation and we just found a cycle.
1522 // This will mark all elements in the cycle as parts-of-cycle.
1523 ScFormulaGroupCycleCheckGuard
aCycleCheckGuard(rRecursionHelper
, pTopCell
);
1524 // Reaching here does not necessarily mean a circular reference, so don't set Err:522 here yet.
1525 // If there is a genuine circular reference, it will be marked so when all groups
1526 // in the cycle get out of dependency evaluation mode.
1527 // But returning without calculation a new value means other cells depending
1528 // on this one would use a possibly invalid value, so ensure the dependency
1529 // computation is aborted without resetting the dirty flag of any cell.
1530 rRecursionHelper
.AbortDependencyComputation();
1531 return bGroupInterpreted
;
1534 #if DEBUG_CALCULATION
1535 static bool bDebugCalculationInit
= true;
1536 if (bDebugCalculationInit
)
1538 aDC
.maTrigger
= aDebugCalculationTriggerAddress
;
1539 aDC
.mbPrintResults
= true;
1540 bDebugCalculationInit
= false;
1542 DebugCalculationStacker
aDebugEntry(aPos
, rDocument
);
1545 if (!IsDirtyOrInTableOpDirty() || rRecursionHelper
.IsInReturn())
1546 return bGroupInterpreted
; // no double/triple processing
1549 // If the call originates from a Reschedule in DdeLink update, leave dirty
1550 // Better: Do a Dde Link Update without Reschedule or do it completely asynchronously!
1551 if ( rDocument
.IsInDdeLinkUpdate() )
1552 return bGroupInterpreted
;
1556 if (!rDocument
.GetDocOptions().IsIter())
1558 aResult
.SetResultError( FormulaError::CircularReference
);
1559 return bGroupInterpreted
;
1562 if (aResult
.GetResultError() == FormulaError::CircularReference
)
1563 aResult
.SetResultError( FormulaError::NONE
);
1565 // Start or add to iteration list.
1566 if (!rRecursionHelper
.IsDoingIteration() ||
1567 !rRecursionHelper
.GetRecursionInIterationStack().top()->bIsIterCell
)
1568 rRecursionHelper
.SetInIterationReturn( true);
1570 return bGroupInterpreted
;
1572 // no multiple interprets for GetErrCode, IsValue, GetValue and
1573 // different entry point recursions. Would also lead to premature
1574 // convergence in iterations.
1575 if (rRecursionHelper
.GetIteration() && nSeenInIteration
==
1576 rRecursionHelper
.GetIteration())
1577 return bGroupInterpreted
;
1579 bool bOldRunning
= bRunning
;
1580 if (rRecursionHelper
.GetRecursionCount() > MAXRECURSION
)
1583 rRecursionHelper
.SetInRecursionReturn( true);
1587 rDocument
.IncInterpretLevel();
1589 #if DEBUG_CALCULATION
1592 bool bPartOfCycleBefore
= mxGroup
&& mxGroup
->mbPartOfCycle
;
1593 bGroupInterpreted
= InterpretFormulaGroup(nStartOffset
, nEndOffset
);
1594 bool bPartOfCycleAfter
= mxGroup
&& mxGroup
->mbPartOfCycle
;
1596 #if DEBUG_CALCULATION
1599 if (!bGroupInterpreted
)
1601 // This call resulted from a dependency calculation for a multigroup-threading attempt,
1602 // but found dependency among the groups.
1603 if (!rRecursionHelper
.AreGroupsIndependent())
1605 rDocument
.DecInterpretLevel();
1606 return bGroupInterpreted
;
1608 // Dependency calc inside InterpretFormulaGroup() failed due to
1609 // detection of a cycle and there are parent FG's in the cycle.
1610 // Skip InterpretTail() in such cases, only run InterpretTail for the "cycle-starting" FG
1611 if (!bPartOfCycleBefore
&& bPartOfCycleAfter
&& rRecursionHelper
.AnyParentFGInCycle())
1613 rDocument
.DecInterpretLevel();
1614 return bGroupInterpreted
;
1617 ScFormulaGroupCycleCheckGuard
aCycleCheckGuard(rRecursionHelper
, this);
1618 ScInterpreterContextGetterGuard
aContextGetterGuard(rDocument
, rDocument
.GetFormatTable());
1619 InterpretTail( *aContextGetterGuard
.GetInterpreterContext(), SCITP_NORMAL
);
1622 rDocument
.DecInterpretLevel();
1625 // While leaving a recursion or iteration stack, insert its cells to the
1626 // recursion list in reverse order.
1627 if (rRecursionHelper
.IsInReturn())
1629 bool bFreeFlyingInserted
= false;
1630 if (rRecursionHelper
.GetRecursionCount() > 0 || !rRecursionHelper
.IsDoingRecursion())
1632 rRecursionHelper
.Insert( this, bOldRunning
, aResult
);
1633 bFreeFlyingInserted
= mbFreeFlying
;
1635 bool bIterationFromRecursion
= false;
1636 bool bResumeIteration
= false;
1639 if ((rRecursionHelper
.IsInIterationReturn() &&
1640 rRecursionHelper
.GetRecursionCount() == 0 &&
1641 !rRecursionHelper
.IsDoingIteration()) ||
1642 bIterationFromRecursion
|| bResumeIteration
)
1644 bool & rDone
= rRecursionHelper
.GetConvergingReference();
1646 if (!bIterationFromRecursion
&& bResumeIteration
)
1648 bResumeIteration
= false;
1649 // Resuming iteration expands the range.
1650 ScFormulaRecursionList::const_iterator
aOldStart(
1651 rRecursionHelper
.GetLastIterationStart());
1652 rRecursionHelper
.ResumeIteration();
1653 // Mark new cells being in iteration.
1654 for (ScFormulaRecursionList::const_iterator
aIter(
1655 rRecursionHelper
.GetIterationStart()); aIter
!=
1658 ScFormulaCell
* pIterCell
= (*aIter
).pCell
;
1659 pIterCell
->bIsIterCell
= true;
1661 // Mark older cells dirty again, in case they converted
1662 // without accounting for all remaining cells in the circle
1663 // that weren't touched so far, e.g. conditional. Restore
1664 // backupped result.
1665 sal_uInt16 nIteration
= rRecursionHelper
.GetIteration();
1666 for (ScFormulaRecursionList::const_iterator
aIter(
1667 aOldStart
); aIter
!=
1668 rRecursionHelper
.GetIterationEnd(); ++aIter
)
1670 ScFormulaCell
* pIterCell
= (*aIter
).pCell
;
1671 if (pIterCell
->nSeenInIteration
== nIteration
)
1673 if (!pIterCell
->bDirty
|| aIter
== aOldStart
)
1675 pIterCell
->aResult
= (*aIter
).aPreviousResult
;
1677 --pIterCell
->nSeenInIteration
;
1679 pIterCell
->bDirty
= true;
1684 bResumeIteration
= false;
1685 // Close circle once. If 'this' is self-referencing only
1686 // (e.g. counter or self-adder) then it is already
1687 // implicitly closed.
1688 /* TODO: does this even make sense anymore? The last cell
1689 * added above with rRecursionHelper.Insert() should always
1690 * be 'this', shouldn't it? */
1691 if (rRecursionHelper
.GetList().size() > 1)
1693 ScFormulaCell
* pLastCell
= rRecursionHelper
.GetList().back().pCell
;
1694 if (pLastCell
!= this)
1696 rDocument
.IncInterpretLevel();
1697 ScInterpreterContextGetterGuard
aContextGetterGuard(rDocument
, rDocument
.GetFormatTable());
1698 pLastCell
->InterpretTail(
1699 *aContextGetterGuard
.GetInterpreterContext(), SCITP_CLOSE_ITERATION_CIRCLE
);
1700 rDocument
.DecInterpretLevel();
1703 // Start at 1, init things.
1704 rRecursionHelper
.StartIteration();
1705 // Mark all cells being in iteration. Reset results to
1706 // original values, formula cells have been interpreted
1707 // already, discard that step.
1708 for (ScFormulaRecursionList::const_iterator
aIter(
1709 rRecursionHelper
.GetIterationStart()); aIter
!=
1710 rRecursionHelper
.GetIterationEnd(); ++aIter
)
1712 ScFormulaCell
* pIterCell
= (*aIter
).pCell
;
1713 pIterCell
->aResult
= (*aIter
).aPreviousResult
;
1714 pIterCell
->bIsIterCell
= true;
1717 bIterationFromRecursion
= false;
1718 sal_uInt16 nIterMax
= rDocument
.GetDocOptions().GetIterCount();
1719 for ( ; rRecursionHelper
.GetIteration() <= nIterMax
&& !rDone
;
1720 rRecursionHelper
.IncIteration())
1724 for ( ScFormulaRecursionList::iterator
aIter(
1725 rRecursionHelper
.GetIterationStart()); aIter
!=
1726 rRecursionHelper
.GetIterationEnd() &&
1727 !rRecursionHelper
.IsInReturn(); ++aIter
)
1729 ScFormulaCell
* pIterCell
= (*aIter
).pCell
;
1730 if (pIterCell
->IsDirtyOrInTableOpDirty() &&
1731 rRecursionHelper
.GetIteration() !=
1732 pIterCell
->GetSeenInIteration())
1734 (*aIter
).aPreviousResult
= pIterCell
->aResult
;
1735 rDocument
.IncInterpretLevel();
1736 ScInterpreterContextGetterGuard
aContextGetterGuard(rDocument
, rDocument
.GetFormatTable());
1737 pIterCell
->InterpretTail( *aContextGetterGuard
.GetInterpreterContext(), SCITP_FROM_ITERATION
);
1738 rDocument
.DecInterpretLevel();
1742 rDone
= !pIterCell
->IsDirtyOrInTableOpDirty();
1747 rDone
= !pIterCell
->IsDirtyOrInTableOpDirty();
1750 if (rRecursionHelper
.IsInReturn())
1752 bResumeIteration
= true;
1754 // Don't increment iteration.
1757 if (!bResumeIteration
)
1761 for (ScFormulaRecursionList::const_iterator
aIter(
1762 rRecursionHelper
.GetIterationStart());
1763 aIter
!= rRecursionHelper
.GetIterationEnd();
1766 ScFormulaCell
* pIterCell
= (*aIter
).pCell
;
1767 pIterCell
->bIsIterCell
= false;
1768 pIterCell
->nSeenInIteration
= 0;
1769 pIterCell
->bRunning
= (*aIter
).bOldRunning
;
1774 for (ScFormulaRecursionList::const_iterator
aIter(
1775 rRecursionHelper
.GetIterationStart());
1776 aIter
!= rRecursionHelper
.GetIterationEnd();
1779 ScFormulaCell
* pIterCell
= (*aIter
).pCell
;
1780 pIterCell
->bIsIterCell
= false;
1781 pIterCell
->nSeenInIteration
= 0;
1782 pIterCell
->bRunning
= (*aIter
).bOldRunning
;
1783 pIterCell
->ResetDirty();
1784 // The difference to Excel is that Excel does not
1785 // produce an error for non-convergence thus a
1786 // delta of 0.001 still works to execute the
1787 // maximum number of iterations and display the
1788 // results no matter if the result anywhere reached
1789 // near delta, but also never indicates whether the
1790 // result actually makes sense in case of
1791 // non-counter context. Calc does check the delta
1792 // in every case. If we wanted to support what
1793 // Excel does then add another option "indicate
1794 // non-convergence error" (default on) and execute
1795 // the following block only if set.
1797 // If one cell didn't converge, all cells of this
1798 // circular dependency don't, no matter whether
1799 // single cells did.
1800 pIterCell
->aResult
.SetResultError( FormulaError::NoConvergence
);
1801 pIterCell
->bChanged
= true;
1805 // End this iteration and remove entries.
1806 rRecursionHelper
.EndIteration();
1807 bResumeIteration
= rRecursionHelper
.IsDoingIteration();
1810 if (rRecursionHelper
.IsInRecursionReturn() &&
1811 rRecursionHelper
.GetRecursionCount() == 0 &&
1812 !rRecursionHelper
.IsDoingRecursion())
1814 bIterationFromRecursion
= false;
1815 // Iterate over cells known so far, start with the last cell
1816 // encountered, inserting new cells if another recursion limit
1817 // is reached. Repeat until solved.
1818 rRecursionHelper
.SetDoingRecursion( true);
1821 rRecursionHelper
.SetInRecursionReturn( false);
1822 for (ScFormulaRecursionList::const_iterator
aIter(
1823 rRecursionHelper
.GetIterationStart());
1824 !rRecursionHelper
.IsInReturn() && aIter
!=
1825 rRecursionHelper
.GetIterationEnd(); ++aIter
)
1827 ScFormulaCell
* pCell
= (*aIter
).pCell
;
1828 if (pCell
->IsDirtyOrInTableOpDirty())
1830 rDocument
.IncInterpretLevel();
1831 ScInterpreterContextGetterGuard
aContextGetterGuard(rDocument
, rDocument
.GetFormatTable());
1832 pCell
->InterpretTail( *aContextGetterGuard
.GetInterpreterContext(), SCITP_NORMAL
);
1833 rDocument
.DecInterpretLevel();
1834 if (!pCell
->IsDirtyOrInTableOpDirty() && !pCell
->IsIterCell())
1835 pCell
->bRunning
= (*aIter
).bOldRunning
;
1838 } while (rRecursionHelper
.IsInRecursionReturn());
1839 rRecursionHelper
.SetDoingRecursion( false);
1840 if (rRecursionHelper
.IsInIterationReturn())
1842 if (!bResumeIteration
)
1843 bIterationFromRecursion
= true;
1845 else if (bResumeIteration
||
1846 rRecursionHelper
.IsDoingIteration())
1847 rRecursionHelper
.GetList().erase(
1848 rRecursionHelper
.GetIterationStart(),
1849 rRecursionHelper
.GetLastIterationStart());
1851 rRecursionHelper
.Clear();
1853 } while (bIterationFromRecursion
|| bResumeIteration
);
1855 if (bFreeFlyingInserted
)
1857 // Remove this from recursion list, it may get deleted.
1858 // It additionally also should mean that the recursion/iteration
1859 // ends here as it must had been triggered by this free-flying
1860 // out-of-sheets cell
1861 const bool bOnlyThis
= (rRecursionHelper
.GetList().size() == 1);
1862 rRecursionHelper
.GetList().remove_if([this](const ScFormulaRecursionEntry
& r
){return r
.pCell
== this;});
1865 assert(rRecursionHelper
.GetList().empty());
1866 if (rRecursionHelper
.GetList().empty())
1867 rRecursionHelper
.EndIteration();
1872 #if DEBUG_CALCULATION
1873 FormulaError nErr
= aResult
.GetResultError();
1874 if (nErr
!= FormulaError::NONE
)
1875 aDC
.storeResultError( nErr
);
1876 else if (aResult
.IsValue())
1877 aDC
.storeResult( aResult
.GetDouble());
1879 aDC
.storeResult( aResult
.GetString());
1882 return bGroupInterpreted
;
1885 void ScFormulaCell::InterpretTail( ScInterpreterContext
& rContext
, ScInterpretTailParameter eTailParam
)
1887 RecursionCounter
aRecursionCounter( rDocument
.GetRecursionHelper(), this);
1888 // TODO If this cell is not an iteration cell, add it to the list of iteration cells?
1890 nSeenInIteration
= rDocument
.GetRecursionHelper().GetIteration();
1891 if( !pCode
->GetCodeLen() && pCode
->GetCodeError() == FormulaError::NONE
)
1893 // #i11719# no RPN and no error and no token code but result string present
1894 // => interpretation of this cell during name-compilation and unknown names
1895 // => can't exchange underlying code array in CompileTokenArray() /
1896 // Compile() because interpreter's token iterator would crash or pCode
1897 // would be deleted twice if this cell was interpreted during
1899 // This should only be a temporary condition and, since we set an
1900 // error, if ran into it again we'd bump into the dirty-clearing
1901 // condition further down.
1902 if ( !pCode
->GetLen() && !aResult
.GetHybridFormula().isEmpty() )
1904 pCode
->SetCodeError( FormulaError::NoCode
);
1905 // This is worth an assertion; if encountered in daily work
1906 // documents we might need another solution. Or just confirm correctness.
1909 CompileTokenArray();
1912 if( pCode
->GetCodeLen() )
1914 std::unique_ptr
<ScInterpreter
> pScopedInterpreter
;
1915 ScInterpreter
* pInterpreter
;
1916 if (rContext
.pInterpreter
)
1918 pInterpreter
= rContext
.pInterpreter
;
1919 pInterpreter
->Init(this, aPos
, *pCode
);
1923 pScopedInterpreter
.reset(new ScInterpreter( this, rDocument
, rContext
, aPos
, *pCode
));
1924 pInterpreter
= pScopedInterpreter
.get();
1927 FormulaError nOldErrCode
= aResult
.GetResultError();
1928 if ( nSeenInIteration
== 0 )
1929 { // Only the first time
1930 // With bChanged=false, if a newly compiled cell has a result of
1931 // 0.0, no change is detected and the cell will not be repainted.
1932 // bChanged = false;
1933 aResult
.SetResultError( FormulaError::NONE
);
1936 switch ( aResult
.GetResultError() )
1938 case FormulaError::CircularReference
: // will be determined again if so
1939 aResult
.SetResultError( FormulaError::NONE
);
1944 bool bOldRunning
= bRunning
;
1946 pInterpreter
->Interpret();
1947 if (rDocument
.GetRecursionHelper().IsInReturn() && eTailParam
!= SCITP_CLOSE_ITERATION_CIRCLE
)
1949 if (nSeenInIteration
> 0)
1950 --nSeenInIteration
; // retry when iteration is resumed
1952 if ( aResult
.GetType() == formula::svUnknown
)
1953 aResult
.SetToken( pInterpreter
->GetResultToken().get() );
1957 bRunning
= bOldRunning
;
1959 // The result may be invalid or depend on another invalid result, just abort
1960 // without updating the cell value. Since the dirty flag will not be reset,
1961 // the proper value will be computed later.
1962 if(rDocument
.GetRecursionHelper().IsAbortingDependencyComputation())
1965 // #i102616# For single-sheet saving consider only content changes, not format type,
1966 // because format type isn't set on loading (might be changed later)
1967 bool bContentChanged
= false;
1969 // Do not create a HyperLink() cell if the formula results in an error.
1970 if( pInterpreter
->GetError() != FormulaError::NONE
&& pCode
->IsHyperLink())
1971 pCode
->SetHyperLink(false);
1973 if( pInterpreter
->GetError() != FormulaError::NONE
&& pInterpreter
->GetError() != FormulaError::CircularReference
)
1977 if (pInterpreter
->GetError() == FormulaError::RetryCircular
)
1979 // Array formula matrix calculation corner case. Keep dirty
1980 // state, do not remove from formula tree or anything else, but
1981 // store FormulaError::CircularReference in case this cell does not get
1983 aResult
.SetResultError( FormulaError::CircularReference
);
1990 if (eTailParam
== SCITP_FROM_ITERATION
&& IsDirtyOrInTableOpDirty())
1992 bool bIsValue
= aResult
.IsValue(); // the previous type
1994 if ((bIsValue
&& pInterpreter
->GetResultType() == svDouble
&& fabs(
1995 pInterpreter
->GetNumResult() - aResult
.GetDouble()) <=
1996 rDocument
.GetDocOptions().GetIterEps()) ||
1997 (!bIsValue
&& pInterpreter
->GetResultType() == svString
&&
1998 pInterpreter
->GetStringResult() == aResult
.GetString()))
2000 // A convergence in the first iteration doesn't necessarily
2001 // mean that it's done, it may be as not all related cells
2002 // of a circle changed their values yet. If the set really
2003 // converges it will do so also during the next iteration. This
2004 // fixes situations like of #i44115#. If this wasn't wanted an
2005 // initial "uncalculated" value would be needed for all cells
2006 // of a circular dependency => graph needed before calculation.
2007 if (nSeenInIteration
> 1 ||
2008 rDocument
.GetDocOptions().GetIterCount() == 1)
2016 if( pInterpreter
->GetError() != nOldErrCode
)
2019 // bContentChanged only has to be set if the file content would be changed
2020 if ( aResult
.GetCellResultType() != svUnknown
)
2021 bContentChanged
= true;
2024 ScFormulaResult
aNewResult( pInterpreter
->GetResultToken().get());
2026 // For IF() and other jumps or changed formatted source data the result
2027 // format may change for different runs, e.g. =IF(B1,B1) with first
2028 // B1:0 boolean FALSE next B1:23 numeric 23, we don't want the 23
2029 // displayed as TRUE. Do not force a general format though if
2030 // mbNeedsNumberFormat is set (because there was a general format..).
2031 // Note that nFormatType may be out of sync here if a format was
2032 // applied or cleared after the last run, but obtaining the current
2033 // format always just to check would be expensive. There may be
2034 // cases where the format should be changed but is not. If that turns
2035 // out to be a real problem then obtain the current format type after
2036 // the initial check when needed.
2037 bool bForceNumberFormat
= (mbAllowNumberFormatChange
&& !mbNeedsNumberFormat
&&
2038 !SvNumberFormatter::IsCompatible( nFormatType
, pInterpreter
->GetRetFormatType()));
2040 // We have some requirements additionally to IsCompatible().
2041 // * Do not apply a NumberFormat::LOGICAL if the result value is not
2043 // * Do not override an already set numeric number format if the result
2044 // is of type NumberFormat::LOGICAL, it could be user applied.
2045 // On the other hand, for an empty jump path instead of FALSE an
2046 // unexpected for example 0% could be displayed. YMMV.
2047 // * Never override a non-standard number format that indicates user
2049 // * NumberFormat::TEXT does not force a change.
2050 if (bForceNumberFormat
)
2052 sal_uInt32 nOldFormatIndex
= NUMBERFORMAT_ENTRY_NOT_FOUND
;
2053 const SvNumFormatType nRetType
= pInterpreter
->GetRetFormatType();
2054 if (nRetType
== SvNumFormatType::LOGICAL
)
2056 double fVal
= aNewResult
.GetDouble();
2057 if (fVal
!= 1.0 && fVal
!= 0.0)
2058 bForceNumberFormat
= false;
2061 nOldFormatIndex
= rDocument
.GetNumberFormat( rContext
, aPos
);
2062 nFormatType
= rContext
.GetFormatTable()->GetType( nOldFormatIndex
);
2063 switch (nFormatType
)
2065 case SvNumFormatType::PERCENT
:
2066 case SvNumFormatType::CURRENCY
:
2067 case SvNumFormatType::SCIENTIFIC
:
2068 case SvNumFormatType::FRACTION
:
2069 bForceNumberFormat
= false;
2071 case SvNumFormatType::NUMBER
:
2072 if ((nOldFormatIndex
% SV_COUNTRY_LANGUAGE_OFFSET
) != 0)
2073 bForceNumberFormat
= false;
2079 else if (nRetType
== SvNumFormatType::TEXT
)
2081 bForceNumberFormat
= false;
2083 if (bForceNumberFormat
)
2085 if (nOldFormatIndex
== NUMBERFORMAT_ENTRY_NOT_FOUND
)
2087 nOldFormatIndex
= rDocument
.GetNumberFormat( rContext
, aPos
);
2088 nFormatType
= rContext
.GetFormatTable()->GetType( nOldFormatIndex
);
2090 if (nOldFormatIndex
!=
2091 ScGlobal::GetStandardFormat(rContext
, nOldFormatIndex
, nFormatType
))
2092 bForceNumberFormat
= false;
2096 if( mbNeedsNumberFormat
|| bForceNumberFormat
)
2098 bool bSetFormat
= true;
2099 const SvNumFormatType nOldFormatType
= nFormatType
;
2100 nFormatType
= pInterpreter
->GetRetFormatType();
2101 sal_uInt32 nFormatIndex
= pInterpreter
->GetRetFormatIndex();
2103 if (nFormatType
== SvNumFormatType::TEXT
)
2105 // Don't set text format as hard format.
2108 else if (nFormatType
== SvNumFormatType::LOGICAL
&& cMatrixFlag
!= ScMatrixMode::NONE
)
2110 // In a matrix range do not set an (inherited) logical format
2111 // as hard format if the value does not represent a strict TRUE
2112 // or FALSE value. But do set for a top left error value so
2113 // following matrix cells can inherit for non-error values.
2114 // This solves a problem with IF() expressions in array context
2115 // where incidentally the top left element results in logical
2116 // type but some others don't. It still doesn't solve the
2117 // reverse case though, where top left is not logical type but
2118 // some other elements should be. We'd need to transport type
2119 // or format information on arrays.
2120 StackVar eNewCellResultType
= aNewResult
.GetCellResultType();
2121 if (eNewCellResultType
!= svError
|| cMatrixFlag
== ScMatrixMode::Reference
)
2123 if (eNewCellResultType
!= svDouble
)
2126 nFormatType
= nOldFormatType
; // that? or number?
2130 double fVal
= aNewResult
.GetDouble();
2131 if (fVal
!= 1.0 && fVal
!= 0.0)
2134 nFormatType
= SvNumFormatType::NUMBER
;
2140 if (bSetFormat
&& (bForceNumberFormat
|| ((nFormatIndex
% SV_COUNTRY_LANGUAGE_OFFSET
) == 0)))
2141 nFormatIndex
= ScGlobal::GetStandardFormat(rContext
, nFormatIndex
, nFormatType
);
2143 // Do not replace a General format (which was the reason why
2144 // mbNeedsNumberFormat was set) with a General format.
2145 // 1. setting a format has quite some overhead in the
2146 // ScPatternAttr/ScAttrArray handling, even if identical.
2147 // 2. the General formats may be of different locales.
2148 // XXX if mbNeedsNumberFormat was set even if the current format
2149 // was not General then we'd have to obtain the current format here
2150 // and check at least the types.
2151 const bool bSetNumberFormat
= bSetFormat
&& (bForceNumberFormat
|| ((nFormatIndex
% SV_COUNTRY_LANGUAGE_OFFSET
) != 0));
2152 if (bSetNumberFormat
&& !rDocument
.IsInLayoutStrings())
2154 // set number format explicitly
2155 if (!rDocument
.IsThreadedGroupCalcInProgress())
2156 rDocument
.SetNumberFormat( aPos
, nFormatIndex
);
2159 // SetNumberFormat() is not thread-safe (modifies ScAttrArray), delay the work
2160 // to the main thread. Since thread calculations operate on formula groups,
2161 // it's enough to store just the row.
2162 DelayedSetNumberFormat data
= { aPos
.Col(), aPos
.Row(), nFormatIndex
};
2163 rContext
.maDelayedSetNumberFormat
.push_back( data
);
2168 // Currently (2019-05-10) nothing else can cope with a duration
2169 // format type, change to time as it was before.
2170 if (nFormatType
== SvNumFormatType::DURATION
)
2171 nFormatType
= SvNumFormatType::TIME
;
2173 mbNeedsNumberFormat
= false;
2176 // In case of changes just obtain the result, no temporary and
2177 // comparison needed anymore.
2180 // #i102616# Compare anyway if the sheet is still marked unchanged for single-sheet saving
2181 // Also handle special cases of initial results after loading.
2182 if ( !bContentChanged
&& rDocument
.IsStreamValid(aPos
.Tab()) )
2184 StackVar eOld
= aResult
.GetCellResultType();
2185 StackVar eNew
= aNewResult
.GetCellResultType();
2186 if ( eOld
== svUnknown
&& ( eNew
== svError
|| ( eNew
== svDouble
&& aNewResult
.GetDouble() == 0.0 ) ) )
2188 // ScXMLTableRowCellContext::EndElement doesn't call SetFormulaResultDouble for 0
2193 if ( eOld
== svHybridCell
) // string result from SetFormulaResultString?
2194 eOld
= svString
; // ScHybridCellToken has a valid GetString method
2196 // #i106045# use approxEqual to compare with stored value
2197 bContentChanged
= (eOld
!= eNew
||
2198 (eNew
== svDouble
&& !rtl::math::approxEqual( aResult
.GetDouble(), aNewResult
.GetDouble() )) ||
2199 (eNew
== svString
&& aResult
.GetString() != aNewResult
.GetString()));
2203 aResult
.SetToken( pInterpreter
->GetResultToken().get() );
2207 StackVar eOld
= aResult
.GetCellResultType();
2208 StackVar eNew
= aNewResult
.GetCellResultType();
2209 bChanged
= (eOld
!= eNew
||
2210 (eNew
== svDouble
&& aResult
.GetDouble() != aNewResult
.GetDouble()) ||
2211 (eNew
== svString
&& aResult
.GetString() != aNewResult
.GetString()));
2213 // #i102616# handle special cases of initial results after loading
2214 // (only if the sheet is still marked unchanged)
2215 if ( bChanged
&& !bContentChanged
&& rDocument
.IsStreamValid(aPos
.Tab()) )
2217 if ((eOld
== svUnknown
&& (eNew
== svError
|| (eNew
== svDouble
&& aNewResult
.GetDouble() == 0.0))) ||
2218 ((eOld
== svHybridCell
) &&
2219 eNew
== svString
&& aResult
.GetString() == aNewResult
.GetString()) ||
2220 (eOld
== svDouble
&& eNew
== svDouble
&&
2221 rtl::math::approxEqual( aResult
.GetDouble(), aNewResult
.GetDouble())))
2223 // no change, see above
2226 bContentChanged
= true;
2229 aResult
.Assign( aNewResult
);
2232 // Precision as shown?
2233 if ( aResult
.IsValue() && pInterpreter
->GetError() == FormulaError::NONE
2234 && rDocument
.GetDocOptions().IsCalcAsShown()
2235 && nFormatType
!= SvNumFormatType::DATE
2236 && nFormatType
!= SvNumFormatType::TIME
2237 && nFormatType
!= SvNumFormatType::DATETIME
)
2239 sal_uInt32 nFormat
= rDocument
.GetNumberFormat( rContext
, aPos
);
2240 aResult
.SetDouble( rDocument
.RoundValueAsShown(
2241 aResult
.GetDouble(), nFormat
, &rContext
));
2243 if (eTailParam
== SCITP_NORMAL
)
2247 if( aResult
.GetMatrix() )
2249 // If the formula wasn't entered as a matrix formula, live on with
2250 // the upper left corner and let reference counting delete the matrix.
2251 if( cMatrixFlag
!= ScMatrixMode::Formula
&& !pCode
->IsHyperLink() )
2252 aResult
.SetToken( aResult
.GetCellResultToken().get());
2254 if ( aResult
.IsValue() && !std::isfinite( aResult
.GetDouble() ) )
2256 // Coded double error may occur via filter import.
2257 FormulaError nErr
= GetDoubleErrorValue( aResult
.GetDouble());
2258 aResult
.SetResultError( nErr
);
2259 bChanged
= bContentChanged
= true;
2262 if (bContentChanged
&& rDocument
.IsStreamValid(aPos
.Tab()))
2264 // pass bIgnoreLock=true, because even if called from pending row height update,
2265 // a changed result must still reset the stream flag
2266 rDocument
.SetStreamValid(aPos
.Tab(), false, true);
2268 if ( !rDocument
.IsThreadedGroupCalcInProgress() && !pCode
->IsRecalcModeAlways() )
2269 rDocument
.RemoveFromFormulaTree( this );
2271 // FORCED cells also immediately tested for validity (start macro possibly)
2273 if ( pCode
->IsRecalcModeForced() )
2275 sal_uInt32 nValidation
= rDocument
.GetAttr(
2276 aPos
.Col(), aPos
.Row(), aPos
.Tab(), ATTR_VALIDDATA
)->GetValue();
2279 const ScValidationData
* pData
= rDocument
.GetValidationEntry( nValidation
);
2280 ScRefCellValue
aTmpCell(this);
2281 if ( pData
&& !pData
->IsDataValid(aTmpCell
, aPos
))
2282 pData
->DoCalcError( this );
2286 // Reschedule slows the whole thing down considerably, thus only execute on percent change
2287 if (!rDocument
.IsThreadedGroupCalcInProgress())
2289 ScProgress
*pProgress
= ScProgress::GetInterpretProgress();
2290 if (pProgress
&& pProgress
->Enabled())
2292 pProgress
->SetStateCountDownOnPercent(
2293 rDocument
.GetFormulaCodeInTree()/MIN_NO_CODES_PER_PROGRESS_UPDATE
);
2296 switch (pInterpreter
->GetVolatileType())
2298 case ScInterpreter::VOLATILE
:
2299 // Volatile via built-in volatile functions. No actions needed.
2301 case ScInterpreter::VOLATILE_MACRO
:
2302 // The formula contains a volatile macro.
2303 pCode
->SetExclusiveRecalcModeAlways();
2304 rDocument
.PutInFormulaTree(this);
2305 StartListeningTo(rDocument
);
2307 case ScInterpreter::NOT_VOLATILE
:
2308 if (pCode
->IsRecalcModeAlways())
2310 // The formula was previously volatile, but no more.
2311 EndListeningTo(rDocument
);
2312 pCode
->SetExclusiveRecalcModeNormal();
2316 // non-volatile formula. End listening to the area in case
2317 // it's listening due to macro module change.
2318 rDocument
.EndListeningArea(BCA_LISTEN_ALWAYS
, false, this);
2320 rDocument
.RemoveFromFormulaTree(this);
2329 // Cells with compiler errors should not be marked dirty forever
2330 OSL_ENSURE( pCode
->GetCodeError() != FormulaError::NONE
, "no RPN code and no errors ?!?!" );
2334 pCode
->ClearRecalcModeMustAfterImport();
2337 void ScFormulaCell::HandleStuffAfterParallelCalculation(ScInterpreter
* pInterpreter
)
2339 aResult
.HandleStuffAfterParallelCalculation();
2341 if( !pCode
->GetCodeLen() )
2344 if ( !pCode
->IsRecalcModeAlways() )
2345 rDocument
.RemoveFromFormulaTree( this );
2347 std::unique_ptr
<ScInterpreter
> pScopedInterpreter
;
2349 pInterpreter
->Init(this, aPos
, *pCode
);
2352 pScopedInterpreter
.reset(new ScInterpreter( this, rDocument
, rDocument
.GetNonThreadedContext(), aPos
, *pCode
));
2353 pInterpreter
= pScopedInterpreter
.get();
2356 switch (pInterpreter
->GetVolatileType())
2358 case ScInterpreter::VOLATILE_MACRO
:
2359 // The formula contains a volatile macro.
2360 pCode
->SetExclusiveRecalcModeAlways();
2361 rDocument
.PutInFormulaTree(this);
2362 StartListeningTo(rDocument
);
2364 case ScInterpreter::NOT_VOLATILE
:
2365 if (pCode
->IsRecalcModeAlways())
2367 // The formula was previously volatile, but no more.
2368 EndListeningTo(rDocument
);
2369 pCode
->SetExclusiveRecalcModeNormal();
2373 // non-volatile formula. End listening to the area in case
2374 // it's listening due to macro module change.
2375 rDocument
.EndListeningArea(BCA_LISTEN_ALWAYS
, false, this);
2377 rDocument
.RemoveFromFormulaTree(this);
2384 void ScFormulaCell::SetCompile( bool bVal
)
2389 void ScFormulaCell::SetMatColsRows( SCCOL nCols
, SCROW nRows
)
2391 ScMatrixFormulaCellToken
* pMat
= aResult
.GetMatrixFormulaCellTokenNonConst();
2393 pMat
->SetMatColsRows( nCols
, nRows
);
2394 else if (nCols
|| nRows
)
2396 aResult
.SetToken( new ScMatrixFormulaCellToken( nCols
, nRows
));
2397 // Setting the new token actually forces an empty result at this top
2398 // left cell, so have that recalculated.
2403 void ScFormulaCell::GetMatColsRows( SCCOL
& nCols
, SCROW
& nRows
) const
2405 const ScMatrixFormulaCellToken
* pMat
= aResult
.GetMatrixFormulaCellToken();
2407 pMat
->GetMatColsRows( nCols
, nRows
);
2415 void ScFormulaCell::SetInChangeTrack( bool bVal
)
2417 bInChangeTrack
= bVal
;
2420 void ScFormulaCell::Notify( const SfxHint
& rHint
)
2422 if (rDocument
.IsInDtorClear())
2425 const SfxHintId nHint
= rHint
.GetId();
2426 if (nHint
== SfxHintId::ScReference
)
2428 const sc::RefHint
& rRefHint
= static_cast<const sc::RefHint
&>(rHint
);
2430 switch (rRefHint
.getType())
2432 case sc::RefHint::ColumnReordered
:
2434 const sc::RefColReorderHint
& rRefColReorder
=
2435 static_cast<const sc::RefColReorderHint
&>(rRefHint
);
2436 if (!IsShared() || IsSharedTop())
2437 pCode
->MoveReferenceColReorder(
2438 aPos
, rRefColReorder
.getTab(),
2439 rRefColReorder
.getStartRow(),
2440 rRefColReorder
.getEndRow(),
2441 rRefColReorder
.getColMap());
2444 case sc::RefHint::RowReordered
:
2446 const sc::RefRowReorderHint
& rRefRowReorder
=
2447 static_cast<const sc::RefRowReorderHint
&>(rRefHint
);
2448 if (!IsShared() || IsSharedTop())
2449 pCode
->MoveReferenceRowReorder(
2450 aPos
, rRefRowReorder
.getTab(),
2451 rRefRowReorder
.getStartColumn(),
2452 rRefRowReorder
.getEndColumn(),
2453 rRefRowReorder
.getRowMap());
2456 case sc::RefHint::StartListening
:
2458 StartListeningTo(rDocument
);
2461 case sc::RefHint::StopListening
:
2463 EndListeningTo(rDocument
);
2473 if ( rDocument
.GetHardRecalcState() != ScDocument::HardRecalcState::OFF
)
2476 if (!(nHint
== SfxHintId::ScDataChanged
|| nHint
== SfxHintId::ScTableOpDirty
|| (bSubTotal
&& nHint
== SfxHintId::ScHiddenRowsChanged
)))
2479 bool bForceTrack
= false;
2480 if ( nHint
== SfxHintId::ScTableOpDirty
)
2482 bForceTrack
= !bTableOpDirty
;
2483 if ( !bTableOpDirty
)
2485 rDocument
.AddTableOpFormulaCell( this );
2486 bTableOpDirty
= true;
2491 bForceTrack
= !bDirty
;
2494 // Don't remove from FormulaTree to put in FormulaTrack to
2495 // put in FormulaTree again and again, only if necessary.
2496 // Any other means except ScRecalcMode::ALWAYS by which a cell could
2497 // be in FormulaTree if it would notify other cells through
2498 // FormulaTrack which weren't in FormulaTrack/FormulaTree before?!?
2499 // Yes. The new TableOpDirty made it necessary to have a
2500 // forced mode where formulas may still be in FormulaTree from
2501 // TableOpDirty but have to notify dependents for normal dirty.
2502 if ( (bForceTrack
|| !rDocument
.IsInFormulaTree( this )
2503 || pCode
->IsRecalcModeAlways())
2504 && !rDocument
.IsInFormulaTrack( this ) )
2505 rDocument
.AppendToFormulaTrack( this );
2508 void ScFormulaCell::Query( SvtListener::QueryBase
& rQuery
) const
2510 switch (rQuery
.getId())
2512 case SC_LISTENER_QUERY_FORMULA_GROUP_POS
:
2514 sc::RefQueryFormulaGroup
& rRefQuery
=
2515 static_cast<sc::RefQueryFormulaGroup
&>(rQuery
);
2517 rRefQuery
.add(aPos
);
2525 void ScFormulaCell::SetDirty( bool bDirtyFlag
)
2527 if (IsInChangeTrack())
2530 if ( rDocument
.GetHardRecalcState() != ScDocument::HardRecalcState::OFF
)
2533 rDocument
.SetStreamValid(aPos
.Tab(), false);
2537 // Avoid multiple formula tracking in Load() and in CompileAll()
2538 // after CopyScenario() and CopyBlockFromClip().
2539 // If unconditional formula tracking is needed, set bDirty=false
2540 // before calling SetDirty(), for example in CompileTokenArray().
2541 if ( !bDirty
|| mbPostponedDirty
|| !rDocument
.IsInFormulaTree( this ) )
2545 rDocument
.AppendToFormulaTrack( this );
2547 // While loading a document listeners have not been established yet.
2548 // Tracking would remove this cell from the FormulaTrack and add it to
2549 // the FormulaTree, once in there it would be assumed that its
2550 // dependents already had been tracked and it would be skipped on a
2551 // subsequent notify. Postpone tracking until all listeners are set.
2552 if (!rDocument
.IsImportingXML() && !rDocument
.IsInsertingFromOtherDoc())
2553 rDocument
.TrackFormulas();
2556 rDocument
.SetStreamValid(aPos
.Tab(), false);
2559 void ScFormulaCell::SetDirtyVar()
2562 mbPostponedDirty
= false;
2563 if (mxGroup
&& mxGroup
->meCalcState
== sc::GroupCalcRunning
)
2565 mxGroup
->meCalcState
= sc::GroupCalcEnabled
;
2566 mxGroup
->mbPartOfCycle
= false;
2569 // mark the sheet of this cell to be calculated
2570 //#FIXME do we need to revert this remnant of old fake vba events? rDocument.AddCalculateTable( aPos.Tab() );
2573 void ScFormulaCell::SetDirtyAfterLoad()
2576 if ( rDocument
.GetHardRecalcState() == ScDocument::HardRecalcState::OFF
)
2577 rDocument
.PutInFormulaTree( this );
2580 void ScFormulaCell::ResetTableOpDirtyVar()
2582 bTableOpDirty
= false;
2585 void ScFormulaCell::SetTableOpDirty()
2587 if ( IsInChangeTrack() )
2590 if ( rDocument
.GetHardRecalcState() != ScDocument::HardRecalcState::OFF
)
2591 bTableOpDirty
= true;
2594 if ( !bTableOpDirty
|| !rDocument
.IsInFormulaTree( this ) )
2596 if ( !bTableOpDirty
)
2598 rDocument
.AddTableOpFormulaCell( this );
2599 bTableOpDirty
= true;
2601 rDocument
.AppendToFormulaTrack( this );
2602 rDocument
.TrackFormulas( SfxHintId::ScTableOpDirty
);
2607 void ScFormulaCell::SetResultDouble( double n
)
2609 aResult
.SetDouble(n
);
2612 void ScFormulaCell::SetResultToken( const formula::FormulaToken
* pToken
)
2614 aResult
.SetToken(pToken
);
2617 const svl::SharedString
& ScFormulaCell::GetResultString() const
2619 return aResult
.GetString();
2622 bool ScFormulaCell::HasHybridStringResult() const
2624 return aResult
.GetType() == formula::svHybridCell
&& !aResult
.GetString().isEmpty();
2627 void ScFormulaCell::SetResultMatrix( SCCOL nCols
, SCROW nRows
, const ScConstMatrixRef
& pMat
, const formula::FormulaToken
* pUL
)
2629 aResult
.SetMatrix(nCols
, nRows
, pMat
, pUL
);
2632 void ScFormulaCell::SetErrCode( FormulaError n
)
2634 /* FIXME: check the numerous places where ScTokenArray::GetCodeError() is
2635 * used whether it is solely for transport of a simple result error and get
2636 * rid of that abuse. */
2637 pCode
->SetCodeError( n
);
2638 // Hard set errors are transported as result type value per convention,
2639 // e.g. via clipboard. ScFormulaResult::IsValue() and
2640 // ScFormulaResult::GetDouble() handle that.
2641 aResult
.SetResultError( n
);
2644 void ScFormulaCell::SetResultError( FormulaError n
)
2646 aResult
.SetResultError( n
);
2649 void ScFormulaCell::AddRecalcMode( ScRecalcMode nBits
)
2651 if ( (nBits
& ScRecalcMode::EMask
) != ScRecalcMode::NORMAL
)
2653 pCode
->AddRecalcMode( nBits
);
2656 void ScFormulaCell::SetHybridDouble( double n
)
2658 aResult
.SetHybridDouble( n
);
2661 void ScFormulaCell::SetHybridString( const svl::SharedString
& r
)
2663 aResult
.SetHybridString( r
);
2666 void ScFormulaCell::SetHybridEmptyDisplayedAsString()
2668 aResult
.SetHybridEmptyDisplayedAsString();
2671 void ScFormulaCell::SetHybridFormula( const OUString
& r
,
2672 const formula::FormulaGrammar::Grammar eGrammar
)
2674 aResult
.SetHybridFormula( r
); eTempGrammar
= eGrammar
;
2677 OUString
ScFormulaCell::GetHybridFormula() const
2679 return aResult
.GetHybridFormula();
2682 // Dynamically create the URLField on a mouse-over action on a hyperlink() cell.
2683 void ScFormulaCell::GetURLResult( OUString
& rURL
, OUString
& rCellText
)
2685 OUString aCellString
;
2687 const Color
* pColor
;
2689 // Cell Text uses the Cell format while the URL uses
2690 // the default format for the type.
2691 const sal_uInt32 nCellFormat
= rDocument
.GetNumberFormat( ScRange(aPos
) );
2692 ScInterpreterContext
& rContext
= rDocument
.GetNonThreadedContext();
2694 const sal_uInt32 nURLFormat
= ScGlobal::GetStandardFormat(rContext
, nCellFormat
, SvNumFormatType::NUMBER
);
2698 double fValue
= GetValue();
2699 rContext
.NFGetOutputString( fValue
, nCellFormat
, rCellText
, &pColor
);
2703 aCellString
= GetString().getString();
2704 rContext
.NFGetOutputString( aCellString
, nCellFormat
, rCellText
, &pColor
);
2706 ScConstMatrixRef
xMat( aResult
.GetMatrix());
2709 // determine if the matrix result is a string or value.
2710 if (!xMat
->IsValue(0, 1))
2711 rURL
= xMat
->GetString(0, 1).getString();
2713 rContext
.NFGetOutputString(
2714 xMat
->GetDouble(0, 1), nURLFormat
, rURL
, &pColor
);
2720 rContext
.NFGetOutputString( GetValue(), nURLFormat
, rURL
, &pColor
);
2722 rContext
.NFGetOutputString( aCellString
, nURLFormat
, rURL
, &pColor
);
2726 bool ScFormulaCell::IsMultilineResult()
2729 return aResult
.IsMultiline();
2733 bool ScFormulaCell::IsHyperLinkCell() const
2735 return pCode
&& pCode
->IsHyperLink();
2738 std::unique_ptr
<EditTextObject
> ScFormulaCell::CreateURLObject()
2742 GetURLResult( aURL
, aCellText
);
2744 return ScEditUtil::CreateURLObjectFromURL( rDocument
, aURL
, aCellText
);
2747 bool ScFormulaCell::IsEmpty()
2750 return aResult
.GetCellResultType() == formula::svEmptyCell
;
2753 bool ScFormulaCell::IsEmptyDisplayedAsString()
2756 return aResult
.IsEmptyDisplayedAsString();
2759 bool ScFormulaCell::IsValue()
2762 return aResult
.IsValue();
2765 bool ScFormulaCell::IsValueNoError()
2768 if (pCode
->GetCodeError() != FormulaError::NONE
)
2771 return aResult
.IsValueNoError();
2774 bool ScFormulaCell::IsValueNoError() const
2776 if (NeedsInterpret())
2777 // false if the cell is dirty & needs to be interpreted.
2780 if (pCode
->GetCodeError() != FormulaError::NONE
)
2783 return aResult
.IsValueNoError();
2786 double ScFormulaCell::GetValue()
2789 return GetRawValue();
2792 const svl::SharedString
& ScFormulaCell::GetString()
2795 return GetRawString();
2798 double ScFormulaCell::GetRawValue() const
2800 if ((pCode
->GetCodeError() == FormulaError::NONE
) &&
2801 aResult
.GetResultError() == FormulaError::NONE
)
2802 return aResult
.GetDouble();
2806 const svl::SharedString
& ScFormulaCell::GetRawString() const
2808 if ((pCode
->GetCodeError() == FormulaError::NONE
) &&
2809 aResult
.GetResultError() == FormulaError::NONE
)
2810 return aResult
.GetString();
2812 return svl::SharedString::getEmptyString();
2815 const ScMatrix
* ScFormulaCell::GetMatrix()
2817 if ( rDocument
.GetAutoCalc() )
2819 if( IsDirtyOrInTableOpDirty()
2820 // Was stored !bDirty but an accompanying matrix cell was bDirty?
2821 || (!bDirty
&& cMatrixFlag
== ScMatrixMode::Formula
&& !aResult
.GetMatrix()))
2824 return aResult
.GetMatrix().get();
2827 bool ScFormulaCell::GetMatrixOrigin( const ScDocument
& rDoc
, ScAddress
& rPos
) const
2829 switch ( cMatrixFlag
)
2831 case ScMatrixMode::Formula
:
2834 case ScMatrixMode::Reference
:
2836 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
2837 formula::FormulaToken
* t
= aIter
.GetNextReferenceRPN();
2840 ScSingleRefData
& rRef
= *t
->GetSingleRef();
2841 ScAddress aAbs
= rRef
.toAbs(rDoc
, aPos
);
2842 if (rDoc
.ValidAddress(aAbs
))
2855 sc::MatrixEdge
ScFormulaCell::GetMatrixEdge( const ScDocument
& rDoc
, ScAddress
& rOrgPos
) const
2857 switch ( cMatrixFlag
)
2859 case ScMatrixMode::Formula
:
2860 case ScMatrixMode::Reference
:
2862 static thread_local SCCOL nC
;
2863 static thread_local SCROW nR
;
2865 if ( !GetMatrixOrigin( rDoc
, aOrg
) )
2866 return sc::MatrixEdge::Nothing
;
2867 if ( aOrg
!= rOrgPos
)
2868 { // First time or a different matrix than last time.
2870 const ScFormulaCell
* pFCell
;
2871 if ( cMatrixFlag
== ScMatrixMode::Reference
)
2872 pFCell
= rDocument
.GetFormulaCell(aOrg
);
2874 pFCell
= this; // this ScMatrixMode::Formula
2875 // There's only one this, don't compare pFCell==this.
2876 if (pFCell
&& pFCell
->cMatrixFlag
== ScMatrixMode::Formula
)
2878 pFCell
->GetMatColsRows( nC
, nR
);
2879 if ( nC
== 0 || nR
== 0 )
2881 // No ScMatrixFormulaCellToken available yet, calculate new.
2885 ScFormulaCell
* pCell
;
2886 ScAddress
aAdr( aOrg
);
2891 pCell
= rDocument
.GetFormulaCell(aAdr
);
2892 if (pCell
&& pCell
->cMatrixFlag
== ScMatrixMode::Reference
&&
2893 pCell
->GetMatrixOrigin(rDocument
, aTmpOrg
) && aTmpOrg
== aOrg
)
2906 pCell
= rDocument
.GetFormulaCell(aAdr
);
2907 if (pCell
&& pCell
->cMatrixFlag
== ScMatrixMode::Reference
&&
2908 pCell
->GetMatrixOrigin(rDocument
, aTmpOrg
) && aTmpOrg
== aOrg
)
2917 const_cast<ScFormulaCell
*>(pFCell
)->SetMatColsRows(nC
, nR
);
2922 #if OSL_DEBUG_LEVEL > 0
2923 SAL_WARN( "sc", "broken Matrix, no MatFormula at origin, Pos: "
2924 << aPos
.Format(ScRefFlags::COL_VALID
| ScRefFlags::ROW_VALID
, &rDocument
)
2926 << aOrg
.Format(ScRefFlags::COL_VALID
| ScRefFlags::ROW_VALID
, &rDocument
) );
2928 return sc::MatrixEdge::Nothing
;
2931 // here we are, healthy and clean, somewhere in between
2932 SCCOL dC
= aPos
.Col() - aOrg
.Col();
2933 SCROW dR
= aPos
.Row() - aOrg
.Row();
2934 sc::MatrixEdge nEdges
= sc::MatrixEdge::Nothing
;
2935 if ( dC
>= 0 && dR
>= 0 && dC
< nC
&& dR
< nR
)
2938 nEdges
|= sc::MatrixEdge::Left
;
2940 nEdges
|= sc::MatrixEdge::Right
;
2942 nEdges
|= sc::MatrixEdge::Top
;
2944 nEdges
|= sc::MatrixEdge::Bottom
;
2945 if ( nEdges
== sc::MatrixEdge::Nothing
)
2946 nEdges
= sc::MatrixEdge::Inside
;
2950 SAL_WARN( "sc", "broken Matrix, Pos: "
2951 << aPos
.Format(ScRefFlags::COL_VALID
| ScRefFlags::ROW_VALID
, &rDocument
)
2953 << aOrg
.Format(ScRefFlags::COL_VALID
| ScRefFlags::ROW_VALID
, &rDocument
)
2954 << ", MatCols: " << static_cast<sal_Int32
>( nC
)
2955 << ", MatRows: " << static_cast<sal_Int32
>( nR
)
2956 << ", DiffCols: " << static_cast<sal_Int32
>( dC
)
2957 << ", DiffRows: " << static_cast<sal_Int32
>( dR
));
2962 return sc::MatrixEdge::Nothing
;
2966 FormulaError
ScFormulaCell::GetErrCode()
2970 /* FIXME: If ScTokenArray::SetCodeError() was really only for code errors
2971 * and not also abused for signaling other error conditions we could bail
2972 * out even before attempting to interpret broken code. */
2973 FormulaError nErr
= pCode
->GetCodeError();
2974 if (nErr
!= FormulaError::NONE
)
2976 return aResult
.GetResultError();
2979 FormulaError
ScFormulaCell::GetRawError() const
2981 FormulaError nErr
= pCode
->GetCodeError();
2982 if (nErr
!= FormulaError::NONE
)
2984 return aResult
.GetResultError();
2987 bool ScFormulaCell::GetErrorOrValue( FormulaError
& rErr
, double& rVal
)
2991 rErr
= pCode
->GetCodeError();
2992 if (rErr
!= FormulaError::NONE
)
2995 return aResult
.GetErrorOrDouble(rErr
, rVal
);
2998 sc::FormulaResultValue
ScFormulaCell::GetResult()
3002 FormulaError nErr
= pCode
->GetCodeError();
3003 if (nErr
!= FormulaError::NONE
)
3004 return sc::FormulaResultValue(nErr
);
3006 return aResult
.GetResult();
3009 sc::FormulaResultValue
ScFormulaCell::GetResult() const
3011 FormulaError nErr
= pCode
->GetCodeError();
3012 if (nErr
!= FormulaError::NONE
)
3013 return sc::FormulaResultValue(nErr
);
3015 return aResult
.GetResult();
3018 bool ScFormulaCell::HasOneReference( ScRange
& r
) const
3020 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
3021 formula::FormulaToken
* p
= aIter
.GetNextReferenceRPN();
3022 if( p
&& !aIter
.GetNextReferenceRPN() ) // only one!
3024 SingleDoubleRefProvider
aProv( *p
);
3025 r
.aStart
= aProv
.Ref1
.toAbs(rDocument
, aPos
);
3026 r
.aEnd
= aProv
.Ref2
.toAbs(rDocument
, aPos
);
3034 ScFormulaCell::HasRefListExpressibleAsOneReference(ScRange
& rRange
) const
3036 /* If there appears just one reference in the formula, it's the same
3037 as HasOneReference(). If there are more of them, they can denote
3038 one range if they are (sole) arguments of one function.
3039 Union of these references must form one range and their
3040 intersection must be empty set.
3043 // Detect the simple case of exactly one reference in advance without all
3045 // #i107741# Doing so actually makes outlines using SUBTOTAL(x;reference)
3046 // work again, where the function does not have only references.
3047 if (HasOneReference( rRange
))
3050 // Get first reference, if any
3051 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
3052 formula::FormulaToken
* const pFirstReference(aIter
.GetNextReferenceRPN());
3053 if (pFirstReference
)
3055 // Collect all consecutive references, starting by the one
3057 std::vector
<formula::FormulaToken
*> aReferences
{ pFirstReference
};
3058 FormulaToken
* pToken(aIter
.NextRPN());
3059 FormulaToken
* pFunction(nullptr);
3062 if (lcl_isReference(*pToken
))
3064 aReferences
.push_back(pToken
);
3065 pToken
= aIter
.NextRPN();
3069 if (pToken
->IsFunction())
3076 if (pFunction
&& !aIter
.GetNextReferenceRPN()
3077 && (pFunction
->GetParamCount() == aReferences
.size()))
3079 return lcl_refListFormsOneRange(rDocument
, aPos
, aReferences
, rRange
);
3085 ScFormulaCell::RelNameRef
ScFormulaCell::HasRelNameReference() const
3087 RelNameRef eRelNameRef
= RelNameRef::NONE
;
3088 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
3089 formula::FormulaToken
* t
;
3090 while ( ( t
= aIter
.GetNextReferenceRPN() ) != nullptr )
3092 switch (t
->GetType())
3094 case formula::svSingleRef
:
3095 if (t
->GetSingleRef()->IsRelName() && eRelNameRef
== RelNameRef::NONE
)
3096 eRelNameRef
= RelNameRef::SINGLE
;
3098 case formula::svDoubleRef
:
3099 if (t
->GetDoubleRef()->Ref1
.IsRelName() || t
->GetDoubleRef()->Ref2
.IsRelName())
3100 // May originate from individual cell names, in which case
3101 // it needs recompilation.
3102 return RelNameRef::DOUBLE
;
3103 /* TODO: have an extra flag at ScComplexRefData if range was
3104 * extended? or too cumbersome? might narrow recompilation to
3105 * only needed cases.
3115 bool ScFormulaCell::UpdatePosOnShift( const sc::RefUpdateContext
& rCxt
)
3117 if (rCxt
.meMode
!= URM_INSDEL
)
3121 if (!rCxt
.mnColDelta
&& !rCxt
.mnRowDelta
&& !rCxt
.mnTabDelta
)
3125 if (!rCxt
.maRange
.Contains(aPos
))
3128 // This formula cell itself is being shifted during cell range
3129 // insertion or deletion. Update its position.
3130 ScAddress
aErrorPos( ScAddress::UNINITIALIZED
);
3131 if (!aPos
.Move(rCxt
.mnColDelta
, rCxt
.mnRowDelta
, rCxt
.mnTabDelta
, aErrorPos
, rCxt
.mrDoc
))
3133 assert(!"can't move ScFormulaCell");
3142 * Check if we need to re-compile column or row names.
3144 bool checkCompileColRowName(
3145 const sc::RefUpdateContext
& rCxt
, ScDocument
& rDoc
, const ScTokenArray
& rCode
,
3146 const ScAddress
& aOldPos
, const ScAddress
& aPos
, bool bValChanged
)
3148 switch (rCxt
.meMode
)
3152 if (rCxt
.mnColDelta
<= 0 && rCxt
.mnRowDelta
<= 0)
3155 formula::FormulaTokenArrayPlainIterator
aIter(rCode
);
3156 formula::FormulaToken
* t
;
3157 ScRangePairList
* pColList
= rDoc
.GetColNameRanges();
3158 ScRangePairList
* pRowList
= rDoc
.GetRowNameRanges();
3159 while ((t
= aIter
.GetNextColRowName()) != nullptr)
3161 ScSingleRefData
& rRef
= *t
->GetSingleRef();
3162 if (rCxt
.mnRowDelta
> 0 && rRef
.IsColRel())
3164 ScAddress aAdr
= rRef
.toAbs(rDoc
, aPos
);
3165 ScRangePair
* pR
= pColList
->Find( aAdr
);
3168 if (pR
->GetRange(1).aStart
.Row() == rCxt
.maRange
.aStart
.Row())
3173 if (aAdr
.Row() + 1 == rCxt
.maRange
.aStart
.Row())
3177 if (rCxt
.mnColDelta
> 0 && rRef
.IsRowRel())
3179 ScAddress aAdr
= rRef
.toAbs(rDoc
, aPos
);
3180 ScRangePair
* pR
= pRowList
->Find( aAdr
);
3183 if ( pR
->GetRange(1).aStart
.Col() == rCxt
.maRange
.aStart
.Col())
3188 if (aAdr
.Col() + 1 == rCxt
.maRange
.aStart
.Col())
3196 { // Recompile for Move/D&D when ColRowName was moved or this Cell
3197 // points to one and was moved.
3198 bool bMoved
= (aPos
!= aOldPos
);
3202 formula::FormulaTokenArrayPlainIterator
aIter(rCode
);
3203 const formula::FormulaToken
* t
= aIter
.GetNextColRowName();
3204 for (; t
; t
= aIter
.GetNextColRowName())
3206 const ScSingleRefData
& rRef
= *t
->GetSingleRef();
3207 ScAddress aAbs
= rRef
.toAbs(rDoc
, aPos
);
3208 if (rDoc
.ValidAddress(aAbs
))
3210 if (rCxt
.maRange
.Contains(aAbs
))
3225 void setOldCodeToUndo(
3226 ScDocument
& rUndoDoc
, const ScAddress
& aUndoPos
, const ScTokenArray
* pOldCode
, FormulaGrammar::Grammar eTempGrammar
, ScMatrixMode cMatrixFlag
)
3228 // Copy the cell to aUndoPos, which is its current position in the document,
3229 // so this works when UpdateReference is called before moving the cells
3230 // (InsertCells/DeleteCells - aPos is changed above) as well as when UpdateReference
3231 // is called after moving the cells (MoveBlock/PasteFromClip - aOldPos is changed).
3233 // If there is already a formula cell in the undo document, don't overwrite it,
3234 // the first (oldest) is the important cell.
3235 if (rUndoDoc
.GetCellType(aUndoPos
) == CELLTYPE_FORMULA
)
3238 ScFormulaCell
* pFCell
=
3240 rUndoDoc
, aUndoPos
, pOldCode
? *pOldCode
: ScTokenArray(rUndoDoc
), eTempGrammar
, cMatrixFlag
);
3242 pFCell
->SetResultToken(nullptr); // to recognize it as changed later (Cut/Paste!)
3243 rUndoDoc
.SetFormulaCell(aUndoPos
, pFCell
);
3248 bool ScFormulaCell::UpdateReferenceOnShift(
3249 const sc::RefUpdateContext
& rCxt
, ScDocument
* pUndoDoc
, const ScAddress
* pUndoCellPos
)
3251 if (rCxt
.meMode
!= URM_INSDEL
)
3255 bool bCellStateChanged
= false;
3256 ScAddress
aUndoPos( aPos
); // position for undo cell in pUndoDoc
3258 aUndoPos
= *pUndoCellPos
;
3259 ScAddress
aOldPos( aPos
);
3260 bCellStateChanged
= UpdatePosOnShift(rCxt
);
3262 // Check presence of any references or column row names.
3263 bool bHasRefs
= pCode
->HasReferences();
3264 bool bHasColRowNames
= false;
3267 bHasColRowNames
= (formula::FormulaTokenArrayPlainIterator(*pCode
).GetNextColRowName() != nullptr);
3268 bHasRefs
= bHasColRowNames
;
3270 bool bOnRefMove
= pCode
->IsRecalcModeOnRefMove();
3272 if (!bHasRefs
&& !bOnRefMove
)
3273 // This formula cell contains no references, nor needs recalculating
3274 // on reference update. Bail out.
3275 return bCellStateChanged
;
3277 std::unique_ptr
<ScTokenArray
> pOldCode
;
3279 pOldCode
= pCode
->Clone();
3281 bool bValChanged
= false;
3282 bool bRefModified
= false;
3283 bool bRecompile
= bCompile
;
3287 // Update cell or range references.
3288 sc::RefUpdateResult aRes
= pCode
->AdjustReferenceOnShift(rCxt
, aOldPos
);
3289 bRefModified
= aRes
.mbReferenceModified
;
3290 bValChanged
= aRes
.mbValueChanged
;
3291 if (aRes
.mbNameModified
)
3295 if (bValChanged
|| bRefModified
)
3296 bCellStateChanged
= true;
3299 // Cell may reference itself, e.g. ocColumn, ocRow without parameter
3300 bOnRefMove
= (bValChanged
|| (aPos
!= aOldPos
) || bRefModified
);
3302 bool bNewListening
= false;
3303 bool bInDeleteUndo
= false;
3307 // Upon Insert ColRowNames have to be recompiled in case the
3308 // insertion occurs right in front of the range.
3309 if (bHasColRowNames
&& !bRecompile
)
3310 bRecompile
= checkCompileColRowName(rCxt
, rDocument
, *pCode
, aOldPos
, aPos
, bValChanged
);
3312 ScChangeTrack
* pChangeTrack
= rDocument
.GetChangeTrack();
3313 bInDeleteUndo
= (pChangeTrack
&& pChangeTrack
->IsInDeleteUndo());
3315 // RelNameRefs are always moved
3316 bool bHasRelName
= false;
3319 RelNameRef eRelNameRef
= HasRelNameReference();
3320 bHasRelName
= (eRelNameRef
!= RelNameRef::NONE
);
3321 bRecompile
= (eRelNameRef
== RelNameRef::DOUBLE
);
3323 // Reference changed and new listening needed?
3324 // Except in Insert/Delete without specialities.
3325 bNewListening
= (bRefModified
|| bRecompile
3326 || (bValChanged
&& bInDeleteUndo
) || bHasRelName
);
3328 if ( bNewListening
)
3329 EndListeningTo(rDocument
, pOldCode
.get(), aOldPos
);
3332 // NeedDirty for changes except for Copy and Move/Insert without RelNames
3333 bool bNeedDirty
= (bValChanged
|| bRecompile
|| bOnRefMove
);
3335 if (pUndoDoc
&& (bValChanged
|| bOnRefMove
))
3336 setOldCodeToUndo(*pUndoDoc
, aUndoPos
, pOldCode
.get(), eTempGrammar
, cMatrixFlag
);
3338 bCompile
|= bRecompile
;
3341 CompileTokenArray( bNewListening
); // no Listening
3345 if ( !bInDeleteUndo
)
3346 { // In ChangeTrack Delete-Reject listeners are established in
3347 // InsertCol/InsertRow
3348 if ( bNewListening
)
3350 // Inserts/Deletes re-establish listeners after all
3351 // UpdateReference calls.
3352 // All replaced shared formula listeners have to be
3353 // established after an Insert or Delete. Do nothing here.
3354 SetNeedsListening( true);
3359 { // Cut off references, invalid or similar?
3360 // Postpone SetDirty() until all listeners have been re-established in
3362 mbPostponedDirty
= true;
3365 return bCellStateChanged
;
3368 bool ScFormulaCell::UpdateReferenceOnMove(
3369 const sc::RefUpdateContext
& rCxt
, ScDocument
* pUndoDoc
, const ScAddress
* pUndoCellPos
)
3371 if (rCxt
.meMode
!= URM_MOVE
)
3374 ScAddress
aUndoPos( aPos
); // position for undo cell in pUndoDoc
3376 aUndoPos
= *pUndoCellPos
;
3377 ScAddress
aOldPos( aPos
);
3379 bool bCellInMoveTarget
= rCxt
.maRange
.Contains(aPos
);
3381 if ( bCellInMoveTarget
)
3383 // The cell is being moved or copied to a new position. I guess the
3384 // position has been updated prior to this call? Determine
3385 // its original position before the move which will be used to adjust
3386 // relative references later.
3387 aOldPos
.Set(aPos
.Col() - rCxt
.mnColDelta
, aPos
.Row() - rCxt
.mnRowDelta
, aPos
.Tab() - rCxt
.mnTabDelta
);
3390 // Check presence of any references or column row names.
3391 bool bHasRefs
= pCode
->HasReferences();
3392 bool bHasColRowNames
= false;
3395 bHasColRowNames
= (formula::FormulaTokenArrayPlainIterator(*pCode
).GetNextColRowName() != nullptr);
3396 bHasRefs
= bHasColRowNames
;
3398 bool bOnRefMove
= pCode
->IsRecalcModeOnRefMove();
3400 if (!bHasRefs
&& !bOnRefMove
)
3401 // This formula cell contains no references, nor needs recalculating
3402 // on reference update. Bail out.
3405 bool bCellStateChanged
= false;
3406 std::unique_ptr
<ScTokenArray
> pOldCode
;
3408 pOldCode
= pCode
->Clone();
3410 bool bValChanged
= false;
3411 bool bRefModified
= false;
3415 // Update cell or range references.
3416 sc::RefUpdateResult aRes
= pCode
->AdjustReferenceOnMove(rCxt
, aOldPos
, aPos
);
3417 bRefModified
= aRes
.mbReferenceModified
|| aRes
.mbNameModified
;
3418 bValChanged
= aRes
.mbValueChanged
;
3419 if (aRes
.mbNameModified
)
3420 // Re-compile to get the RPN token regenerated to reflect updated names.
3424 if (bValChanged
|| bRefModified
)
3425 bCellStateChanged
= true;
3428 // Cell may reference itself, e.g. ocColumn, ocRow without parameter
3429 bOnRefMove
= (bValChanged
|| (aPos
!= aOldPos
));
3431 bool bColRowNameCompile
= false;
3432 bool bHasRelName
= false;
3433 bool bNewListening
= false;
3434 bool bInDeleteUndo
= false;
3438 // Upon Insert ColRowNames have to be recompiled in case the
3439 // insertion occurs right in front of the range.
3440 if (bHasColRowNames
)
3441 bColRowNameCompile
= checkCompileColRowName(rCxt
, rDocument
, *pCode
, aOldPos
, aPos
, bValChanged
);
3443 ScChangeTrack
* pChangeTrack
= rDocument
.GetChangeTrack();
3444 bInDeleteUndo
= (pChangeTrack
&& pChangeTrack
->IsInDeleteUndo());
3446 // RelNameRefs are always moved
3447 RelNameRef eRelNameRef
= HasRelNameReference();
3448 bHasRelName
= (eRelNameRef
!= RelNameRef::NONE
);
3449 bCompile
|= (eRelNameRef
== RelNameRef::DOUBLE
);
3450 // Reference changed and new listening needed?
3451 // Except in Insert/Delete without specialties.
3452 bNewListening
= (bRefModified
|| bColRowNameCompile
3453 || bValChanged
|| bHasRelName
)
3454 // #i36299# Don't duplicate action during cut&paste / drag&drop
3455 // on a cell in the range moved, start/end listeners is done
3456 // via ScDocument::DeleteArea() and ScDocument::CopyFromClip().
3457 && !(rDocument
.IsInsertingFromOtherDoc() && rCxt
.maRange
.Contains(aPos
));
3459 if ( bNewListening
)
3460 EndListeningTo(rDocument
, pOldCode
.get(), aOldPos
);
3463 bool bNeedDirty
= false;
3464 // NeedDirty for changes except for Copy and Move/Insert without RelNames
3465 if ( bRefModified
|| bColRowNameCompile
||
3466 (bValChanged
&& bHasRelName
) || bOnRefMove
)
3469 if (pUndoDoc
&& !bCellInMoveTarget
&& (bValChanged
|| bRefModified
|| bOnRefMove
))
3470 setOldCodeToUndo(*pUndoDoc
, aUndoPos
, pOldCode
.get(), eTempGrammar
, cMatrixFlag
);
3472 bValChanged
= false;
3474 bCompile
= (bCompile
|| bValChanged
|| bColRowNameCompile
);
3477 CompileTokenArray( bNewListening
); // no Listening
3481 if ( !bInDeleteUndo
)
3482 { // In ChangeTrack Delete-Reject listeners are established in
3483 // InsertCol/InsertRow
3484 if ( bNewListening
)
3486 StartListeningTo( rDocument
);
3491 { // Cut off references, invalid or similar?
3492 sc::AutoCalcSwitch
aACSwitch(rDocument
, false);
3496 return bCellStateChanged
;
3499 bool ScFormulaCell::UpdateReferenceOnCopy(
3500 const sc::RefUpdateContext
& rCxt
, ScDocument
* pUndoDoc
, const ScAddress
* pUndoCellPos
)
3502 if (rCxt
.meMode
!= URM_COPY
)
3505 ScAddress
aUndoPos( aPos
); // position for undo cell in pUndoDoc
3507 aUndoPos
= *pUndoCellPos
;
3508 ScAddress
aOldPos( aPos
);
3510 if (rCxt
.maRange
.Contains(aPos
))
3512 // The cell is being moved or copied to a new position. I guess the
3513 // position has been updated prior to this call? Determine
3514 // its original position before the move which will be used to adjust
3515 // relative references later.
3516 aOldPos
.Set(aPos
.Col() - rCxt
.mnColDelta
, aPos
.Row() - rCxt
.mnRowDelta
, aPos
.Tab() - rCxt
.mnTabDelta
);
3519 // Check presence of any references or column row names.
3520 bool bHasRefs
= pCode
->HasReferences();
3521 bool bHasColRowNames
= (formula::FormulaTokenArrayPlainIterator(*pCode
).GetNextColRowName() != nullptr);
3522 bHasRefs
= bHasRefs
|| bHasColRowNames
;
3523 bool bOnRefMove
= pCode
->IsRecalcModeOnRefMove();
3525 if (!bHasRefs
&& !bOnRefMove
)
3526 // This formula cell contains no references, nor needs recalculating
3527 // on reference update. Bail out.
3530 std::unique_ptr
<ScTokenArray
> pOldCode
;
3532 pOldCode
= pCode
->Clone();
3535 // Cell may reference itself, e.g. ocColumn, ocRow without parameter
3536 bOnRefMove
= (aPos
!= aOldPos
);
3538 bool bNeedDirty
= bOnRefMove
;
3540 if (pUndoDoc
&& bOnRefMove
)
3541 setOldCodeToUndo(*pUndoDoc
, aUndoPos
, pOldCode
.get(), eTempGrammar
, cMatrixFlag
);
3545 CompileTokenArray(); // no Listening
3550 { // Cut off references, invalid or similar?
3551 sc::AutoCalcSwitch
aACSwitch(rDocument
, false);
3558 bool ScFormulaCell::UpdateReference(
3559 const sc::RefUpdateContext
& rCxt
, ScDocument
* pUndoDoc
, const ScAddress
* pUndoCellPos
)
3561 if (rDocument
.IsClipOrUndo())
3564 if (mxGroup
&& mxGroup
->mpTopCell
!= this)
3566 // This is not a top cell of a formula group. Don't update references.
3568 switch (rCxt
.meMode
)
3571 return UpdatePosOnShift(rCxt
);
3578 switch (rCxt
.meMode
)
3581 return UpdateReferenceOnShift(rCxt
, pUndoDoc
, pUndoCellPos
);
3583 return UpdateReferenceOnMove(rCxt
, pUndoDoc
, pUndoCellPos
);
3585 return UpdateReferenceOnCopy(rCxt
, pUndoDoc
, pUndoCellPos
);
3593 void ScFormulaCell::UpdateInsertTab( const sc::RefUpdateInsertTabContext
& rCxt
)
3595 // Adjust tokens only when it's not grouped or grouped top cell.
3596 bool bAdjustCode
= !mxGroup
|| mxGroup
->mpTopCell
== this;
3597 bool bPosChanged
= (rCxt
.mnInsertPos
<= aPos
.Tab());
3598 if (rDocument
.IsClipOrUndo() || !pCode
->HasReferences())
3601 aPos
.IncTab(rCxt
.mnSheets
);
3606 EndListeningTo( rDocument
);
3607 ScAddress aOldPos
= aPos
;
3608 // IncTab _after_ EndListeningTo and _before_ Compiler UpdateInsertTab!
3610 aPos
.IncTab(rCxt
.mnSheets
);
3615 sc::RefUpdateResult aRes
= pCode
->AdjustReferenceOnInsertedTab(rCxt
, aOldPos
);
3616 if (aRes
.mbNameModified
)
3617 // Re-compile after new sheet(s) have been inserted.
3620 // no StartListeningTo because the new sheets have not been inserted yet.
3623 void ScFormulaCell::UpdateDeleteTab( const sc::RefUpdateDeleteTabContext
& rCxt
)
3625 // Adjust tokens only when it's not grouped or grouped top cell.
3626 bool bAdjustCode
= !mxGroup
|| mxGroup
->mpTopCell
== this;
3627 bool bPosChanged
= (aPos
.Tab() >= rCxt
.mnDeletePos
+ rCxt
.mnSheets
);
3628 if (rDocument
.IsClipOrUndo() || !pCode
->HasReferences())
3631 aPos
.IncTab(-1*rCxt
.mnSheets
);
3635 EndListeningTo( rDocument
);
3636 // IncTab _after_ EndListeningTo and _before_ Compiler UpdateDeleteTab!
3637 ScAddress aOldPos
= aPos
;
3639 aPos
.IncTab(-1*rCxt
.mnSheets
);
3644 sc::RefUpdateResult aRes
= pCode
->AdjustReferenceOnDeletedTab(rCxt
, aOldPos
);
3645 if (aRes
.mbNameModified
)
3646 // Re-compile after sheet(s) have been deleted.
3650 void ScFormulaCell::UpdateMoveTab( const sc::RefUpdateMoveTabContext
& rCxt
, SCTAB nTabNo
)
3652 // Adjust tokens only when it's not grouped or grouped top cell.
3653 bool bAdjustCode
= !mxGroup
|| mxGroup
->mpTopCell
== this;
3655 if (!pCode
->HasReferences() || rDocument
.IsClipOrUndo())
3657 aPos
.SetTab(nTabNo
);
3661 EndListeningTo(rDocument
);
3662 ScAddress aOldPos
= aPos
;
3663 // SetTab _after_ EndListeningTo and _before_ Compiler UpdateMoveTab !
3664 aPos
.SetTab(nTabNo
);
3666 // no StartListeningTo because pTab[nTab] not yet correct!
3671 sc::RefUpdateResult aRes
= pCode
->AdjustReferenceOnMovedTab(rCxt
, aOldPos
);
3672 if (aRes
.mbNameModified
)
3673 // Re-compile after sheet(s) have been deleted.
3677 void ScFormulaCell::UpdateInsertTabAbs(SCTAB nTable
)
3679 if (rDocument
.IsClipOrUndo())
3682 bool bAdjustCode
= !mxGroup
|| mxGroup
->mpTopCell
== this;
3686 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
3687 formula::FormulaToken
* p
= aIter
.GetNextReferenceRPN();
3690 ScSingleRefData
& rRef1
= *p
->GetSingleRef();
3691 if (!rRef1
.IsTabRel() && nTable
<= rRef1
.Tab())
3693 if (p
->GetType() == formula::svDoubleRef
)
3695 ScSingleRefData
& rRef2
= p
->GetDoubleRef()->Ref2
;
3696 if (!rRef2
.IsTabRel() && nTable
<= rRef2
.Tab())
3699 p
= aIter
.GetNextReferenceRPN();
3703 bool ScFormulaCell::TestTabRefAbs(SCTAB nTable
)
3705 if (rDocument
.IsClipOrUndo())
3708 bool bAdjustCode
= !mxGroup
|| mxGroup
->mpTopCell
== this;
3713 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
3714 formula::FormulaToken
* p
= aIter
.GetNextReferenceRPN();
3717 ScSingleRefData
& rRef1
= *p
->GetSingleRef();
3718 if (!rRef1
.IsTabRel())
3720 if (nTable
!= rRef1
.Tab())
3722 else if (nTable
!= aPos
.Tab())
3723 rRef1
.SetAbsTab(aPos
.Tab());
3725 if (p
->GetType() == formula::svDoubleRef
)
3727 ScSingleRefData
& rRef2
= p
->GetDoubleRef()->Ref2
;
3728 if (!rRef2
.IsTabRel())
3730 if(nTable
!= rRef2
.Tab())
3732 else if (nTable
!= aPos
.Tab())
3733 rRef2
.SetAbsTab(aPos
.Tab());
3736 p
= aIter
.GetNextReferenceRPN();
3741 void ScFormulaCell::UpdateCompile( bool bForceIfNameInUse
)
3743 if ( bForceIfNameInUse
&& !bCompile
)
3744 bCompile
= pCode
->HasNameOrColRowName();
3746 pCode
->SetCodeError( FormulaError::NONE
); // make sure it will really be compiled
3747 CompileTokenArray();
3750 static void lcl_TransposeReference(ScSingleRefData
& rRef
)
3752 // References to or over filtered rows are not adjusted
3753 // analog to the normal (non-transposed) case
3754 SCCOLROW nTemp
= rRef
.Col();
3755 rRef
.SetRelCol(rRef
.Row());
3756 rRef
.SetRelRow(nTemp
);
3759 // Reference transposition is only called in Clipboard Document
3760 void ScFormulaCell::TransposeReference()
3762 bool bFound
= false;
3763 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
3764 formula::FormulaToken
* t
;
3765 while ( ( t
= aIter
.GetNextReference() ) != nullptr )
3767 ScSingleRefData
& rRef1
= *t
->GetSingleRef();
3768 if ( rRef1
.IsColRel() && rRef1
.IsRowRel() )
3770 bool bDouble
= (t
->GetType() == formula::svDoubleRef
);
3771 ScSingleRefData
& rRef2
= (bDouble
? t
->GetDoubleRef()->Ref2
: rRef1
);
3772 if ( !bDouble
|| (rRef2
.IsColRel() && rRef2
.IsRowRel()) )
3774 lcl_TransposeReference(rRef1
);
3777 lcl_TransposeReference(rRef2
);
3788 void ScFormulaCell::UpdateTranspose( const ScRange
& rSource
, const ScAddress
& rDest
,
3789 ScDocument
* pUndoDoc
)
3791 EndListeningTo( rDocument
);
3793 ScAddress aOldPos
= aPos
;
3794 bool bPosChanged
= false; // Whether this cell has been moved
3796 // Dest range is transposed
3797 ScRange
aDestRange( rDest
, ScAddress(
3798 static_cast<SCCOL
>(rDest
.Col() + rSource
.aEnd
.Row() - rSource
.aStart
.Row()),
3799 static_cast<SCROW
>(rDest
.Row() + rSource
.aEnd
.Col() - rSource
.aStart
.Col()),
3800 rDest
.Tab() + rSource
.aEnd
.Tab() - rSource
.aStart
.Tab() ) );
3802 // cell within range
3803 if ( aDestRange
.Contains( aOldPos
) )
3805 // References of these cells were not changed by ScTokenArray::AdjustReferenceOnMove()
3806 // Count back Positions
3807 SCCOL nRelPosX
= aOldPos
.Col();
3808 SCROW nRelPosY
= aOldPos
.Row();
3809 SCTAB nRelPosZ
= aOldPos
.Tab();
3810 ScRefUpdate::DoTranspose( nRelPosX
, nRelPosY
, nRelPosZ
, rDocument
, aDestRange
, rSource
.aStart
);
3811 aOldPos
.Set( nRelPosX
, nRelPosY
, nRelPosZ
);
3815 std::unique_ptr
<ScTokenArray
> pOld
;
3817 pOld
= pCode
->Clone();
3818 bool bRefChanged
= false;
3820 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
3821 formula::FormulaToken
* t
;
3822 while( (t
= aIter
.GetNextReferenceOrName()) != nullptr )
3824 if( t
->GetOpCode() == ocName
)
3826 const ScRangeData
* pName
= rDocument
.FindRangeNameBySheetAndIndex( t
->GetSheet(), t
->GetIndex());
3827 if (pName
&& pName
->IsModified())
3830 else if( t
->GetType() != svIndex
)
3832 SingleDoubleRefModifier
aMod(*t
);
3833 ScComplexRefData
& rRef
= aMod
.Ref();
3834 ScRange aAbs
= rRef
.toAbs(rDocument
, aOldPos
);
3835 bool bMod
= (ScRefUpdate::UpdateTranspose(rDocument
, rSource
, rDest
, aAbs
) != UR_NOTHING
|| bPosChanged
);
3838 rRef
.SetRange(rDocument
.GetSheetLimits(), aAbs
, aPos
); // based on the new anchor position.
3841 // Absolute sheet reference => set 3D flag.
3842 // More than one sheet referenced => has to have both 3D flags.
3843 // If end part has 3D flag => start part must have it too.
3844 // The same behavior as in ScTokenArray::AdjustReferenceOnMove() is used for 3D-Flags.
3845 rRef
.Ref2
.SetFlag3D(aAbs
.aStart
.Tab() != aAbs
.aEnd
.Tab() || !rRef
.Ref2
.IsTabRel());
3846 rRef
.Ref1
.SetFlag3D(
3847 (rSource
.aStart
.Tab() != rDest
.Tab() && !bPosChanged
)
3848 || !rRef
.Ref1
.IsTabRel() || rRef
.Ref2
.IsFlag3D());
3857 // Similar to setOldCodeToUndo(), but it cannot be used due to the check
3858 // pUndoDoc->GetCellType(aPos) == CELLTYPE_FORMULA
3859 ScFormulaCell
* pFCell
= new ScFormulaCell(
3860 *pUndoDoc
, aPos
, pOld
? *pOld
: ScTokenArray(*pUndoDoc
), eTempGrammar
, cMatrixFlag
);
3862 pFCell
->aResult
.SetToken( nullptr); // to recognize it as changed later (Cut/Paste!)
3863 pUndoDoc
->SetFormulaCell(aPos
, pFCell
);
3867 CompileTokenArray(); // also call StartListeningTo
3871 StartListeningTo( rDocument
); // Listener as previous
3874 void ScFormulaCell::UpdateGrow( const ScRange
& rArea
, SCCOL nGrowX
, SCROW nGrowY
)
3876 EndListeningTo( rDocument
);
3878 bool bRefChanged
= false;
3880 formula::FormulaTokenArrayPlainIterator
aIter(*pCode
);
3881 formula::FormulaToken
* t
;
3883 while( (t
= aIter
.GetNextReferenceOrName()) != nullptr )
3885 if( t
->GetOpCode() == ocName
)
3887 const ScRangeData
* pName
= rDocument
.FindRangeNameBySheetAndIndex( t
->GetSheet(), t
->GetIndex());
3888 if (pName
&& pName
->IsModified())
3891 else if( t
->GetType() != svIndex
)
3893 SingleDoubleRefModifier
aMod(*t
);
3894 ScComplexRefData
& rRef
= aMod
.Ref();
3895 ScRange aAbs
= rRef
.toAbs(rDocument
, aPos
);
3896 bool bMod
= (ScRefUpdate::UpdateGrow(rArea
, nGrowX
, nGrowY
, aAbs
) != UR_NOTHING
);
3899 rRef
.SetRange(rDocument
.GetSheetLimits(), aAbs
, aPos
);
3908 CompileTokenArray(); // Also call StartListeningTo
3912 StartListeningTo( rDocument
); // Listener as previous
3915 // See also ScDocument::FindRangeNamesReferencingSheet()
3916 static void lcl_FindRangeNamesInUse(sc::UpdatedRangeNames
& rIndexes
, const ScTokenArray
* pCode
, const ScDocument
& rDoc
,
3919 FormulaTokenArrayPlainIterator
aIter(*pCode
);
3920 for (FormulaToken
* p
= aIter
.First(); p
; p
= aIter
.Next())
3922 if (p
->GetOpCode() == ocName
)
3924 sal_uInt16 nTokenIndex
= p
->GetIndex();
3925 SCTAB nTab
= p
->GetSheet();
3926 rIndexes
.setUpdatedName( nTab
, nTokenIndex
);
3928 if (nRecursion
< 126) // whatever... 42*3
3930 ScRangeData
* pSubName
= rDoc
.FindRangeNameBySheetAndIndex( nTab
, nTokenIndex
);
3932 lcl_FindRangeNamesInUse(rIndexes
, pSubName
->GetCode(), rDoc
, nRecursion
+1);
3938 void ScFormulaCell::FindRangeNamesInUse(sc::UpdatedRangeNames
& rIndexes
) const
3940 lcl_FindRangeNamesInUse( rIndexes
, pCode
, rDocument
, 0);
3943 void ScFormulaCell::SetChanged(bool b
)
3948 void ScFormulaCell::SetCode( std::unique_ptr
<ScTokenArray
> pNew
)
3950 assert(!mxGroup
); // Don't call this if it's shared.
3952 pCode
= pNew
.release(); // takes ownership.
3955 void ScFormulaCell::SetRunning( bool bVal
)
3960 void ScFormulaCell::CompileDBFormula( sc::CompileFormulaContext
& rCxt
)
3962 FormulaTokenArrayPlainIterator
aIter(*pCode
);
3963 for( FormulaToken
* p
= aIter
.First(); p
; p
= aIter
.Next() )
3965 OpCode eOp
= p
->GetOpCode();
3966 if ( eOp
== ocDBArea
|| eOp
== ocTableRef
)
3969 CompileTokenArray(rCxt
);
3976 void ScFormulaCell::CompileColRowNameFormula( sc::CompileFormulaContext
& rCxt
)
3978 FormulaTokenArrayPlainIterator
aIter(*pCode
);
3979 for ( FormulaToken
* p
= aIter
.First(); p
; p
= aIter
.Next() )
3981 if ( p
->GetOpCode() == ocColRowName
)
3984 CompileTokenArray(rCxt
);
3991 void ScFormulaCell::SetPrevious( ScFormulaCell
* pF
) { pPrevious
= pF
; }
3992 void ScFormulaCell::SetNext( ScFormulaCell
* pF
) { pNext
= pF
; }
3993 void ScFormulaCell::SetPreviousTrack( ScFormulaCell
* pF
) { pPreviousTrack
= pF
; }
3994 void ScFormulaCell::SetNextTrack( ScFormulaCell
* pF
) { pNextTrack
= pF
; }
3996 ScFormulaCellGroupRef
ScFormulaCell::CreateCellGroup( SCROW nLen
, bool bInvariant
)
4000 // You can't create a new group if the cell is already a part of a group.
4001 // Is this a sign of some inconsistent or incorrect data structures? Or normal?
4002 SAL_INFO("sc.opencl", "You can't create a new group if the cell is already a part of a group");
4003 return ScFormulaCellGroupRef();
4006 mxGroup
.reset(new ScFormulaCellGroup
);
4007 mxGroup
->mpTopCell
= this;
4008 mxGroup
->mbInvariant
= bInvariant
;
4009 mxGroup
->mnLength
= nLen
;
4010 mxGroup
->mpCode
= std::move(*pCode
); // Move this to the shared location.
4012 pCode
= &*mxGroup
->mpCode
;
4016 void ScFormulaCell::SetCellGroup( const ScFormulaCellGroupRef
&xRef
)
4020 // Make this cell a non-grouped cell.
4022 pCode
= mxGroup
->mpCode
->Clone().release();
4028 // Group object has shared token array.
4030 // Currently not shared. Delete the existing token array first.
4034 pCode
= &*mxGroup
->mpCode
;
4035 mxGroup
->mnWeight
= 0; // invalidate
4038 ScFormulaCell::CompareState
ScFormulaCell::CompareByTokenArray( const ScFormulaCell
& rOther
) const
4040 // no Matrix formulae yet.
4041 if ( GetMatrixFlag() != ScMatrixMode::NONE
)
4044 // are these formulas at all similar ?
4045 if ( GetHash() != rOther
.GetHash() )
4048 if (!pCode
->IsShareable() || !rOther
.pCode
->IsShareable())
4051 FormulaToken
**pThis
= pCode
->GetCode();
4052 sal_uInt16 nThisLen
= pCode
->GetCodeLen();
4053 FormulaToken
**pOther
= rOther
.pCode
->GetCode();
4054 sal_uInt16 nOtherLen
= rOther
.pCode
->GetCodeLen();
4056 if ( !pThis
|| !pOther
)
4058 // Error: no compiled code for cells !"
4062 if ( nThisLen
!= nOtherLen
)
4065 // No tokens can be an error cell so check error code, otherwise we could
4066 // end up with a series of equal error values instead of individual error
4067 // values. Also if for any reason different errors are set even if all
4068 // tokens are equal, the cells are not equal.
4069 if (pCode
->GetCodeError() != rOther
.pCode
->GetCodeError())
4072 bool bInvariant
= true;
4074 // check we are basically the same function
4075 for ( sal_uInt16 i
= 0; i
< nThisLen
; i
++ )
4077 formula::FormulaToken
*pThisTok
= pThis
[i
];
4078 formula::FormulaToken
*pOtherTok
= pOther
[i
];
4080 if ( pThisTok
->GetType() != pOtherTok
->GetType() ||
4081 pThisTok
->GetOpCode() != pOtherTok
->GetOpCode() ||
4082 pThisTok
->GetParamCount() != pOtherTok
->GetParamCount() )
4084 // Incompatible type, op-code or param counts.
4088 switch (pThisTok
->GetType())
4090 case formula::svMatrix
:
4091 case formula::svExternalSingleRef
:
4092 case formula::svExternalDoubleRef
:
4093 // Ignoring matrix and external references for now.
4096 case formula::svSingleRef
:
4098 // Single cell reference.
4099 const ScSingleRefData
& rRef
= *pThisTok
->GetSingleRef();
4100 if (rRef
!= *pOtherTok
->GetSingleRef())
4103 if (rRef
.IsRowRel())
4107 case formula::svDoubleRef
:
4110 const ScSingleRefData
& rRef1
= *pThisTok
->GetSingleRef();
4111 const ScSingleRefData
& rRef2
= *pThisTok
->GetSingleRef2();
4112 if (rRef1
!= *pOtherTok
->GetSingleRef())
4115 if (rRef2
!= *pOtherTok
->GetSingleRef2())
4118 if (rRef1
.IsRowRel())
4121 if (rRef2
.IsRowRel())
4125 case formula::svDouble
:
4127 if(!rtl::math::approxEqual(pThisTok
->GetDouble(), pOtherTok
->GetDouble()))
4131 case formula::svString
:
4133 if(pThisTok
->GetString() != pOtherTok
->GetString())
4137 case formula::svIndex
:
4139 if(pThisTok
->GetIndex() != pOtherTok
->GetIndex() || pThisTok
->GetSheet() != pOtherTok
->GetSheet())
4143 case formula::svByte
:
4145 if(pThisTok
->GetByte() != pOtherTok
->GetByte())
4149 case formula::svExternal
:
4151 if (pThisTok
->GetExternal() != pOtherTok
->GetExternal())
4154 if (pThisTok
->GetByte() != pOtherTok
->GetByte())
4158 case formula::svError
:
4160 if (pThisTok
->GetError() != pOtherTok
->GetError())
4169 // If still the same, check lexical names as different names may result in
4170 // identical RPN code.
4172 pThis
= pCode
->GetArray();
4173 nThisLen
= pCode
->GetLen();
4174 pOther
= rOther
.pCode
->GetArray();
4175 nOtherLen
= rOther
.pCode
->GetLen();
4177 if ( !pThis
|| !pOther
)
4179 // Error: no code for cells !"
4183 if ( nThisLen
!= nOtherLen
)
4186 for ( sal_uInt16 i
= 0; i
< nThisLen
; i
++ )
4188 formula::FormulaToken
*pThisTok
= pThis
[i
];
4189 formula::FormulaToken
*pOtherTok
= pOther
[i
];
4191 if ( pThisTok
->GetType() != pOtherTok
->GetType() ||
4192 pThisTok
->GetOpCode() != pOtherTok
->GetOpCode() ||
4193 pThisTok
->GetParamCount() != pOtherTok
->GetParamCount() )
4195 // Incompatible type, op-code or param counts.
4199 switch (pThisTok
->GetType())
4201 // ScCompiler::HandleIIOpCode() may optimize some refs only in RPN code,
4202 // resulting in identical RPN references that could lead to creating
4203 // a formula group from formulas that should not be merged into a group,
4204 // so check also the formula itself.
4205 case formula::svSingleRef
:
4207 // Single cell reference.
4208 const ScSingleRefData
& rRef
= *pThisTok
->GetSingleRef();
4209 if (rRef
!= *pOtherTok
->GetSingleRef())
4212 if (rRef
.IsRowRel())
4216 case formula::svDoubleRef
:
4219 const ScSingleRefData
& rRef1
= *pThisTok
->GetSingleRef();
4220 const ScSingleRefData
& rRef2
= *pThisTok
->GetSingleRef2();
4221 if (rRef1
!= *pOtherTok
->GetSingleRef())
4224 if (rRef2
!= *pOtherTok
->GetSingleRef2())
4227 if (rRef1
.IsRowRel())
4230 if (rRef2
.IsRowRel())
4234 // All index tokens are names. Different categories already had
4235 // different OpCode values.
4236 case formula::svIndex
:
4238 if (pThisTok
->GetIndex() != pOtherTok
->GetIndex())
4240 switch (pThisTok
->GetOpCode())
4243 // nothing, sheet value assumed as -1, silence
4244 // ScTableRefToken::GetSheet() SAL_WARN about
4248 default: // ocName, ocDBArea
4249 if (pThisTok
->GetSheet() != pOtherTok
->GetSheet())
4259 return bInvariant
? EqualInvariant
: EqualRelativeRef
;
4264 // Split N into optimally equal-sized pieces, each not larger than K.
4265 // Return value P is number of pieces. A returns the number of pieces
4266 // one larger than N/P, 0..P-1.
4268 int splitup(int N
, int K
, int& A
)
4278 const int ideal_num_parts
= N
/ K
;
4279 if (ideal_num_parts
* K
== N
)
4280 return ideal_num_parts
;
4282 const int num_parts
= ideal_num_parts
+ 1;
4283 const int nominal_part_size
= N
/ num_parts
;
4285 A
= N
- num_parts
* nominal_part_size
;
4290 struct ScDependantsCalculator
4293 const ScTokenArray
& mrCode
;
4294 const ScFormulaCellGroupRef
& mxGroup
;
4296 const ScAddress
& mrPos
;
4297 const bool mFromFirstRow
;
4298 const SCROW mnStartOffset
;
4299 const SCROW mnEndOffset
;
4300 const SCROW mnSpanLen
;
4302 ScDependantsCalculator(ScDocument
& rDoc
, const ScTokenArray
& rCode
, const ScFormulaCell
& rCell
,
4303 const ScAddress
& rPos
, bool fromFirstRow
, SCROW nStartOffset
, SCROW nEndOffset
) :
4306 mxGroup(rCell
.GetCellGroup()),
4307 mnLen(mxGroup
->mnLength
),
4309 // ScColumn::FetchVectorRefArray() always fetches data from row 0, even if the data is used
4310 // only from further rows. This data fetching could also lead to Interpret() calls, so
4311 // in OpenCL mode the formula in practice depends on those cells too.
4312 mFromFirstRow(fromFirstRow
),
4313 mnStartOffset(nStartOffset
),
4314 mnEndOffset(nEndOffset
),
4315 mnSpanLen(nEndOffset
- nStartOffset
+ 1)
4319 // FIXME: copy-pasted from ScGroupTokenConverter. factor out somewhere else
4320 // (note already modified a bit, mFromFirstRow)
4322 // I think what this function does is to check whether the relative row reference nRelRow points
4323 // to a row that is inside the range of rows covered by the formula group.
4325 bool isSelfReferenceRelative(const ScAddress
& rRefPos
, SCROW nRelRow
)
4327 if (rRefPos
.Col() != mrPos
.Col() || rRefPos
.Tab() != mrPos
.Tab())
4330 SCROW nEndRow
= mrPos
.Row() + mnLen
- 1;
4334 SCROW nTest
= nEndRow
;
4336 if (nTest
>= mrPos
.Row())
4341 SCROW nTest
= mrPos
.Row(); // top row.
4343 if (nTest
<= nEndRow
)
4345 // If pointing below the formula, it's always included if going from first row.
4353 // FIXME: another copy-paste
4355 // And this correspondingly checks whether an absolute row is inside the range of rows covered
4356 // by the formula group.
4358 bool isSelfReferenceAbsolute(const ScAddress
& rRefPos
)
4360 if (rRefPos
.Col() != mrPos
.Col() || rRefPos
.Tab() != mrPos
.Tab())
4363 SCROW nEndRow
= mrPos
.Row() + mnLen
- 1;
4365 if (rRefPos
.Row() < mrPos
.Row())
4368 // If pointing below the formula, it's always included if going from first row.
4369 if (rRefPos
.Row() > nEndRow
&& !mFromFirstRow
)
4375 // Checks if the doubleref engulfs all of formula group cells
4376 // Note : does not check if there is a partial overlap, that can be done by calling
4377 // isSelfReference[Absolute|Relative]() on both the start and end of the double ref
4378 bool isDoubleRefSpanGroupRange(const ScRange
& rAbs
, bool bIsRef1RowRel
, bool bIsRef2RowRel
)
4380 if (rAbs
.aStart
.Col() > mrPos
.Col() || rAbs
.aEnd
.Col() < mrPos
.Col()
4381 || rAbs
.aStart
.Tab() > mrPos
.Tab() || rAbs
.aEnd
.Tab() < mrPos
.Tab())
4386 SCROW nStartRow
= mrPos
.Row();
4387 SCROW nEndRow
= nStartRow
+ mnLen
- 1;
4388 SCROW nRefStartRow
= rAbs
.aStart
.Row();
4389 SCROW nRefEndRow
= rAbs
.aEnd
.Row();
4391 if (bIsRef1RowRel
&& bIsRef2RowRel
&&
4392 ((nRefStartRow
<= nStartRow
&& nRefEndRow
>= nEndRow
) ||
4393 ((nRefStartRow
+ mnLen
- 1) <= nStartRow
&&
4394 (nRefEndRow
+ mnLen
- 1) >= nEndRow
)))
4397 if (!bIsRef1RowRel
&& nRefStartRow
<= nStartRow
&&
4398 (nRefEndRow
>= nEndRow
|| (nRefEndRow
+ mnLen
- 1) >= nEndRow
))
4401 if (!bIsRef2RowRel
&&
4402 nRefStartRow
<= nStartRow
&& nRefEndRow
>= nEndRow
)
4405 // If going from first row, the referenced range must be entirely above the formula,
4406 // otherwise the formula would be included.
4407 if (mFromFirstRow
&& nRefEndRow
>= nStartRow
)
4413 // FIXME: another copy-paste
4414 SCROW
trimLength(SCTAB nTab
, SCCOL nCol1
, SCCOL nCol2
, SCROW nRow
, SCROW nRowLen
)
4416 SCROW nLastRow
= nRow
+ nRowLen
- 1; // current last row.
4417 nLastRow
= mrDoc
.GetLastDataRow(nTab
, nCol1
, nCol2
, nLastRow
);
4418 if (nLastRow
< (nRow
+ nRowLen
- 1))
4420 // This can end up negative! Was that the original intent, or
4421 // is it accidental? Was it not like that originally but the
4422 // surrounding conditions changed?
4423 const bool bFail
= o3tl::checked_sub(nLastRow
+ 1, nRow
, nRowLen
);
4424 // Anyway, let's assume it doesn't make sense to return a
4425 // negative or zero value here.
4426 if (bFail
|| nRowLen
<= 0)
4429 else if (nLastRow
== 0)
4436 // Because Lookup will extend the Result Vector under certain circumstances listed at:
4437 // https://wiki.documentfoundation.org/Documentation/Calc_Functions/LOOKUP
4438 // then if the Lookup has a Result Vector only accept the Lookup for parallelization
4439 // of the Result Vector has the same dimensions as the Search Vector.
4440 bool LookupResultVectorMismatch(sal_Int32 nTokenIdx
)
4444 FormulaToken
** pRPNArray
= mrCode
.GetCode();
4445 if (pRPNArray
[nTokenIdx
- 1]->GetOpCode() == ocPush
&& // <- result vector
4446 pRPNArray
[nTokenIdx
- 2]->GetOpCode() == ocPush
&& // <- search vector
4447 pRPNArray
[nTokenIdx
- 2]->GetType() == svDoubleRef
&&
4448 pRPNArray
[nTokenIdx
- 3]->GetOpCode() == ocPush
) // <- search criterion
4450 auto res
= pRPNArray
[nTokenIdx
- 1];
4451 // If Result vector is just a single cell reference
4452 // LOOKUP extends it as a column vector.
4453 if (res
->GetType() == svSingleRef
)
4456 // If Result vector is a cell range and the match position
4457 // falls outside its length, it gets automatically extended
4458 // to the length of Search vector, but in the direction of
4460 if (res
->GetType() == svDoubleRef
)
4462 ScComplexRefData aRef1
= *res
->GetDoubleRef();
4463 ScComplexRefData aRef2
= *pRPNArray
[nTokenIdx
- 2]->GetDoubleRef();
4464 ScRange resultRange
= aRef1
.toAbs(mrDoc
, mrPos
);
4465 ScRange sourceRange
= aRef2
.toAbs(mrDoc
, mrPos
);
4467 SCROW nResultRows
= resultRange
.aEnd
.Row() - resultRange
.aStart
.Row();
4468 SCROW nSourceRows
= sourceRange
.aEnd
.Row() - sourceRange
.aStart
.Row();
4469 if (nResultRows
!= nSourceRows
)
4472 SCCOL nResultCols
= resultRange
.aEnd
.Col() - resultRange
.aStart
.Col();
4473 SCCOL nSourceCols
= sourceRange
.aEnd
.Col() - sourceRange
.aStart
.Col();
4474 if (nResultCols
!= nSourceCols
)
4482 bool DoIt(ScRangeList
* pSuccessfulDependencies
, ScAddress
* pDirtiedAddress
)
4484 // Partially from ScGroupTokenConverter::convert in sc/source/core/data/grouptokenconverter.cxx
4486 ScRangeList aRangeList
;
4488 // Self references should be checked by considering the entire formula-group not just the provided span.
4489 bool bHasSelfReferences
= false;
4490 bool bInDocShellRecalc
= mrDoc
.IsInDocShellRecalc();
4492 FormulaToken
** pRPNArray
= mrCode
.GetCode();
4493 sal_uInt16 nCodeLen
= mrCode
.GetCodeLen();
4494 for (sal_Int32 nTokenIdx
= nCodeLen
-1; nTokenIdx
>= 0; --nTokenIdx
)
4496 auto p
= pRPNArray
[nTokenIdx
];
4497 if (!bInDocShellRecalc
)
4499 // The dependency evaluator evaluates all arguments of IF/IFS/SWITCH irrespective
4500 // of the result of the condition expression.
4501 // This is a perf problem if we *don't* intent on recalc'ing all dirty cells
4502 // in the document. So let's disable threading and stop dependency evaluation if
4503 // the call did not originate from ScDocShell::DoRecalc()/ScDocShell::DoHardRecalc()
4504 // for formulae with IF/IFS/SWITCH
4505 OpCode nOpCode
= p
->GetOpCode();
4506 if (nOpCode
== ocIf
|| nOpCode
== ocIfs_MS
|| nOpCode
== ocSwitch_MS
)
4510 if (p
->GetOpCode() == ocLookup
&& LookupResultVectorMismatch(nTokenIdx
))
4512 SAL_INFO("sc.core.formulacell", "Lookup Result Vector size doesn't match Search Vector");
4516 if (p
->GetOpCode() == ocRange
)
4518 // We are just looking at svSingleRef/svDoubleRef, so we will miss that ocRange constructs
4519 // a range from its arguments, and only examining the individual args doesn't capture the
4520 // true range of dependencies
4521 SAL_WARN("sc.core.formulacell", "dynamic range, dropping as candidate for parallelizing");
4525 switch (p
->GetType())
4529 ScSingleRefData aRef
= *p
->GetSingleRef(); // =Sheet1!A1
4530 if( aRef
.IsDeleted())
4532 ScAddress aRefPos
= aRef
.toAbs(mrDoc
, mrPos
);
4534 if (!mrDoc
.HasTable(aRefPos
.Tab()))
4535 return false; // or true?
4537 if (aRef
.IsRowRel())
4539 if (isSelfReferenceRelative(aRefPos
, aRef
.Row()))
4541 bHasSelfReferences
= true;
4545 // Trim data array length to actual data range.
4546 SCROW nTrimLen
= trimLength(aRefPos
.Tab(), aRefPos
.Col(), aRefPos
.Col(), aRefPos
.Row() + mnStartOffset
, mnSpanLen
);
4548 aRangeList
.Join(ScRange(aRefPos
.Col(), aRefPos
.Row() + mnStartOffset
, aRefPos
.Tab(),
4549 aRefPos
.Col(), aRefPos
.Row() + mnStartOffset
+ nTrimLen
- 1, aRefPos
.Tab()));
4553 if (isSelfReferenceAbsolute(aRefPos
))
4555 bHasSelfReferences
= true;
4559 aRangeList
.Join(ScRange(aRefPos
.Col(), aRefPos
.Row(), aRefPos
.Tab()));
4565 ScComplexRefData aRef
= *p
->GetDoubleRef();
4566 if( aRef
.IsDeleted())
4568 ScRange aAbs
= aRef
.toAbs(mrDoc
, mrPos
);
4571 if (aAbs
.aStart
.Tab() != aAbs
.aEnd
.Tab())
4574 bool bIsRef1RowRel
= aRef
.Ref1
.IsRowRel();
4575 // Check for self reference.
4578 if (isSelfReferenceRelative(aAbs
.aStart
, aRef
.Ref1
.Row()))
4580 bHasSelfReferences
= true;
4584 else if (isSelfReferenceAbsolute(aAbs
.aStart
))
4586 bHasSelfReferences
= true;
4590 bool bIsRef2RowRel
= aRef
.Ref2
.IsRowRel();
4593 if (isSelfReferenceRelative(aAbs
.aEnd
, aRef
.Ref2
.Row()))
4595 bHasSelfReferences
= true;
4599 else if (isSelfReferenceAbsolute(aAbs
.aEnd
))
4601 bHasSelfReferences
= true;
4605 if (isDoubleRefSpanGroupRange(aAbs
, bIsRef1RowRel
, bIsRef2RowRel
))
4607 bHasSelfReferences
= true;
4611 SCROW nFirstRefStartRow
= bIsRef1RowRel
? aAbs
.aStart
.Row() + mnStartOffset
: aAbs
.aStart
.Row();
4612 SCROW nLastRefEndRow
= bIsRef2RowRel
? aAbs
.aEnd
.Row() + mnEndOffset
: aAbs
.aEnd
.Row();
4614 SCROW nFirstRefEndRow
= bIsRef1RowRel
? aAbs
.aStart
.Row() + mnEndOffset
: aAbs
.aStart
.Row();
4615 SCROW nLastRefStartRow
= bIsRef2RowRel
? aAbs
.aEnd
.Row() + mnStartOffset
: aAbs
.aEnd
.Row();
4617 // The first row that will be referenced through the doubleref.
4618 SCROW nFirstRefRow
= std::min(nFirstRefStartRow
, nLastRefStartRow
);
4619 // The last row that will be referenced through the doubleref.
4620 SCROW nLastRefRow
= std::max(nLastRefEndRow
, nFirstRefEndRow
);
4622 // Number of rows to be evaluated from nFirstRefRow.
4623 SCROW nArrayLength
= nLastRefRow
- nFirstRefRow
+ 1;
4624 assert(nArrayLength
> 0);
4626 // Trim trailing empty rows.
4627 nArrayLength
= trimLength(aAbs
.aStart
.Tab(), aAbs
.aStart
.Col(), aAbs
.aEnd
.Col(), nFirstRefRow
, nArrayLength
);
4629 aRangeList
.Join(ScRange(aAbs
.aStart
.Col(), nFirstRefRow
, aAbs
.aStart
.Tab(),
4630 aAbs
.aEnd
.Col(), nFirstRefRow
+ nArrayLength
- 1, aAbs
.aEnd
.Tab()));
4638 // Compute dependencies irrespective of the presence of any self references.
4639 // These dependencies would get computed via InterpretTail anyway when we disable group calc, so let's do it now.
4640 // The advantage is that the FG's get marked for cycles early if present, and can avoid lots of complications.
4641 for (size_t i
= 0; i
< aRangeList
.size(); ++i
)
4643 const ScRange
& rRange
= aRangeList
[i
];
4644 assert(rRange
.aStart
.Tab() == rRange
.aEnd
.Tab());
4645 for (auto nCol
= rRange
.aStart
.Col(); nCol
<= rRange
.aEnd
.Col(); nCol
++)
4647 SCROW nStartRow
= rRange
.aStart
.Row();
4648 SCROW nLength
= rRange
.aEnd
.Row() - rRange
.aStart
.Row() + 1;
4650 { // include also all previous rows
4651 nLength
+= nStartRow
;
4654 if (!mrDoc
.HandleRefArrayForParallelism(ScAddress(nCol
, nStartRow
, rRange
.aStart
.Tab()),
4655 nLength
, mxGroup
, pDirtiedAddress
))
4660 if (bHasSelfReferences
)
4661 mxGroup
->mbPartOfCycle
= true;
4663 if (pSuccessfulDependencies
&& !bHasSelfReferences
)
4664 *pSuccessfulDependencies
= std::move(aRangeList
);
4666 return !bHasSelfReferences
;
4670 } // anonymous namespace
4672 bool ScFormulaCell::InterpretFormulaGroup(SCROW nStartOffset
, SCROW nEndOffset
)
4674 if (!mxGroup
|| !pCode
)
4677 auto aScope
= sc::FormulaLogger::get().enterGroup(rDocument
, *this);
4678 ScRecursionHelper
& rRecursionHelper
= rDocument
.GetRecursionHelper();
4680 if (mxGroup
->mbPartOfCycle
)
4682 aScope
.addMessage(u
"This formula-group is part of a cycle"_ustr
);
4686 if (mxGroup
->meCalcState
== sc::GroupCalcDisabled
)
4688 static constexpr OUStringLiteral MESSAGE
= u
"group calc disabled";
4689 aScope
.addMessage(MESSAGE
);
4693 // Use SC_FORCE_CALCULATION=opencl/threads to force calculation e.g. for unittests
4694 static ForceCalculationType forceType
= ScCalcConfig::getForceCalculationType();
4695 if (forceType
== ForceCalculationCore
4696 || ( GetWeight() < ScInterpreter::GetGlobalConfig().mnOpenCLMinimumFormulaGroupSize
4697 && forceType
!= ForceCalculationOpenCL
4698 && forceType
!= ForceCalculationThreads
))
4700 mxGroup
->meCalcState
= sc::GroupCalcDisabled
;
4701 aScope
.addGroupSizeThresholdMessage(*this);
4705 if (cMatrixFlag
!= ScMatrixMode::NONE
)
4707 mxGroup
->meCalcState
= sc::GroupCalcDisabled
;
4708 aScope
.addMessage(u
"matrix skipped"_ustr
);
4712 if( forceType
!= ForceCalculationNone
)
4714 // ScConditionEntry::Interpret() creates a temporary cell and interprets it
4715 // without it actually being in the document at the specified position.
4716 // That would confuse opencl/threading code, as they refer to the cell group
4717 // also using the position. This is normally not triggered (single cells
4718 // are normally not in a cell group), but if forced, check for this explicitly.
4719 if( rDocument
.GetFormulaCell( aPos
) != this )
4721 mxGroup
->meCalcState
= sc::GroupCalcDisabled
;
4722 aScope
.addMessage(u
"cell not in document"_ustr
);
4727 // Get rid of -1's in offsets (defaults) or any invalid offsets.
4728 SCROW nMaxOffset
= mxGroup
->mnLength
- 1;
4729 nStartOffset
= nStartOffset
< 0 ? 0 : std::min(nStartOffset
, nMaxOffset
);
4730 nEndOffset
= nEndOffset
< 0 ? nMaxOffset
: std::min(nEndOffset
, nMaxOffset
);
4732 if (nEndOffset
< nStartOffset
)
4735 nEndOffset
= nMaxOffset
;
4738 if (nEndOffset
== nStartOffset
&& forceType
== ForceCalculationNone
)
4739 return false; // Do not use threads for a single row.
4741 // Guard against endless recursion of Interpret() calls, for this to work
4742 // ScFormulaCell::InterpretFormulaGroup() must never be called through
4743 // anything else than ScFormulaCell::Interpret(), same as
4744 // ScFormulaCell::InterpretTail()
4745 RecursionCounter
aRecursionCounter( rRecursionHelper
, this);
4747 bool bDependencyComputed
= false;
4748 bool bDependencyCheckFailed
= false;
4750 // Preference order: First try OpenCL, then threading.
4751 // TODO: Do formula-group span computation for OCL too if nStartOffset/nEndOffset are non default.
4752 if( InterpretFormulaGroupOpenCL(aScope
, bDependencyComputed
, bDependencyCheckFailed
))
4755 if( InterpretFormulaGroupThreading(aScope
, bDependencyComputed
, bDependencyCheckFailed
, nStartOffset
, nEndOffset
))
4761 bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope
& rScope
, bool fromFirstRow
,
4762 SCROW nStartOffset
, SCROW nEndOffset
,
4763 bool bCalcDependencyOnly
,
4764 ScRangeList
* pSuccessfulDependencies
,
4765 ScAddress
* pDirtiedAddress
)
4767 ScRecursionHelper
& rRecursionHelper
= rDocument
.GetRecursionHelper();
4768 // iterate over code in the formula ...
4769 // ensure all input is pre-calculated -
4770 // to avoid writing during the calculation
4771 if (bCalcDependencyOnly
)
4773 // Let's not use "ScFormulaGroupDependencyComputeGuard" here as there is no corresponding
4774 // "ScFormulaGroupCycleCheckGuard" for this formula-group.
4775 // (We can only reach here from a multi-group dependency evaluation attempt).
4776 // (These two have to be in pairs always for any given formula-group)
4777 ScDependantsCalculator
aCalculator(rDocument
, *pCode
, *this, mxGroup
->mpTopCell
->aPos
, fromFirstRow
, nStartOffset
, nEndOffset
);
4778 return aCalculator
.DoIt(pSuccessfulDependencies
, pDirtiedAddress
);
4781 bool bOKToParallelize
= false;
4783 ScFormulaGroupCycleCheckGuard
aCycleCheckGuard(rRecursionHelper
, this);
4784 if (mxGroup
->mbPartOfCycle
)
4786 mxGroup
->meCalcState
= sc::GroupCalcDisabled
;
4787 rScope
.addMessage(u
"found circular formula-group dependencies"_ustr
);
4791 ScFormulaGroupDependencyComputeGuard
aDepComputeGuard(rRecursionHelper
);
4792 ScDependantsCalculator
aCalculator(rDocument
, *pCode
, *this, mxGroup
->mpTopCell
->aPos
, fromFirstRow
, nStartOffset
, nEndOffset
);
4793 bOKToParallelize
= aCalculator
.DoIt(pSuccessfulDependencies
, pDirtiedAddress
);
4797 if (rRecursionHelper
.IsInRecursionReturn())
4799 mxGroup
->meCalcState
= sc::GroupCalcDisabled
;
4800 rScope
.addMessage(u
"Recursion limit reached, cannot thread this formula group now"_ustr
);
4804 if (mxGroup
->mbPartOfCycle
)
4806 mxGroup
->meCalcState
= sc::GroupCalcDisabled
;
4807 rScope
.addMessage(u
"found circular formula-group dependencies"_ustr
);
4811 if (!rRecursionHelper
.AreGroupsIndependent())
4813 // This call resulted from a dependency calculation for a multigroup-threading attempt,
4814 // but found dependency among the groups.
4815 rScope
.addMessage(u
"multi-group-dependency failed"_ustr
);
4819 if (!bOKToParallelize
)
4821 mxGroup
->meCalcState
= sc::GroupCalcDisabled
;
4822 rScope
.addMessage(u
"could not do new dependencies calculation thing"_ustr
);
4829 static SCCOL
lcl_probeLeftOrRightFGs(const ScFormulaCellGroupRef
& xGroup
, const ScDocument
& rDoc
,
4830 o3tl::sorted_vector
<ScFormulaCellGroup
*>& rFGSet
,
4831 std::map
<SCCOL
, ScFormulaCell
*>& rFGMap
, bool bLeft
)
4833 const SCROW nLen
= xGroup
->mnLength
;
4834 const sal_Int32 nWt
= xGroup
->mnWeight
;
4835 ScAddress
aAddr(xGroup
->mpTopCell
->aPos
);
4837 SCCOL nColRet
= aAddr
.Col();
4839 const SCCOL nMaxCol
= rDoc
.GetAllocatedColumnsCount(aAddr
.Tab()) - 1;
4845 while (nColRet
>= 0 && nColRet
<= nMaxCol
)
4847 aAddr
.SetCol(nColRet
);
4848 const ScFormulaCell
* pCell
= rDoc
.GetFormulaCell(aAddr
);
4852 if (!pCell
->NeedsInterpret())
4855 const ScFormulaCellGroupRef
& xNGroup
= pCell
->GetCellGroup();
4859 if (!pCell
->GetCode()->IsEnabledForThreading())
4862 if (xNGroup
->mpTopCell
->aPos
.Row() != aAddr
.Row())
4865 const SCROW nNLen
= xNGroup
->mnLength
;
4866 const sal_Int32 nNWt
= pCell
->GetWeight();
4867 if (nNLen
!= nLen
|| nNWt
!= nWt
)
4870 rFGSet
.insert(xNGroup
.get());
4871 rFGMap
[nColRet
] = xNGroup
->mpTopCell
;
4887 // To be called only from InterpretFormulaGroup().
4888 bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope
& aScope
,
4889 bool& bDependencyComputed
,
4890 bool& bDependencyCheckFailed
,
4894 static const bool bThreadingProhibited
= std::getenv("SC_NO_THREADED_CALCULATION");
4895 if (!bDependencyCheckFailed
&& !bThreadingProhibited
&&
4896 pCode
->IsEnabledForThreading() &&
4897 ScCalcConfig::isThreadingEnabled())
4899 ScRangeList aOrigDependencies
;
4900 if(!bDependencyComputed
&& !CheckComputeDependencies(aScope
, false, nStartOffset
, nEndOffset
, false, &aOrigDependencies
))
4902 bDependencyComputed
= true;
4903 bDependencyCheckFailed
= true;
4907 bDependencyComputed
= true;
4909 // Then do the threaded calculation
4911 class Executor
: public comphelper::ThreadTask
4914 const unsigned mnThisThread
;
4915 const unsigned mnThreadsTotal
;
4916 ScDocument
* mpDocument
;
4917 ScInterpreterContext
* mpContext
;
4918 const ScAddress
& mrTopPos
;
4921 SCROW mnStartOffset
;
4925 Executor(const std::shared_ptr
<comphelper::ThreadTaskTag
>& rTag
,
4926 unsigned nThisThread
,
4927 unsigned nThreadsTotal
,
4928 ScDocument
* pDocument2
,
4929 ScInterpreterContext
* pContext
,
4930 const ScAddress
& rTopPos
,
4935 comphelper::ThreadTask(rTag
),
4936 mnThisThread(nThisThread
),
4937 mnThreadsTotal(nThreadsTotal
),
4938 mpDocument(pDocument2
),
4939 mpContext(pContext
),
4941 mnStartCol(nStartCol
),
4943 mnStartOffset(nStartOff
),
4944 mnEndOffset(nEndOff
)
4948 virtual void doWork() override
4950 ScRange
aCalcRange(mnStartCol
, mrTopPos
.Row() + mnStartOffset
, mrTopPos
.Tab(),
4951 mnEndCol
, mrTopPos
.Row() + mnEndOffset
, mrTopPos
.Tab());
4952 mpDocument
->CalculateInColumnInThread(*mpContext
, aCalcRange
, mnThisThread
, mnThreadsTotal
);
4957 SvNumberFormatter
* pNonThreadedFormatter
= rDocument
.GetNonThreadedContext().GetFormatTable();
4959 comphelper::ThreadPool
& rThreadPool(comphelper::ThreadPool::getSharedOptimalPool());
4960 sal_Int32 nThreadCount
= rThreadPool
.getWorkerCount();
4962 SAL_INFO("sc.threaded", "Running " << nThreadCount
<< " threads");
4964 o3tl::sorted_vector
<ScFormulaCellGroup
*> aFGSet
;
4965 std::map
<SCCOL
, ScFormulaCell
*> aFGMap
;
4966 aFGSet
.insert(mxGroup
.get());
4968 ScRecursionHelper
& rRecursionHelper
= rDocument
.GetRecursionHelper();
4969 SCCOL nColStart
= aPos
.Col();
4970 SCCOL nColEnd
= nColStart
;
4971 if (!rRecursionHelper
.HasFormulaGroupSet() && rDocument
.IsInDocShellRecalc())
4973 nColStart
= lcl_probeLeftOrRightFGs(mxGroup
, rDocument
, aFGSet
, aFGMap
, true);
4974 nColEnd
= lcl_probeLeftOrRightFGs(mxGroup
, rDocument
, aFGSet
, aFGMap
, false);
4978 ScAddress
aDirtiedAddress(ScAddress::INITIALIZE_INVALID
);
4979 if (nColStart
!= nColEnd
)
4981 ScCheckIndependentFGGuard
aGuard(rRecursionHelper
, &aFGSet
);
4982 for (SCCOL nCurrCol
= nColStart
; nCurrCol
<= nColEnd
; ++nCurrCol
)
4984 if (nCurrCol
== aPos
.Col())
4987 bFGOK
= aFGMap
[nCurrCol
]->CheckComputeDependencies(aScope
, false, nStartOffset
, nEndOffset
,
4988 true, nullptr, &aDirtiedAddress
);
4989 if (!bFGOK
|| !aGuard
.AreGroupsIndependent())
4991 nColEnd
= nColStart
= aPos
.Col();
4997 // tdf#156677 it is possible that if a check of a column in the new range fails that the check has
4998 // now left a cell that the original range depended on in a Dirty state. So if the dirtied cell
4999 // was part of the original dependencies re-run the initial CheckComputeDependencies to fix it.
5000 if (!bFGOK
&& aDirtiedAddress
.IsValid() && aOrigDependencies
.Find(aDirtiedAddress
))
5002 SAL_WARN("sc.core.formulacell", "rechecking dependencies due to a dirtied cell during speculative probe");
5003 const bool bRedoEntryCheckSucceeded
= CheckComputeDependencies(aScope
, false, nStartOffset
, nEndOffset
);
5004 assert(bRedoEntryCheckSucceeded
&& "if it worked on the original range it should work again on that range");
5005 (void)bRedoEntryCheckSucceeded
;
5008 std::vector
<std::unique_ptr
<ScInterpreter
>> aInterpreters(nThreadCount
);
5010 assert(!rDocument
.IsThreadedGroupCalcInProgress());
5011 rDocument
.SetThreadedGroupCalcInProgress(true);
5013 ScMutationDisable
aGuard(rDocument
, ScMutationGuardFlags::CORE
);
5015 // Here we turn off ref-counting for the contents of pCode on the basis
5016 // that pCode is not modified by interpreting and when interpreting is
5017 // complete all token refcounts will be back to their initial ref count
5018 FormulaToken
** pArray
= pCode
->GetArray();
5019 for (sal_uInt16 i
= 0, n
= pCode
->GetLen(); i
< n
; ++i
)
5020 pArray
[i
]->SetRefCntPolicy(RefCntPolicy::None
);
5022 // Start nThreadCount new threads
5023 std::shared_ptr
<comphelper::ThreadTaskTag
> aTag
= comphelper::ThreadPool::createThreadTaskTag();
5024 ScThreadedInterpreterContextGetterGuard
aContextGetterGuard(nThreadCount
, rDocument
, pNonThreadedFormatter
);
5025 ScInterpreterContext
* context
= nullptr;
5027 for (int i
= 0; i
< nThreadCount
; ++i
)
5029 context
= aContextGetterGuard
.GetInterpreterContextForThreadIdx(i
);
5030 assert(!context
->pInterpreter
);
5031 aInterpreters
[i
].reset(new ScInterpreter(this, rDocument
, *context
, mxGroup
->mpTopCell
->aPos
, *pCode
, true));
5032 context
->pInterpreter
= aInterpreters
[i
].get();
5033 rDocument
.SetupContextFromNonThreadedContext(*context
, i
);
5034 rThreadPool
.pushTask(std::make_unique
<Executor
>(aTag
, i
, nThreadCount
, &rDocument
, context
, mxGroup
->mpTopCell
->aPos
,
5035 nColStart
, nColEnd
, nStartOffset
, nEndOffset
));
5038 SAL_INFO("sc.threaded", "Waiting for threads to finish work");
5039 // Do not join the threads here. They will get joined in ScDocument destructor
5040 // if they don't get joined from elsewhere before (via ThreadPool::waitUntilDone).
5041 rThreadPool
.waitUntilDone(aTag
, false);
5043 // Drop any caches that reference Tokens before restoring ref counting policy
5044 for (int i
= 0; i
< nThreadCount
; ++i
)
5045 aInterpreters
[i
]->DropTokenCaches();
5047 for (sal_uInt16 i
= 0, n
= pCode
->GetLen(); i
< n
; ++i
)
5048 pArray
[i
]->SetRefCntPolicy(RefCntPolicy::ThreadSafe
);
5050 rDocument
.SetThreadedGroupCalcInProgress(false);
5052 for (int i
= 0; i
< nThreadCount
; ++i
)
5054 context
= aContextGetterGuard
.GetInterpreterContextForThreadIdx(i
);
5055 // This is intentionally done in this main thread in order to avoid locking.
5056 rDocument
.MergeContextBackIntoNonThreadedContext(*context
, i
);
5057 context
->pInterpreter
= nullptr;
5060 SAL_INFO("sc.threaded", "Done");
5063 ScAddress
aStartPos(mxGroup
->mpTopCell
->aPos
);
5064 SCROW nSpanLen
= nEndOffset
- nStartOffset
+ 1;
5065 aStartPos
.SetRow(aStartPos
.Row() + nStartOffset
);
5066 // Reuse one of the previously allocated interpreter objects here.
5067 rDocument
.HandleStuffAfterParallelCalculation(nColStart
, nColEnd
, aStartPos
.Row(), nSpanLen
,
5068 aStartPos
.Tab(), aInterpreters
[0].get());
5076 // To be called only from InterpretFormulaGroup().
5077 bool ScFormulaCell::InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope
& aScope
,
5078 bool& bDependencyComputed
,
5079 bool& bDependencyCheckFailed
)
5081 bool bCanVectorize
= pCode
->IsEnabledForOpenCL();
5082 switch (pCode
->GetVectorState())
5084 case FormulaVectorEnabled
:
5085 case FormulaVectorCheckReference
:
5089 case FormulaVectorDisabledByOpCode
:
5090 aScope
.addMessage(u
"group calc disabled due to vector state (non-vector-supporting opcode)"_ustr
);
5092 case FormulaVectorDisabledByStackVariable
:
5093 aScope
.addMessage(u
"group calc disabled due to vector state (non-vector-supporting stack variable)"_ustr
);
5095 case FormulaVectorDisabledNotInSubSet
:
5096 aScope
.addMessage(u
"group calc disabled due to vector state (opcode not in subset)"_ustr
);
5098 case FormulaVectorDisabled
:
5099 case FormulaVectorUnknown
:
5101 aScope
.addMessage(u
"group calc disabled due to vector state (unknown)"_ustr
);
5108 if (!ScCalcConfig::isOpenCLEnabled())
5110 aScope
.addMessage(u
"opencl not enabled"_ustr
);
5114 // TableOp does tricks with using a cell with different values, just bail out.
5115 if(rDocument
.IsInInterpreterTableOp())
5118 if (bDependencyCheckFailed
)
5121 if(!bDependencyComputed
&& !CheckComputeDependencies(aScope
, true, 0, mxGroup
->mnLength
- 1))
5123 bDependencyComputed
= true;
5124 bDependencyCheckFailed
= true;
5128 bDependencyComputed
= true;
5130 // TODO : Disable invariant formula group interpretation for now in order
5131 // to get implicit intersection to work.
5132 if (mxGroup
->mbInvariant
&& false)
5133 return InterpretInvariantFormulaGroup();
5135 int nMaxGroupLength
= INT_MAX
;
5138 // Heuristic: Certain old low-end OpenCL implementations don't
5139 // work for us with too large group lengths. 1000 was determined
5140 // empirically to be a good compromise.
5141 if (openclwrapper::gpuEnv
.mbNeedsTDRAvoidance
)
5142 nMaxGroupLength
= 1000;
5145 if (std::getenv("SC_MAX_GROUP_LENGTH"))
5146 nMaxGroupLength
= std::atoi(std::getenv("SC_MAX_GROUP_LENGTH"));
5149 const int nNumParts
= splitup(GetSharedLength(), nMaxGroupLength
, nNumOnePlus
);
5153 ScAddress aOrigPos
= mxGroup
->mpTopCell
->aPos
;
5154 for (int i
= 0; i
< nNumParts
; i
++, nOffset
+= nCurChunkSize
)
5156 nCurChunkSize
= GetSharedLength()/nNumParts
+ (i
< nNumOnePlus
? 1 : 0);
5158 ScFormulaCellGroupRef xGroup
;
5165 xGroup
= new ScFormulaCellGroup();
5166 xGroup
->mpTopCell
= mxGroup
->mpTopCell
;
5167 xGroup
->mpTopCell
->aPos
= aOrigPos
;
5168 xGroup
->mpTopCell
->aPos
.IncRow(nOffset
);
5169 xGroup
->mbInvariant
= mxGroup
->mbInvariant
;
5170 xGroup
->mnLength
= nCurChunkSize
;
5171 xGroup
->mpCode
= std::move(mxGroup
->mpCode
); // temporarily transfer
5174 ScTokenArray
aCode(rDocument
);
5175 ScGroupTokenConverter
aConverter(aCode
, rDocument
, *this, xGroup
->mpTopCell
->aPos
);
5176 // TODO avoid this extra compilation
5177 ScCompiler
aComp( rDocument
, xGroup
->mpTopCell
->aPos
, *pCode
, formula::FormulaGrammar::GRAM_UNSPECIFIED
, true, cMatrixFlag
!= ScMatrixMode::NONE
);
5178 aComp
.CompileTokenArray();
5179 if (aComp
.HasUnhandledPossibleImplicitIntersections() || !aConverter
.convert(*pCode
, aScope
))
5181 if(aComp
.HasUnhandledPossibleImplicitIntersections())
5183 SAL_INFO("sc.opencl", "group " << xGroup
->mpTopCell
->aPos
<< " has unhandled implicit intersections, disabling");
5185 for( const OpCode opcode
: aComp
.UnhandledPossibleImplicitIntersectionsOpCodes())
5187 SAL_INFO("sc.opencl", "unhandled implicit intersection opcode "
5188 << formula::FormulaCompiler().GetOpCodeMap(com::sun::star::sheet::FormulaLanguage::ENGLISH
)->getSymbol(opcode
)
5189 << "(" << int(opcode
) << ")");
5194 SAL_INFO("sc.opencl", "conversion of group " << xGroup
->mpTopCell
->aPos
<< " failed, disabling");
5196 mxGroup
->meCalcState
= sc::GroupCalcDisabled
;
5198 // Undo the hack above
5201 mxGroup
->mpTopCell
->aPos
= aOrigPos
;
5202 xGroup
->mpTopCell
= nullptr;
5203 mxGroup
->mpCode
= std::move(xGroup
->mpCode
);
5206 aScope
.addMessage(u
"group token conversion failed"_ustr
);
5210 // The converted code does not have RPN tokens yet. The interpreter will
5212 xGroup
->meCalcState
= mxGroup
->meCalcState
= sc::GroupCalcRunning
;
5213 sc::FormulaGroupInterpreter
*pInterpreter
= sc::FormulaGroupInterpreter::getStatic();
5215 if (pInterpreter
== nullptr ||
5216 !pInterpreter
->interpret(rDocument
, xGroup
->mpTopCell
->aPos
, xGroup
, aCode
))
5218 SAL_INFO("sc.opencl", "interpreting group " << mxGroup
->mpTopCell
->aPos
5219 << " (state " << static_cast<int>(mxGroup
->meCalcState
) << ") failed, disabling");
5220 mxGroup
->meCalcState
= sc::GroupCalcDisabled
;
5222 // Undo the hack above
5225 mxGroup
->mpTopCell
->aPos
= aOrigPos
;
5226 xGroup
->mpTopCell
= nullptr;
5227 mxGroup
->mpCode
= std::move(xGroup
->mpCode
);
5230 aScope
.addMessage(u
"group interpretation unsuccessful"_ustr
);
5234 aScope
.setCalcComplete();
5238 xGroup
->mpTopCell
= nullptr;
5239 mxGroup
->mpCode
= std::move(xGroup
->mpCode
);
5244 mxGroup
->mpTopCell
->aPos
= aOrigPos
;
5245 mxGroup
->meCalcState
= sc::GroupCalcEnabled
;
5249 bool ScFormulaCell::InterpretInvariantFormulaGroup()
5251 if (pCode
->GetVectorState() == FormulaVectorCheckReference
)
5253 // An invariant group should only have absolute row references, and no
5254 // external references are allowed.
5256 ScTokenArray
aCode(rDocument
);
5257 FormulaTokenArrayPlainIterator
aIter(*pCode
);
5258 for (const formula::FormulaToken
* p
= aIter
.First(); p
; p
= aIter
.Next())
5260 switch (p
->GetType())
5264 ScSingleRefData aRef
= *p
->GetSingleRef();
5265 ScAddress aRefPos
= aRef
.toAbs(rDocument
, aPos
);
5266 formula::FormulaTokenRef pNewToken
= rDocument
.ResolveStaticReference(aRefPos
);
5270 aCode
.AddToken(*pNewToken
);
5275 ScComplexRefData aRef
= *p
->GetDoubleRef();
5276 ScRange aRefRange
= aRef
.toAbs(rDocument
, aPos
);
5277 formula::FormulaTokenRef pNewToken
= rDocument
.ResolveStaticReference(aRefRange
);
5281 aCode
.AddToken(*pNewToken
);
5289 ScCompiler
aComp(rDocument
, aPos
, aCode
, rDocument
.GetGrammar(), true, cMatrixFlag
!= ScMatrixMode::NONE
);
5290 aComp
.CompileTokenArray(); // Create RPN token array.
5291 ScInterpreter
aInterpreter(this, rDocument
, rDocument
.GetNonThreadedContext(), aPos
, aCode
);
5292 aInterpreter
.Interpret();
5293 aResult
.SetToken(aInterpreter
.GetResultToken().get());
5297 // Formula contains no references.
5298 ScInterpreter
aInterpreter(this, rDocument
, rDocument
.GetNonThreadedContext(), aPos
, *pCode
);
5299 aInterpreter
.Interpret();
5300 aResult
.SetToken(aInterpreter
.GetResultToken().get());
5303 for ( sal_Int32 i
= 0; i
< mxGroup
->mnLength
; i
++ )
5305 ScAddress aTmpPos
= aPos
;
5306 aTmpPos
.SetRow(mxGroup
->mpTopCell
->aPos
.Row() + i
);
5307 ScFormulaCell
* pCell
= rDocument
.GetFormulaCell(aTmpPos
);
5310 SAL_WARN("sc.core.formulacell", "GetFormulaCell not found");
5314 // FIXME: this set of horrors is unclear to me ... certainly
5315 // the above GetCell is profoundly nasty & slow ...
5316 // Ensure the cell truly has a result:
5317 pCell
->aResult
= aResult
;
5318 pCell
->ResetDirty();
5319 pCell
->SetChanged(true);
5327 void startListeningArea(
5328 ScFormulaCell
* pCell
, ScDocument
& rDoc
, const ScAddress
& rPos
, const formula::FormulaToken
& rToken
)
5330 const ScSingleRefData
& rRef1
= *rToken
.GetSingleRef();
5331 const ScSingleRefData
& rRef2
= *rToken
.GetSingleRef2();
5332 ScAddress aCell1
= rRef1
.toAbs(rDoc
, rPos
);
5333 ScAddress aCell2
= rRef2
.toAbs(rDoc
, rPos
);
5334 if (!(aCell1
.IsValid() && aCell2
.IsValid()))
5337 if (rToken
.GetOpCode() == ocColRowNameAuto
)
5339 if ( rRef1
.IsColRel() )
5341 aCell2
.SetRow(rDoc
.MaxRow());
5345 aCell2
.SetCol(rDoc
.MaxCol());
5348 rDoc
.StartListeningArea(ScRange(aCell1
, aCell2
), false, pCell
);
5353 void ScFormulaCell::StartListeningTo( ScDocument
& rDoc
)
5356 mxGroup
->endAllGroupListening(rDoc
);
5358 if (rDoc
.IsClipOrUndo() || rDoc
.GetNoListening() || IsInChangeTrack())
5361 rDoc
.SetDetectiveDirty(true); // It has changed something
5363 ScTokenArray
* pArr
= GetCode();
5364 if( pArr
->IsRecalcModeAlways() )
5366 rDoc
.StartListeningArea(BCA_LISTEN_ALWAYS
, false, this);
5367 SetNeedsListening( false);
5371 formula::FormulaTokenArrayPlainIterator
aIter(*pArr
);
5372 formula::FormulaToken
* t
;
5373 while ( ( t
= aIter
.GetNextReferenceRPN() ) != nullptr )
5375 switch (t
->GetType())
5379 ScAddress aCell
= t
->GetSingleRef()->toAbs(rDocument
, aPos
);
5380 if (aCell
.IsValid())
5381 rDoc
.StartListeningCell(aCell
, this);
5385 startListeningArea(this, rDoc
, aPos
, *t
);
5391 SetNeedsListening( false);
5394 void ScFormulaCell::StartListeningTo( sc::StartListeningContext
& rCxt
)
5396 ScDocument
& rDoc
= rCxt
.getDoc();
5399 mxGroup
->endAllGroupListening(rDoc
);
5401 if (rDoc
.IsClipOrUndo() || rDoc
.GetNoListening() || IsInChangeTrack())
5404 rDoc
.SetDetectiveDirty(true); // It has changed something
5406 ScTokenArray
* pArr
= GetCode();
5407 if( pArr
->IsRecalcModeAlways() )
5409 rDoc
.StartListeningArea(BCA_LISTEN_ALWAYS
, false, this);
5410 SetNeedsListening( false);
5414 formula::FormulaTokenArrayPlainIterator
aIter(*pArr
);
5415 formula::FormulaToken
* t
;
5416 while ( ( t
= aIter
.GetNextReferenceRPN() ) != nullptr )
5418 switch (t
->GetType())
5422 ScAddress aCell
= t
->GetSingleRef()->toAbs(rDocument
, aPos
);
5423 if (aCell
.IsValid())
5424 rDoc
.StartListeningCell(rCxt
, aCell
, *this);
5428 startListeningArea(this, rDoc
, aPos
, *t
);
5434 SetNeedsListening( false);
5439 void endListeningArea(
5440 ScFormulaCell
* pCell
, ScDocument
& rDoc
, const ScAddress
& rPos
, const formula::FormulaToken
& rToken
)
5442 const ScSingleRefData
& rRef1
= *rToken
.GetSingleRef();
5443 const ScSingleRefData
& rRef2
= *rToken
.GetSingleRef2();
5444 ScAddress aCell1
= rRef1
.toAbs(rDoc
, rPos
);
5445 ScAddress aCell2
= rRef2
.toAbs(rDoc
, rPos
);
5446 if (!(aCell1
.IsValid() && aCell2
.IsValid()))
5449 if (rToken
.GetOpCode() == ocColRowNameAuto
)
5451 if ( rRef1
.IsColRel() )
5453 aCell2
.SetRow(rDoc
.MaxRow());
5457 aCell2
.SetCol(rDoc
.MaxCol());
5461 rDoc
.EndListeningArea(ScRange(aCell1
, aCell2
), false, pCell
);
5466 void ScFormulaCell::EndListeningTo( ScDocument
& rDoc
, ScTokenArray
* pArr
,
5467 ScAddress aCellPos
)
5470 mxGroup
->endAllGroupListening(rDoc
);
5472 if (rDoc
.IsClipOrUndo() || IsInChangeTrack())
5475 if (!HasBroadcaster())
5478 rDoc
.SetDetectiveDirty(true); // It has changed something
5480 if ( GetCode()->IsRecalcModeAlways() )
5482 rDoc
.EndListeningArea(BCA_LISTEN_ALWAYS
, false, this);
5491 formula::FormulaTokenArrayPlainIterator
aIter(*pArr
);
5492 formula::FormulaToken
* t
;
5493 while ( ( t
= aIter
.GetNextReferenceRPN() ) != nullptr )
5495 switch (t
->GetType())
5499 ScAddress aCell
= t
->GetSingleRef()->toAbs(rDocument
, aCellPos
);
5500 if (aCell
.IsValid())
5501 rDoc
.EndListeningCell(aCell
, this);
5505 endListeningArea(this, rDoc
, aCellPos
, *t
);
5513 void ScFormulaCell::EndListeningTo( sc::EndListeningContext
& rCxt
)
5516 mxGroup
->endAllGroupListening(rCxt
.getDoc());
5518 if (rCxt
.getDoc().IsClipOrUndo() || IsInChangeTrack())
5521 if (!HasBroadcaster())
5524 ScDocument
& rDoc
= rCxt
.getDoc();
5525 rDoc
.SetDetectiveDirty(true); // It has changed something
5527 ScTokenArray
* pArr
= rCxt
.getOldCode();
5528 ScAddress aCellPos
= rCxt
.getOldPosition(aPos
);
5532 if (pArr
->IsRecalcModeAlways())
5534 rDoc
.EndListeningArea(BCA_LISTEN_ALWAYS
, false, this);
5538 formula::FormulaTokenArrayPlainIterator
aIter(*pArr
);
5539 formula::FormulaToken
* t
;
5540 while ( ( t
= aIter
.GetNextReferenceRPN() ) != nullptr )
5542 switch (t
->GetType())
5546 ScAddress aCell
= t
->GetSingleRef()->toAbs(rDocument
, aCellPos
);
5547 if (aCell
.IsValid())
5548 rDoc
.EndListeningCell(rCxt
, aCell
, *this);
5552 endListeningArea(this, rDoc
, aCellPos
, *t
);
5560 bool ScFormulaCell::IsShared() const
5562 return bool(mxGroup
);
5565 bool ScFormulaCell::IsSharedTop() const
5570 return mxGroup
->mpTopCell
== this;
5573 SCROW
ScFormulaCell::GetSharedTopRow() const
5575 return mxGroup
? mxGroup
->mpTopCell
->aPos
.Row() : -1;
5578 SCROW
ScFormulaCell::GetSharedLength() const
5580 return mxGroup
? mxGroup
->mnLength
: 0;
5583 sal_Int32
ScFormulaCell::GetWeight() const
5588 if (mxGroup
->mnWeight
> 0)
5589 return mxGroup
->mnWeight
;
5591 double nSharedCodeWeight
= GetSharedCode()->GetWeight();
5592 double nResult
= nSharedCodeWeight
* GetSharedLength();
5593 if (nResult
< SAL_MAX_INT32
)
5594 mxGroup
->mnWeight
= nResult
;
5596 mxGroup
->mnWeight
= SAL_MAX_INT32
;
5598 return mxGroup
->mnWeight
;
5601 ScTokenArray
* ScFormulaCell::GetSharedCode()
5603 return mxGroup
? &*mxGroup
->mpCode
: nullptr;
5606 const ScTokenArray
* ScFormulaCell::GetSharedCode() const
5608 return mxGroup
? &*mxGroup
->mpCode
: nullptr;
5611 void ScFormulaCell::SyncSharedCode()
5614 // Not a shared formula cell.
5617 pCode
= &*mxGroup
->mpCode
;
5620 #if DUMP_COLUMN_STORAGE
5622 void ScFormulaCell::Dump() const
5624 cout
<< "-- formula cell (" << aPos
.Format(ScRefFlags::VALID
| ScRefFlags::TAB_3D
, &rDocument
) << ")" << endl
;
5625 cout
<< " * shared: " << (mxGroup
? "true" : "false") << endl
;
5628 cout
<< " * shared length: " << mxGroup
->mnLength
<< endl
;
5629 cout
<< " * shared calc state: " << mxGroup
->meCalcState
<< endl
;
5632 sc::TokenStringContext
aCxt(rDocument
, rDocument
.GetGrammar());
5633 cout
<< " * code: " << pCode
->CreateString(aCxt
, aPos
) << endl
;
5635 FormulaError nErrCode
= pCode
->GetCodeError();
5636 cout
<< " * code error: ";
5637 if (nErrCode
== FormulaError::NONE
)
5641 OUString aStr
= ScGlobal::GetErrorString(nErrCode
);
5642 cout
<< " * code error: " << aStr
<< " (" << int(nErrCode
) << ")";
5646 cout
<< " * result: ";
5647 sc::FormulaResultValue aRV
= aResult
.GetResult();
5650 case sc::FormulaResultValue::Value
:
5651 cout
<< aRV
.mfValue
<< " (value)";
5653 case sc::FormulaResultValue::String
:
5654 cout
<< aRV
.maString
.getString() << " (string)";
5656 case sc::FormulaResultValue::Error
:
5657 cout
<< ScGlobal::GetErrorString(aRV
.mnError
) << " (error: " << int(aRV
.mnError
) << ")";
5659 case sc::FormulaResultValue::Invalid
:
5660 cout
<< "(invalid)";
5670 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */