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