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