diff options
Diffstat (limited to '')
-rw-r--r-- | svl/source/notify/SfxBroadcaster.cxx | 178 | ||||
-rw-r--r-- | svl/source/notify/broadcast.cxx | 267 | ||||
-rw-r--r-- | svl/source/notify/hint.cxx | 31 | ||||
-rw-r--r-- | svl/source/notify/isethint.cxx | 38 | ||||
-rw-r--r-- | svl/source/notify/listener.cxx | 108 | ||||
-rw-r--r-- | svl/source/notify/lstner.cxx | 173 |
6 files changed, 795 insertions, 0 deletions
diff --git a/svl/source/notify/SfxBroadcaster.cxx b/svl/source/notify/SfxBroadcaster.cxx new file mode 100644 index 000000000..2e312a80c --- /dev/null +++ b/svl/source/notify/SfxBroadcaster.cxx @@ -0,0 +1,178 @@ +/* -*- 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 <svl/SfxBroadcaster.hxx> + +#include <svl/hint.hxx> +#include <svl/lstner.hxx> +#include <tools/debug.hxx> + +#include <algorithm> +#include <cassert> +#include <vector> + + +typedef std::vector<SfxListener*> SfxListenerArr_Impl; + +struct SfxBroadcaster::Impl +{ + /** Contains the positions of removed listeners. */ + std::vector<size_t> m_RemovedPositions; + SfxListenerArr_Impl m_Listeners; +}; + +// broadcast immediately + +void SfxBroadcaster::Broadcast( const SfxHint &rHint ) +{ + // notify all registered listeners exactly once + for (size_t i = 0; i < mpImpl->m_Listeners.size(); ++i) + { + SfxListener *const pListener = mpImpl->m_Listeners[i]; + if (pListener) + pListener->Notify( *this, rHint ); + } +} + +// unregister all listeners + +SfxBroadcaster::~SfxBroadcaster() COVERITY_NOEXCEPT_FALSE +{ + Broadcast( SfxHint(SfxHintId::Dying) ); + + // remove all still registered listeners + for (size_t i = 0; i < mpImpl->m_Listeners.size(); ++i) + { + SfxListener *const pListener = mpImpl->m_Listeners[i]; + if (pListener) + pListener->RemoveBroadcaster_Impl(*this); + } +} + + +// simple ctor of class SfxBroadcaster + +SfxBroadcaster::SfxBroadcaster() : mpImpl(new Impl) +{ +} + + +// copy ctor of class SfxBroadcaster + + +SfxBroadcaster::SfxBroadcaster( const SfxBroadcaster &rBC ) : mpImpl(new Impl) +{ + for (size_t i = 0; i < rBC.mpImpl->m_Listeners.size(); ++i) + { + SfxListener *const pListener = rBC.mpImpl->m_Listeners[i]; + if (pListener) + pListener->StartListening( *this ); + } +} + + +// add a new SfxListener to the list + +void SfxBroadcaster::AddListener( SfxListener& rListener ) +{ + DBG_TESTSOLARMUTEX(); + if (mpImpl->m_RemovedPositions.empty()) + { + mpImpl->m_Listeners.push_back(&rListener); + } + else + { + size_t targetPosition = mpImpl->m_RemovedPositions.back(); + mpImpl->m_RemovedPositions.pop_back(); + assert(mpImpl->m_Listeners[targetPosition] == nullptr); + mpImpl->m_Listeners[targetPosition] = &rListener; + } +} + + +// forward a notification to all registered listeners + +void SfxBroadcaster::Forward(SfxBroadcaster& rBC, const SfxHint& rHint) +{ + for (size_t i = 0; i < mpImpl->m_Listeners.size(); ++i) + { + SfxListener *const pListener = mpImpl->m_Listeners[i]; + if (pListener) + pListener->Notify( rBC, rHint ); + } +} + + +// remove one SfxListener from the list + +void SfxBroadcaster::RemoveListener( SfxListener& rListener ) +{ + DBG_TESTSOLARMUTEX(); + + // First, check the slots either side of the last removed slot, makes a significant + // difference when the list is large. + int positionOfRemovedElement = -1; + if (!mpImpl->m_RemovedPositions.empty()) + { + auto i = mpImpl->m_RemovedPositions.back(); + if (i < mpImpl->m_Listeners.size() - 2 && mpImpl->m_Listeners[i+1] == &rListener) + { + positionOfRemovedElement = i + 1; + } + else if (i > 0 && mpImpl->m_Listeners[i-1] == &rListener) + { + positionOfRemovedElement = i-1; + } + } + // then scan the whole list if we didn't find it + if (positionOfRemovedElement == -1) + { + SfxListenerArr_Impl::iterator aIter = std::find( + mpImpl->m_Listeners.begin(), mpImpl->m_Listeners.end(), &rListener); + positionOfRemovedElement = std::distance(mpImpl->m_Listeners.begin(), aIter); + } + // DO NOT erase the listener, set the pointer to 0 + // because the current continuation may contain this->Broadcast + mpImpl->m_Listeners[positionOfRemovedElement] = nullptr; + mpImpl->m_RemovedPositions.push_back(positionOfRemovedElement); +} + +bool SfxBroadcaster::HasListeners() const +{ + return (GetListenerCount() != 0); +} + +size_t SfxBroadcaster::GetListenerCount() const +{ + assert(mpImpl->m_Listeners.size() >= mpImpl->m_RemovedPositions.size()); + return mpImpl->m_Listeners.size() - mpImpl->m_RemovedPositions.size(); +} + +size_t SfxBroadcaster::GetSizeOfVector() const +{ + return mpImpl->m_Listeners.size(); +} + +SfxListener* SfxBroadcaster::GetListener( size_t nNo ) const +{ + return mpImpl->m_Listeners[nNo]; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/notify/broadcast.cxx b/svl/source/notify/broadcast.cxx new file mode 100644 index 000000000..586a09600 --- /dev/null +++ b/svl/source/notify/broadcast.cxx @@ -0,0 +1,267 @@ +/* -*- 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 <svl/broadcast.hxx> +#include <svl/listener.hxx> +#include <svl/hint.hxx> +#include <o3tl/safeint.hxx> +#include <cassert> +#include <algorithm> + +/** + Design Notes + ------------------------------- + + This class is extremely heavily used - we can have millions of broadcasters and listeners and we can also + have a broadcaster that has a million listeners. + + So we do a number of things + (*) use a cache-dense listener list (std::vector) because caching effects dominate a lot of operations + (*) use a sorted list to speed up find operations + (*) only sort the list when we absolutely have to, to speed up sequential add/remove operations + (*) defer removing items from the list by (ab)using the last bit of the pointer + + Also we have some complications around destruction because + (*) we broadcast a message to indicate that we are destructing + (*) which can trigger arbitrality complicated behaviour, including + (*) adding a removing things from the in-destruction object! + +*/ + +static bool isDeletedPtr(SvtListener* p) +{ + /** mark deleted entries by toggling the last bit,which is effectively unused, since the struct we point + * to is at least 16-bit aligned. This allows the binary seach to continue working even when we have + * deleted entries */ +#if SAL_TYPES_SIZEOFPOINTER == 4 + return (reinterpret_cast<sal_uInt32>(p) & 0x01) == 0x01; +#else + return (reinterpret_cast<sal_uInt64>(p) & 0x01) == 0x01; +#endif +} + +static void markDeletedPtr(SvtListener*& rp) +{ +#if SAL_TYPES_SIZEOFPOINTER == 4 + reinterpret_cast<sal_uInt32&>(rp) |= 0x01; +#else + reinterpret_cast<sal_uInt64&>(rp) |= 0x01; +#endif +} + +static void sortListeners(std::vector<SvtListener*>& listeners, size_t firstUnsorted) +{ + // Add() only appends new values, so often the container will be sorted expect for one + // or few last items. For larger containers it is much more efficient to just handle + // the unsorted part. + auto sortedEnd = firstUnsorted == 0 + ? std::is_sorted_until(listeners.begin(),listeners.end()) + : listeners.begin() + firstUnsorted; + if( listeners.end() - sortedEnd == 1 ) + { // Just one item, insert it in the right place. + SvtListener* item = listeners.back(); + listeners.pop_back(); + listeners.insert( std::upper_bound( listeners.begin(), listeners.end(), item ), item ); + } + else if( o3tl::make_unsigned( sortedEnd - listeners.begin()) > listeners.size() * 3 / 4 ) + { // Sort the unsorted part and then merge. + std::sort( sortedEnd, listeners.end()); + std::inplace_merge( listeners.begin(), sortedEnd, listeners.end()); + } + else + { + std::sort(listeners.begin(), listeners.end()); + } +} + +void SvtBroadcaster::Normalize() const +{ + // clear empty slots first, because then we often have to do very little sorting + if (mnEmptySlots) + { + maListeners.erase( + std::remove_if(maListeners.begin(), maListeners.end(), [] (SvtListener* p) { return isDeletedPtr(p); }), + maListeners.end()); + mnEmptySlots = 0; + } + + if (mnListenersFirstUnsorted != static_cast<sal_Int32>(maListeners.size())) + { + sortListeners(maListeners, mnListenersFirstUnsorted); + mnListenersFirstUnsorted = maListeners.size(); + } + + if (!mbDestNormalized) + { + sortListeners(maDestructedListeners, 0); + mbDestNormalized = true; + } +} + +void SvtBroadcaster::Add( SvtListener* p ) +{ + assert(!mbDisposing && "called inside my own destructor?"); + assert(!mbAboutToDie && "called after PrepareForDestruction()?"); + if (mbDisposing || mbAboutToDie) + return; + // Avoid normalizing if the item appended keeps the container sorted. + auto nRealSize = static_cast<sal_Int32>(maListeners.size() - mnEmptySlots); + auto bSorted = mnListenersFirstUnsorted == nRealSize; + if (maListeners.empty() || (bSorted && maListeners.back() <= p)) + { + ++mnListenersFirstUnsorted; + maListeners.push_back(p); + return; + } + // see if we can stuff it into an empty slot, which succeeds surprisingly often in + // some calc workloads where it removes and then re-adds the same listener + if (mnEmptySlots && bSorted) + { + auto it = std::lower_bound(maListeners.begin(), maListeners.end(), p); + if (it != maListeners.end() && isDeletedPtr(*it)) + { + *it = p; + ++mnListenersFirstUnsorted; + --mnEmptySlots; + return; + } + } + maListeners.push_back(p); +} + +void SvtBroadcaster::Remove( SvtListener* p ) +{ + if (mbDisposing) + return; + + if (mbAboutToDie) + { + // only reset mbDestNormalized if we are going to become unsorted + if (!maDestructedListeners.empty() && maDestructedListeners.back() > p) + mbDestNormalized = false; + maDestructedListeners.push_back(p); + return; + } + + // We only need to fully normalize if one or more Add()s have been performed that make the array unsorted. + auto nRealSize = static_cast<sal_Int32>(maListeners.size() - mnEmptySlots); + if (mnListenersFirstUnsorted != nRealSize || (maListeners.size() > 1024 && maListeners.size() / nRealSize > 16)) + Normalize(); + + auto it = std::lower_bound(maListeners.begin(), maListeners.end(), p); + assert (it != maListeners.end() && *it == p); + if (it != maListeners.end() && *it == p) + { + markDeletedPtr(*it); + ++mnEmptySlots; + --mnListenersFirstUnsorted; + } + + if (maListeners.size() - mnEmptySlots == 0) + ListenersGone(); +} + +SvtBroadcaster::SvtBroadcaster() + : mnEmptySlots(0) + , mnListenersFirstUnsorted(0) + , mbAboutToDie(false) + , mbDisposing(false) + , mbDestNormalized(true) +{} + +SvtBroadcaster::SvtBroadcaster( const SvtBroadcaster &rBC ) : + mnEmptySlots(0), mnListenersFirstUnsorted(0), + mbAboutToDie(false), mbDisposing(false), + mbDestNormalized(true) +{ + assert(!rBC.mbAboutToDie && "copying an object marked with PrepareForDestruction()?"); + assert(!rBC.mbDisposing && "copying an object that is in its destructor?"); + + rBC.Normalize(); // so that insert into ourself is in-order, and therefore we do not need to Normalize() + maListeners.reserve(rBC.maListeners.size()); + for (SvtListener* p : rBC.maListeners) + p->StartListening(*this); // this will call back into this->Add() +} + +SvtBroadcaster::~SvtBroadcaster() +{ + mbDisposing = true; + Broadcast( SfxHint(SfxHintId::Dying) ); + + Normalize(); + + // now when both lists are sorted, we can linearly unregister all + // listeners, with the exception of those that already asked to be removed + // during their own destruction + ListenersType::const_iterator dest(maDestructedListeners.begin()); + for (auto& rpListener : maListeners) + { + // skip the destructed ones + while (dest != maDestructedListeners.end() && (*dest < rpListener)) + ++dest; + + if (dest == maDestructedListeners.end() || *dest != rpListener) + rpListener->BroadcasterDying(*this); + } +} + +void SvtBroadcaster::Broadcast( const SfxHint &rHint ) +{ + Normalize(); + + ListenersType::const_iterator dest(maDestructedListeners.begin()); + ListenersType aListeners(maListeners); // this copy is important to avoid erasing entries while iterating + for (auto& rpListener : aListeners) + { + // skip the destructed ones + while (dest != maDestructedListeners.end() && (*dest < rpListener)) + ++dest; + + if (dest == maDestructedListeners.end() || *dest != rpListener) + rpListener->Notify(rHint); + } +} + +void SvtBroadcaster::ListenersGone() {} + +SvtBroadcaster::ListenersType& SvtBroadcaster::GetAllListeners() +{ + Normalize(); + return maListeners; +} + +const SvtBroadcaster::ListenersType& SvtBroadcaster::GetAllListeners() const +{ + Normalize(); + return maListeners; +} + +bool SvtBroadcaster::HasListeners() const +{ + return (maListeners.size() - mnEmptySlots) > 0; +} + +void SvtBroadcaster::PrepareForDestruction() +{ + mbAboutToDie = true; + // the reserve() serves two purpose (1) performance (2) makes sure our iterators do not become invalid + maDestructedListeners.reserve(maListeners.size()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/notify/hint.cxx b/svl/source/notify/hint.cxx new file mode 100644 index 000000000..ad36d5098 --- /dev/null +++ b/svl/source/notify/hint.cxx @@ -0,0 +1,31 @@ +/* -*- 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 <svl/hint.hxx> + + +// virtual dtor for the typical base-class Hint + +SfxHint::~SfxHint() COVERITY_NOEXCEPT_FALSE +{ +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/notify/isethint.cxx b/svl/source/notify/isethint.cxx new file mode 100644 index 000000000..6f810240a --- /dev/null +++ b/svl/source/notify/isethint.cxx @@ -0,0 +1,38 @@ +/* -*- 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 <svl/isethint.hxx> +#include <svl/itemset.hxx> + + +/** + * Copies the SfxItemSet passed as a parameter. + */ +SfxItemSetHint::SfxItemSetHint( const SfxItemSet &rItemSet ) +: _pItemSet( rItemSet.Clone() ) +{ +} + +SfxItemSetHint::~SfxItemSetHint() +{ +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/notify/listener.cxx b/svl/source/notify/listener.cxx new file mode 100644 index 000000000..51564ce9b --- /dev/null +++ b/svl/source/notify/listener.cxx @@ -0,0 +1,108 @@ +/* -*- 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 <svl/listener.hxx> +#include <svl/broadcast.hxx> + +SvtListener::QueryBase::QueryBase( sal_uInt16 nId ) : mnId(nId) {} +SvtListener::QueryBase::~QueryBase() {} + +sal_uInt16 SvtListener::QueryBase::getId() const +{ + return mnId; +} + +SvtListener::SvtListener() {} + +SvtListener::SvtListener( const SvtListener & ) {} + +SvtListener::~SvtListener() COVERITY_NOEXCEPT_FALSE +{ + // Unregister itself from all broadcasters it's listening to. + EndListeningAll(); +} + +// registers at a specific SvtBroadcaster + +bool SvtListener::StartListening( SvtBroadcaster& rBroadcaster ) +{ + std::pair<BroadcastersType::const_iterator, bool> r = + maBroadcasters.insert(&rBroadcaster); + if (r.second) + { + // This is a new broadcaster. + rBroadcaster.Add(this); + } + return r.second; +} + +bool SvtListener::EndListening( SvtBroadcaster& rBroadcaster ) +{ + BroadcastersType::const_iterator it = maBroadcasters.find(&rBroadcaster); + if (it == maBroadcasters.end()) + // Not listening to this broadcaster. + return false; + + rBroadcaster.Remove(this); + maBroadcasters.erase(it); + return true; +} + +// called from the SvtBroadcaster destructor, used to avoid calling +// back into the broadcaster again +void SvtListener::BroadcasterDying( SvtBroadcaster& rBroadcaster ) +{ + BroadcastersType::const_iterator it = maBroadcasters.find(&rBroadcaster); + if (it != maBroadcasters.end()) + maBroadcasters.erase(it); +} + +void SvtListener::EndListeningAll() +{ + for (SvtBroadcaster* p : maBroadcasters) + { + SvtBroadcaster& rBC = *p; + rBC.Remove(this); + } + maBroadcasters.clear(); +} + + +void SvtListener::CopyAllBroadcasters( const SvtListener& r ) +{ + EndListeningAll(); + BroadcastersType aCopy(r.maBroadcasters); + maBroadcasters.swap(aCopy); + for (SvtBroadcaster* p : maBroadcasters) + { + p->Add(this); + } +} + +bool SvtListener::HasBroadcaster() const +{ + return !maBroadcasters.empty(); +} + +void SvtListener::Notify( const SfxHint& /*rHint*/ ) {} + +void SvtListener::Query( QueryBase& /*rQuery*/ ) const {} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/notify/lstner.cxx b/svl/source/notify/lstner.cxx new file mode 100644 index 000000000..fe6f2b8af --- /dev/null +++ b/svl/source/notify/lstner.cxx @@ -0,0 +1,173 @@ +/* -*- 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 <svl/lstner.hxx> + +#include <svl/SfxBroadcaster.hxx> +#include <sal/backtrace.hxx> +#include <sal/log.hxx> + +#include <algorithm> +#include <cassert> +#include <vector> +#include <memory> +#include <map> + +struct SfxListener::Impl +{ + std::vector<SfxBroadcaster*> maBCs; +#ifdef DBG_UTIL + std::map<SfxBroadcaster*, std::unique_ptr<sal::BacktraceState>> + maCallStacks; +#endif +}; + +// simple ctor of class SfxListener + +SfxListener::SfxListener() : mpImpl(new Impl) +{ +} + +// copy ctor of class SfxListener + +SfxListener::SfxListener( const SfxListener &rListener ) : mpImpl(new Impl) +{ + for ( size_t n = 0; n < rListener.mpImpl->maBCs.size(); ++n ) + StartListening( *rListener.mpImpl->maBCs[n] ); +} + +// unregisters the SfxListener from its SfxBroadcasters + +SfxListener::~SfxListener() COVERITY_NOEXCEPT_FALSE +{ + // unregister at all remaining broadcasters + for ( size_t nPos = 0; nPos < mpImpl->maBCs.size(); ++nPos ) + { + SfxBroadcaster *pBC = mpImpl->maBCs[nPos]; + pBC->RemoveListener(*this); + } +} + + +// unregisters a specific SfxBroadcaster + +void SfxListener::RemoveBroadcaster_Impl( SfxBroadcaster& rBroadcaster ) +{ + auto it = std::find( mpImpl->maBCs.begin(), mpImpl->maBCs.end(), &rBroadcaster ); + if (it != mpImpl->maBCs.end()) { + mpImpl->maBCs.erase( it ); +#ifdef DBG_UTIL + mpImpl->maCallStacks.erase( &rBroadcaster ); +#endif + } +} + + + +/** + Registers a specific SfxBroadcaster. + + Some code uses duplicates as a kind of ref-counting thing i.e. they add and remove listeners + on different code paths, and they only really stop listening when the last EndListening() is called. +*/ +void SfxListener::StartListening(SfxBroadcaster& rBroadcaster, DuplicateHandling eDuplicateHanding) +{ + bool bListeningAlready = IsListening( rBroadcaster ); + +#ifdef DBG_UTIL + if (bListeningAlready && eDuplicateHanding == DuplicateHandling::Unexpected) + { + auto f = mpImpl->maCallStacks.find( &rBroadcaster ); + SAL_WARN("svl", "previous StartListening call came from: " << sal::backtrace_to_string(f->second.get())); + } +#endif + assert(!(bListeningAlready && eDuplicateHanding == DuplicateHandling::Unexpected) && "duplicate listener, try building with DBG_UTIL to find the other insert site."); + + if (!bListeningAlready || eDuplicateHanding != DuplicateHandling::Prevent) + { + rBroadcaster.AddListener(*this); + mpImpl->maBCs.push_back( &rBroadcaster ); +#ifdef DBG_UTIL + mpImpl->maCallStacks.emplace( &rBroadcaster, sal::backtrace_get(10) ); +#endif + assert(IsListening(rBroadcaster) && "StartListening failed"); + } +} + +// unregisters a specific SfxBroadcaster + +void SfxListener::EndListening( SfxBroadcaster& rBroadcaster, bool bRemoveAllDuplicates ) +{ + auto beginIt = mpImpl->maBCs.begin(); + do + { + auto it = std::find( beginIt, mpImpl->maBCs.end(), &rBroadcaster ); + if ( it == mpImpl->maBCs.end() ) + { + break; + } + rBroadcaster.RemoveListener(*this); + beginIt = mpImpl->maBCs.erase( it ); +#ifdef DBG_UTIL + mpImpl->maCallStacks.erase( &rBroadcaster ); +#endif + } + while ( bRemoveAllDuplicates ); +} + + +// unregisters all Broadcasters + +void SfxListener::EndListeningAll() +{ + std::vector<SfxBroadcaster*> aBroadcasters; + std::swap(mpImpl->maBCs, aBroadcasters); + for (SfxBroadcaster *pBC : aBroadcasters) + pBC->RemoveListener(*this); +#ifdef DBG_UTIL + mpImpl->maCallStacks.clear(); +#endif +} + + +bool SfxListener::IsListening( SfxBroadcaster& rBroadcaster ) const +{ + return mpImpl->maBCs.end() != std::find( mpImpl->maBCs.begin(), mpImpl->maBCs.end(), &rBroadcaster ); +} + +sal_uInt16 SfxListener::GetBroadcasterCount() const +{ + return mpImpl->maBCs.size(); +} + +SfxBroadcaster* SfxListener::GetBroadcasterJOE( sal_uInt16 nNo ) const +{ + return mpImpl->maBCs[nNo]; +} + + +// base implementation of notification handler + +void SfxListener::Notify( SfxBroadcaster& rBroadcaster, const SfxHint& ) +{ + (void) rBroadcaster; + assert(IsListening(rBroadcaster)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |