Branch data Line data Source code
1 : : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : : /*************************************************************************
3 : : *
4 : : * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 : : *
6 : : * Copyright 2000, 2010 Oracle and/or its affiliates.
7 : : *
8 : : * OpenOffice.org - a multi-platform office productivity suite
9 : : *
10 : : * This file is part of OpenOffice.org.
11 : : *
12 : : * OpenOffice.org is free software: you can redistribute it and/or modify
13 : : * it under the terms of the GNU Lesser General Public License version 3
14 : : * only, as published by the Free Software Foundation.
15 : : *
16 : : * OpenOffice.org is distributed in the hope that it will be useful,
17 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 : : * GNU Lesser General Public License version 3 for more details
20 : : * (a copy is included in the LICENSE file that accompanied this code).
21 : : *
22 : : * You should have received a copy of the GNU Lesser General Public License
23 : : * version 3 along with OpenOffice.org. If not, see
24 : : * <http://www.openoffice.org/license.html>
25 : : * for a copy of the LGPLv3 License.
26 : : *
27 : : ************************************************************************/
28 : :
29 : : #include <sfx2/objsh.hxx>
30 : : #include <svl/listener.hxx>
31 : : #include <svl/listeneriter.hxx>
32 : :
33 : : #include "document.hxx"
34 : : #include "brdcst.hxx"
35 : : #include "bcaslot.hxx"
36 : : #include "scerrors.hxx"
37 : : #include "docoptio.hxx"
38 : : #include "refupdat.hxx"
39 : : #include "table.hxx"
40 : :
41 : : // Number of slots per dimension
42 : : // must be integer divisors of MAXCOLCOUNT respectively MAXROWCOUNT
43 : : #define BCA_SLOTS_COL ((MAXCOLCOUNT_DEFINE) / 16)
44 : : #if MAXROWCOUNT_DEFINE == 32000
45 : : #define BCA_SLOTS_ROW 256
46 : : #define BCA_SLICE 125
47 : : #else
48 : : #define BCA_SLICE 128
49 : : #define BCA_SLOTS_ROW ((MAXROWCOUNT_DEFINE) / BCA_SLICE)
50 : : #endif
51 : : #define BCA_SLOT_COLS ((MAXCOLCOUNT_DEFINE) / BCA_SLOTS_COL)
52 : : #define BCA_SLOT_ROWS ((MAXROWCOUNT_DEFINE) / BCA_SLOTS_ROW)
53 : : // multiple?
54 : : #if (BCA_SLOT_COLS * BCA_SLOTS_COL) != (MAXCOLCOUNT_DEFINE)
55 : : #error bad BCA_SLOTS_COL value!
56 : : #endif
57 : : #if (BCA_SLOT_ROWS * BCA_SLOTS_ROW) != (MAXROWCOUNT_DEFINE)
58 : : #error bad BCA_SLOTS_ROW value!
59 : : #endif
60 : : // size of slot array if linear
61 : : #define BCA_SLOTS_DEFINE (BCA_SLOTS_COL * BCA_SLOTS_ROW)
62 : : // Arbitrary 2**31/8, assuming size_t can hold at least 2^31 values and
63 : : // sizeof_ptr is at most 8 bytes. You'd probably doom your machine's memory
64 : : // anyway, once you reached these values..
65 : : #if BCA_SLOTS_DEFINE > 268435456
66 : : #error BCA_SLOTS_DEFINE DOOMed!
67 : : #endif
68 : :
69 : : // STATIC DATA -----------------------------------------------------------
70 : :
71 [ + + ][ + - ]: 12121 : TYPEINIT1( ScHint, SfxSimpleHint );
72 [ - + ][ - + ]: 9 : TYPEINIT1( ScAreaChangedHint, SfxHint );
73 : :
74 : : struct ScSlotData
75 : : {
76 : : SCROW nStartRow; // first row of this segment
77 : : SCROW nStopRow; // first row of next segment
78 : : SCSIZE nSlice; // slice size in this segment
79 : : SCSIZE nCumulated; // cumulated slots of previous segments
80 : :
81 : 306 : ScSlotData( SCROW r1, SCROW r2, SCSIZE s, SCSIZE c ) : nStartRow(r1), nStopRow(r2), nSlice(s), nCumulated(c) {}
82 : : };
83 : : typedef ::std::vector< ScSlotData > ScSlotDistribution;
84 : : #if MAXROWCOUNT_DEFINE <= 65536
85 : : // Linear distribution.
86 : : static ScSlotDistribution aSlotDistribution( ScSlotData( 0, MAXROWCOUNT, BCA_SLOT_ROWS, 0));
87 : : static SCSIZE nBcaSlotsRow = BCA_SLOTS_ROW;
88 : : static SCSIZE nBcaSlots = BCA_SLOTS_DEFINE;
89 : : #else
90 : : // Logarithmic or any other distribution.
91 : : // Upper sheet part usually is more populated and referenced and gets fine
92 : : // grained resolution, larger data in larger hunks.
93 : : // Could be further enhanced by also applying a different distribution of
94 : : // column slots.
95 : 51 : static SCSIZE initSlotDistribution( ScSlotDistribution & rSD, SCSIZE & rBSR )
96 : : {
97 : 51 : SCSIZE nSlots = 0;
98 : 51 : SCROW nRow1 = 0;
99 : 51 : SCROW nRow2 = 32*1024;
100 : 51 : SCSIZE nSlice = 128;
101 : : // Must be sorted by row1,row2!
102 [ + + ]: 357 : while (nRow2 <= MAXROWCOUNT)
103 : : {
104 [ + - ]: 306 : rSD.push_back( ScSlotData( nRow1, nRow2, nSlice, nSlots));
105 : 306 : nSlots += (nRow2 - nRow1) / nSlice;
106 : 306 : nRow1 = nRow2;
107 : 306 : nRow2 *= 2;
108 : 306 : nSlice *= 2;
109 : : }
110 : 51 : rBSR = nSlots;
111 : 51 : return nSlots;
112 : : }
113 : 51 : static ScSlotDistribution aSlotDistribution;
114 : : static SCSIZE nBcaSlotsRow;
115 : 51 : static SCSIZE nBcaSlots = initSlotDistribution( aSlotDistribution, nBcaSlotsRow) * BCA_SLOTS_COL;
116 : : // Ensure that all static variables are initialized with this one call.
117 : : #endif
118 : :
119 : :
120 : 2857 : ScBroadcastAreaSlot::ScBroadcastAreaSlot( ScDocument* pDocument,
121 : : ScBroadcastAreaSlotMachine* pBASMa ) :
122 : : aTmpSeekBroadcastArea( ScRange()),
123 : : pDoc( pDocument ),
124 [ + - ][ + - ]: 2857 : pBASM( pBASMa )
125 : : {
126 : 2857 : }
127 : :
128 : :
129 [ + - ]: 163 : ScBroadcastAreaSlot::~ScBroadcastAreaSlot()
130 : : {
131 [ + - ][ + + ]: 3928 : for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
132 [ + - ]: 3765 : aIter != aBroadcastAreaTbl.end(); /* none */)
133 : : {
134 : : // Prevent hash from accessing dangling pointer in case area is
135 : : // deleted.
136 [ + - ]: 3602 : ScBroadcastArea* pArea = *aIter;
137 : : // Erase all so no hash will be accessed upon destruction of the
138 : : // boost::unordered_map.
139 [ + - ][ + - ]: 3602 : aBroadcastAreaTbl.erase( aIter++);
140 [ + + ]: 3602 : if (!pArea->DecRef())
141 [ + - ][ + - ]: 3575 : delete pArea;
142 : : }
143 : 163 : }
144 : :
145 : :
146 : 13056 : bool ScBroadcastAreaSlot::CheckHardRecalcStateCondition() const
147 : : {
148 [ - + ]: 13056 : if ( pDoc->GetHardRecalcState() )
149 : 0 : return true;
150 [ - + ]: 13056 : if (aBroadcastAreaTbl.size() >= aBroadcastAreaTbl.max_size())
151 : : { // this is more hypothetical now, check existed for old SV_PTRARR_SORT
152 [ # # ]: 0 : if ( !pDoc->GetHardRecalcState() )
153 : : {
154 : 0 : SfxObjectShell* pShell = pDoc->GetDocumentShell();
155 : : OSL_ENSURE( pShell, "Missing DocShell :-/" );
156 : :
157 [ # # ]: 0 : if ( pShell )
158 [ # # ]: 0 : pShell->SetError( SCWARN_CORE_HARD_RECALC, ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( OSL_LOG_PREFIX ) ) );
159 : :
160 : 0 : pDoc->SetAutoCalc( false );
161 : 0 : pDoc->SetHardRecalcState( true );
162 : : }
163 : 0 : return true;
164 : : }
165 : 13056 : return false;
166 : : }
167 : :
168 : :
169 : 10344 : bool ScBroadcastAreaSlot::StartListeningArea( const ScRange& rRange,
170 : : SvtListener* pListener, ScBroadcastArea*& rpArea )
171 : : {
172 : 10344 : bool bNewArea = false;
173 : : OSL_ENSURE(pListener, "StartListeningArea: pListener Null");
174 [ - + ]: 10344 : if (CheckHardRecalcStateCondition())
175 : 0 : return false;
176 [ + - ]: 10344 : if ( !rpArea )
177 : : {
178 : : // Even if most times the area doesn't exist yet and immediately trying
179 : : // to new and insert it would save an attempt to find it, on mass
180 : : // operations like identical large [HV]LOOKUP() areas the new/delete
181 : : // would add quite some penalty for all but the first formula cell.
182 [ + - ]: 10344 : ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange));
183 [ + - ][ + + ]: 10344 : if (aIter != aBroadcastAreaTbl.end())
184 [ + - ]: 6682 : rpArea = *aIter;
185 : : else
186 : : {
187 [ + - ][ + - ]: 3662 : rpArea = new ScBroadcastArea( rRange);
188 [ + - ][ + - ]: 3662 : if (aBroadcastAreaTbl.insert( rpArea).second)
189 : : {
190 : 3662 : rpArea->IncRef();
191 : 3662 : bNewArea = true;
192 : : }
193 : : else
194 : : {
195 : : OSL_FAIL("StartListeningArea: area not found and not inserted in slot?!?");
196 [ # # ][ # # ]: 0 : delete rpArea;
197 : 0 : rpArea = 0;
198 : : }
199 : : }
200 [ + - ]: 10344 : if (rpArea)
201 [ + - ]: 10344 : pListener->StartListening( rpArea->GetBroadcaster());
202 : : }
203 : : else
204 : : {
205 [ # # ]: 0 : if (aBroadcastAreaTbl.insert( rpArea).second)
206 : 0 : rpArea->IncRef();
207 : : }
208 : 10344 : return bNewArea;
209 : : }
210 : :
211 : :
212 : 2712 : void ScBroadcastAreaSlot::InsertListeningArea( ScBroadcastArea* pArea )
213 : : {
214 : : OSL_ENSURE( pArea, "InsertListeningArea: pArea NULL");
215 [ - + ]: 2712 : if (CheckHardRecalcStateCondition())
216 : 2712 : return;
217 [ + - ]: 2712 : if (aBroadcastAreaTbl.insert( pArea).second)
218 : 2712 : pArea->IncRef();
219 : : }
220 : :
221 : :
222 : : // If rpArea != NULL then no listeners are stopped, only the area is removed
223 : : // and the reference count decremented.
224 : 282 : void ScBroadcastAreaSlot::EndListeningArea( const ScRange& rRange,
225 : : SvtListener* pListener, ScBroadcastArea*& rpArea )
226 : : {
227 : : OSL_ENSURE(pListener, "EndListeningArea: pListener Null");
228 [ + - ]: 282 : if ( !rpArea )
229 : : {
230 [ + - ]: 282 : ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange));
231 [ + - ][ + + ]: 282 : if (aIter == aBroadcastAreaTbl.end())
232 : : return;
233 [ + - ]: 279 : rpArea = *aIter;
234 [ + - ]: 279 : pListener->EndListening( rpArea->GetBroadcaster() );
235 [ + + ]: 279 : if ( !rpArea->GetBroadcaster().HasListeners() )
236 : : { // if nobody is listening we can dispose it
237 [ + - ]: 77 : aBroadcastAreaTbl.erase( aIter);
238 [ + - ]: 77 : if ( !rpArea->DecRef() )
239 : : {
240 [ + - ][ + - ]: 77 : delete rpArea;
241 : 282 : rpArea = NULL;
242 : : }
243 : : }
244 : : }
245 : : else
246 : : {
247 [ # # ]: 0 : if ( !rpArea->GetBroadcaster().HasListeners() )
248 : : {
249 [ # # ]: 0 : ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange));
250 [ # # ][ # # ]: 0 : if (aIter == aBroadcastAreaTbl.end())
251 : : return;
252 : : OSL_ENSURE( *aIter == rpArea, "EndListeningArea: area pointer mismatch");
253 [ # # ]: 0 : aBroadcastAreaTbl.erase( aIter);
254 [ # # ]: 0 : if ( !rpArea->DecRef() )
255 : : {
256 [ # # ][ # # ]: 0 : delete rpArea;
257 : 282 : rpArea = NULL;
258 : : }
259 : : }
260 : : }
261 : : }
262 : :
263 : :
264 : 10626 : ScBroadcastAreas::iterator ScBroadcastAreaSlot::FindBroadcastArea(
265 : : const ScRange& rRange ) const
266 : : {
267 : 10626 : aTmpSeekBroadcastArea.UpdateRange( rRange);
268 [ + - ]: 10626 : return aBroadcastAreaTbl.find( &aTmpSeekBroadcastArea);
269 : : }
270 : :
271 : :
272 : 2829 : sal_Bool ScBroadcastAreaSlot::AreaBroadcast( const ScHint& rHint) const
273 : : {
274 [ + + ]: 2829 : if (aBroadcastAreaTbl.empty())
275 : 837 : return false;
276 : 1992 : sal_Bool bIsBroadcasted = false;
277 : 1992 : const ScAddress& rAddress = rHint.GetAddress();
278 [ + + ][ + - ]: 6537 : for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin());
279 [ + - ]: 4545 : aIter != aBroadcastAreaTbl.end(); /* increment in body */ )
280 : : {
281 [ + - ]: 2553 : ScBroadcastArea* pArea = *aIter;
282 : : // A Notify() during broadcast may call EndListeningArea() and thus
283 : : // dispose this area if it was the last listener, which would
284 : : // invalidate the iterator, hence increment before call.
285 : 2553 : ++aIter;
286 : 2553 : const ScRange& rAreaRange = pArea->GetRange();
287 [ + + ]: 2553 : if (rAreaRange.In( rAddress))
288 : : {
289 [ + + ][ + - ]: 472 : if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
[ + + ][ + + ]
290 : : {
291 [ + - ]: 266 : pArea->GetBroadcaster().Broadcast( rHint);
292 : 266 : bIsBroadcasted = sal_True;
293 : : }
294 : : }
295 : : }
296 : 2829 : return bIsBroadcasted;
297 : : }
298 : :
299 : :
300 : 4 : sal_Bool ScBroadcastAreaSlot::AreaBroadcastInRange( const ScRange& rRange,
301 : : const ScHint& rHint) const
302 : : {
303 [ + + ]: 4 : if (aBroadcastAreaTbl.empty())
304 : 1 : return false;
305 : 3 : sal_Bool bIsBroadcasted = false;
306 [ + - ][ + + ]: 9 : for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin());
307 [ + - ]: 6 : aIter != aBroadcastAreaTbl.end(); /* increment in body */ )
308 : : {
309 [ + - ]: 3 : ScBroadcastArea* pArea = *aIter;
310 : : // A Notify() during broadcast may call EndListeningArea() and thus
311 : : // dispose this area if it was the last listener, which would
312 : : // invalidate the iterator, hence increment before call.
313 : 3 : ++aIter;
314 : 3 : const ScRange& rAreaRange = pArea->GetRange();
315 [ - + ][ + - ]: 3 : if (rAreaRange.Intersects( rRange ))
316 : : {
317 [ # # ][ # # ]: 0 : if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
[ # # ][ # # ]
318 : : {
319 [ # # ]: 0 : pArea->GetBroadcaster().Broadcast( rHint);
320 : 0 : bIsBroadcasted = sal_True;
321 : : }
322 : : }
323 : : }
324 : 4 : return bIsBroadcasted;
325 : : }
326 : :
327 : :
328 : 6 : void ScBroadcastAreaSlot::DelBroadcastAreasInRange( const ScRange& rRange )
329 : : {
330 [ - + ]: 6 : if (aBroadcastAreaTbl.empty())
331 : 6 : return;
332 [ + - ][ + + ]: 21 : for (ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
333 [ + - ]: 15 : aIter != aBroadcastAreaTbl.end(); /* increment in body */ )
334 : : {
335 [ + - ]: 9 : const ScRange& rAreaRange = (*aIter)->GetRange();
336 [ + + ]: 9 : if (rRange.In( rAreaRange))
337 : : {
338 [ + - ]: 3 : ScBroadcastArea* pArea = *aIter;
339 [ + - ][ + - ]: 3 : aBroadcastAreaTbl.erase( aIter++); // erase before modifying
340 [ + - ]: 3 : if (!pArea->DecRef())
341 : : {
342 [ - + ]: 3 : if (pBASM->IsInBulkBroadcast())
343 [ # # ]: 0 : pBASM->RemoveBulkArea( pArea);
344 [ + - ][ + - ]: 3 : delete pArea;
345 : : }
346 : : }
347 : : else
348 : 6 : ++aIter;
349 : : }
350 : : }
351 : :
352 : :
353 : 6 : void ScBroadcastAreaSlot::UpdateRemove( UpdateRefMode eUpdateRefMode,
354 : : const ScRange& rRange, SCsCOL nDx, SCsROW nDy, SCsTAB nDz )
355 : : {
356 [ + + ]: 6 : if (aBroadcastAreaTbl.empty())
357 : 6 : return;
358 : :
359 : : SCCOL nCol1, nCol2, theCol1, theCol2;
360 : : SCROW nRow1, nRow2, theRow1, theRow2;
361 : : SCTAB nTab1, nTab2, theTab1, theTab2;
362 : 3 : rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
363 [ + + ][ + - ]: 15 : for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
364 [ + - ]: 9 : aIter != aBroadcastAreaTbl.end(); /* increment in body */ )
365 : : {
366 [ + - ]: 6 : ScBroadcastArea* pArea = *aIter;
367 [ - + ]: 6 : if ( pArea->IsInUpdateChain() )
368 : : {
369 [ # # ][ # # ]: 0 : aBroadcastAreaTbl.erase( aIter++);
370 : 0 : pArea->DecRef();
371 : : }
372 : : else
373 : : {
374 : 6 : pArea->GetRange().GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2);
375 [ + + ]: 6 : if ( ScRefUpdate::Update( pDoc, eUpdateRefMode,
376 : : nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz,
377 [ + - ]: 6 : theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ))
378 : : {
379 [ + - ][ + - ]: 3 : aBroadcastAreaTbl.erase( aIter++);
380 : 3 : pArea->DecRef();
381 [ - + ]: 3 : if (pBASM->IsInBulkBroadcast())
382 [ # # ]: 0 : pBASM->RemoveBulkArea( pArea);
383 : 3 : pArea->SetInUpdateChain( sal_True );
384 : 3 : ScBroadcastArea* pUC = pBASM->GetEOUpdateChain();
385 [ + + ]: 3 : if ( pUC )
386 : 1 : pUC->SetUpdateChainNext( pArea );
387 : : else // no tail => no head
388 : 2 : pBASM->SetUpdateChain( pArea );
389 : 3 : pBASM->SetEOUpdateChain( pArea );
390 : : }
391 : : else
392 : 3 : ++aIter;
393 : : }
394 : : }
395 : : }
396 : :
397 : :
398 : 0 : void ScBroadcastAreaSlot::UpdateRemoveArea( ScBroadcastArea* pArea )
399 : : {
400 [ # # ]: 0 : ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.find( pArea));
401 [ # # ][ # # ]: 0 : if (aIter == aBroadcastAreaTbl.end())
402 : 0 : return;
403 [ # # ][ # # ]: 0 : if (*aIter != pArea)
404 : : OSL_FAIL( "UpdateRemoveArea: area pointer mismatch");
405 : : else
406 : : {
407 [ # # ]: 0 : aBroadcastAreaTbl.erase( aIter);
408 : 0 : pArea->DecRef();
409 : : }
410 : : }
411 : :
412 : :
413 : 3 : void ScBroadcastAreaSlot::UpdateInsert( ScBroadcastArea* pArea )
414 : : {
415 : : ::std::pair< ScBroadcastAreas::iterator, bool > aPair =
416 [ + - ]: 3 : aBroadcastAreaTbl.insert( pArea );
417 [ + + ]: 3 : if (aPair.second)
418 : 2 : pArea->IncRef();
419 : : else
420 : : {
421 : : // Identical area already exists, add listeners.
422 [ + - ]: 1 : ScBroadcastArea* pTarget = *(aPair.first);
423 [ + - ]: 1 : if (pArea != pTarget)
424 : : {
425 : 1 : SvtBroadcaster& rTarget = pTarget->GetBroadcaster();
426 [ + - ]: 1 : SvtListenerIter it( pArea->GetBroadcaster());
427 [ + - ][ + - ]: 2 : for (SvtListener* pListener = it.GetCurr(); pListener;
[ + + ]
428 : : pListener = it.GoNext())
429 : : {
430 [ + - ]: 1 : pListener->StartListening( rTarget);
431 [ + - ]: 1 : }
432 : : }
433 : : }
434 : 3 : }
435 : :
436 : :
437 : : // --- ScBroadcastAreaSlotMachine -------------------------------------
438 : :
439 : 145 : ScBroadcastAreaSlotMachine::TableSlots::TableSlots()
440 : : {
441 : 145 : ppSlots = new ScBroadcastAreaSlot* [ nBcaSlots ];
442 : 145 : memset( ppSlots, 0 , sizeof( ScBroadcastAreaSlot* ) * nBcaSlots );
443 : 145 : }
444 : :
445 : :
446 : 136 : ScBroadcastAreaSlotMachine::TableSlots::~TableSlots()
447 : : {
448 [ + + ]: 7798920 : for ( ScBroadcastAreaSlot** pp = ppSlots + nBcaSlots; --pp >= ppSlots; /* nothing */ )
449 : : {
450 [ + + ]: 7798784 : if (*pp)
451 [ + - ]: 163 : delete *pp;
452 : : }
453 [ + - ]: 136 : delete [] ppSlots;
454 : 136 : }
455 : :
456 : :
457 : 846 : ScBroadcastAreaSlotMachine::ScBroadcastAreaSlotMachine(
458 : : ScDocument* pDocument ) :
459 : : pBCAlways( NULL ),
460 : : pDoc( pDocument ),
461 : : pUpdateChain( NULL ),
462 : : pEOUpdateChain( NULL ),
463 [ + - ][ + - ]: 846 : nInBulkBroadcast( 0 )
464 : : {
465 : 846 : }
466 : :
467 : :
468 : 695 : ScBroadcastAreaSlotMachine::~ScBroadcastAreaSlotMachine()
469 : : {
470 [ + + ]: 1656 : for (TableSlotsMap::iterator iTab( aTableSlotsMap.begin());
471 : 828 : iTab != aTableSlotsMap.end(); ++iTab)
472 : : {
473 [ + - ][ + - ]: 133 : delete (*iTab).second;
474 : : }
475 [ + + ][ + - ]: 695 : delete pBCAlways;
476 : 695 : }
477 : :
478 : :
479 : 35022 : inline SCSIZE ScBroadcastAreaSlotMachine::ComputeSlotOffset(
480 : : const ScAddress& rAddress ) const
481 : : {
482 : 35022 : SCROW nRow = rAddress.Row();
483 : 35022 : SCCOL nCol = rAddress.Col();
484 [ - + ][ - + ]: 35022 : if ( !ValidRow(nRow) || !ValidCol(nCol) )
[ + - ]
485 : : {
486 : : OSL_FAIL( "Row/Col invalid, using first slot!" );
487 : 0 : return 0;
488 : : }
489 [ + - ]: 35142 : for (size_t i=0; i < aSlotDistribution.size(); ++i)
490 : : {
491 [ + + ]: 35142 : if (nRow < aSlotDistribution[i].nStopRow)
492 : : {
493 : 35022 : const ScSlotData& rSD = aSlotDistribution[i];
494 : : return rSD.nCumulated +
495 : : (static_cast<SCSIZE>(nRow - rSD.nStartRow)) / rSD.nSlice +
496 : 35022 : static_cast<SCSIZE>(nCol) / BCA_SLOT_COLS * nBcaSlotsRow;
497 : : }
498 : : }
499 : : OSL_FAIL( "No slot found, using last!" );
500 : 35022 : return nBcaSlots - 1;
501 : : }
502 : :
503 : :
504 : 10645 : void ScBroadcastAreaSlotMachine::ComputeAreaPoints( const ScRange& rRange,
505 : : SCSIZE& rStart, SCSIZE& rEnd, SCSIZE& rRowBreak ) const
506 : : {
507 : 10645 : rStart = ComputeSlotOffset( rRange.aStart );
508 : 10645 : rEnd = ComputeSlotOffset( rRange.aEnd );
509 : : // count of row slots per column minus one
510 : : rRowBreak = ComputeSlotOffset(
511 [ + - ]: 10645 : ScAddress( rRange.aStart.Col(), rRange.aEnd.Row(), 0 ) ) - rStart;
512 : 10645 : }
513 : :
514 : :
515 : 13537 : inline void ComputeNextSlot( SCSIZE & nOff, SCSIZE & nBreak, ScBroadcastAreaSlot** & pp,
516 : : SCSIZE & nStart, ScBroadcastAreaSlot** const & ppSlots, SCSIZE const & nRowBreak )
517 : : {
518 [ + + ]: 13537 : if ( nOff < nBreak )
519 : : {
520 : 2712 : ++nOff;
521 : 2712 : ++pp;
522 : : }
523 : : else
524 : : {
525 : 10825 : nStart += nBcaSlotsRow;
526 : 10825 : nOff = nStart;
527 : 10825 : pp = ppSlots + nOff;
528 : 10825 : nBreak = nOff + nRowBreak;
529 : : }
530 : 13537 : }
531 : :
532 : :
533 : 10410 : void ScBroadcastAreaSlotMachine::StartListeningArea( const ScRange& rRange,
534 : : SvtListener* pListener )
535 : : {
536 [ + + ]: 10410 : if ( rRange == BCA_LISTEN_ALWAYS )
537 : : {
538 [ + + ]: 66 : if ( !pBCAlways )
539 [ + - ]: 15 : pBCAlways = new SvtBroadcaster;
540 : 66 : pListener->StartListening( *pBCAlways );
541 : : }
542 : : else
543 : : {
544 : 10344 : bool bDone = false;
545 [ + + + + ]: 34694 : for (SCTAB nTab = rRange.aStart.Tab();
[ + + ]
546 : 14006 : !bDone && nTab <= rRange.aEnd.Tab(); ++nTab)
547 : : {
548 [ + - ]: 10344 : TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
549 [ + + ]: 10344 : if (iTab == aTableSlotsMap.end())
550 : : iTab = aTableSlotsMap.insert( TableSlotsMap::value_type(
551 [ + - ][ + - ]: 145 : nTab, new TableSlots)).first;
[ + - ]
552 : 10344 : ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
553 : : SCSIZE nStart, nEnd, nRowBreak;
554 [ + - ]: 10344 : ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
555 : 10344 : SCSIZE nOff = nStart;
556 : 10344 : SCSIZE nBreak = nOff + nRowBreak;
557 : 10344 : ScBroadcastAreaSlot** pp = ppSlots + nOff;
558 : 10344 : ScBroadcastArea* pArea = NULL;
559 [ + + ][ + + ]: 23400 : while ( !bDone && nOff <= nEnd )
[ + + ]
560 : : {
561 [ + + ]: 13056 : if ( !*pp )
562 [ + - ][ + - ]: 2857 : *pp = new ScBroadcastAreaSlot( pDoc, this );
563 [ + + ]: 13056 : if (!pArea)
564 : : {
565 : : // If the call to StartListeningArea didn't create the
566 : : // ScBroadcastArea, listeners were added to an already
567 : : // existing identical area that doesn't need to be inserted
568 : : // to slots again.
569 [ + - ][ + + ]: 10344 : if (!(*pp)->StartListeningArea( rRange, pListener, pArea))
570 : 6682 : bDone = true;
571 : : }
572 : : else
573 [ + - ]: 2712 : (*pp)->InsertListeningArea( pArea);
574 : 13056 : ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak);
575 : : }
576 : : }
577 : : }
578 : 10410 : }
579 : :
580 : :
581 : 2769 : void ScBroadcastAreaSlotMachine::EndListeningArea( const ScRange& rRange,
582 : : SvtListener* pListener )
583 : : {
584 [ + + ]: 2769 : if ( rRange == BCA_LISTEN_ALWAYS )
585 : : {
586 [ + + ]: 2487 : if ( pBCAlways )
587 : : {
588 : 711 : pListener->EndListening( *pBCAlways);
589 [ - + ]: 711 : if (!pBCAlways->HasListeners())
590 : : {
591 [ # # ]: 0 : delete pBCAlways;
592 : 0 : pBCAlways = NULL;
593 : : }
594 : : }
595 : : }
596 : : else
597 : : {
598 : 282 : SCTAB nEndTab = rRange.aEnd.Tab();
599 [ + + + + ]: 1992 : for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
[ + + ][ + - ]
600 [ + - ]: 1428 : iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
601 : : {
602 : 282 : ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
603 : : SCSIZE nStart, nEnd, nRowBreak;
604 [ + - ]: 282 : ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
605 : 282 : SCSIZE nOff = nStart;
606 : 282 : SCSIZE nBreak = nOff + nRowBreak;
607 : 282 : ScBroadcastAreaSlot** pp = ppSlots + nOff;
608 : 282 : ScBroadcastArea* pArea = NULL;
609 [ + - ][ - + ]: 282 : if (nOff == 0 && nEnd == nBcaSlots-1)
610 : : {
611 : : // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
612 : : // happen for insertion and deletion of sheets.
613 : 0 : ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
614 [ # # ]: 0 : do
615 : : {
616 [ # # ]: 0 : if ( *pp )
617 [ # # ]: 0 : (*pp)->EndListeningArea( rRange, pListener, pArea );
618 : 0 : } while (++pp < pStop);
619 : : }
620 : : else
621 : : {
622 [ + + ]: 564 : while ( nOff <= nEnd )
623 : : {
624 [ + - ]: 282 : if ( *pp )
625 [ + - ]: 282 : (*pp)->EndListeningArea( rRange, pListener, pArea );
626 : 282 : ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak);
627 : : }
628 : : }
629 : : }
630 : : }
631 : 2769 : }
632 : :
633 : :
634 : 43916 : sal_Bool ScBroadcastAreaSlotMachine::AreaBroadcast( const ScHint& rHint ) const
635 : : {
636 : 43916 : const ScAddress& rAddress = rHint.GetAddress();
637 [ + + ]: 43916 : if ( rAddress == BCA_BRDCST_ALWAYS )
638 : : {
639 [ - + ]: 9929 : if ( pBCAlways )
640 : : {
641 : 0 : pBCAlways->Broadcast( rHint );
642 : 0 : return sal_True;
643 : : }
644 : : else
645 : 9929 : return false;
646 : : }
647 : : else
648 : : {
649 [ + - ]: 33987 : TableSlotsMap::const_iterator iTab( aTableSlotsMap.find( rAddress.Tab()));
650 [ + + ]: 33987 : if (iTab == aTableSlotsMap.end())
651 : 30900 : return false;
652 : 3087 : ScBroadcastAreaSlot* pSlot = (*iTab).second->getAreaSlot(
653 [ + - ]: 6174 : ComputeSlotOffset( rAddress));
654 [ + + ]: 3087 : if ( pSlot )
655 [ + - ]: 2829 : return pSlot->AreaBroadcast( rHint );
656 : : else
657 : 43916 : return false;
658 : : }
659 : : }
660 : :
661 : :
662 : 13 : sal_Bool ScBroadcastAreaSlotMachine::AreaBroadcastInRange( const ScRange& rRange,
663 : : const ScHint& rHint ) const
664 : : {
665 : 13 : sal_Bool bBroadcasted = false;
666 : 13 : SCTAB nEndTab = rRange.aEnd.Tab();
667 [ + + + - ]: 55 : for (TableSlotsMap::const_iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
[ + + ][ + - ]
668 [ + - ]: 38 : iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
669 : : {
670 : 4 : ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
671 : : SCSIZE nStart, nEnd, nRowBreak;
672 [ + - ]: 4 : ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
673 : 4 : SCSIZE nOff = nStart;
674 : 4 : SCSIZE nBreak = nOff + nRowBreak;
675 : 4 : ScBroadcastAreaSlot** pp = ppSlots + nOff;
676 [ + + ]: 8 : while ( nOff <= nEnd )
677 : : {
678 [ + - ]: 4 : if ( *pp )
679 [ + - ]: 4 : bBroadcasted |= (*pp)->AreaBroadcastInRange( rRange, rHint );
680 : 4 : ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak);
681 : : }
682 : : }
683 : 13 : return bBroadcasted;
684 : : }
685 : :
686 : :
687 : 128 : void ScBroadcastAreaSlotMachine::DelBroadcastAreasInRange(
688 : : const ScRange& rRange )
689 : : {
690 : 128 : SCTAB nEndTab = rRange.aEnd.Tab();
691 [ + + + - ]: 408 : for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
[ + + ][ + - ]
692 [ + - ]: 274 : iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
693 : : {
694 : 6 : ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
695 : : SCSIZE nStart, nEnd, nRowBreak;
696 [ + - ]: 6 : ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
697 : 6 : SCSIZE nOff = nStart;
698 : 6 : SCSIZE nBreak = nOff + nRowBreak;
699 : 6 : ScBroadcastAreaSlot** pp = ppSlots + nOff;
700 [ + - ][ + + ]: 6 : if (nOff == 0 && nEnd == nBcaSlots-1)
701 : : {
702 : : // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
703 : : // happen for insertion and deletion of sheets.
704 : 3 : ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
705 [ + + ]: 172029 : do
706 : : {
707 [ + + ]: 172029 : if ( *pp )
708 [ + - ]: 3 : (*pp)->DelBroadcastAreasInRange( rRange );
709 : 3 : } while (++pp < pStop);
710 : : }
711 : : else
712 : : {
713 [ + + ]: 195 : while ( nOff <= nEnd )
714 : : {
715 [ + + ]: 192 : if ( *pp )
716 [ + - ]: 3 : (*pp)->DelBroadcastAreasInRange( rRange );
717 : 192 : ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak);
718 : : }
719 : : }
720 : : }
721 : 128 : }
722 : :
723 : :
724 : : // for all affected: remove, chain, update range, insert, and maybe delete
725 : 221 : void ScBroadcastAreaSlotMachine::UpdateBroadcastAreas(
726 : : UpdateRefMode eUpdateRefMode,
727 : : const ScRange& rRange, SCsCOL nDx, SCsROW nDy, SCsTAB nDz )
728 : : {
729 : : // remove affected and put in chain
730 : 221 : SCTAB nEndTab = rRange.aEnd.Tab();
731 [ + + + - ]: 687 : for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
[ + + ][ + - ]
732 [ + - ]: 460 : iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
733 : : {
734 : 6 : ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
735 : : SCSIZE nStart, nEnd, nRowBreak;
736 [ + - ]: 6 : ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
737 : 6 : SCSIZE nOff = nStart;
738 : 6 : SCSIZE nBreak = nOff + nRowBreak;
739 : 6 : ScBroadcastAreaSlot** pp = ppSlots + nOff;
740 [ + - ][ + - ]: 6 : if (nOff == 0 && nEnd == nBcaSlots-1)
741 : : {
742 : : // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
743 : : // happen for insertion and deletion of sheets.
744 : 6 : ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
745 [ + + ]: 344058 : do
746 : : {
747 [ + + ]: 344058 : if ( *pp )
748 [ + - ]: 6 : (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz );
749 : 6 : } while (++pp < pStop);
750 : : }
751 : : else
752 : : {
753 [ # # ]: 0 : while ( nOff <= nEnd )
754 : : {
755 [ # # ]: 0 : if ( *pp )
756 [ # # ]: 0 : (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz );
757 : 0 : ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak);
758 : : }
759 : : }
760 : : }
761 : :
762 : : // Updating an area's range will modify the hash key, remove areas from all
763 : : // affected slots. Will be reinserted later with the updated range.
764 : 221 : ScBroadcastArea* pChain = pUpdateChain;
765 [ + + ]: 224 : while (pChain)
766 : : {
767 : 3 : ScBroadcastArea* pArea = pChain;
768 : 3 : pChain = pArea->GetUpdateChainNext();
769 : 3 : ScRange aRange( pArea->GetRange());
770 : : // remove from slots
771 [ + - ][ - + ]: 3 : for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab() && pArea->GetRef(); ++nTab)
[ - + ]
772 : : {
773 [ # # ]: 0 : TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
774 [ # # ]: 0 : if (iTab == aTableSlotsMap.end())
775 : : {
776 : : OSL_FAIL( "UpdateBroadcastAreas: Where's the TableSlot?!?");
777 : 0 : continue; // for
778 : : }
779 : 0 : ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
780 : : SCSIZE nStart, nEnd, nRowBreak;
781 [ # # ]: 0 : ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak );
782 : 0 : SCSIZE nOff = nStart;
783 : 0 : SCSIZE nBreak = nOff + nRowBreak;
784 : 0 : ScBroadcastAreaSlot** pp = ppSlots + nOff;
785 [ # # ][ # # ]: 0 : while ( nOff <= nEnd && pArea->GetRef() )
[ # # ]
786 : : {
787 [ # # ]: 0 : if (*pp)
788 [ # # ]: 0 : (*pp)->UpdateRemoveArea( pArea);
789 : 0 : ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak);
790 : : }
791 : : }
792 : :
793 : : }
794 : :
795 : : // shift sheets
796 [ + + ]: 221 : if (nDz)
797 : : {
798 [ + + ]: 181 : if (nDz < 0)
799 : : {
800 [ + - ]: 112 : TableSlotsMap::iterator iDel( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
801 [ + - ]: 112 : TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab() - nDz));
802 : : // Remove sheets, if any, iDel or/and iTab may as well point to end().
803 [ + + ]: 115 : while (iDel != iTab)
804 : : {
805 [ + - ][ + - ]: 3 : delete (*iDel).second;
806 [ + - ]: 3 : aTableSlotsMap.erase( iDel++);
807 : : }
808 : : // shift remaining down
809 [ - + ]: 112 : while (iTab != aTableSlotsMap.end())
810 : : {
811 : 0 : SCTAB nTab = (*iTab).first + nDz;
812 [ # # ]: 0 : aTableSlotsMap[nTab] = (*iTab).second;
813 [ # # ]: 0 : aTableSlotsMap.erase( iTab++);
814 : : }
815 : : }
816 : : else
817 : : {
818 [ + - ]: 69 : TableSlotsMap::iterator iStop( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
819 [ - + ]: 69 : if (iStop != aTableSlotsMap.end())
820 : : {
821 : 0 : bool bStopIsBegin = (iStop == aTableSlotsMap.begin());
822 [ # # ]: 0 : if (!bStopIsBegin)
823 : 0 : --iStop;
824 : 0 : TableSlotsMap::iterator iTab( aTableSlotsMap.end());
825 : 0 : --iTab;
826 [ # # ]: 0 : while (iTab != iStop)
827 : : {
828 : 0 : SCTAB nTab = (*iTab).first + nDz;
829 [ # # ]: 0 : aTableSlotsMap[nTab] = (*iTab).second;
830 [ # # ]: 0 : aTableSlotsMap.erase( iTab--);
831 : : }
832 : : // Shift the very first, iTab==iStop in this case.
833 [ # # ]: 0 : if (bStopIsBegin)
834 : : {
835 : 0 : SCTAB nTab = (*iTab).first + nDz;
836 [ # # ]: 0 : aTableSlotsMap[nTab] = (*iTab).second;
837 [ # # ]: 69 : aTableSlotsMap.erase( iStop);
838 : : }
839 : : }
840 : : }
841 : : }
842 : :
843 : : // work off chain
844 : : SCCOL nCol1, nCol2, theCol1, theCol2;
845 : : SCROW nRow1, nRow2, theRow1, theRow2;
846 : : SCTAB nTab1, nTab2, theTab1, theTab2;
847 : 221 : rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
848 [ + + ]: 224 : while ( pUpdateChain )
849 : : {
850 : 3 : ScBroadcastArea* pArea = pUpdateChain;
851 : 3 : ScRange aRange( pArea->GetRange());
852 : 3 : pUpdateChain = pArea->GetUpdateChainNext();
853 : :
854 : : // update range
855 : 3 : aRange.GetVars( theCol1, theRow1, theTab1, theCol2, theRow2, theTab2);
856 [ + - ]: 3 : if ( ScRefUpdate::Update( pDoc, eUpdateRefMode,
857 : : nCol1,nRow1,nTab1, nCol2,nRow2,nTab2, nDx,nDy,nDz,
858 [ + - ]: 3 : theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ))
859 : : {
860 : 3 : aRange = ScRange( theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 );
861 : 3 : pArea->UpdateRange( aRange );
862 [ + - ][ + - ]: 3 : pArea->GetBroadcaster().Broadcast( ScAreaChangedHint( aRange ) ); // for DDE
[ + - ]
863 : : }
864 : :
865 : : // insert to slots
866 [ + + ]: 6 : for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab(); ++nTab)
867 : : {
868 [ + - ]: 3 : TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
869 [ - + ]: 3 : if (iTab == aTableSlotsMap.end())
870 : : iTab = aTableSlotsMap.insert( TableSlotsMap::value_type(
871 [ # # ][ # # ]: 0 : nTab, new TableSlots)).first;
[ # # ]
872 : 3 : ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
873 : : SCSIZE nStart, nEnd, nRowBreak;
874 [ + - ]: 3 : ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak );
875 : 3 : SCSIZE nOff = nStart;
876 : 3 : SCSIZE nBreak = nOff + nRowBreak;
877 : 3 : ScBroadcastAreaSlot** pp = ppSlots + nOff;
878 [ + + ]: 6 : while ( nOff <= nEnd )
879 : : {
880 [ - + ]: 3 : if (!*pp)
881 [ # # ][ # # ]: 0 : *pp = new ScBroadcastAreaSlot( pDoc, this );
882 [ + - ]: 3 : (*pp)->UpdateInsert( pArea );
883 : 3 : ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak);
884 : : }
885 : : }
886 : :
887 : : // unchain
888 : 3 : pArea->SetUpdateChainNext( NULL );
889 : 3 : pArea->SetInUpdateChain( false );
890 : :
891 : : // Delete if not inserted to any slot. RemoveBulkArea(pArea) was
892 : : // already executed in UpdateRemove().
893 [ + + ]: 3 : if (!pArea->GetRef())
894 [ + - ][ + - ]: 3 : delete pArea;
895 : : }
896 : 221 : pEOUpdateChain = NULL;
897 : 221 : }
898 : :
899 : :
900 : 42678 : void ScBroadcastAreaSlotMachine::EnterBulkBroadcast()
901 : : {
902 : 42678 : ++nInBulkBroadcast;
903 : 42678 : }
904 : :
905 : :
906 : 42678 : void ScBroadcastAreaSlotMachine::LeaveBulkBroadcast()
907 : : {
908 [ + - ]: 42678 : if (nInBulkBroadcast > 0)
909 : : {
910 [ + + ]: 42678 : if (--nInBulkBroadcast == 0)
911 [ + - ][ + - ]: 39407 : ScBroadcastAreasBulk().swap( aBulkBroadcastAreas);
[ + - ]
912 : : }
913 : 42678 : }
914 : :
915 : :
916 : 432 : bool ScBroadcastAreaSlotMachine::InsertBulkArea( const ScBroadcastArea* pArea )
917 : : {
918 : 432 : return aBulkBroadcastAreas.insert( pArea ).second;
919 : : }
920 : :
921 : :
922 : 0 : size_t ScBroadcastAreaSlotMachine::RemoveBulkArea( const ScBroadcastArea* pArea )
923 : : {
924 : 0 : return aBulkBroadcastAreas.erase( pArea );
925 [ + - ][ + - ]: 153 : }
926 : :
927 : : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|