tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / sc / source / core / data / documen7.cxx
blob6d9f29d83beed43ae02e12e1e7e64987ca3386a5
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
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>
35 void ScDocument::StartListeningArea(
36 const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
38 if (!pBASM)
39 return;
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))
50 pBASM->StartListeningArea(rRange, bGroupListening, pListener);
51 return;
54 // If both sheets are out-of-bounds in the same direction then just bail out.
55 if (bEntirelyOut)
56 return;
58 pBASM->StartListeningArea( aLimitedRange, bGroupListening, pListener);
61 void ScDocument::EndListeningArea( const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
63 if (!pBASM)
64 return;
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))
72 pBASM->EndListeningArea(rRange, bGroupListening, pListener);
73 return;
76 // If both sheets are out-of-bounds in the same direction then just bail out.
77 if (bEntirelyOut)
78 return;
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))
88 return false;
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;
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;
104 return 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);
111 o_rRange = rRange;
112 o_rRange.aStart.SetTab(nTab1);
113 o_rRange.aEnd.SetTab(nTab2);
114 return true;
117 void ScDocument::Broadcast( const ScHint& rHint )
119 if ( !pBASM )
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;
152 pLastBC = pBC;
155 return bIsBroadcasted;
158 void ScDocument::BroadcastCells( const ScRange& rRange, SfxHintId nHint, bool bBroadcastSingleBroadcasters )
160 PrepareFormulaCalc();
162 if (!pBASM)
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);
182 if (!pTab)
183 continue;
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);
196 if (pTab)
197 pTab->SetStreamValid(false);
200 BroadcastUno(SfxHint(SfxHintId::ScDataChanged));
203 void ScDocument::AreaBroadcast( const ScHint& rHint )
205 if ( !pBASM )
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 )
217 if ( pBASM )
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 )
255 if (rCells.empty())
256 return;
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 );
269 // append
270 ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
271 if ( pEOFormulaTree )
272 pEOFormulaTree->SetNext( pCell );
273 else
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?!?
292 if ( pPrev )
294 assert(pFormulaTree != pCell); // if this cell is also head something's wrong
295 pPrev->SetNext( pNext ); // predecessor exists, set successor
297 else
299 pFormulaTree = pNext; // this cell was first cell
301 if ( pNext )
303 assert(pEOFormulaTree != pCell); // if this cell is also tail something's wrong
304 pNext->SetPrevious( pPrev ); // successor exists, set predecessor
306 else
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;
315 else
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() )
333 return ;
335 ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
336 mpFormulaGroupCxt.reset();
337 bCalculatingFormulaTree = true;
339 SetForcedFormulaPending( false );
340 bool bOldIdleEnabled = IsIdleEnabled();
341 EnableIdle(false);
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
345 bAutoCalc = true;
346 if (eHardRecalcState == HardRecalcState::ETERNAL)
347 CalcAll();
348 else
350 ::std::vector<ScFormulaCell*> vAlwaysDirty;
351 ScFormulaCell* pCell = pFormulaTree;
352 while ( pCell )
354 if ( pCell->GetDirty() )
355 ; // nothing to do
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)
372 pCell = rpCell;
373 if (!pCell->GetDirty())
374 pCell->SetDirty();
377 bool bProgress = !bOnlyForced && nFormulaCodeInTree && bProgressBar;
378 if ( bProgress )
379 ScProgress::CreateInterpretProgress( this );
381 pCell = pFormulaTree;
382 ScFormulaCell* pLastNoGood = nullptr;
383 while ( pCell )
385 // Interpret resets bDirty and calls Remove, also the referenced!
386 // the Cell remains when ScRecalcMode::ALWAYS.
387 if ( bOnlyForced )
389 if ( pCell->GetCode()->IsRecalcModeForced() )
390 pCell->Interpret();
392 else
394 pCell->Interpret();
396 if ( pCell->GetPrevious() || pCell == pFormulaTree )
397 { // (IsInFormulaTree(pCell)) no Remove was called => next
398 pLastNoGood = pCell;
399 pCell = pCell->GetNext();
401 else
403 if ( pFormulaTree )
405 if ( pFormulaTree->GetDirty() && !bOnlyForced )
407 pCell = pFormulaTree;
408 pLastNoGood = nullptr;
410 else
412 // IsInFormulaTree(pLastNoGood)
413 if ( pLastNoGood && (pLastNoGood->GetPrevious() ||
414 pLastNoGood == pFormulaTree) )
415 pCell = pLastNoGood->GetNext();
416 else
418 pCell = pFormulaTree;
419 while ( pCell && !pCell->GetDirty() )
420 pCell = pCell->GetNext();
421 if ( pCell )
422 pLastNoGood = pCell->GetPrevious();
426 else
427 pCell = nullptr;
430 if ( bProgress )
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;
444 while ( pTree )
446 pCell = pTree;
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 );
461 else
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) )
476 return;
478 ScFormulaCell* pNext = pCell->GetNextTrack();
479 assert(pNext != pCell); // pointing to itself?!?
480 if ( pPrev )
482 assert(pFormulaTrack != pCell); // if this cell is also head something's wrong
483 pPrev->SetNextTrack( pNext ); // predecessor exists, set successor
485 else
487 pFormulaTrack = pNext; // this cell was first cell
489 if ( pNext )
491 assert(pEOFormulaTrack != pCell); // if this cell is also tail something's wrong
492 pNext->SetPreviousTrack( pPrev ); // successor exists, set predecessor
494 else
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 )
526 if (!pBASM)
527 return;
529 if (pBASM->IsInBulkBroadcast() && !IsFinalTrackFormulas() &&
530 (nHintId == SfxHintId::ScDataChanged || nHintId == SfxHintId::ScHiddenRowsChanged))
532 SetTrackFormulasPending();
533 return;
536 if ( pFormulaTrack )
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())
542 SCROW rowCount = 1;
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()))
548 break;
549 ++rowCount;
550 pTrack = pNext;
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
556 if ( bCalcEvent )
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() )
566 bHaveForced = true;
567 pTrack = pNext;
569 if ( bHaveForced )
571 SetForcedFormulas( true );
572 if ( bAutoCalc && !IsAutoCalcShellDisabled() && !IsInInterpreter()
573 && !IsCalculatingFormulaTree() )
574 CalcFormulaTree( true );
575 else
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 )
586 if ( i )
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());
597 if ( pBASM )
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: */