1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <sal/log.hxx>
21 #include <osl/diagnose.h>
22 
23 #include <document.hxx>
24 #include <brdcst.hxx>
25 #include <bcaslot.hxx>
26 #include <formulacell.hxx>
27 #include <table.hxx>
28 #include <progress.hxx>
29 #include <scmod.hxx>
30 #include <inputopt.hxx>
31 #include <sheetevents.hxx>
32 #include <tokenarray.hxx>
33 #include <listenercontext.hxx>
34 
StartListeningArea(const ScRange & rRange,bool bGroupListening,SvtListener * pListener)35 void ScDocument::StartListeningArea(
36     const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
37 {
38     if (!pBASM)
39         return;
40 
41     // Ensure sane ranges for the slots, specifically don't attempt to listen
42     // to more sheets than the document has. The slot machine handles it but
43     // with memory waste. Binary import filters can set out-of-bounds ranges
44     // in formula expressions' references, so all middle layers would have to
45     // check it, rather have this central point here.
46     ScRange aLimitedRange( ScAddress::UNINITIALIZED );
47     bool bEntirelyOut;
48     if (!LimitRangeToAvailableSheets( rRange, aLimitedRange, bEntirelyOut))
49     {
50         pBASM->StartListeningArea(rRange, bGroupListening, pListener);
51         return;
52     }
53 
54     // If both sheets are out-of-bounds in the same direction then just bail out.
55     if (bEntirelyOut)
56         return;
57 
58     pBASM->StartListeningArea( aLimitedRange, bGroupListening, pListener);
59 }
60 
EndListeningArea(const ScRange & rRange,bool bGroupListening,SvtListener * pListener)61 void ScDocument::EndListeningArea( const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
62 {
63     if (!pBASM)
64         return;
65 
66     // End listening has to limit the range exactly the same as in
67     // StartListeningArea(), otherwise the range would not be found.
68     ScRange aLimitedRange( ScAddress::UNINITIALIZED );
69     bool bEntirelyOut;
70     if (!LimitRangeToAvailableSheets( rRange, aLimitedRange, bEntirelyOut))
71     {
72         pBASM->EndListeningArea(rRange, bGroupListening, pListener);
73         return;
74     }
75 
76     // If both sheets are out-of-bounds in the same direction then just bail out.
77     if (bEntirelyOut)
78         return;
79 
80     pBASM->EndListeningArea( aLimitedRange, bGroupListening, pListener);
81 }
82 
LimitRangeToAvailableSheets(const ScRange & rRange,ScRange & o_rRange,bool & o_bEntirelyOutOfBounds) const83 bool ScDocument::LimitRangeToAvailableSheets( const ScRange& rRange, ScRange& o_rRange,
84         bool& o_bEntirelyOutOfBounds ) const
85 {
86     const SCTAB nMaxTab = GetTableCount() - 1;
87     if (ValidTab( rRange.aStart.Tab(), nMaxTab) && ValidTab( rRange.aEnd.Tab(), nMaxTab))
88         return false;
89 
90     // Originally BCA_LISTEN_ALWAYS uses an implicit tab 0 and should had been
91     // valid already, but in case that would change...
92     if (rRange == BCA_LISTEN_ALWAYS)
93         return false;
94 
95     SCTAB nTab1 = rRange.aStart.Tab();
96     SCTAB nTab2 = rRange.aEnd.Tab();
97     SAL_WARN("sc.core","ScDocument::LimitRangeToAvailableSheets - bad sheet range: " << nTab1 << ".." << nTab2 <<
98             ", sheets: 0.." << nMaxTab);
99 
100     // Both sheets are out-of-bounds in the same direction.
101     if ((nTab1 < 0 && nTab2 < 0) || (nMaxTab < nTab1 && nMaxTab < nTab2))
102     {
103         o_bEntirelyOutOfBounds = true;
104         return true;
105     }
106 
107     // Limit the sheet range to bounds.
108     o_bEntirelyOutOfBounds = false;
109     nTab1 = std::clamp<SCTAB>( nTab1, 0, nMaxTab);
110     nTab2 = std::clamp<SCTAB>( nTab2, 0, nMaxTab);
111     o_rRange = rRange;
112     o_rRange.aStart.SetTab(nTab1);
113     o_rRange.aEnd.SetTab(nTab2);
114     return true;
115 }
116 
Broadcast(const ScHint & rHint)117 void ScDocument::Broadcast( const ScHint& rHint )
118 {
119     if ( !pBASM )
120         return ;    // Clipboard or Undo
121     if ( eHardRecalcState == HardRecalcState::OFF )
122     {
123         ScBulkBroadcast aBulkBroadcast( pBASM.get(), rHint.GetId());     // scoped bulk broadcast
124         bool bIsBroadcasted = false;
125         SvtBroadcaster* pBC = GetBroadcaster(rHint.GetAddress());
126         if ( pBC )
127         {
128             pBC->Broadcast( rHint );
129             bIsBroadcasted = true;
130         }
131         if ( pBASM->AreaBroadcast( rHint ) || bIsBroadcasted )
132             TrackFormulas( rHint.GetId() );
133     }
134 
135     if ( rHint.GetAddress() != BCA_BRDCST_ALWAYS )
136     {
137         SCTAB nTab = rHint.GetAddress().Tab();
138         if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
139             maTabs[nTab]->SetStreamValid(false);
140     }
141 }
142 
BroadcastCells(const ScRange & rRange,SfxHintId nHint,bool bBroadcastSingleBroadcasters)143 void ScDocument::BroadcastCells( const ScRange& rRange, SfxHintId nHint, bool bBroadcastSingleBroadcasters )
144 {
145     PrepareFormulaCalc();
146 
147     if (!pBASM)
148         return;    // Clipboard or Undo
149 
150     SCTAB nTab1 = rRange.aStart.Tab();
151     SCTAB nTab2 = rRange.aEnd.Tab();
152     SCROW nRow1 = rRange.aStart.Row();
153     SCROW nRow2 = rRange.aEnd.Row();
154     SCCOL nCol1 = rRange.aStart.Col();
155     SCCOL nCol2 = rRange.aEnd.Col();
156 
157     if (eHardRecalcState == HardRecalcState::OFF)
158     {
159         ScBulkBroadcast aBulkBroadcast( pBASM.get(), nHint);     // scoped bulk broadcast
160         bool bIsBroadcasted = false;
161 
162         if (bBroadcastSingleBroadcasters)
163         {
164             ScHint aHint(nHint, ScAddress());
165 
166             for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
167             {
168                 ScTable* pTab = FetchTable(nTab);
169                 if (!pTab)
170                     continue;
171 
172                 bIsBroadcasted |= pTab->BroadcastBroadcasters( nCol1, nRow1, nCol2, nRow2, aHint);
173             }
174         }
175 
176         if (pBASM->AreaBroadcast(rRange, nHint) || bIsBroadcasted)
177             TrackFormulas(nHint);
178     }
179 
180     for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
181     {
182         ScTable* pTab = FetchTable(nTab);
183         if (pTab)
184             pTab->SetStreamValid(false);
185     }
186 
187     BroadcastUno(SfxHint(SfxHintId::ScDataChanged));
188 }
189 
AreaBroadcast(const ScHint & rHint)190 void ScDocument::AreaBroadcast( const ScHint& rHint )
191 {
192     if ( !pBASM )
193         return ;    // Clipboard or Undo
194     if (eHardRecalcState == HardRecalcState::OFF)
195     {
196         ScBulkBroadcast aBulkBroadcast( pBASM.get(), rHint.GetId());     // scoped bulk broadcast
197         if ( pBASM->AreaBroadcast( rHint ) )
198             TrackFormulas( rHint.GetId() );
199     }
200 }
201 
DelBroadcastAreasInRange(const ScRange & rRange)202 void ScDocument::DelBroadcastAreasInRange( const ScRange& rRange )
203 {
204     if ( pBASM )
205         pBASM->DelBroadcastAreasInRange( rRange );
206 }
207 
StartListeningCell(const ScAddress & rAddress,SvtListener * pListener)208 void ScDocument::StartListeningCell( const ScAddress& rAddress,
209                                             SvtListener* pListener )
210 {
211     OSL_ENSURE(pListener, "StartListeningCell: pListener Null");
212     SCTAB nTab = rAddress.Tab();
213     if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
214         maTabs[nTab]->StartListening( rAddress, pListener );
215 }
216 
EndListeningCell(const ScAddress & rAddress,SvtListener * pListener)217 void ScDocument::EndListeningCell( const ScAddress& rAddress,
218                                             SvtListener* pListener )
219 {
220     OSL_ENSURE(pListener, "EndListeningCell: pListener Null");
221     SCTAB nTab = rAddress.Tab();
222     if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
223         maTabs[nTab]->EndListening( rAddress, pListener );
224 }
225 
StartListeningCell(sc::StartListeningContext & rCxt,const ScAddress & rPos,SvtListener & rListener)226 void ScDocument::StartListeningCell(
227     sc::StartListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener )
228 {
229     ScTable* pTab = FetchTable(rPos.Tab());
230     if (!pTab)
231         return;
232 
233     pTab->StartListening(rCxt, rPos, rListener);
234 }
235 
EndListeningCell(sc::EndListeningContext & rCxt,const ScAddress & rPos,SvtListener & rListener)236 void ScDocument::EndListeningCell(
237     sc::EndListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener )
238 {
239     ScTable* pTab = FetchTable(rPos.Tab());
240     if (!pTab)
241         return;
242 
243     pTab->EndListening(rCxt, rPos, rListener);
244 }
245 
EndListeningFormulaCells(std::vector<ScFormulaCell * > & rCells)246 void ScDocument::EndListeningFormulaCells( std::vector<ScFormulaCell*>& rCells )
247 {
248     if (rCells.empty())
249         return;
250 
251     sc::EndListeningContext aCxt(*this);
252     for (auto& pCell : rCells)
253         pCell->EndListeningTo(aCxt);
254 
255     aCxt.purgeEmptyBroadcasters();
256 }
257 
PutInFormulaTree(ScFormulaCell * pCell)258 void ScDocument::PutInFormulaTree( ScFormulaCell* pCell )
259 {
260     OSL_ENSURE( pCell, "PutInFormulaTree: pCell Null" );
261     RemoveFromFormulaTree( pCell );
262     // append
263     ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
264     if ( pEOFormulaTree )
265         pEOFormulaTree->SetNext( pCell );
266     else
267         pFormulaTree = pCell;               // No end, no beginning...
268     pCell->SetPrevious( pEOFormulaTree );
269     pCell->SetNext( nullptr );
270     pEOFormulaTree = pCell;
271     nFormulaCodeInTree += pCell->GetCode()->GetCodeLen();
272 }
273 
RemoveFromFormulaTree(ScFormulaCell * pCell)274 void ScDocument::RemoveFromFormulaTree( ScFormulaCell* pCell )
275 {
276     ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
277     OSL_ENSURE( pCell, "RemoveFromFormulaTree: pCell Null" );
278     ScFormulaCell* pPrev = pCell->GetPrevious();
279     assert(pPrev != pCell);                 // pointing to itself?!?
280     // if the cell is first or somewhere in chain
281     if ( pPrev || pFormulaTree == pCell )
282     {
283         ScFormulaCell* pNext = pCell->GetNext();
284         assert(pNext != pCell);             // pointing to itself?!?
285         if ( pPrev )
286         {
287             assert(pFormulaTree != pCell);  // if this cell is also head something's wrong
288             pPrev->SetNext( pNext );        // predecessor exists, set successor
289         }
290         else
291         {
292             pFormulaTree = pNext;           // this cell was first cell
293         }
294         if ( pNext )
295         {
296             assert(pEOFormulaTree != pCell); // if this cell is also tail something's wrong
297             pNext->SetPrevious( pPrev );    // successor exists, set predecessor
298         }
299         else
300         {
301             pEOFormulaTree = pPrev;         // this cell was last cell
302         }
303         pCell->SetPrevious( nullptr );
304         pCell->SetNext( nullptr );
305         sal_uInt16 nRPN = pCell->GetCode()->GetCodeLen();
306         if ( nFormulaCodeInTree >= nRPN )
307             nFormulaCodeInTree -= nRPN;
308         else
309         {
310             OSL_FAIL( "RemoveFromFormulaTree: nFormulaCodeInTree < nRPN" );
311             nFormulaCodeInTree = 0;
312         }
313     }
314     else if ( !pFormulaTree && nFormulaCodeInTree )
315     {
316         OSL_FAIL( "!pFormulaTree && nFormulaCodeInTree != 0" );
317         nFormulaCodeInTree = 0;
318     }
319 }
320 
IsInFormulaTree(const ScFormulaCell * pCell) const321 bool ScDocument::IsInFormulaTree( const ScFormulaCell* pCell ) const
322 {
323     return pCell->GetPrevious() || pFormulaTree == pCell;
324 }
325 
CalcFormulaTree(bool bOnlyForced,bool bProgressBar,bool bSetAllDirty)326 void ScDocument::CalcFormulaTree( bool bOnlyForced, bool bProgressBar, bool bSetAllDirty )
327 {
328     OSL_ENSURE( !IsCalculatingFormulaTree(), "CalcFormulaTree recursion" );
329     // never ever recurse into this, might end up lost in infinity
330     if ( IsCalculatingFormulaTree() )
331         return ;
332 
333     ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
334     mpFormulaGroupCxt.reset();
335     bCalculatingFormulaTree = true;
336 
337     SetForcedFormulaPending( false );
338     bool bOldIdleEnabled = IsIdleEnabled();
339     EnableIdle(false);
340     bool bOldAutoCalc = GetAutoCalc();
341     //ATTENTION: _not_ SetAutoCalc( true ) because this might call CalcFormulaTree( true )
342     //ATTENTION: if it was disabled before and bHasForcedFormulas is set
343     bAutoCalc = true;
344     if (eHardRecalcState == HardRecalcState::ETERNAL)
345         CalcAll();
346     else
347     {
348         ::std::vector<ScFormulaCell*> vAlwaysDirty;
349         ScFormulaCell* pCell = pFormulaTree;
350         while ( pCell )
351         {
352             if ( pCell->GetDirty() )
353                 ;   // nothing to do
354             else if ( pCell->GetCode()->IsRecalcModeAlways() )
355             {
356                 // pCell and dependents are to be set dirty again, collect
357                 // them first and broadcast afterwards to not break the
358                 // FormulaTree chain here.
359                 vAlwaysDirty.push_back( pCell);
360             }
361             else if ( bSetAllDirty )
362             {
363                 // Force calculating all in tree, without broadcasting.
364                 pCell->SetDirtyVar();
365             }
366             pCell = pCell->GetNext();
367         }
368         for (const auto& rpCell : vAlwaysDirty)
369         {
370             pCell = rpCell;
371             if (!pCell->GetDirty())
372                 pCell->SetDirty();
373         }
374 
375         bool bProgress = !bOnlyForced && nFormulaCodeInTree && bProgressBar;
376         if ( bProgress )
377             ScProgress::CreateInterpretProgress( this );
378 
379         pCell = pFormulaTree;
380         ScFormulaCell* pLastNoGood = nullptr;
381         while ( pCell )
382         {
383             // Interpret resets bDirty and calls Remove, also the referenced!
384             // the Cell remains when ScRecalcMode::ALWAYS.
385             if ( bOnlyForced )
386             {
387                 if ( pCell->GetCode()->IsRecalcModeForced() )
388                     pCell->Interpret();
389             }
390             else
391             {
392                 pCell->Interpret();
393             }
394             if ( pCell->GetPrevious() || pCell == pFormulaTree )
395             {   // (IsInFormulaTree(pCell)) no Remove was called => next
396                 pLastNoGood = pCell;
397                 pCell = pCell->GetNext();
398             }
399             else
400             {
401                 if ( pFormulaTree )
402                 {
403                     if ( pFormulaTree->GetDirty() && !bOnlyForced )
404                     {
405                         pCell = pFormulaTree;
406                         pLastNoGood = nullptr;
407                     }
408                     else
409                     {
410                         // IsInFormulaTree(pLastNoGood)
411                         if ( pLastNoGood && (pLastNoGood->GetPrevious() ||
412                                 pLastNoGood == pFormulaTree) )
413                             pCell = pLastNoGood->GetNext();
414                         else
415                         {
416                             pCell = pFormulaTree;
417                             while ( pCell && !pCell->GetDirty() )
418                                 pCell = pCell->GetNext();
419                             if ( pCell )
420                                 pLastNoGood = pCell->GetPrevious();
421                         }
422                     }
423                 }
424                 else
425                     pCell = nullptr;
426             }
427         }
428         if ( bProgress )
429             ScProgress::DeleteInterpretProgress();
430     }
431     bAutoCalc = bOldAutoCalc;
432     EnableIdle(bOldIdleEnabled);
433     bCalculatingFormulaTree = false;
434 
435     mpFormulaGroupCxt.reset();
436 }
437 
ClearFormulaTree()438 void ScDocument::ClearFormulaTree()
439 {
440     ScFormulaCell* pCell;
441     ScFormulaCell* pTree = pFormulaTree;
442     while ( pTree )
443     {
444         pCell = pTree;
445         pTree = pCell->GetNext();
446         if ( !pCell->GetCode()->IsRecalcModeAlways() )
447             RemoveFromFormulaTree( pCell );
448     }
449 }
450 
AppendToFormulaTrack(ScFormulaCell * pCell)451 void ScDocument::AppendToFormulaTrack( ScFormulaCell* pCell )
452 {
453     OSL_ENSURE( pCell, "AppendToFormulaTrack: pCell Null" );
454     // The cell can not be in both lists at the same time
455     RemoveFromFormulaTrack( pCell );
456     RemoveFromFormulaTree( pCell );
457     if ( pEOFormulaTrack )
458         pEOFormulaTrack->SetNextTrack( pCell );
459     else
460         pFormulaTrack = pCell;              // No end, no beginning...
461     pCell->SetPreviousTrack( pEOFormulaTrack );
462     pCell->SetNextTrack( nullptr );
463     pEOFormulaTrack = pCell;
464     ++nFormulaTrackCount;
465 }
466 
RemoveFromFormulaTrack(ScFormulaCell * pCell)467 void ScDocument::RemoveFromFormulaTrack( ScFormulaCell* pCell )
468 {
469     OSL_ENSURE( pCell, "RemoveFromFormulaTrack: pCell Null" );
470     ScFormulaCell* pPrev = pCell->GetPreviousTrack();
471     assert(pPrev != pCell);                     // pointing to itself?!?
472     // if the cell is first or somewhere in chain
473     if ( !(pPrev || pFormulaTrack == pCell) )
474         return;
475 
476     ScFormulaCell* pNext = pCell->GetNextTrack();
477     assert(pNext != pCell);                 // pointing to itself?!?
478     if ( pPrev )
479     {
480         assert(pFormulaTrack != pCell);     // if this cell is also head something's wrong
481         pPrev->SetNextTrack( pNext );       // predecessor exists, set successor
482     }
483     else
484     {
485         pFormulaTrack = pNext;              // this cell was first cell
486     }
487     if ( pNext )
488     {
489         assert(pEOFormulaTrack != pCell);   // if this cell is also tail something's wrong
490         pNext->SetPreviousTrack( pPrev );   // successor exists, set predecessor
491     }
492     else
493     {
494         pEOFormulaTrack = pPrev;            // this cell was last cell
495     }
496     pCell->SetPreviousTrack( nullptr );
497     pCell->SetNextTrack( nullptr );
498     --nFormulaTrackCount;
499 }
500 
IsInFormulaTrack(const ScFormulaCell * pCell) const501 bool ScDocument::IsInFormulaTrack( const ScFormulaCell* pCell ) const
502 {
503     return pCell->GetPreviousTrack() || pFormulaTrack == pCell;
504 }
505 
FinalTrackFormulas(SfxHintId nHintId)506 void ScDocument::FinalTrackFormulas( SfxHintId nHintId )
507 {
508     mbTrackFormulasPending = false;
509     mbFinalTrackFormulas = true;
510     {
511         ScBulkBroadcast aBulk( GetBASM(), nHintId);
512         // Collect all pending formula cells in bulk.
513         TrackFormulas( nHintId );
514     }
515     // A final round not in bulk to track all remaining formula cells and their
516     // dependents that were collected during ScBulkBroadcast dtor.
517     TrackFormulas( nHintId );
518     mbFinalTrackFormulas = false;
519 }
520 
521 /*
522     The first is broadcasted,
523     the ones that are created through this are appended to the Track by Notify.
524     The next is broadcasted again, and so on.
525     View initiates Interpret.
526  */
TrackFormulas(SfxHintId nHintId)527 void ScDocument::TrackFormulas( SfxHintId nHintId )
528 {
529     if (!pBASM)
530         return;
531 
532     if (pBASM->IsInBulkBroadcast() && !IsFinalTrackFormulas() &&
533             (nHintId == SfxHintId::ScDataChanged || nHintId == SfxHintId::ScHiddenRowsChanged))
534     {
535         SetTrackFormulasPending();
536         return;
537     }
538 
539     if ( pFormulaTrack )
540     {
541         // outside the loop, check if any sheet has a "calculate" event script
542         bool bCalcEvent = HasAnySheetEventScript( ScSheetEventId::CALCULATE, true );
543         ScFormulaCell* pTrack;
544         ScFormulaCell* pNext;
545         pTrack = pFormulaTrack;
546         do
547         {
548             SvtBroadcaster* pBC = GetBroadcaster(pTrack->aPos);
549             ScHint aHint(nHintId, pTrack->aPos);
550             if (pBC)
551                 pBC->Broadcast( aHint );
552             pBASM->AreaBroadcast( aHint );
553             // for "calculate" event, keep track of which sheets are affected by tracked formulas
554             if ( bCalcEvent )
555                 SetCalcNotification( pTrack->aPos.Tab() );
556             pTrack = pTrack->GetNextTrack();
557         } while ( pTrack );
558         pTrack = pFormulaTrack;
559         bool bHaveForced = false;
560         do
561         {
562             pNext = pTrack->GetNextTrack();
563             RemoveFromFormulaTrack( pTrack );
564             PutInFormulaTree( pTrack );
565             if ( pTrack->GetCode()->IsRecalcModeForced() )
566                 bHaveForced = true;
567             pTrack = pNext;
568         } while ( pTrack );
569         if ( bHaveForced )
570         {
571             SetForcedFormulas( true );
572             if ( bAutoCalc && !IsAutoCalcShellDisabled() && !IsInInterpreter()
573                     && !IsCalculatingFormulaTree() )
574                 CalcFormulaTree( true );
575             else
576                 SetForcedFormulaPending( true );
577         }
578     }
579     OSL_ENSURE( nFormulaTrackCount==0, "TrackFormulas: nFormulaTrackCount!=0" );
580 }
581 
StartAllListeners()582 void ScDocument::StartAllListeners()
583 {
584     sc::StartListeningContext aCxt(*this);
585     for ( SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); ++i )
586         if ( maTabs[i] )
587             maTabs[i]->StartListeners(aCxt, true);
588 }
589 
UpdateBroadcastAreas(UpdateRefMode eUpdateRefMode,const ScRange & rRange,SCCOL nDx,SCROW nDy,SCTAB nDz)590 void ScDocument::UpdateBroadcastAreas( UpdateRefMode eUpdateRefMode,
591         const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz
592     )
593 {
594     bool bExpandRefsOld = IsExpandRefs();
595     if ( eUpdateRefMode == URM_INSDEL && (nDx > 0 || nDy > 0 || nDz > 0) )
596         SetExpandRefs( SC_MOD()->GetInputOptions().GetExpandRefs() );
597     if ( pBASM )
598         pBASM->UpdateBroadcastAreas( eUpdateRefMode, rRange, nDx, nDy, nDz );
599     SetExpandRefs( bExpandRefsOld );
600 }
601 
SetAutoCalc(bool bNewAutoCalc)602 void ScDocument::SetAutoCalc( bool bNewAutoCalc )
603 {
604     bool bOld = bAutoCalc;
605     bAutoCalc = bNewAutoCalc;
606     if ( !bOld && bNewAutoCalc && bHasForcedFormulas )
607     {
608         if ( IsAutoCalcShellDisabled() )
609             SetForcedFormulaPending( true );
610         else if ( !IsInInterpreter() )
611             CalcFormulaTree( true );
612     }
613 }
614 
615 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
616