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 <sfx2/objsh.hxx>
21 #include <svl/listener.hxx>
22 #include <sal/log.hxx>
23 #include <osl/diagnose.h>
24
25 #include <document.hxx>
26 #include <brdcst.hxx>
27 #include <bcaslot.hxx>
28 #include <scerrors.hxx>
29 #include <refupdat.hxx>
30 #include <bulkdatahint.hxx>
31 #include <columnspanset.hxx>
32
33 #if DEBUG_AREA_BROADCASTER
34 #include <formulacell.hxx>
35 #include <grouparealistener.hxx>
36 #endif
37
38 // Number of slots per dimension
39 // must be integer divisors of MAXCOLCOUNT respectively MAXROWCOUNT
40 constexpr SCCOL BCA_SLOTS_COL = MAXCOLCOUNT / 16;
41 constexpr SCCOL BCA_SLOT_COLS = MAXCOLCOUNT / BCA_SLOTS_COL;
42 #if !defined NDEBUG
43 constexpr SCROW BCA_SLICE = 128;
BCA_SLOTS_ROW(const ScSheetLimits & rLimits)44 static SCROW BCA_SLOTS_ROW(const ScSheetLimits& rLimits) { return rLimits.GetMaxRowCount() / BCA_SLICE; }
BCA_SLOT_ROWS(const ScSheetLimits & rLimits)45 static SCROW BCA_SLOT_ROWS(const ScSheetLimits& rLimits)
46 {
47 auto nMaxRowCount = rLimits.GetMaxRowCount();
48 auto nSlotsRow = BCA_SLOTS_ROW(rLimits);
49 assert((nMaxRowCount / nSlotsRow * nSlotsRow) == nMaxRowCount && "bad BCA_SLOTS_ROW value");
50 return nMaxRowCount / nSlotsRow;
51 }
52 #endif
53 // multiple?
54 static_assert((BCA_SLOT_COLS * BCA_SLOTS_COL) == MAXCOLCOUNT, "bad BCA_SLOTS_COL value");
55
56 // size of slot array if linear
57 #if !defined NDEBUG
BCA_SLOTS(const ScSheetLimits & rLimits)58 static int BCA_SLOTS(const ScSheetLimits& rLimits) { return BCA_SLOTS_COL * BCA_SLOTS_ROW(rLimits); }
59 #endif
60
ScBroadcastArea(const ScRange & rRange)61 ScBroadcastArea::ScBroadcastArea( const ScRange& rRange ) :
62 pUpdateChainNext(nullptr),
63 aRange(rRange),
64 nRefCount(0),
65 mbInUpdateChain(false),
66 mbGroupListening(false) {}
67
ScBroadcastAreaSlot(ScDocument * pDocument,ScBroadcastAreaSlotMachine * pBASMa)68 ScBroadcastAreaSlot::ScBroadcastAreaSlot( ScDocument* pDocument,
69 ScBroadcastAreaSlotMachine* pBASMa ) :
70 aTmpSeekBroadcastArea( ScRange()),
71 pDoc( pDocument ),
72 pBASM( pBASMa ),
73 mbInBroadcastIteration( false),
74 mbHasErasedArea(false)
75 {
76 }
77
~ScBroadcastAreaSlot()78 ScBroadcastAreaSlot::~ScBroadcastAreaSlot()
79 {
80 for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
81 aIter != aBroadcastAreaTbl.end(); /* none */)
82 {
83 // Prevent hash from accessing dangling pointer in case area is
84 // deleted.
85 ScBroadcastArea* pArea = (*aIter).mpArea;
86 // Erase all so no hash will be accessed upon destruction of the
87 // unordered_map.
88 aBroadcastAreaTbl.erase( aIter++);
89 if (!pArea->DecRef())
90 delete pArea;
91 }
92 }
93
CheckHardRecalcStateCondition() const94 ScDocument::HardRecalcState ScBroadcastAreaSlot::CheckHardRecalcStateCondition() const
95 {
96 ScDocument::HardRecalcState eState = pDoc->GetHardRecalcState();
97 if (eState == ScDocument::HardRecalcState::OFF)
98 {
99 if (aBroadcastAreaTbl.size() >= aBroadcastAreaTbl.max_size())
100 { // this is more hypothetical now, check existed for old SV_PTRARR_SORT
101 SfxObjectShell* pShell = pDoc->GetDocumentShell();
102 OSL_ENSURE( pShell, "Missing DocShell :-/" );
103
104 if ( pShell )
105 pShell->SetError(SCWARN_CORE_HARD_RECALC);
106
107 pDoc->SetAutoCalc( false );
108 eState = ScDocument::HardRecalcState::ETERNAL;
109 pDoc->SetHardRecalcState( eState );
110 }
111 }
112 return eState;
113 }
114
StartListeningArea(const ScRange & rRange,bool bGroupListening,SvtListener * pListener,ScBroadcastArea * & rpArea)115 bool ScBroadcastAreaSlot::StartListeningArea(
116 const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea )
117 {
118 bool bNewArea = false;
119 OSL_ENSURE(pListener, "StartListeningArea: pListener Null");
120 assert(!pDoc->IsDelayedFormulaGrouping()); // otherwise the group size might be incorrect
121 if (CheckHardRecalcStateCondition() == ScDocument::HardRecalcState::ETERNAL)
122 return false;
123 if ( !rpArea )
124 {
125 // Even if most times the area doesn't exist yet and immediately trying
126 // to new and insert it would save an attempt to find it, on massive
127 // operations like identical large [HV]LOOKUP() areas the new/delete
128 // would add quite some penalty for all but the first formula cell.
129 ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange, bGroupListening));
130 if (aIter != aBroadcastAreaTbl.end())
131 rpArea = (*aIter).mpArea;
132 else
133 {
134 rpArea = new ScBroadcastArea( rRange);
135 rpArea->SetGroupListening(bGroupListening);
136 if (aBroadcastAreaTbl.insert( rpArea).second)
137 {
138 rpArea->IncRef();
139 bNewArea = true;
140 }
141 else
142 {
143 OSL_FAIL("StartListeningArea: area not found and not inserted in slot?!?");
144 delete rpArea;
145 rpArea = nullptr;
146 }
147 }
148 if (rpArea)
149 pListener->StartListening( rpArea->GetBroadcaster());
150 }
151 else
152 {
153 if (aBroadcastAreaTbl.insert( rpArea).second)
154 rpArea->IncRef();
155 }
156 return bNewArea;
157 }
158
InsertListeningArea(ScBroadcastArea * pArea)159 void ScBroadcastAreaSlot::InsertListeningArea( ScBroadcastArea* pArea )
160 {
161 OSL_ENSURE( pArea, "InsertListeningArea: pArea NULL");
162 if (CheckHardRecalcStateCondition() == ScDocument::HardRecalcState::ETERNAL)
163 return;
164 if (aBroadcastAreaTbl.insert( pArea).second)
165 pArea->IncRef();
166 }
167
168 // If rpArea != NULL then no listeners are stopped, only the area is removed
169 // and the reference count decremented.
EndListeningArea(const ScRange & rRange,bool bGroupListening,SvtListener * pListener,ScBroadcastArea * & rpArea)170 void ScBroadcastAreaSlot::EndListeningArea(
171 const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea )
172 {
173 OSL_ENSURE(pListener, "EndListeningArea: pListener Null");
174 if ( !rpArea )
175 {
176 ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange, bGroupListening));
177 if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter))
178 return;
179 rpArea = (*aIter).mpArea;
180 pListener->EndListening( rpArea->GetBroadcaster() );
181 if ( !rpArea->GetBroadcaster().HasListeners() )
182 { // if nobody is listening we can dispose it
183 if (rpArea->GetRef() == 1)
184 rpArea = nullptr; // will be deleted by erase
185 EraseArea( aIter);
186 }
187 }
188 else
189 {
190 if (rpArea && !rpArea->GetBroadcaster().HasListeners())
191 {
192 ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange, bGroupListening));
193 if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter))
194 return;
195 OSL_ENSURE( (*aIter).mpArea == rpArea, "EndListeningArea: area pointer mismatch");
196 if (rpArea->GetRef() == 1)
197 rpArea = nullptr; // will be deleted by erase
198 EraseArea( aIter);
199 }
200 }
201 }
202
FindBroadcastArea(const ScRange & rRange,bool bGroupListening)203 ScBroadcastAreas::iterator ScBroadcastAreaSlot::FindBroadcastArea(
204 const ScRange& rRange, bool bGroupListening )
205 {
206 aTmpSeekBroadcastArea.UpdateRange( rRange);
207 aTmpSeekBroadcastArea.SetGroupListening(bGroupListening);
208 return aBroadcastAreaTbl.find( &aTmpSeekBroadcastArea);
209 }
210
211 namespace {
212
broadcastRangeByCell(SvtBroadcaster & rBC,const ScRange & rRange,SfxHintId nHint)213 void broadcastRangeByCell( SvtBroadcaster& rBC, const ScRange& rRange, SfxHintId nHint )
214 {
215 ScHint aHint(nHint, ScAddress());
216 ScAddress& rPos = aHint.GetAddress();
217 for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
218 {
219 rPos.SetTab(nTab);
220 for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
221 {
222 rPos.SetCol(nCol);
223 for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow)
224 {
225 rPos.SetRow(nRow);
226 rBC.Broadcast(aHint);
227 }
228 }
229 }
230 }
231
232 }
233
AreaBroadcast(const ScRange & rRange,SfxHintId nHint)234 bool ScBroadcastAreaSlot::AreaBroadcast( const ScRange& rRange, SfxHintId nHint )
235 {
236 if (aBroadcastAreaTbl.empty())
237 return false;
238
239 bool bInBroadcast = mbInBroadcastIteration;
240 mbInBroadcastIteration = true;
241 bool bIsBroadcasted = false;
242
243 mbHasErasedArea = false;
244
245 for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()),
246 aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter )
247 {
248 if (mbHasErasedArea && isMarkedErased( aIter))
249 continue;
250
251 ScBroadcastArea* pArea = (*aIter).mpArea;
252 const ScRange& rAreaRange = pArea->GetRange();
253
254 // Take the intersection of the area range and the broadcast range.
255 ScRange aIntersection = rAreaRange.Intersection(rRange);
256 if (!aIntersection.IsValid())
257 continue;
258
259 if (pArea->IsGroupListening())
260 {
261 if (pBASM->IsInBulkBroadcast())
262 {
263 pBASM->InsertBulkGroupArea(pArea, aIntersection);
264 }
265 else
266 {
267 broadcastRangeByCell(pArea->GetBroadcaster(), aIntersection, nHint);
268 bIsBroadcasted = true;
269 }
270 }
271 else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
272 {
273 broadcastRangeByCell(pArea->GetBroadcaster(), aIntersection, nHint);
274 bIsBroadcasted = true;
275 }
276 }
277
278 mbInBroadcastIteration = bInBroadcast;
279
280 // A Notify() during broadcast may call EndListeningArea() and thus dispose
281 // an area if it was the last listener, which would invalidate an iterator
282 // pointing to it, hence the real erase is done afterwards.
283 FinallyEraseAreas();
284
285 return bIsBroadcasted;
286 }
287
AreaBroadcast(const ScHint & rHint)288 bool ScBroadcastAreaSlot::AreaBroadcast( const ScHint& rHint)
289 {
290 if (aBroadcastAreaTbl.empty())
291 return false;
292
293 bool bInBroadcast = mbInBroadcastIteration;
294 mbInBroadcastIteration = true;
295 bool bIsBroadcasted = false;
296
297 mbHasErasedArea = false;
298
299 const ScAddress& rAddress = rHint.GetAddress();
300 for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()),
301 aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter )
302 {
303 if (mbHasErasedArea && isMarkedErased( aIter))
304 continue;
305
306 ScBroadcastArea* pArea = (*aIter).mpArea;
307 const ScRange& rAreaRange = pArea->GetRange();
308 if (rAreaRange.In( rAddress))
309 {
310 if (pArea->IsGroupListening())
311 {
312 if (pBASM->IsInBulkBroadcast())
313 {
314 pBASM->InsertBulkGroupArea(pArea, rAddress);
315 }
316 else
317 {
318 pArea->GetBroadcaster().Broadcast( rHint);
319 bIsBroadcasted = true;
320 }
321 }
322 else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
323 {
324 pArea->GetBroadcaster().Broadcast( rHint);
325 bIsBroadcasted = true;
326 }
327 }
328 }
329
330 mbInBroadcastIteration = bInBroadcast;
331
332 // A Notify() during broadcast may call EndListeningArea() and thus dispose
333 // an area if it was the last listener, which would invalidate an iterator
334 // pointing to it, hence the real erase is done afterwards.
335 FinallyEraseAreas();
336
337 return bIsBroadcasted;
338 }
339
DelBroadcastAreasInRange(const ScRange & rRange)340 void ScBroadcastAreaSlot::DelBroadcastAreasInRange( const ScRange& rRange )
341 {
342 if (aBroadcastAreaTbl.empty())
343 return;
344 for (ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
345 aIter != aBroadcastAreaTbl.end(); /* increment in body */ )
346 {
347 const ScRange& rAreaRange = (*aIter).mpArea->GetRange();
348 if (rRange.In( rAreaRange))
349 {
350 ScBroadcastArea* pArea = (*aIter).mpArea;
351 aBroadcastAreaTbl.erase( aIter++); // erase before modifying
352 if (!pArea->DecRef())
353 {
354 if (pBASM->IsInBulkBroadcast())
355 pBASM->RemoveBulkArea( pArea);
356 delete pArea;
357 }
358 }
359 else
360 ++aIter;
361 }
362 }
363
UpdateRemove(UpdateRefMode eUpdateRefMode,const ScRange & rRange,SCCOL nDx,SCROW nDy,SCTAB nDz)364 void ScBroadcastAreaSlot::UpdateRemove( UpdateRefMode eUpdateRefMode,
365 const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz )
366 {
367 if (aBroadcastAreaTbl.empty())
368 return;
369
370 SCCOL nCol1, nCol2, theCol1, theCol2;
371 SCROW nRow1, nRow2, theRow1, theRow2;
372 SCTAB nTab1, nTab2, theTab1, theTab2;
373 rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
374 for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
375 aIter != aBroadcastAreaTbl.end(); /* increment in body */ )
376 {
377 ScBroadcastArea* pArea = (*aIter).mpArea;
378 if ( pArea->IsInUpdateChain() )
379 {
380 aBroadcastAreaTbl.erase( aIter++);
381 pArea->DecRef();
382 }
383 else
384 {
385 pArea->GetRange().GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2);
386 if ( ScRefUpdate::Update( pDoc, eUpdateRefMode,
387 nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz,
388 theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ))
389 {
390 aBroadcastAreaTbl.erase( aIter++);
391 pArea->DecRef();
392 if (pBASM->IsInBulkBroadcast())
393 pBASM->RemoveBulkArea( pArea);
394 pArea->SetInUpdateChain( true );
395 ScBroadcastArea* pUC = pBASM->GetEOUpdateChain();
396 if ( pUC )
397 pUC->SetUpdateChainNext( pArea );
398 else // no tail => no head
399 pBASM->SetUpdateChain( pArea );
400 pBASM->SetEOUpdateChain( pArea );
401 }
402 else
403 ++aIter;
404 }
405 }
406 }
407
UpdateRemoveArea(ScBroadcastArea * pArea)408 void ScBroadcastAreaSlot::UpdateRemoveArea( ScBroadcastArea* pArea )
409 {
410 ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.find( pArea));
411 if (aIter == aBroadcastAreaTbl.end())
412 return;
413 if ((*aIter).mpArea != pArea)
414 OSL_FAIL( "UpdateRemoveArea: area pointer mismatch");
415 else
416 {
417 aBroadcastAreaTbl.erase( aIter);
418 pArea->DecRef();
419 }
420 }
421
UpdateInsert(ScBroadcastArea * pArea)422 void ScBroadcastAreaSlot::UpdateInsert( ScBroadcastArea* pArea )
423 {
424 ::std::pair< ScBroadcastAreas::iterator, bool > aPair =
425 aBroadcastAreaTbl.insert( pArea);
426 if (aPair.second)
427 pArea->IncRef();
428 else
429 {
430 // Identical area already exists, add listeners.
431 ScBroadcastArea* pTarget = (*(aPair.first)).mpArea;
432 if (pArea != pTarget)
433 {
434 SvtBroadcaster& rTarget = pTarget->GetBroadcaster();
435 SvtBroadcaster::ListenersType& rListeners = pArea->GetBroadcaster().GetAllListeners();
436 for (auto& pListener : rListeners)
437 {
438 SvtListener& rListener = *pListener;
439 rListener.StartListening(rTarget);
440 }
441 }
442 }
443 }
444
EraseArea(ScBroadcastAreas::iterator & rIter)445 void ScBroadcastAreaSlot::EraseArea( ScBroadcastAreas::iterator& rIter )
446 {
447 if (mbInBroadcastIteration)
448 {
449 (*rIter).mbErasure = true; // mark for erasure
450 mbHasErasedArea = true; // at least one area is marked for erasure.
451 pBASM->PushAreaToBeErased( this, rIter);
452 }
453 else
454 {
455 ScBroadcastArea* pArea = (*rIter).mpArea;
456 aBroadcastAreaTbl.erase( rIter);
457 if (!pArea->DecRef())
458 {
459 if (pBASM->IsInBulkBroadcast())
460 pBASM->RemoveBulkGroupArea(pArea);
461 delete pArea;
462 }
463 }
464 }
465
GetAllListeners(const ScRange & rRange,std::vector<sc::AreaListener> & rListeners,sc::AreaOverlapType eType,sc::ListenerGroupType eGroup)466 void ScBroadcastAreaSlot::GetAllListeners(
467 const ScRange& rRange, std::vector<sc::AreaListener>& rListeners,
468 sc::AreaOverlapType eType, sc::ListenerGroupType eGroup )
469 {
470 for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()),
471 aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter )
472 {
473 if (isMarkedErased( aIter))
474 continue;
475
476 ScBroadcastArea* pArea = (*aIter).mpArea;
477 const ScRange& rAreaRange = pArea->GetRange();
478 switch (eGroup)
479 {
480 case sc::ListenerGroupType::Group:
481 if (!pArea->IsGroupListening())
482 continue;
483 break;
484 case sc::ListenerGroupType::Both:
485 default:
486 ;
487 }
488
489 switch (eType)
490 {
491 case sc::AreaOverlapType::Inside:
492 if (!rRange.In(rAreaRange))
493 // The range needs to be fully inside specified range.
494 continue;
495 break;
496 case sc::AreaOverlapType::InsideOrOverlap:
497 if (!rRange.Intersects(rAreaRange))
498 // The range needs to be partially overlapping or fully inside.
499 continue;
500 break;
501 case sc::AreaOverlapType::OneRowInside:
502 if (rAreaRange.aStart.Row() != rAreaRange.aEnd.Row() || !rRange.In(rAreaRange))
503 // The range needs to be one single row and fully inside
504 // specified range.
505 continue;
506 break;
507 case sc::AreaOverlapType::OneColumnInside:
508 if (rAreaRange.aStart.Col() != rAreaRange.aEnd.Col() || !rRange.In(rAreaRange))
509 // The range needs to be one single column and fully inside
510 // specified range.
511 continue;
512 break;
513 }
514
515 SvtBroadcaster::ListenersType& rLst = pArea->GetBroadcaster().GetAllListeners();
516 for (const auto& pListener : rLst)
517 {
518 sc::AreaListener aEntry;
519 aEntry.maArea = rAreaRange;
520 aEntry.mbGroupListening = pArea->IsGroupListening();
521 aEntry.mpListener = pListener;
522 rListeners.push_back(aEntry);
523 }
524 }
525 }
526
527 #if DEBUG_AREA_BROADCASTER
Dump() const528 void ScBroadcastAreaSlot::Dump() const
529 {
530 for (const ScBroadcastAreaEntry& rEntry : aBroadcastAreaTbl)
531 {
532 const ScBroadcastArea* pArea = rEntry.mpArea;
533 const SvtBroadcaster& rBC = pArea->GetBroadcaster();
534 const SvtBroadcaster::ListenersType& rListeners = rBC.GetAllListeners();
535 size_t n = rListeners.size();
536
537 cout << " * range: " << OUStringToOString(pArea->GetRange().Format(ScRefFlags::VALID|ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr()
538 << ", group: " << pArea->IsGroupListening()
539 << ", listener count: " << n << endl;
540
541 for (size_t i = 0; i < n; ++i)
542 {
543 const ScFormulaCell* pFC = dynamic_cast<const ScFormulaCell*>(rListeners[i]);
544 if (pFC)
545 {
546 cout << " * listener: formula cell: "
547 << OUStringToOString(pFC->aPos.Format(ScRefFlags::VALID|ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr()
548 << endl;
549 continue;
550 }
551
552 const sc::FormulaGroupAreaListener* pFGListener = dynamic_cast<const sc::FormulaGroupAreaListener*>(rListeners[i]);
553 if (pFGListener)
554 {
555 cout << " * listener: formula group: (pos: "
556 << OUStringToOString(pFGListener->getTopCellPos().Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr()
557 << ", length: " << pFGListener->getGroupLength()
558 << ")" << endl;
559 continue;
560 }
561
562 cout << " * listener: unknown" << endl;
563 }
564 }
565 }
566 #endif
567
FinallyEraseAreas()568 void ScBroadcastAreaSlot::FinallyEraseAreas()
569 {
570 pBASM->FinallyEraseAreas( this);
571 }
572
573 // --- ScBroadcastAreaSlotMachine -------------------------------------
574
TableSlots(SCSIZE nBcaSlots)575 ScBroadcastAreaSlotMachine::TableSlots::TableSlots(SCSIZE nBcaSlots)
576 : mnBcaSlots(nBcaSlots)
577 {
578 ppSlots.reset( new ScBroadcastAreaSlot* [ nBcaSlots ] );
579 memset( ppSlots.get(), 0 , sizeof( ScBroadcastAreaSlot* ) * nBcaSlots );
580 }
581
~TableSlots()582 ScBroadcastAreaSlotMachine::TableSlots::~TableSlots()
583 {
584 for ( ScBroadcastAreaSlot** pp = ppSlots.get() + mnBcaSlots; --pp >= ppSlots.get(); /* nothing */ )
585 delete *pp;
586 }
587
ScBroadcastAreaSlotMachine(ScDocument * pDocument)588 ScBroadcastAreaSlotMachine::ScBroadcastAreaSlotMachine(
589 ScDocument* pDocument ) :
590 pDoc( pDocument ),
591 pUpdateChain( nullptr ),
592 pEOUpdateChain( nullptr ),
593 nInBulkBroadcast( 0 )
594 {
595 const ScSheetLimits& rSheetLimits = pDoc->GetSheetLimits();
596
597 assert((BCA_SLOT_ROWS(rSheetLimits) * BCA_SLOTS_ROW(rSheetLimits)) == pDoc->GetSheetLimits().GetMaxRowCount() && "bad BCA_SLOTS_ROW value");
598 // Arbitrary 2**31/8, assuming size_t can hold at least 2^31 values and
599 // sizeof_ptr is at most 8 bytes. You'd probably doom your machine's memory
600 // anyway, once you reached these values...
601 assert(BCA_SLOTS(rSheetLimits) <= 268435456 && "DOOMed");
602
603 // initSlotDistribution ---------
604 // Logarithmic or any other distribution.
605 // Upper sheet part usually is more populated and referenced and gets fine
606 // grained resolution, larger data in larger hunks.
607 // Could be further enhanced by also applying a different distribution of
608 // column slots.
609 SCSIZE nSlots = 0;
610 SCROW nRow1 = 0;
611 SCROW nRow2 = 32*1024;
612 SCSIZE nSlice = 128;
613 // Must be sorted by row1,row2!
614 while (nRow2 <= rSheetLimits.GetMaxRowCount())
615 {
616 maSlotDistribution.emplace_back( nRow1, nRow2, nSlice, nSlots);
617 nSlots += (nRow2 - nRow1) / nSlice;
618 nRow1 = nRow2;
619 nRow2 *= 2;
620 nSlice *= 2;
621 }
622 mnBcaSlotsRow = nSlots;
623 mnBcaSlots = mnBcaSlotsRow * BCA_SLOTS_COL;
624 }
625
~ScBroadcastAreaSlotMachine()626 ScBroadcastAreaSlotMachine::~ScBroadcastAreaSlotMachine()
627 {
628 aTableSlotsMap.clear();
629 pBCAlways.reset();
630 // Areas to-be-erased still present is a serious error in handling, but at
631 // this stage there's nothing we can do anymore.
632 SAL_WARN_IF( !maAreasToBeErased.empty(), "sc.core", "ScBroadcastAreaSlotMachine::dtor: maAreasToBeErased not empty");
633 }
634
ComputeSlotOffset(const ScAddress & rAddress) const635 inline SCSIZE ScBroadcastAreaSlotMachine::ComputeSlotOffset(
636 const ScAddress& rAddress ) const
637 {
638 SCROW nRow = rAddress.Row();
639 SCCOL nCol = rAddress.Col();
640 if ( !pDoc->ValidRow(nRow) || !pDoc->ValidCol(nCol) )
641 {
642 OSL_FAIL( "Row/Col invalid, using first slot!" );
643 return 0;
644 }
645 for (const ScSlotData & i : maSlotDistribution)
646 {
647 if (nRow < i.nStopRow)
648 {
649 const ScSlotData& rSD = i;
650 return rSD.nCumulated +
651 static_cast<SCSIZE>(nRow - rSD.nStartRow) / rSD.nSlice +
652 static_cast<SCSIZE>(nCol) / BCA_SLOT_COLS * mnBcaSlotsRow;
653 }
654 }
655 OSL_FAIL( "No slot found, using last!" );
656 return mnBcaSlots - 1;
657 }
658
ComputeAreaPoints(const ScRange & rRange,SCSIZE & rStart,SCSIZE & rEnd,SCSIZE & rRowBreak) const659 void ScBroadcastAreaSlotMachine::ComputeAreaPoints( const ScRange& rRange,
660 SCSIZE& rStart, SCSIZE& rEnd, SCSIZE& rRowBreak ) const
661 {
662 rStart = ComputeSlotOffset( rRange.aStart );
663 rEnd = ComputeSlotOffset( rRange.aEnd );
664 // count of row slots per column minus one
665 rRowBreak = ComputeSlotOffset(
666 ScAddress( rRange.aStart.Col(), rRange.aEnd.Row(), 0 ) ) - rStart;
667 }
668
ComputeNextSlot(SCSIZE & nOff,SCSIZE & nBreak,ScBroadcastAreaSlot ** & pp,SCSIZE & nStart,ScBroadcastAreaSlot ** const & ppSlots,SCSIZE nRowBreak,SCSIZE nBcaSlotsRow)669 static void ComputeNextSlot( SCSIZE & nOff, SCSIZE & nBreak, ScBroadcastAreaSlot** & pp,
670 SCSIZE & nStart, ScBroadcastAreaSlot** const & ppSlots, SCSIZE nRowBreak, SCSIZE nBcaSlotsRow )
671 {
672 if ( nOff < nBreak )
673 {
674 ++nOff;
675 ++pp;
676 }
677 else
678 {
679 nStart += nBcaSlotsRow;
680 nOff = nStart;
681 pp = ppSlots + nOff;
682 nBreak = nOff + nRowBreak;
683 }
684 }
685
StartListeningArea(const ScRange & rRange,bool bGroupListening,SvtListener * pListener)686 void ScBroadcastAreaSlotMachine::StartListeningArea(
687 const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
688 {
689 if ( rRange == BCA_LISTEN_ALWAYS )
690 {
691 if ( !pBCAlways )
692 pBCAlways.reset( new SvtBroadcaster );
693 pListener->StartListening( *pBCAlways );
694 }
695 else
696 {
697 // A new area needs to be inserted to the corresponding slots, for 3D
698 // ranges for all sheets, do not slice into per sheet areas or the
699 // !bDone will break too early (i.e. after the first sheet) if
700 // subsequent listeners are to be added.
701 ScBroadcastArea* pArea = nullptr;
702 bool bDone = false;
703 for (SCTAB nTab = rRange.aStart.Tab();
704 !bDone && nTab <= rRange.aEnd.Tab(); ++nTab)
705 {
706 TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
707 if (iTab == aTableSlotsMap.end())
708 iTab = aTableSlotsMap.emplace(nTab, std::make_unique<TableSlots>(mnBcaSlots)).first;
709 ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
710 SCSIZE nStart, nEnd, nRowBreak;
711 ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
712 SCSIZE nOff = nStart;
713 SCSIZE nBreak = nOff + nRowBreak;
714 ScBroadcastAreaSlot** pp = ppSlots + nOff;
715 while ( !bDone && nOff <= nEnd )
716 {
717 if ( !*pp )
718 *pp = new ScBroadcastAreaSlot( pDoc, this );
719 if (!pArea)
720 {
721 // If the call to StartListeningArea didn't create the
722 // ScBroadcastArea, listeners were added to an already
723 // existing identical area that doesn't need to be inserted
724 // to slots again.
725 if (!(*pp)->StartListeningArea( rRange, bGroupListening, pListener, pArea))
726 bDone = true;
727 }
728 else
729 (*pp)->InsertListeningArea( pArea);
730 ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsRow);
731 }
732 }
733 }
734 }
735
EndListeningArea(const ScRange & rRange,bool bGroupListening,SvtListener * pListener)736 void ScBroadcastAreaSlotMachine::EndListeningArea(
737 const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
738 {
739 if ( rRange == BCA_LISTEN_ALWAYS )
740 {
741 if ( pBCAlways )
742 {
743 pListener->EndListening( *pBCAlways);
744 if (!pBCAlways->HasListeners())
745 {
746 pBCAlways.reset();
747 }
748 }
749 }
750 else
751 {
752 SCTAB nEndTab = rRange.aEnd.Tab();
753 for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
754 iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
755 {
756 ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
757 SCSIZE nStart, nEnd, nRowBreak;
758 ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
759 SCSIZE nOff = nStart;
760 SCSIZE nBreak = nOff + nRowBreak;
761 ScBroadcastAreaSlot** pp = ppSlots + nOff;
762 ScBroadcastArea* pArea = nullptr;
763 if (nOff == 0 && nEnd == mnBcaSlots-1)
764 {
765 // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
766 // happen for insertion and deletion of sheets.
767 ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
768 do
769 {
770 if ( *pp )
771 (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea);
772 } while (++pp < pStop);
773 }
774 else
775 {
776 while ( nOff <= nEnd )
777 {
778 if ( *pp )
779 (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea);
780 ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsRow);
781 }
782 }
783 }
784 }
785 }
786
AreaBroadcast(const ScRange & rRange,SfxHintId nHint)787 bool ScBroadcastAreaSlotMachine::AreaBroadcast( const ScRange& rRange, SfxHintId nHint )
788 {
789 bool bBroadcasted = false;
790 SCTAB nEndTab = rRange.aEnd.Tab();
791 for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
792 iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
793 {
794 ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
795 SCSIZE nStart, nEnd, nRowBreak;
796 ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
797 SCSIZE nOff = nStart;
798 SCSIZE nBreak = nOff + nRowBreak;
799 ScBroadcastAreaSlot** pp = ppSlots + nOff;
800 while ( nOff <= nEnd )
801 {
802 if ( *pp )
803 bBroadcasted |= (*pp)->AreaBroadcast( rRange, nHint );
804 ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsRow);
805 }
806 }
807 return bBroadcasted;
808 }
809
AreaBroadcast(const ScHint & rHint) const810 bool ScBroadcastAreaSlotMachine::AreaBroadcast( const ScHint& rHint ) const
811 {
812 const ScAddress& rAddress = rHint.GetAddress();
813 if ( rAddress == BCA_BRDCST_ALWAYS )
814 {
815 if ( pBCAlways )
816 {
817 pBCAlways->Broadcast( rHint );
818 return true;
819 }
820 else
821 return false;
822 }
823 else
824 {
825 TableSlotsMap::const_iterator iTab( aTableSlotsMap.find( rAddress.Tab()));
826 if (iTab == aTableSlotsMap.end())
827 return false;
828 ScBroadcastAreaSlot* pSlot = (*iTab).second->getAreaSlot(
829 ComputeSlotOffset( rAddress));
830 if ( pSlot )
831 return pSlot->AreaBroadcast( rHint );
832 else
833 return false;
834 }
835 }
836
DelBroadcastAreasInRange(const ScRange & rRange)837 void ScBroadcastAreaSlotMachine::DelBroadcastAreasInRange(
838 const ScRange& rRange )
839 {
840 SCTAB nEndTab = rRange.aEnd.Tab();
841 for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
842 iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
843 {
844 ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
845 SCSIZE nStart, nEnd, nRowBreak;
846 ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
847 SCSIZE nOff = nStart;
848 SCSIZE nBreak = nOff + nRowBreak;
849 ScBroadcastAreaSlot** pp = ppSlots + nOff;
850 if (nOff == 0 && nEnd == mnBcaSlots-1)
851 {
852 // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
853 // happen for insertion and deletion of sheets.
854 ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
855 do
856 {
857 if ( *pp )
858 (*pp)->DelBroadcastAreasInRange( rRange );
859 } while (++pp < pStop);
860 }
861 else
862 {
863 while ( nOff <= nEnd )
864 {
865 if ( *pp )
866 (*pp)->DelBroadcastAreasInRange( rRange );
867 ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsRow);
868 }
869 }
870 }
871 }
872
873 // for all affected: remove, chain, update range, insert, and maybe delete
UpdateBroadcastAreas(UpdateRefMode eUpdateRefMode,const ScRange & rRange,SCCOL nDx,SCROW nDy,SCTAB nDz)874 void ScBroadcastAreaSlotMachine::UpdateBroadcastAreas(
875 UpdateRefMode eUpdateRefMode,
876 const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz )
877 {
878 // remove affected and put in chain
879 SCTAB nEndTab = rRange.aEnd.Tab();
880 for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
881 iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
882 {
883 ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
884 SCSIZE nStart, nEnd, nRowBreak;
885 ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
886 SCSIZE nOff = nStart;
887 SCSIZE nBreak = nOff + nRowBreak;
888 ScBroadcastAreaSlot** pp = ppSlots + nOff;
889 if (nOff == 0 && nEnd == mnBcaSlots-1)
890 {
891 // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
892 // happen for insertion and deletion of sheets.
893 ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
894 do
895 {
896 if ( *pp )
897 (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz );
898 } while (++pp < pStop);
899 }
900 else
901 {
902 while ( nOff <= nEnd )
903 {
904 if ( *pp )
905 (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz );
906 ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsRow);
907 }
908 }
909 }
910
911 // Updating an area's range will modify the hash key, remove areas from all
912 // affected slots. Will be reinserted later with the updated range.
913 ScBroadcastArea* pChain = pUpdateChain;
914 while (pChain)
915 {
916 ScBroadcastArea* pArea = pChain;
917 pChain = pArea->GetUpdateChainNext();
918 ScRange aRange( pArea->GetRange());
919 // remove from slots
920 for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab() && pArea->GetRef(); ++nTab)
921 {
922 TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
923 if (iTab == aTableSlotsMap.end())
924 {
925 OSL_FAIL( "UpdateBroadcastAreas: Where's the TableSlot?!?");
926 continue; // for
927 }
928 ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
929 SCSIZE nStart, nEnd, nRowBreak;
930 ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak );
931 SCSIZE nOff = nStart;
932 SCSIZE nBreak = nOff + nRowBreak;
933 ScBroadcastAreaSlot** pp = ppSlots + nOff;
934 while ( nOff <= nEnd && pArea->GetRef() )
935 {
936 if (*pp)
937 (*pp)->UpdateRemoveArea( pArea);
938 ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsRow);
939 }
940 }
941
942 }
943
944 // shift sheets
945 if (nDz)
946 {
947 if (nDz < 0)
948 {
949 TableSlotsMap::iterator iDel( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
950 TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab() - nDz));
951 // Remove sheets, if any, iDel or/and iTab may as well point to end().
952 while (iDel != iTab)
953 {
954 aTableSlotsMap.erase( iDel++);
955 }
956 // shift remaining down
957 while (iTab != aTableSlotsMap.end())
958 {
959 SCTAB nTab = (*iTab).first + nDz;
960 aTableSlotsMap[nTab] = std::move((*iTab).second);
961 aTableSlotsMap.erase( iTab++);
962 }
963 }
964 else
965 {
966 TableSlotsMap::iterator iStop( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
967 if (iStop != aTableSlotsMap.end())
968 {
969 bool bStopIsBegin = (iStop == aTableSlotsMap.begin());
970 if (!bStopIsBegin)
971 --iStop;
972 TableSlotsMap::iterator iTab( aTableSlotsMap.end());
973 --iTab;
974 while (iTab != iStop)
975 {
976 SCTAB nTab = (*iTab).first + nDz;
977 aTableSlotsMap[nTab] = std::move((*iTab).second);
978 aTableSlotsMap.erase( iTab--);
979 }
980 // Shift the very first, iTab==iStop in this case.
981 if (bStopIsBegin)
982 {
983 SCTAB nTab = (*iTab).first + nDz;
984 aTableSlotsMap[nTab] = std::move((*iTab).second);
985 aTableSlotsMap.erase( iStop);
986 }
987 }
988 }
989 }
990
991 // work off chain
992 SCCOL nCol1, nCol2, theCol1, theCol2;
993 SCROW nRow1, nRow2, theRow1, theRow2;
994 SCTAB nTab1, nTab2, theTab1, theTab2;
995 rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
996 while ( pUpdateChain )
997 {
998 ScBroadcastArea* pArea = pUpdateChain;
999 ScRange aRange( pArea->GetRange());
1000 pUpdateChain = pArea->GetUpdateChainNext();
1001
1002 // update range
1003 aRange.GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2);
1004 if ( ScRefUpdate::Update( pDoc, eUpdateRefMode,
1005 nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz,
1006 theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ))
1007 {
1008 aRange = ScRange( theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 );
1009 pArea->UpdateRange( aRange );
1010 // For DDE and ScLookupCache
1011 pArea->GetBroadcaster().Broadcast( ScAreaChangedHint( aRange ) );
1012 }
1013
1014 // insert to slots
1015 for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab(); ++nTab)
1016 {
1017 TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
1018 if (iTab == aTableSlotsMap.end())
1019 iTab = aTableSlotsMap.emplace(nTab, std::make_unique<TableSlots>(mnBcaSlots)).first;
1020 ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
1021 SCSIZE nStart, nEnd, nRowBreak;
1022 ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak );
1023 SCSIZE nOff = nStart;
1024 SCSIZE nBreak = nOff + nRowBreak;
1025 ScBroadcastAreaSlot** pp = ppSlots + nOff;
1026 while ( nOff <= nEnd )
1027 {
1028 if (!*pp)
1029 *pp = new ScBroadcastAreaSlot( pDoc, this );
1030 (*pp)->UpdateInsert( pArea );
1031 ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsRow);
1032 }
1033 }
1034
1035 // unchain
1036 pArea->SetUpdateChainNext( nullptr );
1037 pArea->SetInUpdateChain( false );
1038
1039 // Delete if not inserted to any slot. RemoveBulkArea(pArea) was
1040 // already executed in UpdateRemove().
1041 if (!pArea->GetRef())
1042 delete pArea;
1043 }
1044 pEOUpdateChain = nullptr;
1045 }
1046
EnterBulkBroadcast()1047 void ScBroadcastAreaSlotMachine::EnterBulkBroadcast()
1048 {
1049 ++nInBulkBroadcast;
1050 }
1051
LeaveBulkBroadcast(SfxHintId nHintId)1052 void ScBroadcastAreaSlotMachine::LeaveBulkBroadcast( SfxHintId nHintId )
1053 {
1054 if (nInBulkBroadcast <= 0)
1055 return;
1056
1057 if (--nInBulkBroadcast == 0)
1058 {
1059 ScBroadcastAreasBulk().swap( aBulkBroadcastAreas);
1060 bool bBroadcasted = BulkBroadcastGroupAreas( nHintId );
1061 // Trigger the "final" tracking.
1062 if (pDoc->IsTrackFormulasPending())
1063 pDoc->FinalTrackFormulas( nHintId );
1064 else if (bBroadcasted)
1065 pDoc->TrackFormulas( nHintId );
1066 }
1067 }
1068
InsertBulkArea(const ScBroadcastArea * pArea)1069 bool ScBroadcastAreaSlotMachine::InsertBulkArea( const ScBroadcastArea* pArea )
1070 {
1071 return aBulkBroadcastAreas.insert( pArea ).second;
1072 }
1073
InsertBulkGroupArea(ScBroadcastArea * pArea,const ScRange & rRange)1074 void ScBroadcastAreaSlotMachine::InsertBulkGroupArea( ScBroadcastArea* pArea, const ScRange& rRange )
1075 {
1076 BulkGroupAreasType::iterator it = m_BulkGroupAreas.lower_bound(pArea);
1077 if (it == m_BulkGroupAreas.end() || m_BulkGroupAreas.key_comp()(pArea, it->first))
1078 {
1079 // Insert a new one.
1080 it = m_BulkGroupAreas.insert(it, std::make_pair(pArea, std::make_unique<sc::ColumnSpanSet>()));
1081 }
1082
1083 sc::ColumnSpanSet *const pSet = it->second.get();
1084 assert(pSet);
1085 pSet->set(*pDoc, rRange, true);
1086 }
1087
BulkBroadcastGroupAreas(SfxHintId nHintId)1088 bool ScBroadcastAreaSlotMachine::BulkBroadcastGroupAreas( SfxHintId nHintId )
1089 {
1090 if (m_BulkGroupAreas.empty())
1091 return false;
1092
1093 sc::BulkDataHint aHint( *pDoc, nHintId);
1094
1095 bool bBroadcasted = false;
1096 for (const auto& [pArea, rxSpans] : m_BulkGroupAreas)
1097 {
1098 assert(pArea);
1099 SvtBroadcaster& rBC = pArea->GetBroadcaster();
1100 if (!rBC.HasListeners())
1101 {
1102 /* FIXME: find the cause where the last listener is removed and
1103 * this area is still listed here. */
1104 SAL_WARN("sc.core","ScBroadcastAreaSlotMachine::BulkBroadcastGroupAreas - pArea has no listeners and should had been removed already");
1105 }
1106 else
1107 {
1108 const sc::ColumnSpanSet *const pSpans = rxSpans.get();
1109 assert(pSpans);
1110 aHint.setSpans(pSpans);
1111 rBC.Broadcast(aHint);
1112 bBroadcasted = true;
1113 }
1114 }
1115
1116 m_BulkGroupAreas.clear();
1117
1118 return bBroadcasted;
1119 }
1120
RemoveBulkArea(const ScBroadcastArea * pArea)1121 size_t ScBroadcastAreaSlotMachine::RemoveBulkArea( const ScBroadcastArea* pArea )
1122 {
1123 return aBulkBroadcastAreas.erase( pArea );
1124 }
1125
RemoveBulkGroupArea(ScBroadcastArea * pArea)1126 void ScBroadcastAreaSlotMachine::RemoveBulkGroupArea( ScBroadcastArea* pArea )
1127 {
1128 m_BulkGroupAreas.erase(pArea);
1129 }
1130
PushAreaToBeErased(ScBroadcastAreaSlot * pSlot,ScBroadcastAreas::iterator & rIter)1131 void ScBroadcastAreaSlotMachine::PushAreaToBeErased( ScBroadcastAreaSlot* pSlot,
1132 ScBroadcastAreas::iterator& rIter )
1133 {
1134 maAreasToBeErased.emplace_back( pSlot, rIter);
1135 }
1136
FinallyEraseAreas(ScBroadcastAreaSlot * pSlot)1137 void ScBroadcastAreaSlotMachine::FinallyEraseAreas( ScBroadcastAreaSlot* pSlot )
1138 {
1139 SAL_WARN_IF( pSlot->IsInBroadcastIteration(), "sc.core",
1140 "ScBroadcastAreaSlotMachine::FinallyEraseAreas: during iteration? NO!");
1141 if (pSlot->IsInBroadcastIteration())
1142 return;
1143
1144 // maAreasToBeErased is a simple vector so erasing an element may
1145 // invalidate iterators and would be inefficient anyway. Instead, copy
1146 // elements to be preserved (usually none!) to temporary vector and swap.
1147 AreasToBeErased aCopy;
1148 for (auto& rArea : maAreasToBeErased)
1149 {
1150 if (rArea.first == pSlot)
1151 pSlot->EraseArea( rArea.second);
1152 else
1153 aCopy.push_back( rArea);
1154 }
1155 maAreasToBeErased.swap( aCopy);
1156 }
1157
GetAllListeners(const ScRange & rRange,sc::AreaOverlapType eType,sc::ListenerGroupType eGroup)1158 std::vector<sc::AreaListener> ScBroadcastAreaSlotMachine::GetAllListeners(
1159 const ScRange& rRange, sc::AreaOverlapType eType, sc::ListenerGroupType eGroup )
1160 {
1161 std::vector<sc::AreaListener> aRet;
1162
1163 SCTAB nEndTab = rRange.aEnd.Tab();
1164 for (TableSlotsMap::const_iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
1165 iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
1166 {
1167 ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
1168 SCSIZE nStart, nEnd, nRowBreak;
1169 ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
1170 SCSIZE nOff = nStart;
1171 SCSIZE nBreak = nOff + nRowBreak;
1172 ScBroadcastAreaSlot** pp = ppSlots + nOff;
1173 while ( nOff <= nEnd )
1174 {
1175 ScBroadcastAreaSlot* p = *pp;
1176 if (p)
1177 p->GetAllListeners(rRange, aRet, eType, eGroup);
1178 ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsRow);
1179 }
1180 }
1181
1182 return aRet;
1183 }
1184
1185 #if DEBUG_AREA_BROADCASTER
Dump() const1186 void ScBroadcastAreaSlotMachine::Dump() const
1187 {
1188 cout << "slot distribution count: " << nBcaSlots << endl;
1189 for (const auto& [rIndex, pTabSlots] : aTableSlotsMap)
1190 {
1191 cout << "-- sheet (index: " << rIndex << ")" << endl;
1192
1193 assert(pTabSlots);
1194 ScBroadcastAreaSlot** ppSlots = pTabSlots->getSlots();
1195 for (SCSIZE i = 0; i < nBcaSlots; ++i)
1196 {
1197 const ScBroadcastAreaSlot* pSlot = ppSlots[i];
1198 if (pSlot)
1199 {
1200 cout << "* slot " << i << endl;
1201 pSlot->Dump();
1202 }
1203 }
1204 }
1205 }
1206 #endif
1207
1208 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1209