diff options
Diffstat (limited to 'sc/source/core/data/bcaslot.cxx')
-rw-r--r-- | sc/source/core/data/bcaslot.cxx | 1300 |
1 files changed, 1300 insertions, 0 deletions
diff --git a/sc/source/core/data/bcaslot.cxx b/sc/source/core/data/bcaslot.cxx new file mode 100644 index 000000000..094939634 --- /dev/null +++ b/sc/source/core/data/bcaslot.cxx @@ -0,0 +1,1300 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sfx2/objsh.hxx> +#include <svl/listener.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <document.hxx> +#include <brdcst.hxx> +#include <bcaslot.hxx> +#include <scerrors.hxx> +#include <refupdat.hxx> +#include <bulkdatahint.hxx> +#include <columnspanset.hxx> + +#if DEBUG_AREA_BROADCASTER +#include <formulacell.hxx> +#include <grouparealistener.hxx> +#endif + +ScBroadcastArea::ScBroadcastArea( const ScRange& rRange ) : + pUpdateChainNext(nullptr), + aRange(rRange), + nRefCount(0), + mbInUpdateChain(false), + mbGroupListening(false) {} + +ScBroadcastAreaSlot::ScBroadcastAreaSlot( ScDocument* pDocument, + ScBroadcastAreaSlotMachine* pBASMa ) : + aTmpSeekBroadcastArea( ScRange()), + pDoc( pDocument ), + pBASM( pBASMa ), + mbInBroadcastIteration( false), + mbHasErasedArea(false) +{ +} + +ScBroadcastAreaSlot::~ScBroadcastAreaSlot() +{ + for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin()); + aIter != aBroadcastAreaTbl.end(); /* none */) + { + // Prevent hash from accessing dangling pointer in case area is + // deleted. + ScBroadcastArea* pArea = (*aIter).mpArea; + // Erase all so no hash will be accessed upon destruction of the + // unordered_map. + aIter = aBroadcastAreaTbl.erase(aIter); + if (!pArea->DecRef()) + delete pArea; + } +} + +ScDocument::HardRecalcState ScBroadcastAreaSlot::CheckHardRecalcStateCondition() const +{ + ScDocument::HardRecalcState eState = pDoc->GetHardRecalcState(); + if (eState == ScDocument::HardRecalcState::OFF) + { + if (aBroadcastAreaTbl.size() >= aBroadcastAreaTbl.max_size()) + { // this is more hypothetical now, check existed for old SV_PTRARR_SORT + SfxObjectShell* pShell = pDoc->GetDocumentShell(); + OSL_ENSURE( pShell, "Missing DocShell :-/" ); + + if ( pShell ) + pShell->SetError(SCWARN_CORE_HARD_RECALC); + + pDoc->SetAutoCalc( false ); + eState = ScDocument::HardRecalcState::ETERNAL; + pDoc->SetHardRecalcState( eState ); + } + } + return eState; +} + +bool ScBroadcastAreaSlot::StartListeningArea( + const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea ) +{ + bool bNewArea = false; + OSL_ENSURE(pListener, "StartListeningArea: pListener Null"); + assert(!pDoc->IsDelayedFormulaGrouping()); // otherwise the group size might be incorrect + if (CheckHardRecalcStateCondition() == ScDocument::HardRecalcState::ETERNAL) + return false; + if ( !rpArea ) + { + // Even if most times the area doesn't exist yet and immediately trying + // to new and insert it would save an attempt to find it, on massive + // operations like identical large [HV]LOOKUP() areas the new/delete + // would add quite some penalty for all but the first formula cell. + ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange, bGroupListening)); + if (aIter != aBroadcastAreaTbl.end()) + rpArea = (*aIter).mpArea; + else + { + rpArea = new ScBroadcastArea( rRange); + rpArea->SetGroupListening(bGroupListening); + if (aBroadcastAreaTbl.insert( rpArea).second) + { + rpArea->IncRef(); + bNewArea = true; + } + else + { + OSL_FAIL("StartListeningArea: area not found and not inserted in slot?!?"); + delete rpArea; + rpArea = nullptr; + } + } + if (rpArea) + pListener->StartListening( rpArea->GetBroadcaster()); + } + else + { + if (aBroadcastAreaTbl.insert( rpArea).second) + rpArea->IncRef(); + } + return bNewArea; +} + +void ScBroadcastAreaSlot::InsertListeningArea( ScBroadcastArea* pArea ) +{ + OSL_ENSURE( pArea, "InsertListeningArea: pArea NULL"); + if (CheckHardRecalcStateCondition() == ScDocument::HardRecalcState::ETERNAL) + return; + if (aBroadcastAreaTbl.insert( pArea).second) + pArea->IncRef(); +} + +// If rpArea != NULL then no listeners are stopped, only the area is removed +// and the reference count decremented. +void ScBroadcastAreaSlot::EndListeningArea( + const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea ) +{ + OSL_ENSURE(pListener, "EndListeningArea: pListener Null"); + if ( !rpArea ) + { + ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange, bGroupListening)); + if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter)) + return; + rpArea = (*aIter).mpArea; + pListener->EndListening( rpArea->GetBroadcaster() ); + if ( !rpArea->GetBroadcaster().HasListeners() ) + { // if nobody is listening we can dispose it + if (rpArea->GetRef() == 1) + rpArea = nullptr; // will be deleted by erase + EraseArea( aIter); + } + } + else + { + if (rpArea && !rpArea->GetBroadcaster().HasListeners()) + { + ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange, bGroupListening)); + if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter)) + return; + OSL_ENSURE( (*aIter).mpArea == rpArea, "EndListeningArea: area pointer mismatch"); + if (rpArea->GetRef() == 1) + rpArea = nullptr; // will be deleted by erase + EraseArea( aIter); + } + } +} + +ScBroadcastAreas::iterator ScBroadcastAreaSlot::FindBroadcastArea( + const ScRange& rRange, bool bGroupListening ) +{ + aTmpSeekBroadcastArea.UpdateRange( rRange); + aTmpSeekBroadcastArea.SetGroupListening(bGroupListening); + return aBroadcastAreaTbl.find( &aTmpSeekBroadcastArea); +} + +namespace { + +void broadcastRangeByCell( SvtBroadcaster& rBC, const ScRange& rRange, SfxHintId nHint ) +{ + ScHint aHint(nHint, ScAddress()); + for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab) + { + aHint.SetAddressTab(nTab); + for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol) + { + aHint.SetAddressCol(nCol); + for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow) + { + aHint.SetAddressRow(nRow); + rBC.Broadcast(aHint); + } + } + } +} + +} + +bool ScBroadcastAreaSlot::AreaBroadcast( const ScRange& rRange, SfxHintId nHint ) +{ + if (aBroadcastAreaTbl.empty()) + return false; + + bool bInBroadcast = mbInBroadcastIteration; + mbInBroadcastIteration = true; + bool bIsBroadcasted = false; + + mbHasErasedArea = false; + + for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()), + aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter ) + { + if (mbHasErasedArea && isMarkedErased( aIter)) + continue; + + ScBroadcastArea* pArea = (*aIter).mpArea; + const ScRange& rAreaRange = pArea->GetRange(); + + // Take the intersection of the area range and the broadcast range. + ScRange aIntersection = rAreaRange.Intersection(rRange); + if (!aIntersection.IsValid()) + continue; + + if (pArea->IsGroupListening()) + { + if (pBASM->IsInBulkBroadcast()) + { + pBASM->InsertBulkGroupArea(pArea, aIntersection); + } + else + { + broadcastRangeByCell(pArea->GetBroadcaster(), aIntersection, nHint); + bIsBroadcasted = true; + } + } + else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea)) + { + broadcastRangeByCell(pArea->GetBroadcaster(), aIntersection, nHint); + bIsBroadcasted = true; + } + } + + mbInBroadcastIteration = bInBroadcast; + + // A Notify() during broadcast may call EndListeningArea() and thus dispose + // an area if it was the last listener, which would invalidate an iterator + // pointing to it, hence the real erase is done afterwards. + FinallyEraseAreas(); + + return bIsBroadcasted; +} + +bool ScBroadcastAreaSlot::AreaBroadcast( const ScHint& rHint) +{ + if (aBroadcastAreaTbl.empty()) + return false; + + bool bInBroadcast = mbInBroadcastIteration; + mbInBroadcastIteration = true; + bool bIsBroadcasted = false; + + mbHasErasedArea = false; + + const ScRange& rRange = rHint.GetRange(); + for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()), + aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter ) + { + if (mbHasErasedArea && isMarkedErased( aIter)) + continue; + + ScBroadcastArea* pArea = (*aIter).mpArea; + const ScRange& rAreaRange = pArea->GetRange(); + if (rAreaRange.Intersects( rRange)) + { + if (pArea->IsGroupListening()) + { + if (pBASM->IsInBulkBroadcast()) + { + pBASM->InsertBulkGroupArea(pArea, rRange); + } + else + { + pArea->GetBroadcaster().Broadcast( rHint); + bIsBroadcasted = true; + } + } + else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea)) + { + pArea->GetBroadcaster().Broadcast( rHint); + bIsBroadcasted = true; + } + } + } + + mbInBroadcastIteration = bInBroadcast; + + // A Notify() during broadcast may call EndListeningArea() and thus dispose + // an area if it was the last listener, which would invalidate an iterator + // pointing to it, hence the real erase is done afterwards. + FinallyEraseAreas(); + + return bIsBroadcasted; +} + +void ScBroadcastAreaSlot::DelBroadcastAreasInRange( const ScRange& rRange ) +{ + if (aBroadcastAreaTbl.empty()) + return; + for (ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin()); + aIter != aBroadcastAreaTbl.end(); /* increment in body */ ) + { + const ScRange& rAreaRange = (*aIter).mpArea->GetRange(); + if (rRange.Contains( rAreaRange)) + { + ScBroadcastArea* pArea = (*aIter).mpArea; + aIter = aBroadcastAreaTbl.erase(aIter); // erase before modifying + if (!pArea->DecRef()) + { + if (pBASM->IsInBulkBroadcast()) + pBASM->RemoveBulkArea( pArea); + delete pArea; + } + } + else + ++aIter; + } +} + +void ScBroadcastAreaSlot::UpdateRemove( UpdateRefMode eUpdateRefMode, + const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + if (aBroadcastAreaTbl.empty()) + return; + + SCCOL nCol1, nCol2, theCol1, theCol2; + SCROW nRow1, nRow2, theRow1, theRow2; + SCTAB nTab1, nTab2, theTab1, theTab2; + rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin()); + aIter != aBroadcastAreaTbl.end(); /* increment in body */ ) + { + ScBroadcastArea* pArea = (*aIter).mpArea; + if ( pArea->IsInUpdateChain() ) + { + aIter = aBroadcastAreaTbl.erase(aIter); + pArea->DecRef(); + } + else + { + pArea->GetRange().GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2); + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, + nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz, + theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 )) + { + aIter = aBroadcastAreaTbl.erase(aIter); + pArea->DecRef(); + if (pBASM->IsInBulkBroadcast()) + pBASM->RemoveBulkArea( pArea); + pArea->SetInUpdateChain( true ); + ScBroadcastArea* pUC = pBASM->GetEOUpdateChain(); + if ( pUC ) + pUC->SetUpdateChainNext( pArea ); + else // no tail => no head + pBASM->SetUpdateChain( pArea ); + pBASM->SetEOUpdateChain( pArea ); + } + else + ++aIter; + } + } +} + +void ScBroadcastAreaSlot::UpdateRemoveArea( ScBroadcastArea* pArea ) +{ + ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.find( pArea)); + if (aIter == aBroadcastAreaTbl.end()) + return; + if ((*aIter).mpArea != pArea) + OSL_FAIL( "UpdateRemoveArea: area pointer mismatch"); + else + { + aBroadcastAreaTbl.erase( aIter); + pArea->DecRef(); + } +} + +void ScBroadcastAreaSlot::UpdateInsert( ScBroadcastArea* pArea ) +{ + ::std::pair< ScBroadcastAreas::iterator, bool > aPair = + aBroadcastAreaTbl.insert( pArea); + if (aPair.second) + pArea->IncRef(); + else + { + // Identical area already exists, add listeners. + ScBroadcastArea* pTarget = (*(aPair.first)).mpArea; + if (pArea != pTarget) + { + SvtBroadcaster& rTarget = pTarget->GetBroadcaster(); + SvtBroadcaster::ListenersType& rListeners = pArea->GetBroadcaster().GetAllListeners(); + for (auto& pListener : rListeners) + { + SvtListener& rListener = *pListener; + rListener.StartListening(rTarget); + } + } + } +} + +void ScBroadcastAreaSlot::EraseArea( ScBroadcastAreas::iterator& rIter ) +{ + if (mbInBroadcastIteration) + { + (*rIter).mbErasure = true; // mark for erasure + mbHasErasedArea = true; // at least one area is marked for erasure. + pBASM->PushAreaToBeErased( this, rIter); + } + else + { + ScBroadcastArea* pArea = (*rIter).mpArea; + aBroadcastAreaTbl.erase( rIter); + if (!pArea->DecRef()) + { + if (pBASM->IsInBulkBroadcast()) + pBASM->RemoveBulkGroupArea(pArea); + delete pArea; + } + } +} + +void ScBroadcastAreaSlot::GetAllListeners( + const ScRange& rRange, std::vector<sc::AreaListener>& rListeners, + sc::AreaOverlapType eType, sc::ListenerGroupType eGroup ) +{ + for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()), + aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter ) + { + if (isMarkedErased( aIter)) + continue; + + ScBroadcastArea* pArea = (*aIter).mpArea; + const ScRange& rAreaRange = pArea->GetRange(); + switch (eGroup) + { + case sc::ListenerGroupType::Group: + if (!pArea->IsGroupListening()) + continue; + break; + case sc::ListenerGroupType::Both: + default: + ; + } + + switch (eType) + { + case sc::AreaOverlapType::Inside: + if (!rRange.Contains(rAreaRange)) + // The range needs to be fully inside specified range. + continue; + break; + case sc::AreaOverlapType::InsideOrOverlap: + if (!rRange.Intersects(rAreaRange)) + // The range needs to be partially overlapping or fully inside. + continue; + break; + case sc::AreaOverlapType::OneRowInside: + if (rAreaRange.aStart.Row() != rAreaRange.aEnd.Row() || !rRange.Contains(rAreaRange)) + // The range needs to be one single row and fully inside + // specified range. + continue; + break; + case sc::AreaOverlapType::OneColumnInside: + if (rAreaRange.aStart.Col() != rAreaRange.aEnd.Col() || !rRange.Contains(rAreaRange)) + // The range needs to be one single column and fully inside + // specified range. + continue; + break; + } + + SvtBroadcaster::ListenersType& rLst = pArea->GetBroadcaster().GetAllListeners(); + for (const auto& pListener : rLst) + { + sc::AreaListener aEntry; + aEntry.maArea = rAreaRange; + aEntry.mbGroupListening = pArea->IsGroupListening(); + aEntry.mpListener = pListener; + rListeners.push_back(aEntry); + } + } +} + +#if DEBUG_AREA_BROADCASTER +void ScBroadcastAreaSlot::Dump() const +{ + for (const ScBroadcastAreaEntry& rEntry : aBroadcastAreaTbl) + { + const ScBroadcastArea* pArea = rEntry.mpArea; + const SvtBroadcaster& rBC = pArea->GetBroadcaster(); + const SvtBroadcaster::ListenersType& rListeners = rBC.GetAllListeners(); + size_t n = rListeners.size(); + + cout << " * range: " << OUStringToOString(pArea->GetRange().Format(ScRefFlags::VALID|ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr() + << ", group: " << pArea->IsGroupListening() + << ", listener count: " << n << endl; + + for (size_t i = 0; i < n; ++i) + { + const ScFormulaCell* pFC = dynamic_cast<const ScFormulaCell*>(rListeners[i]); + if (pFC) + { + cout << " * listener: formula cell: " + << OUStringToOString(pFC->aPos.Format(ScRefFlags::VALID|ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr() + << endl; + continue; + } + + const sc::FormulaGroupAreaListener* pFGListener = dynamic_cast<const sc::FormulaGroupAreaListener*>(rListeners[i]); + if (pFGListener) + { + cout << " * listener: formula group: (pos: " + << OUStringToOString(pFGListener->getTopCellPos().Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr() + << ", length: " << pFGListener->getGroupLength() + << ")" << endl; + continue; + } + + cout << " * listener: unknown" << endl; + } + } +} +#endif + +void ScBroadcastAreaSlot::FinallyEraseAreas() +{ + pBASM->FinallyEraseAreas( this); +} + +// --- ScBroadcastAreaSlotMachine ------------------------------------- + +ScBroadcastAreaSlotMachine::TableSlots::TableSlots(SCSIZE nBcaSlots) + : mnBcaSlots(nBcaSlots) +{ + ppSlots.reset( new ScBroadcastAreaSlot* [ nBcaSlots ] ); + memset( ppSlots.get(), 0 , sizeof( ScBroadcastAreaSlot* ) * nBcaSlots ); +} + +ScBroadcastAreaSlotMachine::TableSlots::~TableSlots() +{ + for ( ScBroadcastAreaSlot** pp = ppSlots.get() + mnBcaSlots; --pp >= ppSlots.get(); /* nothing */ ) + delete *pp; +} + +ScBroadcastAreaSlotMachine::ScBroadcastAreaSlotMachine( + ScDocument* pDocument ) : + pDoc( pDocument ), + pUpdateChain( nullptr ), + pEOUpdateChain( nullptr ), + nInBulkBroadcast( 0 ) +{ + // initSlotDistribution --------- + // Logarithmic or any other distribution. + // Upper and leftmost sheet part usually is more populated and referenced and gets fine + // grained resolution, larger data in larger hunks. + // Just like with cells, slots are organized in columns. Slot 0 is for first nSliceRow x nSliceCol + // cells, slot 1 is for next nSliceRow x nSliceCel cells below, etc. After a while the size of row + // slice doubles (making more cells share the same slot), this distribution data is stored + // in ScSlotData including ranges of cells. This is repeated for another column of nSliceCol cells, + // again with the column slice doubling after some time. + // Functions ComputeSlotOffset(), ComputeArePoints() and ComputeNextSlot() do the necessary + // calculations. + SCSIZE nSlots = 0; + // This should be SCCOL, but that's only 16bit and would overflow when doubling 16k columns. + sal_Int32 nCol1 = 0; + sal_Int32 nCol2 = 1024; + SCSIZE nSliceCol = 16; + while (nCol2 <= pDoc->GetMaxColCount()) + { + SCROW nRow1 = 0; + SCROW nRow2 = 32*1024; + SCSIZE nSliceRow = 128; + SCSIZE nSlotsCol = 0; + SCSIZE nSlotsStartCol = nSlots; + // Must be sorted by row1,row2! + while (nRow2 <= pDoc->GetMaxRowCount()) + { + maSlotDistribution.emplace_back(nRow1, nRow2, nSliceRow, nSlotsCol, nCol1, nCol2, nSliceCol, nSlotsStartCol); + nSlotsCol += (nRow2 - nRow1) / nSliceRow; + nRow1 = nRow2; + nRow2 *= 2; + nSliceRow *= 2; + } + // Store the number of slots in a column in mnBcaSlotsCol, so that finding a slot + // to the right can be computed quickly in ComputeNextSlot(). + if(nCol1 == 0) + mnBcaSlotsCol = nSlotsCol; + assert(nSlotsCol == mnBcaSlotsCol); + nSlots += (nCol2 - nCol1) / nSliceCol * nSlotsCol; + nCol1 = nCol2; + nCol2 *= 2; + nSliceCol *= 2; + } + mnBcaSlots = nSlots; +#ifdef DBG_UTIL + DoChecks(); +#endif +} + +ScBroadcastAreaSlotMachine::~ScBroadcastAreaSlotMachine() +{ + aTableSlotsMap.clear(); + pBCAlways.reset(); + // Areas to-be-erased still present is a serious error in handling, but at + // this stage there's nothing we can do anymore. + SAL_WARN_IF( !maAreasToBeErased.empty(), "sc.core", "ScBroadcastAreaSlotMachine::dtor: maAreasToBeErased not empty"); +} + +inline SCSIZE ScBroadcastAreaSlotMachine::ComputeSlotOffset( + const ScAddress& rAddress ) const +{ + SCROW nRow = rAddress.Row(); + SCCOL nCol = rAddress.Col(); + if ( !pDoc->ValidRow(nRow) || !pDoc->ValidCol(nCol) ) + { + OSL_FAIL( "Row/Col invalid, using first slot!" ); + return 0; + } + for (const ScSlotData& rSD : maSlotDistribution) + { + if (nRow < rSD.nStopRow && nCol < rSD.nStopCol) + { + assert(nRow >= rSD.nStartRow); + assert(nCol >= rSD.nStartCol); + SCSIZE slot = rSD.nCumulatedRow + + static_cast<SCSIZE>(nRow - rSD.nStartRow) / rSD.nSliceRow + + rSD.nCumulatedCol + + static_cast<SCSIZE>(nCol - rSD.nStartCol) / rSD.nSliceCol * mnBcaSlotsCol; + assert(slot < mnBcaSlots); + return slot; + } + } + OSL_FAIL( "No slot found, using last!" ); + return mnBcaSlots - 1; +} + +void ScBroadcastAreaSlotMachine::ComputeAreaPoints( const ScRange& rRange, + SCSIZE& rStart, SCSIZE& rEnd, SCSIZE& rRowBreak ) const +{ + rStart = ComputeSlotOffset( rRange.aStart ); + rEnd = ComputeSlotOffset( rRange.aEnd ); + // count of row slots per column minus one + rRowBreak = ComputeSlotOffset( + ScAddress( rRange.aStart.Col(), rRange.aEnd.Row(), 0 ) ) - rStart; +} + +static void ComputeNextSlot( SCSIZE & nOff, SCSIZE & nBreak, ScBroadcastAreaSlot** & pp, + SCSIZE & nStart, ScBroadcastAreaSlot** const & ppSlots, SCSIZE nRowBreak, SCSIZE nBcaSlotsCol ) +{ + if ( nOff < nBreak ) + { + ++nOff; + ++pp; + } + else + { + nStart += nBcaSlotsCol; + nOff = nStart; + pp = ppSlots + nOff; + nBreak = nOff + nRowBreak; + } +} + +#ifdef DBG_UTIL +static void compare(SCSIZE value1, SCSIZE value2, int line) +{ + if(value1!=value2) + SAL_WARN("sc", "V1:" << value1 << " V2:" << value2 << " (" << line << ")"); + assert(value1 == value2); +} + +// Basic checks that the calculations work correctly. +void ScBroadcastAreaSlotMachine::DoChecks() +{ + // Copy&paste from the ctor. + constexpr SCSIZE nSliceRow = 128; + constexpr SCSIZE nSliceCol = 16; + // First and second column are in the same slice and so get the same slot. + compare( ComputeSlotOffset( ScAddress( 0, 0, 0 )), ComputeSlotOffset( ScAddress( 1, 0, 0 )), __LINE__); + // Each nSliceRow rows are offset by one slot (at the start of the logarithmic distribution). + compare( ComputeSlotOffset( ScAddress( 0, 0, 0 )), + ComputeSlotOffset( ScAddress( 0, nSliceRow, 0 )) - 1, __LINE__ ); + compare( ComputeSlotOffset( ScAddress( nSliceCol - 1, 0, 0 )), + ComputeSlotOffset( ScAddress( nSliceCol, 0, 0 )) - mnBcaSlotsCol, __LINE__ ); + // Check that last cell is the last slot. + compare( ComputeSlotOffset( ScAddress( pDoc->GetMaxColCount() - 1, pDoc->GetMaxRowCount() - 1, 0 )), + mnBcaSlots - 1, __LINE__ ); + // Check that adjacent rows in the same column but in different distribution areas differ by one slot. + for( size_t i = 0; i < maSlotDistribution.size() - 1; ++i ) + { + const ScSlotData& s1 = maSlotDistribution[ i ]; + const ScSlotData& s2 = maSlotDistribution[ i + 1 ]; + if( s1.nStartCol == s2.nStartCol ) + { + assert( s1.nStopRow == s2.nStartRow ); + compare( ComputeSlotOffset( ScAddress( s1.nStartCol, s1.nStopRow - 1, 0 )), + ComputeSlotOffset( ScAddress( s1.nStartCol, s1.nStopRow, 0 )) - 1, __LINE__ ); + } + } + // Check that adjacent columns in the same row but in different distribution areas differ by mnBcaSlotsCol. + for( size_t i = 0; i < maSlotDistribution.size() - 1; ++i ) + { + const ScSlotData& s1 = maSlotDistribution[ i ]; + for( size_t j = i + 1; j < maSlotDistribution.size(); ++j ) + { + const ScSlotData& s2 = maSlotDistribution[ i + 1 ]; + if( s1.nStartRow == s2.nStartRow && s1.nStopCol == s2.nStartCol ) + { + assert( s1.nStopRow == s2.nStartRow ); + compare( ComputeSlotOffset( ScAddress( s1.nStopCol - 1, s1.nStartRow, 0 )), + ComputeSlotOffset( ScAddress( s1.nStopCol, s1.nStartRow, 0 )) - mnBcaSlotsCol, __LINE__ ); + } + } + } + // Iterate all slots. + ScRange range( ScAddress( 0, 0, 0 ), ScAddress( pDoc->MaxCol(), pDoc->MaxRow(), 0 )); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( range, nStart, nEnd, nRowBreak ); + assert( nStart == 0 ); + assert( nEnd == mnBcaSlots - 1 ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + std::unique_ptr<ScBroadcastAreaSlot*[]> slots( new ScBroadcastAreaSlot*[ mnBcaSlots ] ); // dummy, not accessed + ScBroadcastAreaSlot** ppSlots = slots.get(); + ScBroadcastAreaSlot** pp = ppSlots; + while ( nOff <= nEnd ) + { + SCSIZE previous = nOff; + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol); + compare( nOff, previous + 1, __LINE__ ); + } + // Iterate slots in the last row (each will differ by mnBcaSlotsCol). + range = ScRange( ScAddress( 0, pDoc->MaxRow(), 0 ), + ScAddress( pDoc->MaxCol(), pDoc->MaxRow() - 1, 0 )); + ComputeAreaPoints( range, nStart, nEnd, nRowBreak ); + assert( nStart == mnBcaSlotsCol - 1 ); + assert( nEnd == mnBcaSlots - 1 ); + nOff = nStart; + nBreak = nOff + nRowBreak; + ppSlots = slots.get(); + pp = ppSlots; + while ( nOff <= nEnd ) + { + SCSIZE previous = nOff; + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol); + compare( nOff, previous + mnBcaSlotsCol, __LINE__ ); + } +} +#endif + +void ScBroadcastAreaSlotMachine::StartListeningArea( + const ScRange& rRange, bool bGroupListening, SvtListener* pListener ) +{ + if ( rRange == BCA_LISTEN_ALWAYS ) + { + if ( !pBCAlways ) + pBCAlways.reset( new SvtBroadcaster ); + pListener->StartListening( *pBCAlways ); + } + else + { + // A new area needs to be inserted to the corresponding slots, for 3D + // ranges for all sheets, do not slice into per sheet areas or the + // !bDone will break too early (i.e. after the first sheet) if + // subsequent listeners are to be added. + ScBroadcastArea* pArea = nullptr; + bool bDone = false; + for (SCTAB nTab = rRange.aStart.Tab(); + !bDone && nTab <= rRange.aEnd.Tab(); ++nTab) + { + TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab)); + if (iTab == aTableSlotsMap.end()) + iTab = aTableSlotsMap.emplace(nTab, std::make_unique<TableSlots>(mnBcaSlots)).first; + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + while ( !bDone && nOff <= nEnd ) + { + if ( !*pp ) + *pp = new ScBroadcastAreaSlot( pDoc, this ); + if (!pArea) + { + // If the call to StartListeningArea didn't create the + // ScBroadcastArea, listeners were added to an already + // existing identical area that doesn't need to be inserted + // to slots again. + if (!(*pp)->StartListeningArea( rRange, bGroupListening, pListener, pArea)) + bDone = true; + } + else + (*pp)->InsertListeningArea( pArea); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol); + } + } + } +} + +void ScBroadcastAreaSlotMachine::EndListeningArea( + const ScRange& rRange, bool bGroupListening, SvtListener* pListener ) +{ + if ( rRange == BCA_LISTEN_ALWAYS ) + { + if ( pBCAlways ) + { + pListener->EndListening( *pBCAlways); + if (!pBCAlways->HasListeners()) + { + pBCAlways.reset(); + } + } + } + else + { + SCTAB nEndTab = rRange.aEnd.Tab(); + for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab) + { + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + ScBroadcastArea* pArea = nullptr; + if (nOff == 0 && nEnd == mnBcaSlots-1) + { + // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they + // happen for insertion and deletion of sheets. + ScBroadcastAreaSlot** const pStop = ppSlots + nEnd; + do + { + if ( *pp ) + (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea); + } while (++pp < pStop); + } + else + { + while ( nOff <= nEnd ) + { + if ( *pp ) + (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol); + } + } + } + } +} + +bool ScBroadcastAreaSlotMachine::AreaBroadcast( const ScRange& rRange, SfxHintId nHint ) +{ + bool bBroadcasted = false; + SCTAB nEndTab = rRange.aEnd.Tab(); + for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab) + { + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + while ( nOff <= nEnd ) + { + if ( *pp ) + bBroadcasted |= (*pp)->AreaBroadcast( rRange, nHint ); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol); + } + } + return bBroadcasted; +} + +bool ScBroadcastAreaSlotMachine::AreaBroadcast( const ScHint& rHint ) const +{ + const ScAddress& rAddress = rHint.GetStartAddress(); + if ( rAddress == BCA_BRDCST_ALWAYS ) + { + if ( pBCAlways ) + { + pBCAlways->Broadcast( rHint ); + return true; + } + else + return false; + } + else + { + TableSlotsMap::const_iterator iTab( aTableSlotsMap.find( rAddress.Tab())); + if (iTab == aTableSlotsMap.end()) + return false; + // Process all slots for the given row range. + ScRange broadcastRange( rAddress, + ScAddress( rAddress.Col(), rAddress.Row() + rHint.GetRowCount() - 1, rAddress.Tab())); + bool bBroadcasted = false; + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( broadcastRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + while ( nOff <= nEnd ) + { + if ( *pp ) + bBroadcasted |= (*pp)->AreaBroadcast( rHint ); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol); + } + return bBroadcasted; + } +} + +void ScBroadcastAreaSlotMachine::DelBroadcastAreasInRange( + const ScRange& rRange ) +{ + SCTAB nEndTab = rRange.aEnd.Tab(); + for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab) + { + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + if (nOff == 0 && nEnd == mnBcaSlots-1) + { + // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they + // happen for insertion and deletion of sheets. + ScBroadcastAreaSlot** const pStop = ppSlots + nEnd; + do + { + if ( *pp ) + (*pp)->DelBroadcastAreasInRange( rRange ); + } while (++pp < pStop); + } + else + { + while ( nOff <= nEnd ) + { + if ( *pp ) + (*pp)->DelBroadcastAreasInRange( rRange ); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol); + } + } + } +} + +// for all affected: remove, chain, update range, insert, and maybe delete +void ScBroadcastAreaSlotMachine::UpdateBroadcastAreas( + UpdateRefMode eUpdateRefMode, + const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz ) +{ + // remove affected and put in chain + SCTAB nEndTab = rRange.aEnd.Tab(); + for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab) + { + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + if (nOff == 0 && nEnd == mnBcaSlots-1) + { + // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they + // happen for insertion and deletion of sheets. + ScBroadcastAreaSlot** const pStop = ppSlots + nEnd; + do + { + if ( *pp ) + (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz ); + } while (++pp < pStop); + } + else + { + while ( nOff <= nEnd ) + { + if ( *pp ) + (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz ); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol); + } + } + } + + // Updating an area's range will modify the hash key, remove areas from all + // affected slots. Will be reinserted later with the updated range. + ScBroadcastArea* pChain = pUpdateChain; + while (pChain) + { + ScBroadcastArea* pArea = pChain; + pChain = pArea->GetUpdateChainNext(); + ScRange aRange( pArea->GetRange()); + // remove from slots + for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab() && pArea->GetRef(); ++nTab) + { + TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab)); + if (iTab == aTableSlotsMap.end()) + { + OSL_FAIL( "UpdateBroadcastAreas: Where's the TableSlot?!?"); + continue; // for + } + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + while ( nOff <= nEnd && pArea->GetRef() ) + { + if (*pp) + (*pp)->UpdateRemoveArea( pArea); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol); + } + } + + } + + // shift sheets + if (nDz) + { + if (nDz < 0) + { + TableSlotsMap::iterator iDel( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab() - nDz)); + // Remove sheets, if any, iDel or/and iTab may as well point to end(). + while (iDel != iTab) + { + iDel = aTableSlotsMap.erase(iDel); + } + // shift remaining down + while (iTab != aTableSlotsMap.end()) + { + SCTAB nTab = (*iTab).first + nDz; + aTableSlotsMap[nTab] = std::move((*iTab).second); + iTab = aTableSlotsMap.erase(iTab); + } + } + else + { + TableSlotsMap::iterator iStop( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + if (iStop != aTableSlotsMap.end()) + { + bool bStopIsBegin = (iStop == aTableSlotsMap.begin()); + if (!bStopIsBegin) + --iStop; + TableSlotsMap::iterator iTab( aTableSlotsMap.end()); + --iTab; + while (iTab != iStop) + { + SCTAB nTab = (*iTab).first + nDz; + aTableSlotsMap[nTab] = std::move((*iTab).second); + aTableSlotsMap.erase( iTab--); + } + // Shift the very first, iTab==iStop in this case. + if (bStopIsBegin) + { + SCTAB nTab = (*iTab).first + nDz; + aTableSlotsMap[nTab] = std::move((*iTab).second); + aTableSlotsMap.erase( iStop); + } + } + } + } + + // work off chain + SCCOL nCol1, nCol2, theCol1, theCol2; + SCROW nRow1, nRow2, theRow1, theRow2; + SCTAB nTab1, nTab2, theTab1, theTab2; + rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + while ( pUpdateChain ) + { + ScBroadcastArea* pArea = pUpdateChain; + ScRange aRange( pArea->GetRange()); + pUpdateChain = pArea->GetUpdateChainNext(); + + // update range + aRange.GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2); + if ( ScRefUpdate::Update( pDoc, eUpdateRefMode, + nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz, + theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 )) + { + aRange = ScRange( theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ); + pArea->UpdateRange( aRange ); + // For DDE and ScLookupCache + pArea->GetBroadcaster().Broadcast( ScAreaChangedHint( aRange ) ); + } + + // insert to slots + for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab(); ++nTab) + { + TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab)); + if (iTab == aTableSlotsMap.end()) + iTab = aTableSlotsMap.emplace(nTab, std::make_unique<TableSlots>(mnBcaSlots)).first; + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + while ( nOff <= nEnd ) + { + if (!*pp) + *pp = new ScBroadcastAreaSlot( pDoc, this ); + (*pp)->UpdateInsert( pArea ); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol); + } + } + + // unchain + pArea->SetUpdateChainNext( nullptr ); + pArea->SetInUpdateChain( false ); + + // Delete if not inserted to any slot. RemoveBulkArea(pArea) was + // already executed in UpdateRemove(). + if (!pArea->GetRef()) + delete pArea; + } + pEOUpdateChain = nullptr; +} + +void ScBroadcastAreaSlotMachine::EnterBulkBroadcast() +{ + ++nInBulkBroadcast; +} + +void ScBroadcastAreaSlotMachine::LeaveBulkBroadcast( SfxHintId nHintId ) +{ + if (nInBulkBroadcast <= 0) + return; + + if (--nInBulkBroadcast == 0) + { + ScBroadcastAreasBulk().swap( aBulkBroadcastAreas); + bool bBroadcasted = BulkBroadcastGroupAreas( nHintId ); + // Trigger the "final" tracking. + if (pDoc->IsTrackFormulasPending()) + pDoc->FinalTrackFormulas( nHintId ); + else if (bBroadcasted) + pDoc->TrackFormulas( nHintId ); + } +} + +bool ScBroadcastAreaSlotMachine::InsertBulkArea( const ScBroadcastArea* pArea ) +{ + return aBulkBroadcastAreas.insert( pArea ).second; +} + +void ScBroadcastAreaSlotMachine::InsertBulkGroupArea( ScBroadcastArea* pArea, const ScRange& rRange ) +{ + BulkGroupAreasType::iterator it = m_BulkGroupAreas.lower_bound(pArea); + if (it == m_BulkGroupAreas.end() || m_BulkGroupAreas.key_comp()(pArea, it->first)) + { + // Insert a new one. + it = m_BulkGroupAreas.insert(it, std::make_pair(pArea, sc::ColumnSpanSet())); + } + + sc::ColumnSpanSet& rSet = it->second; + rSet.set(*pDoc, rRange, true); +} + +bool ScBroadcastAreaSlotMachine::BulkBroadcastGroupAreas( SfxHintId nHintId ) +{ + if (m_BulkGroupAreas.empty()) + return false; + + sc::BulkDataHint aHint( *pDoc, nHintId); + + bool bBroadcasted = false; + for (const auto& [pArea, rSpans] : m_BulkGroupAreas) + { + assert(pArea); + SvtBroadcaster& rBC = pArea->GetBroadcaster(); + if (!rBC.HasListeners()) + { + /* FIXME: find the cause where the last listener is removed and + * this area is still listed here. */ + SAL_WARN("sc.core","ScBroadcastAreaSlotMachine::BulkBroadcastGroupAreas - pArea has no listeners and should had been removed already"); + } + else + { + aHint.setSpans(&rSpans); + rBC.Broadcast(aHint); + bBroadcasted = true; + } + } + + m_BulkGroupAreas.clear(); + + return bBroadcasted; +} + +size_t ScBroadcastAreaSlotMachine::RemoveBulkArea( const ScBroadcastArea* pArea ) +{ + return aBulkBroadcastAreas.erase( pArea ); +} + +void ScBroadcastAreaSlotMachine::RemoveBulkGroupArea( ScBroadcastArea* pArea ) +{ + m_BulkGroupAreas.erase(pArea); +} + +void ScBroadcastAreaSlotMachine::PushAreaToBeErased( ScBroadcastAreaSlot* pSlot, + ScBroadcastAreas::iterator& rIter ) +{ + maAreasToBeErased.emplace_back( pSlot, rIter); +} + +void ScBroadcastAreaSlotMachine::FinallyEraseAreas( ScBroadcastAreaSlot* pSlot ) +{ + SAL_WARN_IF( pSlot->IsInBroadcastIteration(), "sc.core", + "ScBroadcastAreaSlotMachine::FinallyEraseAreas: during iteration? NO!"); + if (pSlot->IsInBroadcastIteration()) + return; + + // maAreasToBeErased is a simple vector so erasing an element may + // invalidate iterators and would be inefficient anyway. Instead, copy + // elements to be preserved (usually none!) to temporary vector and swap. + AreasToBeErased aCopy; + for (auto& rArea : maAreasToBeErased) + { + if (rArea.first == pSlot) + pSlot->EraseArea( rArea.second); + else + aCopy.push_back( rArea); + } + maAreasToBeErased.swap( aCopy); +} + +std::vector<sc::AreaListener> ScBroadcastAreaSlotMachine::GetAllListeners( + const ScRange& rRange, sc::AreaOverlapType eType, sc::ListenerGroupType eGroup ) +{ + std::vector<sc::AreaListener> aRet; + + SCTAB nEndTab = rRange.aEnd.Tab(); + for (TableSlotsMap::const_iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab())); + iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab) + { + ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots(); + SCSIZE nStart, nEnd, nRowBreak; + ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak ); + SCSIZE nOff = nStart; + SCSIZE nBreak = nOff + nRowBreak; + ScBroadcastAreaSlot** pp = ppSlots + nOff; + while ( nOff <= nEnd ) + { + ScBroadcastAreaSlot* p = *pp; + if (p) + p->GetAllListeners(rRange, aRet, eType, eGroup); + ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol); + } + } + + return aRet; +} + +#if DEBUG_AREA_BROADCASTER +void ScBroadcastAreaSlotMachine::Dump() const +{ + cout << "slot distribution count: " << nBcaSlots << endl; + for (const auto& [rIndex, pTabSlots] : aTableSlotsMap) + { + cout << "-- sheet (index: " << rIndex << ")" << endl; + + assert(pTabSlots); + ScBroadcastAreaSlot** ppSlots = pTabSlots->getSlots(); + for (SCSIZE i = 0; i < nBcaSlots; ++i) + { + const ScBroadcastAreaSlot* pSlot = ppSlots[i]; + if (pSlot) + { + cout << "* slot " << i << endl; + pSlot->Dump(); + } + } + } +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |