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