1
0
Fork 0
libreoffice/sw/source/core/attr/calbck.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

325 lines
10 KiB
C++

/* -*- 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 <algorithm>
#include <format.hxx>
#include <hintids.hxx>
#include <hints.hxx>
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <tools/debug.hxx>
#ifdef DBG_UTIL
#include <sal/backtrace.hxx>
#endif
namespace sw
{
bool ListenerEntry::GetInfo(SwFindNearestNode& rInfo) const
{ return m_pToTell == nullptr || m_pToTell->GetInfo( rInfo ); }
void ListenerEntry::SwClientNotify(const SwModify& rModify, const SfxHint& rHint)
{
if (rHint.GetId() == SfxHintId::SwLegacyModify)
{
auto pLegacyHint = static_cast<const sw::LegacyModifyHint*>(&rHint);
if (pLegacyHint->m_pNew && pLegacyHint->m_pNew->Which() == RES_OBJECTDYING)
{
auto pModifyChanged = CheckRegistration(pLegacyHint->m_pOld);
if (pModifyChanged)
m_pToTell->SwClientNotify(rModify, *pModifyChanged);
}
else if (m_pToTell)
m_pToTell->SwClientNotify(rModify, rHint);
}
else if (m_pToTell)
m_pToTell->SwClientNotify(rModify, rHint);
}
}
sw::LegacyModifyHint::~LegacyModifyHint() {}
SwClient::SwClient(SwClient&& o) noexcept
: m_pRegisteredIn(nullptr)
{
if(o.m_pRegisteredIn)
{
o.m_pRegisteredIn->Add(*this);
o.EndListeningAll();
}
}
SwClient::~SwClient()
{
if(GetRegisteredIn())
DBG_TESTSOLARMUTEX();
OSL_ENSURE( !m_pRegisteredIn || m_pRegisteredIn->HasWriterListeners(), "SwModify still known, but Client already disconnected!" );
if( m_pRegisteredIn && m_pRegisteredIn->HasWriterListeners() )
m_pRegisteredIn->Remove(*this);
}
std::optional<sw::ModifyChangedHint> SwClient::CheckRegistration( const SfxPoolItem* pOld )
{
DBG_TESTSOLARMUTEX();
// this method only handles notification about dying SwModify objects
if( !pOld || pOld->Which() != RES_OBJECTDYING )
return {};
assert(dynamic_cast<const SwPtrMsgPoolItem*>(pOld));
const SwPtrMsgPoolItem* pDead = static_cast<const SwPtrMsgPoolItem*>(pOld);
if(pDead->pObject != m_pRegisteredIn)
{
// we should only care received death notes from objects we are following
return {};
}
// I've got a notification from the object I know
SwModify* pAbove = m_pRegisteredIn->GetRegisteredIn();
if(pAbove)
{
// if the dying object itself was listening at an SwModify, I take over
// adding myself to pAbove will automatically remove me from my current pRegisteredIn
pAbove->Add(*this);
}
else
{
// destroy connection
EndListeningAll();
}
return sw::ModifyChangedHint(pAbove);
}
void SwClient::CheckRegistrationFormat(SwFormat& rOld)
{
assert(GetRegisteredIn() == &rOld);
auto pNew = rOld.DerivedFrom();
SAL_INFO("sw.core", "reparenting " << typeid(*this).name() << " at " << this << " from " << typeid(rOld).name() << " at " << &rOld << " to " << typeid(*pNew).name() << " at " << pNew);
assert(pNew);
pNew->Add(*this);
const SwFormatChangeHint aHint(&rOld, pNew);
SwClientNotify(rOld, aHint);
}
void SwClient::SwClientNotify(const SwModify&, const SfxHint& rHint)
{
if (rHint.GetId() != SfxHintId::SwLegacyModify)
return;
auto pLegacyHint = static_cast<const sw::LegacyModifyHint*>(&rHint);
CheckRegistration(pLegacyHint->m_pOld);
};
void SwClient::StartListeningToSameModifyAs(const SwClient& other)
{
if(other.m_pRegisteredIn)
other.m_pRegisteredIn->Add(*this);
else
EndListeningAll();
}
void SwClient::EndListeningAll()
{
if(m_pRegisteredIn)
m_pRegisteredIn->Remove(*this);
}
SwModify::~SwModify()
{
DBG_TESTSOLARMUTEX();
OSL_ENSURE( !IsModifyLocked(), "Modify destroyed but locked." );
// notify all clients that they shall remove themselves
SwPtrMsgPoolItem aDyObject( RES_OBJECTDYING, this );
SwModify::SwClientNotify(*this, sw::LegacyModifyHint(&aDyObject, &aDyObject));
const bool hasListenersOnDeath = m_pWriterListeners;
(void)hasListenersOnDeath;
while(m_pWriterListeners)
{
SAL_WARN("sw.core", "lost a client of type: " << typeid(*m_pWriterListeners).name() << " at " << m_pWriterListeners << " still registered on type: " << typeid(*this).name() << " at " << this << ".");
static_cast<SwClient*>(m_pWriterListeners)->CheckRegistration(&aDyObject);
}
assert(!hasListenersOnDeath);
}
bool SwModify::GetInfo( SwFindNearestNode& rInfo ) const
{
if(!m_pWriterListeners)
return true;
SwIterator<SwClient,SwModify> aIter(*this);
for(SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next())
if(!pClient->GetInfo( rInfo ))
return false;
return true;
}
void SwModify::Add(SwClient& rDepend)
{
DBG_TESTSOLARMUTEX();
#ifdef DBG_UTIL
// You should not EVER use SwModify directly in new code:
// - Preexisting SwModifys should only ever be used via sw::BroadcastingModify.
// This includes sw::BroadcastMixin, which is the long-term target (without
// SwModify).
// - New classes should use sw::BroadcastMixin alone.
if(!dynamic_cast<sw::BroadcastingModify*>(this))
{
auto pBT = sal::backtrace_get(20);
SAL_WARN("sw.core", "Modify that is not broadcasting used!\n" << sal::backtrace_to_string(pBT.get()));
}
#endif
if (rDepend.m_pRegisteredIn == this)
return;
// deregister new client in case it is already registered elsewhere
if( rDepend.m_pRegisteredIn != nullptr )
rDepend.m_pRegisteredIn->Remove(rDepend);
if( !m_pWriterListeners )
{
// first client added
m_pWriterListeners = &rDepend;
m_pWriterListeners->m_pLeft = nullptr;
m_pWriterListeners->m_pRight = nullptr;
}
else
{
// append client
rDepend.m_pRight = m_pWriterListeners->m_pRight;
m_pWriterListeners->m_pRight = &rDepend;
rDepend.m_pLeft = m_pWriterListeners;
if( rDepend.m_pRight )
rDepend.m_pRight->m_pLeft = &rDepend;
}
// connect client to me
rDepend.m_pRegisteredIn = this;
}
void SwModify::Remove(SwClient& rDepend)
{
DBG_TESTSOLARMUTEX();
assert(rDepend.m_pRegisteredIn == this);
// SwClient is my listener
// remove it from my list
::sw::WriterListener* pR = rDepend.m_pRight;
::sw::WriterListener* pL = rDepend.m_pLeft;
if( m_pWriterListeners == &rDepend )
m_pWriterListeners = pL ? pL : pR;
if( pL )
pL->m_pRight = pR;
if( pR )
pR->m_pLeft = pL;
// update ClientIterators
if(sw::ClientIteratorBase::s_pClientIters)
{
for(auto& rIter : sw::ClientIteratorBase::s_pClientIters->GetRingContainer())
{
if (&rIter.m_rRoot == this &&
(rIter.m_pCurrent == &rDepend || rIter.m_pPosition == &rDepend))
{
// if object being removed is the current or next object in an
// iterator, advance this iterator
rIter.m_pPosition = pR;
}
}
}
rDepend.m_pLeft = nullptr;
rDepend.m_pRight = nullptr;
rDepend.m_pRegisteredIn = nullptr;
}
sw::WriterMultiListener::WriterMultiListener(SwClient& rToTell)
: m_rToTell(rToTell)
{}
sw::WriterMultiListener::~WriterMultiListener()
{}
void sw::WriterMultiListener::StartListening(SwModify* pDepend)
{
EndListening(nullptr);
m_vDepends.emplace_back(&m_rToTell, pDepend);
}
bool sw::WriterMultiListener::IsListeningTo(const SwModify* const pBroadcaster) const
{
return std::any_of(m_vDepends.begin(), m_vDepends.end(),
[&pBroadcaster](const ListenerEntry& aListener)
{
return aListener.GetRegisteredIn() == pBroadcaster;
});
}
void sw::WriterMultiListener::EndListening(SwModify* pBroadcaster)
{
std::erase_if(
m_vDepends,
[&pBroadcaster](const ListenerEntry& aListener)
{
return aListener.GetRegisteredIn() == nullptr || aListener.GetRegisteredIn() == pBroadcaster;
});
}
void sw::WriterMultiListener::EndListeningAll()
{
m_vDepends.clear();
}
sw::ClientIteratorBase* sw::ClientIteratorBase::s_pClientIters = nullptr;
void SwModify::SwClientNotify(const SwModify&, const SfxHint& rHint)
{
if (rHint.GetId() != SfxHintId::SwLegacyModify && rHint.GetId() != SfxHintId::SwRemoveUnoObject && rHint.GetId() != SfxHintId::SwRemoveUnoObject)
return;
DBG_TESTSOLARMUTEX();
if(IsModifyLocked())
return;
LockModify();
CallSwClientNotify(rHint);
UnlockModify();
}
void SwModify::CallSwClientNotify( const SfxHint& rHint ) const
{
DBG_TESTSOLARMUTEX();
SwIterator<SwClient,SwModify> aIter(*this);
for(SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next())
pClient->SwClientNotify( *this, rHint );
}
void sw::BroadcastingModify::CallSwClientNotify(const SfxHint& rHint) const
{
SwModify::CallSwClientNotify(rHint);
const_cast<BroadcastingModify*>(this)->GetNotifier().Broadcast(rHint);
}
void sw::ClientNotifyAttrChg(SwModify& rModify, const SwAttrSet& aSet, SwAttrSet& aOld, SwAttrSet& aNew)
{
const SwAttrSetChg aChgOld(aSet, aOld);
const SwAttrSetChg aChgNew(aSet, aNew);
const sw::LegacyModifyHint aHint(&aChgOld, &aChgNew);
rModify.SwClientNotify(rModify, aHint);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */