fix baseline build (old cairo) - 'cairo_rectangle_int_t' does not name a type
[LibreOffice.git] / sc / source / core / tool / chartlis.cxx
blobdf74b9c3dafeb747559038383683fc1fba2f3560
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <vcl/svapp.hxx>
22 #include "chartlis.hxx"
23 #include "brdcst.hxx"
24 #include "document.hxx"
25 #include "reftokenhelper.hxx"
27 #include <boost/checked_delete.hpp>
29 using namespace com::sun::star;
30 using ::std::vector;
31 using ::std::list;
32 using ::std::unary_function;
33 using ::std::for_each;
35 // Update chart listeners quickly, to get a similar behavior to loaded charts
36 // which register UNO listeners.
38 class ScChartUnoData
40 uno::Reference< chart::XChartDataChangeEventListener > xListener;
41 uno::Reference< chart::XChartData > xSource;
43 public:
44 ScChartUnoData( const uno::Reference< chart::XChartDataChangeEventListener >& rL,
45 const uno::Reference< chart::XChartData >& rS ) :
46 xListener( rL ), xSource( rS ) {}
47 ~ScChartUnoData() {}
49 const uno::Reference< chart::XChartDataChangeEventListener >& GetListener() const { return xListener; }
50 const uno::Reference< chart::XChartData >& GetSource() const { return xSource; }
53 // ScChartListener
54 ScChartListener::ExternalRefListener::ExternalRefListener(ScChartListener& rParent, ScDocument* pDoc) :
55 mrParent(rParent), mpDoc(pDoc)
59 ScChartListener::ExternalRefListener::~ExternalRefListener()
61 if (!mpDoc || mpDoc->IsInDtorClear())
62 // The document is being destroyed. Do nothing.
63 return;
65 // Make sure to remove all pointers to this object.
66 mpDoc->GetExternalRefManager()->removeLinkListener(this);
69 void ScChartListener::ExternalRefListener::notify(sal_uInt16 nFileId, ScExternalRefManager::LinkUpdateType eType)
71 switch (eType)
73 case ScExternalRefManager::LINK_MODIFIED:
75 if (maFileIds.count(nFileId))
76 // We are listening to this external document. Send an update
77 // requst to the chart.
78 mrParent.SetUpdateQueue();
80 break;
81 case ScExternalRefManager::LINK_BROKEN:
82 removeFileId(nFileId);
83 break;
87 void ScChartListener::ExternalRefListener::addFileId(sal_uInt16 nFileId)
89 maFileIds.insert(nFileId);
92 void ScChartListener::ExternalRefListener::removeFileId(sal_uInt16 nFileId)
94 maFileIds.erase(nFileId);
97 ScChartListener::ScChartListener( const OUString& rName, ScDocument* pDocP,
98 const ScRangeListRef& rRangeList ) :
99 SvtListener(),
100 mpExtRefListener(NULL),
101 mpTokens(new vector<ScTokenRef>),
102 maName(rName),
103 pUnoData( NULL ),
104 mpDoc( pDocP ),
105 bUsed( false ),
106 bDirty( false ),
107 bSeriesRangesScheduled( false )
109 ScRefTokenHelper::getTokensFromRangeList(*mpTokens, *rRangeList);
112 ScChartListener::ScChartListener( const OUString& rName, ScDocument* pDocP, vector<ScTokenRef>* pTokens ) :
113 SvtListener(),
114 mpExtRefListener(NULL),
115 mpTokens(pTokens),
116 maName(rName),
117 pUnoData( NULL ),
118 mpDoc( pDocP ),
119 bUsed( false ),
120 bDirty( false ),
121 bSeriesRangesScheduled( false )
125 ScChartListener::ScChartListener( const ScChartListener& r ) :
126 SvtListener(),
127 mpExtRefListener(NULL),
128 mpTokens(new vector<ScTokenRef>(*r.mpTokens)),
129 maName(r.maName),
130 pUnoData( NULL ),
131 mpDoc( r.mpDoc ),
132 bUsed( false ),
133 bDirty( r.bDirty ),
134 bSeriesRangesScheduled( r.bSeriesRangesScheduled )
136 if ( r.pUnoData )
137 pUnoData = new ScChartUnoData( *r.pUnoData );
139 if (r.mpExtRefListener.get())
141 // Re-register this new listener for the files that the old listener
142 // was listening to.
144 ScExternalRefManager* pRefMgr = mpDoc->GetExternalRefManager();
145 const std::unordered_set<sal_uInt16>& rFileIds = r.mpExtRefListener->getAllFileIds();
146 mpExtRefListener.reset(new ExternalRefListener(*this, mpDoc));
147 std::unordered_set<sal_uInt16>::const_iterator itr = rFileIds.begin(), itrEnd = rFileIds.end();
148 for (; itr != itrEnd; ++itr)
150 pRefMgr->addLinkListener(*itr, mpExtRefListener.get());
151 mpExtRefListener->addFileId(*itr);
156 ScChartListener::~ScChartListener()
158 if ( HasBroadcaster() )
159 EndListeningTo();
160 delete pUnoData;
162 if (mpExtRefListener.get())
164 // Stop listening to all external files.
165 ScExternalRefManager* pRefMgr = mpDoc->GetExternalRefManager();
166 const std::unordered_set<sal_uInt16>& rFileIds = mpExtRefListener->getAllFileIds();
167 std::unordered_set<sal_uInt16>::const_iterator itr = rFileIds.begin(), itrEnd = rFileIds.end();
168 for (; itr != itrEnd; ++itr)
169 pRefMgr->removeLinkListener(*itr, mpExtRefListener.get());
173 void ScChartListener::SetUno(
174 const uno::Reference< chart::XChartDataChangeEventListener >& rListener,
175 const uno::Reference< chart::XChartData >& rSource )
177 delete pUnoData;
178 pUnoData = new ScChartUnoData( rListener, rSource );
181 uno::Reference< chart::XChartDataChangeEventListener > ScChartListener::GetUnoListener() const
183 if ( pUnoData )
184 return pUnoData->GetListener();
185 return uno::Reference< chart::XChartDataChangeEventListener >();
188 uno::Reference< chart::XChartData > ScChartListener::GetUnoSource() const
190 if ( pUnoData )
191 return pUnoData->GetSource();
192 return uno::Reference< chart::XChartData >();
195 void ScChartListener::Notify( const SfxHint& rHint )
197 const ScHint* p = dynamic_cast<const ScHint*>(&rHint);
198 if (p && (p->GetId() & SC_HINT_DATACHANGED))
199 SetUpdateQueue();
202 void ScChartListener::Update()
204 if ( mpDoc->IsInInterpreter() )
205 { // If interpreting do nothing and restart timer so we don't
206 // interfere with interpreter and don't produce an Err522 or similar.
207 // This may happen if we are rescheduled via Basic function.
208 mpDoc->GetChartListenerCollection()->StartTimer();
209 return ;
211 if ( pUnoData )
213 bDirty = false;
214 // recognize some day what has changed inside the Chart
215 chart::ChartDataChangeEvent aEvent( pUnoData->GetSource(),
216 chart::ChartDataChangeType_ALL,
217 0, 0, 0, 0 );
218 pUnoData->GetListener()->chartDataChanged( aEvent );
220 else if ( mpDoc->GetAutoCalc() )
222 bDirty = false;
223 mpDoc->UpdateChart(GetName());
227 ScRangeListRef ScChartListener::GetRangeList() const
229 ScRangeListRef aRLRef(new ScRangeList);
230 ScRefTokenHelper::getRangeListFromTokens(*aRLRef, *mpTokens, ScAddress());
231 return aRLRef;
234 void ScChartListener::SetRangeList( const ScRangeListRef& rNew )
236 vector<ScTokenRef> aTokens;
237 ScRefTokenHelper::getTokensFromRangeList(aTokens, *rNew);
238 mpTokens->swap(aTokens);
241 namespace {
243 class StartEndListening : public unary_function<ScTokenRef, void>
245 public:
246 StartEndListening(ScDocument* pDoc, ScChartListener& rParent, bool bStart) :
247 mpDoc(pDoc), mrParent(rParent), mbStart(bStart) {}
249 void operator() (const ScTokenRef& pToken)
251 if (!ScRefTokenHelper::isRef(pToken))
252 return;
254 bool bExternal = ScRefTokenHelper::isExternalRef(pToken);
255 if (bExternal)
257 sal_uInt16 nFileId = pToken->GetIndex();
258 ScExternalRefManager* pRefMgr = mpDoc->GetExternalRefManager();
259 ScChartListener::ExternalRefListener* pExtRefListener = mrParent.GetExtRefListener();
260 if (mbStart)
262 pRefMgr->addLinkListener(nFileId, pExtRefListener);
263 pExtRefListener->addFileId(nFileId);
265 else
267 pRefMgr->removeLinkListener(nFileId, pExtRefListener);
268 pExtRefListener->removeFileId(nFileId);
271 else
273 ScRange aRange;
274 ScRefTokenHelper::getRangeFromToken(aRange, pToken, ScAddress(), bExternal);
275 if (mbStart)
276 startListening(aRange);
277 else
278 endListening(aRange);
281 private:
282 void startListening(const ScRange& rRange)
284 if (rRange.aStart == rRange.aEnd)
285 mpDoc->StartListeningCell(rRange.aStart, &mrParent);
286 else
287 mpDoc->StartListeningArea(rRange, false, &mrParent);
290 void endListening(const ScRange& rRange)
292 if (rRange.aStart == rRange.aEnd)
293 mpDoc->EndListeningCell(rRange.aStart, &mrParent);
294 else
295 mpDoc->EndListeningArea(rRange, false, &mrParent);
297 private:
298 ScDocument* mpDoc;
299 ScChartListener& mrParent;
300 bool mbStart;
305 void ScChartListener::StartListeningTo()
307 if (!mpTokens.get() || mpTokens->empty())
308 // no references to listen to.
309 return;
311 for_each(mpTokens->begin(), mpTokens->end(), StartEndListening(mpDoc, *this, true));
314 void ScChartListener::EndListeningTo()
316 if (!mpTokens.get() || mpTokens->empty())
317 // no references to listen to.
318 return;
320 for_each(mpTokens->begin(), mpTokens->end(), StartEndListening(mpDoc, *this, false));
323 void ScChartListener::ChangeListening( const ScRangeListRef& rRangeListRef,
324 bool bDirtyP )
326 EndListeningTo();
327 SetRangeList( rRangeListRef );
328 StartListeningTo();
329 if ( bDirtyP )
330 SetDirty( true );
333 void ScChartListener::UpdateScheduledSeriesRanges()
335 if ( bSeriesRangesScheduled )
337 bSeriesRangesScheduled = false;
338 UpdateSeriesRanges();
342 void ScChartListener::UpdateChartIntersecting( const ScRange& rRange )
344 ScTokenRef pToken;
345 ScRefTokenHelper::getTokenFromRange(pToken, rRange);
347 if (ScRefTokenHelper::intersects(*mpTokens, pToken, ScAddress()))
349 // force update (chart has to be loaded), don't use ScChartListener::Update
350 mpDoc->UpdateChart(GetName());
354 void ScChartListener::UpdateSeriesRanges()
356 ScRangeListRef pRangeList(new ScRangeList);
357 ScRefTokenHelper::getRangeListFromTokens(*pRangeList, *mpTokens, ScAddress());
358 mpDoc->SetChartRangeList(GetName(), pRangeList);
361 ScChartListener::ExternalRefListener* ScChartListener::GetExtRefListener()
363 if (!mpExtRefListener.get())
364 mpExtRefListener.reset(new ExternalRefListener(*this, mpDoc));
366 return mpExtRefListener.get();
369 void ScChartListener::SetUpdateQueue()
371 bDirty = true;
372 mpDoc->GetChartListenerCollection()->StartTimer();
375 bool ScChartListener::operator==( const ScChartListener& r ) const
377 bool b1 = (mpTokens.get() && !mpTokens->empty());
378 bool b2 = (r.mpTokens.get() && !r.mpTokens->empty());
380 if (mpDoc != r.mpDoc || bUsed != r.bUsed || bDirty != r.bDirty ||
381 bSeriesRangesScheduled != r.bSeriesRangesScheduled ||
382 GetName() != r.GetName() || b1 != b2)
383 return false;
385 if (!b1 && !b2)
386 // both token list instances are empty.
387 return true;
389 return *mpTokens == *r.mpTokens;
392 bool ScChartListener::operator!=( const ScChartListener& r ) const
394 return !operator==(r);
397 ScChartHiddenRangeListener::ScChartHiddenRangeListener()
401 ScChartHiddenRangeListener::~ScChartHiddenRangeListener()
403 // empty d'tor
406 // ScChartListenerCollection
407 ScChartListenerCollection::RangeListenerItem::RangeListenerItem(const ScRange& rRange, ScChartHiddenRangeListener* p) :
408 maRange(rRange), mpListener(p)
412 ScChartListenerCollection::ScChartListenerCollection( ScDocument* pDocP ) :
413 meModifiedDuringUpdate( SC_CLCUPDATE_NONE ),
414 pDoc( pDocP )
416 aIdle.SetIdleHdl( LINK( this, ScChartListenerCollection, TimerHdl ) );
419 ScChartListenerCollection::ScChartListenerCollection(
420 const ScChartListenerCollection& rColl ) :
421 meModifiedDuringUpdate( SC_CLCUPDATE_NONE ),
422 pDoc( rColl.pDoc )
424 aIdle.SetIdleHdl( LINK( this, ScChartListenerCollection, TimerHdl ) );
427 ScChartListenerCollection::~ScChartListenerCollection()
429 // remove ChartListener objects before aIdle dtor is called, because
430 // ScChartListener::EndListeningTo may cause ScChartListenerCollection::StartTimer
431 // to be called if an empty ScNoteCell is deleted
433 maListeners.clear();
436 void ScChartListenerCollection::StartAllListeners()
438 ListenersType::iterator it = maListeners.begin(), itEnd = maListeners.end();
439 for (; it != itEnd; ++it)
440 it->second->StartListeningTo();
443 void ScChartListenerCollection::insert(ScChartListener* pListener)
445 if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING)
446 meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED;
447 OUString aName = pListener->GetName();
448 maListeners.insert(aName, pListener);
451 void ScChartListenerCollection::removeByName(const OUString& rName)
453 if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING)
454 meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED;
455 maListeners.erase(rName);
458 ScChartListener* ScChartListenerCollection::findByName(const OUString& rName)
460 ListenersType::iterator it = maListeners.find(rName);
461 return it == maListeners.end() ? NULL : it->second;
464 const ScChartListener* ScChartListenerCollection::findByName(const OUString& rName) const
466 ListenersType::const_iterator it = maListeners.find(rName);
467 return it == maListeners.end() ? NULL : it->second;
470 bool ScChartListenerCollection::hasListeners() const
472 return !maListeners.empty();
475 OUString ScChartListenerCollection::getUniqueName(const OUString& rPrefix) const
477 for (sal_Int32 nNum = 1; nNum < 10000; ++nNum) // arbitrary limit to prevent infinite loop.
479 OUStringBuffer aBuf(rPrefix);
480 aBuf.append(nNum);
481 OUString aTestName = aBuf.makeStringAndClear();
482 if (maListeners.find(aTestName) == maListeners.end())
483 return aTestName;
485 return OUString();
488 void ScChartListenerCollection::ChangeListening( const OUString& rName,
489 const ScRangeListRef& rRangeListRef, bool bDirty )
491 ScChartListener* pCL = findByName(rName);
492 if (pCL)
494 pCL->EndListeningTo();
495 pCL->SetRangeList( rRangeListRef );
497 else
499 pCL = new ScChartListener(rName, pDoc, rRangeListRef);
500 insert(pCL);
502 pCL->StartListeningTo();
503 if ( bDirty )
504 pCL->SetDirty( true );
507 namespace {
509 class InsertChartListener : public std::unary_function<ScChartListener*, void>
511 ScChartListenerCollection::ListenersType& mrListeners;
512 public:
513 InsertChartListener(ScChartListenerCollection::ListenersType& rListeners) :
514 mrListeners(rListeners) {}
516 void operator() (ScChartListener* p)
518 OUString aName = p->GetName();
519 mrListeners.insert(aName, p);
525 void ScChartListenerCollection::FreeUnused()
527 if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING)
528 meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED;
530 ListenersType aUsed, aUnused;
532 // First, filter each listener into 'used' and 'unused' categories.
534 while(!maListeners.empty())
536 ScChartListener* p = maListeners.begin()->second;
537 if (p->IsUno())
539 // We don't delete UNO charts; they are to be deleted separately via FreeUno().
540 aUsed.transfer(maListeners.begin(), maListeners);
541 continue;
544 if (p->IsUsed())
546 p->SetUsed(false);
547 aUsed.transfer(maListeners.begin(), maListeners);
549 else
550 aUnused.transfer(maListeners.begin(), maListeners);
555 std::swap(aUsed, maListeners);
558 void ScChartListenerCollection::FreeUno( const uno::Reference< chart::XChartDataChangeEventListener >& rListener,
559 const uno::Reference< chart::XChartData >& rSource )
561 if (meModifiedDuringUpdate == SC_CLCUPDATE_RUNNING)
562 meModifiedDuringUpdate = SC_CLCUPDATE_MODIFIED;
564 std::vector<ScChartListener*> aUsed, aUnused;
566 // First, filter each listener into 'used' and 'unused' categories.
568 ListenersType::iterator it = maListeners.begin(), itEnd = maListeners.end();
569 for (; it != itEnd; ++it)
571 ScChartListener* p = it->second;
572 if (p->IsUno() && p->GetUnoListener() == rListener && p->GetUnoSource() == rSource)
573 aUnused.push_back(p);
574 else
575 aUsed.push_back(p);
579 // Release all pointers currently managed by the ptr_map container.
580 // coverity[leaked_storage] - no leak, because because we will take care of them below
581 maListeners.release().release();
583 // Re-insert the listeners we need to keep.
584 std::for_each(aUsed.begin(), aUsed.end(), InsertChartListener(maListeners));
586 // Now, delete the ones no longer needed.
587 std::for_each(aUnused.begin(), aUnused.end(), boost::checked_deleter<ScChartListener>());
590 void ScChartListenerCollection::StartTimer()
592 aIdle.SetPriority( SchedulerPriority::REPAINT );
593 aIdle.Start();
596 IMPL_LINK_NOARG_TYPED(ScChartListenerCollection, TimerHdl, Idle *, void)
598 if ( Application::AnyInput( VclInputFlags::KEYBOARD ) )
600 aIdle.Start();
601 return;
603 UpdateDirtyCharts();
606 void ScChartListenerCollection::UpdateDirtyCharts()
608 // During ScChartListener::Update() the most nasty things can happen due to
609 // UNO listeners, e.g. reentrant calls via BASIC to insert() and FreeUno()
610 // and similar that modify maListeners and invalidate iterators.
611 meModifiedDuringUpdate = SC_CLCUPDATE_RUNNING;
613 ListenersType::iterator it = maListeners.begin(), itEnd = maListeners.end();
614 for (; it != itEnd; ++it)
616 ScChartListener* p = it->second;
617 if (p->IsDirty())
618 p->Update();
620 if (meModifiedDuringUpdate == SC_CLCUPDATE_MODIFIED)
621 break; // iterator is invalid
623 if (aIdle.IsActive() && !pDoc->IsImportingXML())
624 break; // one interfered
626 meModifiedDuringUpdate = SC_CLCUPDATE_NONE;
629 void ScChartListenerCollection::SetDirty()
631 ListenersType::iterator it = maListeners.begin(), itEnd = maListeners.end();
632 for (; it != itEnd; ++it)
633 it->second->SetDirty(true);
635 StartTimer();
638 void ScChartListenerCollection::SetDiffDirty(
639 const ScChartListenerCollection& rCmp, bool bSetChartRangeLists )
641 bool bDirty = false;
642 ListenersType::iterator it = maListeners.begin(), itEnd = maListeners.end();
643 for (; it != itEnd; ++it)
645 ScChartListener* pCL = it->second;
646 OSL_ASSERT(pCL);
647 const ScChartListener* pCLCmp = rCmp.findByName(pCL->GetName());
648 if (!pCLCmp || *pCL != *pCLCmp)
650 if ( bSetChartRangeLists )
652 if (pCLCmp)
654 const ScRangeListRef& rList1 = pCL->GetRangeList();
655 const ScRangeListRef& rList2 = pCLCmp->GetRangeList();
656 bool b1 = rList1.Is();
657 bool b2 = rList2.Is();
658 if ( b1 != b2 || (b1 && b2 && (*rList1 != *rList2)) )
659 pDoc->SetChartRangeList( pCL->GetName(), rList1 );
661 else
662 pDoc->SetChartRangeList( pCL->GetName(), pCL->GetRangeList() );
664 bDirty = true;
665 pCL->SetDirty( true );
668 if ( bDirty )
669 StartTimer();
672 void ScChartListenerCollection::SetRangeDirty( const ScRange& rRange )
674 bool bDirty = false;
675 ListenersType::iterator it = maListeners.begin(), itEnd = maListeners.end();
676 for (; it != itEnd; ++it)
678 ScChartListener* pCL = it->second;
679 const ScRangeListRef& rList = pCL->GetRangeList();
680 if ( rList.Is() && rList->Intersects( rRange ) )
682 bDirty = true;
683 pCL->SetDirty( true );
686 if ( bDirty )
687 StartTimer();
689 // New hidden range listener implementation
690 for (list<RangeListenerItem>::iterator itr = maHiddenListeners.begin(), itrEnd = maHiddenListeners.end();
691 itr != itrEnd; ++itr)
693 if (itr->maRange.Intersects(rRange))
694 itr->mpListener->notify();
698 void ScChartListenerCollection::UpdateScheduledSeriesRanges()
700 ListenersType::iterator it = maListeners.begin(), itEnd = maListeners.end();
701 for (; it != itEnd; ++it)
702 it->second->UpdateScheduledSeriesRanges();
705 void ScChartListenerCollection::UpdateChartsContainingTab( SCTAB nTab )
707 ScRange aRange( 0, 0, nTab, MAXCOL, MAXROW, nTab );
708 ListenersType::iterator it = maListeners.begin(), itEnd = maListeners.end();
709 for (; it != itEnd; ++it)
710 it->second->UpdateChartIntersecting(aRange);
713 bool ScChartListenerCollection::operator==( const ScChartListenerCollection& r ) const
715 // Do not use ScStrCollection::operator==() here that uses IsEqual und Compare.
716 // Use ScChartListener::operator==() instead.
717 if (pDoc != r.pDoc || maListeners.size() != r.maListeners.size())
718 return false;
720 ListenersType::const_iterator it = maListeners.begin(), itEnd = maListeners.end();
721 ListenersType::const_iterator it2 = r.maListeners.begin();
722 for (; it != itEnd; ++it, ++it2)
724 if (*it != *it2)
725 return false;
727 return true;
730 bool ScChartListenerCollection::operator!=( const ScChartListenerCollection& r ) const
732 return !operator==(r);
735 void ScChartListenerCollection::StartListeningHiddenRange( const ScRange& rRange, ScChartHiddenRangeListener* pListener )
737 RangeListenerItem aItem(rRange, pListener);
738 maHiddenListeners.push_back(aItem);
741 namespace {
743 struct MatchListener : public ::std::unary_function<
744 ScChartListenerCollection::RangeListenerItem, bool>
746 MatchListener(const ScChartHiddenRangeListener* pMatch) :
747 mpMatch(pMatch)
751 bool operator() (const ScChartListenerCollection::RangeListenerItem& rItem) const
753 return mpMatch == rItem.mpListener;
756 private:
757 const ScChartHiddenRangeListener* mpMatch;
761 void ScChartListenerCollection::EndListeningHiddenRange( ScChartHiddenRangeListener* pListener )
763 maHiddenListeners.remove_if(MatchListener(pListener));
766 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */