1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <sal/log.hxx>
21 #include <osl/diagnose.h>
23 #include <document.hxx>
25 #include <bcaslot.hxx>
26 #include <formulacell.hxx>
28 #include <progress.hxx>
30 #include <inputopt.hxx>
31 #include <sheetevents.hxx>
32 #include <tokenarray.hxx>
33 #include <listenercontext.hxx>
35 void ScDocument::StartListeningArea(
36 const ScRange
& rRange
, bool bGroupListening
, SvtListener
* pListener
)
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
);
48 if (!LimitRangeToAvailableSheets( rRange
, aLimitedRange
, bEntirelyOut
))
50 pBASM
->StartListeningArea(rRange
, bGroupListening
, pListener
);
54 // If both sheets are out-of-bounds in the same direction then just bail out.
58 pBASM
->StartListeningArea( aLimitedRange
, bGroupListening
, pListener
);
61 void ScDocument::EndListeningArea( const ScRange
& rRange
, bool bGroupListening
, SvtListener
* pListener
)
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
);
70 if (!LimitRangeToAvailableSheets( rRange
, aLimitedRange
, bEntirelyOut
))
72 pBASM
->EndListeningArea(rRange
, bGroupListening
, pListener
);
76 // If both sheets are out-of-bounds in the same direction then just bail out.
80 pBASM
->EndListeningArea( aLimitedRange
, bGroupListening
, pListener
);
83 bool ScDocument::LimitRangeToAvailableSheets( const ScRange
& rRange
, ScRange
& o_rRange
,
84 bool& o_bEntirelyOutOfBounds
) const
86 const SCTAB nMaxTab
= GetTableCount() - 1;
87 if (ValidTab( rRange
.aStart
.Tab(), nMaxTab
) && ValidTab( rRange
.aEnd
.Tab(), nMaxTab
))
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
)
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
);
100 // Both sheets are out-of-bounds in the same direction.
101 if ((nTab1
< 0 && nTab2
< 0) || (nMaxTab
< nTab1
&& nMaxTab
< nTab2
))
103 o_bEntirelyOutOfBounds
= true;
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
);
112 o_rRange
.aStart
.SetTab(nTab1
);
113 o_rRange
.aEnd
.SetTab(nTab2
);
117 void ScDocument::Broadcast( const ScHint
& rHint
)
120 return ; // Clipboard or Undo
121 if ( eHardRecalcState
== HardRecalcState::OFF
)
123 ScBulkBroadcast
aBulkBroadcast( pBASM
.get(), rHint
.GetId()); // scoped bulk broadcast
124 bool bIsBroadcasted
= BroadcastHintInternal(rHint
);
125 if ( pBASM
->AreaBroadcast( rHint
) || bIsBroadcasted
)
126 TrackFormulas( rHint
.GetId() );
129 if ( rHint
.GetStartAddress() != BCA_BRDCST_ALWAYS
)
131 SCTAB nTab
= rHint
.GetStartAddress().Tab();
132 if (nTab
< GetTableCount() && maTabs
[nTab
])
133 maTabs
[nTab
]->SetStreamValid(false);
137 bool ScDocument::BroadcastHintInternal( const ScHint
& rHint
)
139 bool bIsBroadcasted
= false;
140 const ScAddress
& address(rHint
.GetStartAddress());
141 SvtBroadcaster
* pLastBC
= nullptr;
142 // Process all broadcasters for the given row range.
143 for( SCROW nRow
= 0; nRow
< rHint
.GetRowCount(); ++nRow
)
145 ScAddress
a(address
);
146 a
.SetRow(address
.Row() + nRow
);
147 SvtBroadcaster
* pBC
= GetBroadcaster(a
);
148 if ( pBC
&& pBC
!= pLastBC
)
150 pBC
->Broadcast( rHint
);
151 bIsBroadcasted
= true;
155 return bIsBroadcasted
;
158 void ScDocument::BroadcastCells( const ScRange
& rRange
, SfxHintId nHint
, bool bBroadcastSingleBroadcasters
)
160 PrepareFormulaCalc();
163 return; // Clipboard or Undo
165 SCTAB nTab1
= rRange
.aStart
.Tab();
166 SCTAB nTab2
= rRange
.aEnd
.Tab();
167 SCROW nRow1
= rRange
.aStart
.Row();
168 SCROW nRow2
= rRange
.aEnd
.Row();
169 SCCOL nCol1
= rRange
.aStart
.Col();
170 SCCOL nCol2
= rRange
.aEnd
.Col();
172 if (eHardRecalcState
== HardRecalcState::OFF
)
174 ScBulkBroadcast
aBulkBroadcast( pBASM
.get(), nHint
); // scoped bulk broadcast
175 bool bIsBroadcasted
= false;
177 if (bBroadcastSingleBroadcasters
)
179 for (SCTAB nTab
= nTab1
; nTab
<= nTab2
; ++nTab
)
181 ScTable
* pTab
= FetchTable(nTab
);
185 bIsBroadcasted
|= pTab
->BroadcastBroadcasters( nCol1
, nRow1
, nCol2
, nRow2
, nHint
);
189 if (pBASM
->AreaBroadcast(rRange
, nHint
) || bIsBroadcasted
)
190 TrackFormulas(nHint
);
193 for (SCTAB nTab
= nTab1
; nTab
<= nTab2
; ++nTab
)
195 ScTable
* pTab
= FetchTable(nTab
);
197 pTab
->SetStreamValid(false);
200 BroadcastUno(SfxHint(SfxHintId::ScDataChanged
));
203 void ScDocument::AreaBroadcast( const ScHint
& rHint
)
206 return ; // Clipboard or Undo
207 if (eHardRecalcState
== HardRecalcState::OFF
)
209 ScBulkBroadcast
aBulkBroadcast( pBASM
.get(), rHint
.GetId()); // scoped bulk broadcast
210 if ( pBASM
->AreaBroadcast( rHint
) )
211 TrackFormulas( rHint
.GetId() );
215 void ScDocument::DelBroadcastAreasInRange( const ScRange
& rRange
)
218 pBASM
->DelBroadcastAreasInRange( rRange
);
221 void ScDocument::StartListeningCell( const ScAddress
& rAddress
,
222 SvtListener
* pListener
)
224 OSL_ENSURE(pListener
, "StartListeningCell: pListener Null");
225 SCTAB nTab
= rAddress
.Tab();
226 if (ScTable
* pTable
= FetchTable(nTab
))
227 pTable
->StartListening(rAddress
, pListener
);
230 void ScDocument::EndListeningCell( const ScAddress
& rAddress
,
231 SvtListener
* pListener
)
233 OSL_ENSURE(pListener
, "EndListeningCell: pListener Null");
234 SCTAB nTab
= rAddress
.Tab();
235 if (ScTable
* pTable
= FetchTable(nTab
))
236 pTable
->EndListening( rAddress
, pListener
);
239 void ScDocument::StartListeningCell(
240 sc::StartListeningContext
& rCxt
, const ScAddress
& rPos
, SvtListener
& rListener
)
242 if (ScTable
* pTable
= FetchTable(rPos
.Tab()))
243 pTable
->StartListening(rCxt
, rPos
, rListener
);
246 void ScDocument::EndListeningCell(
247 sc::EndListeningContext
& rCxt
, const ScAddress
& rPos
, SvtListener
& rListener
)
249 if (ScTable
* pTable
= FetchTable(rPos
.Tab()))
250 pTable
->EndListening(rCxt
, rPos
, rListener
);
253 void ScDocument::EndListeningFormulaCells( std::vector
<ScFormulaCell
*>& rCells
)
258 sc::EndListeningContext
aCxt(*this);
259 for (auto& pCell
: rCells
)
260 pCell
->EndListeningTo(aCxt
);
262 aCxt
.purgeEmptyBroadcasters();
265 void ScDocument::PutInFormulaTree( ScFormulaCell
* pCell
)
267 OSL_ENSURE( pCell
, "PutInFormulaTree: pCell Null" );
268 RemoveFromFormulaTree( pCell
);
270 ScMutationGuard
aGuard(*this, ScMutationGuardFlags::CORE
);
271 if ( pEOFormulaTree
)
272 pEOFormulaTree
->SetNext( pCell
);
274 pFormulaTree
= pCell
; // No end, no beginning...
275 pCell
->SetPrevious( pEOFormulaTree
);
276 pCell
->SetNext( nullptr );
277 pEOFormulaTree
= pCell
;
278 nFormulaCodeInTree
+= pCell
->GetCode()->GetCodeLen();
281 void ScDocument::RemoveFromFormulaTree( ScFormulaCell
* pCell
)
283 ScMutationGuard
aGuard(*this, ScMutationGuardFlags::CORE
);
284 assert(pCell
&& "RemoveFromFormulaTree: pCell Null");
285 ScFormulaCell
* pPrev
= pCell
->GetPrevious();
286 assert(pPrev
!= pCell
); // pointing to itself?!?
287 // if the cell is first or somewhere in chain
288 if ( pPrev
|| pFormulaTree
== pCell
)
290 ScFormulaCell
* pNext
= pCell
->GetNext();
291 assert(pNext
!= pCell
); // pointing to itself?!?
294 assert(pFormulaTree
!= pCell
); // if this cell is also head something's wrong
295 pPrev
->SetNext( pNext
); // predecessor exists, set successor
299 pFormulaTree
= pNext
; // this cell was first cell
303 assert(pEOFormulaTree
!= pCell
); // if this cell is also tail something's wrong
304 pNext
->SetPrevious( pPrev
); // successor exists, set predecessor
308 pEOFormulaTree
= pPrev
; // this cell was last cell
310 pCell
->SetPrevious( nullptr );
311 pCell
->SetNext( nullptr );
312 sal_uInt16 nRPN
= pCell
->GetCode()->GetCodeLen();
313 if ( nFormulaCodeInTree
>= nRPN
)
314 nFormulaCodeInTree
-= nRPN
;
317 OSL_FAIL( "RemoveFromFormulaTree: nFormulaCodeInTree < nRPN" );
318 nFormulaCodeInTree
= 0;
321 else if ( !pFormulaTree
&& nFormulaCodeInTree
)
323 OSL_FAIL( "!pFormulaTree && nFormulaCodeInTree != 0" );
324 nFormulaCodeInTree
= 0;
328 void ScDocument::CalcFormulaTree( bool bOnlyForced
, bool bProgressBar
, bool bSetAllDirty
)
330 OSL_ENSURE( !IsCalculatingFormulaTree(), "CalcFormulaTree recursion" );
331 // never ever recurse into this, might end up lost in infinity
332 if ( IsCalculatingFormulaTree() )
335 ScMutationGuard
aGuard(*this, ScMutationGuardFlags::CORE
);
336 mpFormulaGroupCxt
.reset();
337 bCalculatingFormulaTree
= true;
339 SetForcedFormulaPending( false );
340 bool bOldIdleEnabled
= IsIdleEnabled();
342 bool bOldAutoCalc
= GetAutoCalc();
343 //ATTENTION: _not_ SetAutoCalc( true ) because this might call CalcFormulaTree( true )
344 //ATTENTION: if it was disabled before and bHasForcedFormulas is set
346 if (eHardRecalcState
== HardRecalcState::ETERNAL
)
350 ::std::vector
<ScFormulaCell
*> vAlwaysDirty
;
351 ScFormulaCell
* pCell
= pFormulaTree
;
354 if ( pCell
->GetDirty() )
356 else if ( pCell
->GetCode()->IsRecalcModeAlways() )
358 // pCell and dependents are to be set dirty again, collect
359 // them first and broadcast afterwards to not break the
360 // FormulaTree chain here.
361 vAlwaysDirty
.push_back( pCell
);
363 else if ( bSetAllDirty
)
365 // Force calculating all in tree, without broadcasting.
366 pCell
->SetDirtyVar();
368 pCell
= pCell
->GetNext();
370 for (const auto& rpCell
: vAlwaysDirty
)
373 if (!pCell
->GetDirty())
377 bool bProgress
= !bOnlyForced
&& nFormulaCodeInTree
&& bProgressBar
;
379 ScProgress::CreateInterpretProgress( this );
381 pCell
= pFormulaTree
;
382 ScFormulaCell
* pLastNoGood
= nullptr;
385 // Interpret resets bDirty and calls Remove, also the referenced!
386 // the Cell remains when ScRecalcMode::ALWAYS.
389 if ( pCell
->GetCode()->IsRecalcModeForced() )
396 if ( pCell
->GetPrevious() || pCell
== pFormulaTree
)
397 { // (IsInFormulaTree(pCell)) no Remove was called => next
399 pCell
= pCell
->GetNext();
405 if ( pFormulaTree
->GetDirty() && !bOnlyForced
)
407 pCell
= pFormulaTree
;
408 pLastNoGood
= nullptr;
412 // IsInFormulaTree(pLastNoGood)
413 if ( pLastNoGood
&& (pLastNoGood
->GetPrevious() ||
414 pLastNoGood
== pFormulaTree
) )
415 pCell
= pLastNoGood
->GetNext();
418 pCell
= pFormulaTree
;
419 while ( pCell
&& !pCell
->GetDirty() )
420 pCell
= pCell
->GetNext();
422 pLastNoGood
= pCell
->GetPrevious();
431 ScProgress::DeleteInterpretProgress();
433 bAutoCalc
= bOldAutoCalc
;
434 EnableIdle(bOldIdleEnabled
);
435 bCalculatingFormulaTree
= false;
437 mpFormulaGroupCxt
.reset();
440 void ScDocument::ClearFormulaTree()
442 ScFormulaCell
* pCell
;
443 ScFormulaCell
* pTree
= pFormulaTree
;
447 pTree
= pCell
->GetNext();
448 if ( !pCell
->GetCode()->IsRecalcModeAlways() )
449 RemoveFromFormulaTree( pCell
);
453 void ScDocument::AppendToFormulaTrack( ScFormulaCell
* pCell
)
455 OSL_ENSURE( pCell
, "AppendToFormulaTrack: pCell Null" );
456 // The cell can not be in both lists at the same time
457 RemoveFromFormulaTrack( pCell
);
458 RemoveFromFormulaTree( pCell
);
459 if ( pEOFormulaTrack
)
460 pEOFormulaTrack
->SetNextTrack( pCell
);
462 pFormulaTrack
= pCell
; // No end, no beginning...
463 pCell
->SetPreviousTrack( pEOFormulaTrack
);
464 pCell
->SetNextTrack( nullptr );
465 pEOFormulaTrack
= pCell
;
466 ++nFormulaTrackCount
;
469 void ScDocument::RemoveFromFormulaTrack( ScFormulaCell
* pCell
)
471 assert(pCell
&& "RemoveFromFormulaTrack: pCell Null");
472 ScFormulaCell
* pPrev
= pCell
->GetPreviousTrack();
473 assert(pPrev
!= pCell
); // pointing to itself?!?
474 // if the cell is first or somewhere in chain
475 if ( !(pPrev
|| pFormulaTrack
== pCell
) )
478 ScFormulaCell
* pNext
= pCell
->GetNextTrack();
479 assert(pNext
!= pCell
); // pointing to itself?!?
482 assert(pFormulaTrack
!= pCell
); // if this cell is also head something's wrong
483 pPrev
->SetNextTrack( pNext
); // predecessor exists, set successor
487 pFormulaTrack
= pNext
; // this cell was first cell
491 assert(pEOFormulaTrack
!= pCell
); // if this cell is also tail something's wrong
492 pNext
->SetPreviousTrack( pPrev
); // successor exists, set predecessor
496 pEOFormulaTrack
= pPrev
; // this cell was last cell
498 pCell
->SetPreviousTrack( nullptr );
499 pCell
->SetNextTrack( nullptr );
500 --nFormulaTrackCount
;
503 void ScDocument::FinalTrackFormulas( SfxHintId nHintId
)
505 mbTrackFormulasPending
= false;
506 mbFinalTrackFormulas
= true;
508 ScBulkBroadcast
aBulk( GetBASM(), nHintId
);
509 // Collect all pending formula cells in bulk.
510 TrackFormulas( nHintId
);
512 // A final round not in bulk to track all remaining formula cells and their
513 // dependents that were collected during ScBulkBroadcast dtor.
514 TrackFormulas( nHintId
);
515 mbFinalTrackFormulas
= false;
519 The first is broadcasted,
520 the ones that are created through this are appended to the Track by Notify.
521 The next is broadcasted again, and so on.
522 View initiates Interpret.
524 void ScDocument::TrackFormulas( SfxHintId nHintId
)
529 if (pBASM
->IsInBulkBroadcast() && !IsFinalTrackFormulas() &&
530 (nHintId
== SfxHintId::ScDataChanged
|| nHintId
== SfxHintId::ScHiddenRowsChanged
))
532 SetTrackFormulasPending();
538 // outside the loop, check if any sheet has a "calculate" event script
539 bool bCalcEvent
= HasAnySheetEventScript( ScSheetEventId::CALCULATE
, true );
540 for( ScFormulaCell
* pTrack
= pFormulaTrack
; pTrack
!= nullptr; pTrack
= pTrack
->GetNextTrack())
543 ScAddress address
= pTrack
->aPos
;
544 // Compress to include all adjacent cells in the same column.
545 for(ScFormulaCell
* pNext
= pTrack
->GetNextTrack(); pNext
!= nullptr; pNext
= pNext
->GetNextTrack())
547 if(pNext
->aPos
!= ScAddress(address
.Col(), address
.Row() + rowCount
, address
.Tab()))
552 ScHint
aHint( nHintId
, address
, rowCount
);
553 BroadcastHintInternal( aHint
);
554 pBASM
->AreaBroadcast( aHint
);
555 // for "calculate" event, keep track of which sheets are affected by tracked formulas
557 SetCalcNotification( address
.Tab() );
559 bool bHaveForced
= false;
560 for( ScFormulaCell
* pTrack
= pFormulaTrack
; pTrack
!= nullptr;)
562 ScFormulaCell
* pNext
= pTrack
->GetNextTrack();
563 RemoveFromFormulaTrack( pTrack
);
564 PutInFormulaTree( pTrack
);
565 if ( pTrack
->GetCode()->IsRecalcModeForced() )
571 SetForcedFormulas( true );
572 if ( bAutoCalc
&& !IsAutoCalcShellDisabled() && !IsInInterpreter()
573 && !IsCalculatingFormulaTree() )
574 CalcFormulaTree( true );
576 SetForcedFormulaPending( true );
579 OSL_ENSURE( nFormulaTrackCount
==0, "TrackFormulas: nFormulaTrackCount!=0" );
582 void ScDocument::StartAllListeners()
584 sc::StartListeningContext
aCxt(*this);
585 for ( auto const & i
: maTabs
)
587 i
->StartListeners(aCxt
, true);
590 void ScDocument::UpdateBroadcastAreas( UpdateRefMode eUpdateRefMode
,
591 const ScRange
& rRange
, SCCOL nDx
, SCROW nDy
, SCTAB nDz
594 bool bExpandRefsOld
= IsExpandRefs();
595 if ( eUpdateRefMode
== URM_INSDEL
&& (nDx
> 0 || nDy
> 0 || nDz
> 0) )
596 SetExpandRefs(ScModule::get()->GetInputOptions().GetExpandRefs());
598 pBASM
->UpdateBroadcastAreas( eUpdateRefMode
, rRange
, nDx
, nDy
, nDz
);
599 SetExpandRefs( bExpandRefsOld
);
602 void ScDocument::SetAutoCalc( bool bNewAutoCalc
)
604 bool bOld
= bAutoCalc
;
605 bAutoCalc
= bNewAutoCalc
;
606 if ( !bOld
&& bNewAutoCalc
&& bHasForcedFormulas
)
608 if ( IsAutoCalcShellDisabled() )
609 SetForcedFormulaPending( true );
610 else if ( !IsInInterpreter() )
611 CalcFormulaTree( true );
615 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */