1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <frame.hxx>
21 #include <hintids.hxx>
22 #include <hints.hxx>
23 #include <swcache.hxx>
24 #include <swfntcch.hxx>
25 #include <tools/debug.hxx>
26 #include <sal/log.hxx>
27 #include <algorithm>
28
29 namespace sw
30 {
GetInfo(SfxPoolItem & rInfo) const31 bool ListenerEntry::GetInfo(SfxPoolItem& rInfo) const
32 { return m_pToTell == nullptr || m_pToTell->GetInfo( rInfo ); }
Modify(const SfxPoolItem * const pOldValue,const SfxPoolItem * const pNewValue)33 void ListenerEntry::Modify(const SfxPoolItem *const pOldValue,
34 const SfxPoolItem *const pNewValue)
35 {
36 SwClientNotify(*GetRegisteredIn(), sw::LegacyModifyHint(pOldValue, pNewValue));
37 }
SwClientNotify(const SwModify & rModify,const SfxHint & rHint)38 void ListenerEntry::SwClientNotify(const SwModify& rModify, const SfxHint& rHint)
39 {
40 if (auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>(&rHint))
41 {
42 if (pLegacyHint->m_pNew && pLegacyHint->m_pNew->Which() == RES_OBJECTDYING)
43 {
44 auto pModifyChanged = CheckRegistration(pLegacyHint->m_pOld);
45 if (pModifyChanged)
46 m_pToTell->SwClientNotify(rModify, *pModifyChanged);
47 }
48 else if (m_pToTell)
49 m_pToTell->SwClientNotifyCall(rModify, rHint);
50 }
51 else if (m_pToTell)
52 m_pToTell->SwClientNotifyCall(rModify, rHint);
53 }
54 }
55
~LegacyModifyHint()56 sw::LegacyModifyHint::~LegacyModifyHint() {}
~ModifyChangedHint()57 sw::ModifyChangedHint::~ModifyChangedHint() {}
58
SwClient(SwClient && o)59 SwClient::SwClient(SwClient&& o) noexcept
60 : m_pRegisteredIn(nullptr)
61 {
62 if(o.m_pRegisteredIn)
63 {
64 o.m_pRegisteredIn->Add(this);
65 o.EndListeningAll();
66 }
67 }
68
~SwClient()69 SwClient::~SwClient()
70 {
71 if(GetRegisteredIn())
72 DBG_TESTSOLARMUTEX();
73 OSL_ENSURE( !m_pRegisteredIn || m_pRegisteredIn->HasWriterListeners(), "SwModify still known, but Client already disconnected!" );
74 if( m_pRegisteredIn && m_pRegisteredIn->HasWriterListeners() )
75 m_pRegisteredIn->Remove( this );
76 }
77
CheckRegistration(const SfxPoolItem * pOld)78 std::unique_ptr<sw::ModifyChangedHint> SwClient::CheckRegistration( const SfxPoolItem* pOld )
79 {
80 DBG_TESTSOLARMUTEX();
81 // this method only handles notification about dying SwModify objects
82 if( !pOld || pOld->Which() != RES_OBJECTDYING )
83 return nullptr;
84
85 assert(dynamic_cast<const SwPtrMsgPoolItem*>(pOld));
86 const SwPtrMsgPoolItem* pDead = static_cast<const SwPtrMsgPoolItem*>(pOld);
87 if(pDead->pObject != m_pRegisteredIn)
88 {
89 // we should only care received death notes from objects we are following
90 return nullptr;
91 }
92 // I've got a notification from the object I know
93 SwModify* pAbove = m_pRegisteredIn->GetRegisteredIn();
94 if(pAbove)
95 {
96 // if the dying object itself was listening at an SwModify, I take over
97 // adding myself to pAbove will automatically remove me from my current pRegisteredIn
98 pAbove->Add(this);
99 }
100 else
101 {
102 // destroy connection
103 EndListeningAll();
104 }
105 return std::unique_ptr<sw::ModifyChangedHint>(new sw::ModifyChangedHint(pAbove));
106 }
107
SwClientNotify(const SwModify &,const SfxHint & rHint)108 void SwClient::SwClientNotify(const SwModify&, const SfxHint& rHint)
109 {
110 if (auto pLegacyHint = dynamic_cast<const sw::LegacyModifyHint*>(&rHint))
111 {
112 Modify(pLegacyHint->m_pOld, pLegacyHint->m_pNew);
113 }
114 };
115
StartListeningToSameModifyAs(const SwClient & other)116 void SwClient::StartListeningToSameModifyAs(const SwClient& other)
117 {
118 if(other.m_pRegisteredIn)
119 other.m_pRegisteredIn->Add(this);
120 else
121 EndListeningAll();
122 }
123
EndListeningAll()124 void SwClient::EndListeningAll()
125 {
126 if(m_pRegisteredIn)
127 m_pRegisteredIn->Remove(this);
128 }
129
Modify(SfxPoolItem const * const pOldValue,SfxPoolItem const * const)130 void SwClient::Modify(SfxPoolItem const*const pOldValue, SfxPoolItem const*const /*pNewValue*/)
131 {
132 CheckRegistration( pOldValue );
133 }
134
SetInDocDTOR()135 void SwModify::SetInDocDTOR()
136 {
137 // If the document gets destroyed anyway, just tell clients to
138 // forget me so that they don't try to get removed from my list
139 // later when they also get destroyed
140 SwIterator<SwClient,SwModify> aIter(*this);
141 for(SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next())
142 pClient->m_pRegisteredIn = nullptr;
143 m_pWriterListeners = nullptr;
144 }
145
~SwModify()146 SwModify::~SwModify()
147 {
148 DBG_TESTSOLARMUTEX();
149 OSL_ENSURE( !IsModifyLocked(), "Modify destroyed but locked." );
150
151 if ( IsInCache() )
152 SwFrame::GetCache().Delete( this );
153
154 if ( IsInSwFntCache() )
155 pSwFontCache->Delete( this );
156
157 // notify all clients that they shall remove themselves
158 SwPtrMsgPoolItem aDyObject( RES_OBJECTDYING, this );
159 NotifyClients( &aDyObject, &aDyObject );
160
161 // remove all clients that have not done themselves
162 // mba: possibly a hotfix for forgotten base class calls?!
163 while( m_pWriterListeners )
164 static_cast<SwClient*>(m_pWriterListeners)->CheckRegistration( &aDyObject );
165 }
166
NotifyClients(const SfxPoolItem * pOldValue,const SfxPoolItem * pNewValue)167 void SwModify::NotifyClients( const SfxPoolItem* pOldValue, const SfxPoolItem* pNewValue )
168 {
169 DBG_TESTSOLARMUTEX();
170 if ( IsInCache() || IsInSwFntCache() )
171 {
172 const sal_uInt16 nWhich = pOldValue ? pOldValue->Which() :
173 pNewValue ? pNewValue->Which() : 0;
174 CheckCaching( nWhich );
175 }
176
177 if ( !m_pWriterListeners || IsModifyLocked() )
178 return;
179
180 LockModify();
181
182 // mba: WTF?!
183 if( !pOldValue )
184 {
185 m_bLockClientList = true;
186 }
187 else
188 {
189 switch( pOldValue->Which() )
190 {
191 case RES_OBJECTDYING:
192 case RES_REMOVE_UNO_OBJECT:
193 m_bLockClientList = static_cast<const SwPtrMsgPoolItem*>(pOldValue)->pObject != this;
194 break;
195
196 default:
197 m_bLockClientList = true;
198 }
199 }
200
201 ModifyBroadcast( pOldValue, pNewValue );
202 m_bLockClientList = false;
203 UnlockModify();
204 }
205
GetInfo(SfxPoolItem & rInfo) const206 bool SwModify::GetInfo( SfxPoolItem& rInfo ) const
207 {
208 if(!m_pWriterListeners)
209 return true;
210 SwIterator<SwClient,SwModify> aIter(*this);
211 for(SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next())
212 if(!pClient->GetInfo( rInfo ))
213 return false;
214 return true;
215 }
216
Add(SwClient * pDepend)217 void SwModify::Add( SwClient* pDepend )
218 {
219 DBG_TESTSOLARMUTEX();
220 OSL_ENSURE( !m_bLockClientList, "Client inserted while in Modify" );
221
222 if(pDepend->m_pRegisteredIn != this )
223 {
224 #if OSL_DEBUG_LEVEL > 0
225 if(sw::ClientIteratorBase::s_pClientIters)
226 {
227 for(auto& rIter : sw::ClientIteratorBase::s_pClientIters->GetRingContainer())
228 {
229 SAL_WARN_IF(&rIter.m_rRoot == m_pWriterListeners, "sw.core", "a " << typeid(*pDepend).name() << " client added as listener to a " << typeid(*this).name() << " during client iteration.");
230 }
231 }
232 #endif
233 // deregister new client in case it is already registered elsewhere
234 if( pDepend->m_pRegisteredIn != nullptr )
235 pDepend->m_pRegisteredIn->Remove( pDepend );
236
237 if( !m_pWriterListeners )
238 {
239 // first client added
240 m_pWriterListeners = pDepend;
241 m_pWriterListeners->m_pLeft = nullptr;
242 m_pWriterListeners->m_pRight = nullptr;
243 }
244 else
245 {
246 // append client
247 pDepend->m_pRight = m_pWriterListeners->m_pRight;
248 m_pWriterListeners->m_pRight = pDepend;
249 pDepend->m_pLeft = m_pWriterListeners;
250 if( pDepend->m_pRight )
251 pDepend->m_pRight->m_pLeft = pDepend;
252 }
253
254 // connect client to me
255 pDepend->m_pRegisteredIn = this;
256 }
257 }
258
Remove(SwClient * pDepend)259 SwClient* SwModify::Remove( SwClient* pDepend )
260 {
261 DBG_TESTSOLARMUTEX();
262 assert(pDepend->m_pRegisteredIn == this);
263
264 // SwClient is my listener
265 // remove it from my list
266 ::sw::WriterListener* pR = pDepend->m_pRight;
267 ::sw::WriterListener* pL = pDepend->m_pLeft;
268 if( m_pWriterListeners == pDepend )
269 m_pWriterListeners = pL ? pL : pR;
270
271 if( pL )
272 pL->m_pRight = pR;
273 if( pR )
274 pR->m_pLeft = pL;
275
276 // update ClientIterators
277 if(sw::ClientIteratorBase::s_pClientIters)
278 {
279 for(auto& rIter : sw::ClientIteratorBase::s_pClientIters->GetRingContainer())
280 {
281 if (&rIter.m_rRoot == this &&
282 (rIter.m_pCurrent == pDepend || rIter.m_pPosition == pDepend))
283 {
284 // if object being removed is the current or next object in an
285 // iterator, advance this iterator
286 rIter.m_pPosition = pR;
287 }
288 }
289 }
290 pDepend->m_pLeft = nullptr;
291 pDepend->m_pRight = nullptr;
292 pDepend->m_pRegisteredIn = nullptr;
293 return pDepend;
294 }
295
CheckCaching(const sal_uInt16 nWhich)296 void SwModify::CheckCaching( const sal_uInt16 nWhich )
297 {
298 if( isCHRATR( nWhich ) )
299 {
300 SetInSwFntCache( false );
301 }
302 else
303 {
304 switch( nWhich )
305 {
306 case RES_OBJECTDYING:
307 case RES_FMT_CHG:
308 case RES_ATTRSET_CHG:
309 SetInSwFntCache( false );
310 [[fallthrough]];
311 case RES_UL_SPACE:
312 case RES_LR_SPACE:
313 case RES_BOX:
314 case RES_SHADOW:
315 case RES_FRM_SIZE:
316 case RES_KEEP:
317 case RES_BREAK:
318 if( IsInCache() )
319 {
320 SwFrame::GetCache().Delete( this );
321 SetInCache( false );
322 }
323 break;
324 }
325 }
326 }
327
WriterMultiListener(SwClient & rToTell)328 sw::WriterMultiListener::WriterMultiListener(SwClient& rToTell)
329 : m_rToTell(rToTell)
330 {}
331
~WriterMultiListener()332 sw::WriterMultiListener::~WriterMultiListener()
333 {}
334
StartListening(SwModify * pDepend)335 void sw::WriterMultiListener::StartListening(SwModify* pDepend)
336 {
337 EndListening(nullptr);
338 m_vDepends.emplace_back(ListenerEntry(&m_rToTell, pDepend));
339 }
340
341
IsListeningTo(const SwModify * const pBroadcaster) const342 bool sw::WriterMultiListener::IsListeningTo(const SwModify* const pBroadcaster) const
343 {
344 return std::any_of(m_vDepends.begin(), m_vDepends.end(),
345 [&pBroadcaster](const ListenerEntry& aListener)
346 {
347 return aListener.GetRegisteredIn() == pBroadcaster;
348 });
349 }
350
EndListening(SwModify * pBroadcaster)351 void sw::WriterMultiListener::EndListening(SwModify* pBroadcaster)
352 {
353 m_vDepends.erase(
354 std::remove_if( m_vDepends.begin(), m_vDepends.end(),
355 [&pBroadcaster](const ListenerEntry& aListener)
356 {
357 return aListener.GetRegisteredIn() == nullptr || aListener.GetRegisteredIn() == pBroadcaster;
358 }),
359 m_vDepends.end());
360 }
361
EndListeningAll()362 void sw::WriterMultiListener::EndListeningAll()
363 {
364 m_vDepends.clear();
365 }
366
367 sw::ClientIteratorBase* sw::ClientIteratorBase::s_pClientIters = nullptr;
368
CallSwClientNotify(const SfxHint & rHint) const369 void SwModify::CallSwClientNotify( const SfxHint& rHint ) const
370 {
371 SwIterator<SwClient,SwModify> aIter(*this);
372 for(SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next())
373 pClient->SwClientNotify( *this, rHint );
374 }
375
CallSwClientNotify(const SfxHint & rHint) const376 void sw::BroadcastingModify::CallSwClientNotify(const SfxHint& rHint) const
377 {
378 SwModify::CallSwClientNotify(rHint);
379 const_cast<BroadcastingModify*>(this)->GetNotifier().Broadcast(rHint);
380 }
381 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
382