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 <helper/mischelper.hxx>
21 #include <com/sun/star/lang/IllegalArgumentException.hpp>
22 #include <com/sun/star/lang/XComponent.hpp>
23 #include <com/sun/star/lang/XEventListener.hpp>
24 #include <com/sun/star/lang/XServiceInfo.hpp>
25 #include <com/sun/star/ui/XContextChangeEventMultiplexer.hpp>
26 #include <com/sun/star/uno/XComponentContext.hpp>
27 
28 #include <cppuhelper/compbase.hxx>
29 #include <cppuhelper/supportsservice.hxx>
30 #include <cppuhelper/basemutex.hxx>
31 
32 #include <algorithm>
33 #include <map>
34 #include <vector>
35 
36 namespace cssl = css::lang;
37 namespace cssu = css::uno;
38 
39 using namespace css;
40 using namespace css::uno;
41 
42 namespace {
43 
44 typedef ::cppu::WeakComponentImplHelper <
45     css::ui::XContextChangeEventMultiplexer,
46     css::lang::XServiceInfo,
47     css::lang::XEventListener
48     > ContextChangeEventMultiplexerInterfaceBase;
49 
50 class ContextChangeEventMultiplexer
51     : private ::cppu::BaseMutex,
52       public ContextChangeEventMultiplexerInterfaceBase
53 {
54 public:
55     ContextChangeEventMultiplexer();
56     ContextChangeEventMultiplexer(const ContextChangeEventMultiplexer&) = delete;
57     ContextChangeEventMultiplexer& operator=(const ContextChangeEventMultiplexer&) = delete;
58 
59     virtual void SAL_CALL disposing() override;
60 
61     // XContextChangeEventMultiplexer
62     virtual void SAL_CALL addContextChangeEventListener (
63         const cssu::Reference<css::ui::XContextChangeEventListener>& rxListener,
64         const cssu::Reference<cssu::XInterface>& rxEventFocus) override;
65     virtual void SAL_CALL removeContextChangeEventListener (
66         const cssu::Reference<css::ui::XContextChangeEventListener>& rxListener,
67         const cssu::Reference<cssu::XInterface>& rxEventFocus) override;
68     virtual void SAL_CALL removeAllContextChangeEventListeners (
69         const cssu::Reference<css::ui::XContextChangeEventListener>& rxListener) override;
70     virtual void SAL_CALL broadcastContextChangeEvent (
71         const css::ui::ContextChangeEventObject& rContextChangeEventObject,
72         const cssu::Reference<cssu::XInterface>& rxEventFocus) override;
73 
74     // XServiceInfo
75     virtual OUString SAL_CALL getImplementationName() override;
76     virtual sal_Bool SAL_CALL supportsService  (
77         const OUString& rsServiceName) override;
78     virtual cssu::Sequence< OUString> SAL_CALL getSupportedServiceNames() override;
79 
80     // XEventListener
81     virtual void SAL_CALL disposing (
82         const css::lang::EventObject& rEvent) override;
83 
84     typedef ::std::vector<cssu::Reference<css::ui::XContextChangeEventListener> > ListenerContainer;
85     class FocusDescriptor
86     {
87     public:
88         ListenerContainer maListeners;
89         OUString msCurrentApplicationName;
90         OUString msCurrentContextName;
91     };
92     typedef ::std::map<cssu::Reference<cssu::XInterface>, FocusDescriptor> ListenerMap;
93     ListenerMap maListeners;
94 
95     /** Notify all listeners in the container that is associated with
96         the given event focus.
97 
98         Typically called twice from broadcastEvent(), once for the
99         given event focus and once for NULL.
100     */
101     void BroadcastEventToSingleContainer (
102         const css::ui::ContextChangeEventObject& rEventObject,
103         const cssu::Reference<cssu::XInterface>& rxEventFocus);
104     FocusDescriptor* GetFocusDescriptor (
105         const cssu::Reference<cssu::XInterface>& rxEventFocus,
106         const bool bCreateWhenMissing);
107 };
108 
ContextChangeEventMultiplexer()109 ContextChangeEventMultiplexer::ContextChangeEventMultiplexer()
110     : ContextChangeEventMultiplexerInterfaceBase(m_aMutex),
111       maListeners()
112 {
113 }
114 
disposing()115 void SAL_CALL ContextChangeEventMultiplexer::disposing()
116 {
117     ListenerMap aListeners;
118     aListeners.swap(maListeners);
119 
120     cssu::Reference<cssu::XInterface> xThis (static_cast<XWeak*>(this));
121     css::lang::EventObject aEvent (xThis);
122     for (auto const& container : aListeners)
123     {
124         // Unregister from the focus object.
125         Reference<lang::XComponent> xComponent (container.first, UNO_QUERY);
126         if (xComponent.is())
127             xComponent->removeEventListener(this);
128 
129         // Tell all listeners that we are being disposed.
130         const FocusDescriptor& rFocusDescriptor (container.second);
131         for (auto const& listener : rFocusDescriptor.maListeners)
132         {
133             listener->disposing(aEvent);
134         }
135     }
136 }
137 
138 // XContextChangeEventMultiplexer
addContextChangeEventListener(const cssu::Reference<css::ui::XContextChangeEventListener> & rxListener,const cssu::Reference<cssu::XInterface> & rxEventFocus)139 void SAL_CALL ContextChangeEventMultiplexer::addContextChangeEventListener (
140     const cssu::Reference<css::ui::XContextChangeEventListener>& rxListener,
141     const cssu::Reference<cssu::XInterface>& rxEventFocus)
142 {
143     if ( ! rxListener.is())
144         throw css::lang::IllegalArgumentException(
145             "can not add an empty reference",
146             static_cast<XWeak*>(this),
147             0);
148 
149     FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, true);
150     if (pFocusDescriptor != nullptr)
151     {
152         ListenerContainer& rContainer (pFocusDescriptor->maListeners);
153         if (::std::find(rContainer.begin(), rContainer.end(), rxListener) != rContainer.end())
154         {
155             // The listener was added for the same event focus
156             // previously.  That is an error.
157             throw cssl::IllegalArgumentException("listener added twice", static_cast<XWeak*>(this), 0);
158         }
159         rContainer.push_back(rxListener);
160     }
161 
162     // Send out an initial event that informs the new listener about
163     // the current context.
164     if (rxEventFocus.is() && pFocusDescriptor!=nullptr)
165     {
166         css::ui::ContextChangeEventObject aEvent (
167             nullptr,
168             pFocusDescriptor->msCurrentApplicationName,
169             pFocusDescriptor->msCurrentContextName);
170         rxListener->notifyContextChangeEvent(aEvent);
171     }
172 }
173 
removeContextChangeEventListener(const cssu::Reference<css::ui::XContextChangeEventListener> & rxListener,const cssu::Reference<cssu::XInterface> & rxEventFocus)174 void SAL_CALL ContextChangeEventMultiplexer::removeContextChangeEventListener (
175     const cssu::Reference<css::ui::XContextChangeEventListener>& rxListener,
176     const cssu::Reference<cssu::XInterface>& rxEventFocus)
177 {
178     if ( ! rxListener.is())
179         throw cssl::IllegalArgumentException(
180             "can not remove an empty reference",
181             static_cast<XWeak*>(this), 0);
182 
183     FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, false);
184     if (pFocusDescriptor != nullptr)
185     {
186         ListenerContainer& rContainer (pFocusDescriptor->maListeners);
187         const ListenerContainer::iterator iListener (
188             ::std::find(rContainer.begin(), rContainer.end(), rxListener));
189         if (iListener != rContainer.end())
190         {
191             rContainer.erase(iListener);
192 
193             // We hold on to the focus descriptor even when the last listener has been removed.
194             // This allows us to keep track of the current context and send it to new listeners.
195         }
196     }
197 
198 }
199 
removeAllContextChangeEventListeners(const cssu::Reference<css::ui::XContextChangeEventListener> & rxListener)200 void SAL_CALL ContextChangeEventMultiplexer::removeAllContextChangeEventListeners (
201     const cssu::Reference<css::ui::XContextChangeEventListener>& rxListener)
202 {
203     if ( ! rxListener.is())
204         throw cssl::IllegalArgumentException(
205             "can not remove an empty reference",
206             static_cast<XWeak*>(this), 0);
207 
208     for (auto& rContainer : maListeners)
209     {
210         const ListenerContainer::iterator iListener (
211             ::std::find(rContainer.second.maListeners.begin(), rContainer.second.maListeners.end(), rxListener));
212         if (iListener != rContainer.second.maListeners.end())
213         {
214             rContainer.second.maListeners.erase(iListener);
215 
216             // We hold on to the focus descriptor even when the last listener has been removed.
217             // This allows us to keep track of the current context and send it to new listeners.
218         }
219     }
220 }
221 
broadcastContextChangeEvent(const css::ui::ContextChangeEventObject & rEventObject,const cssu::Reference<cssu::XInterface> & rxEventFocus)222 void SAL_CALL ContextChangeEventMultiplexer::broadcastContextChangeEvent (
223     const css::ui::ContextChangeEventObject& rEventObject,
224     const cssu::Reference<cssu::XInterface>& rxEventFocus)
225 {
226     // Remember the current context.
227     if (rxEventFocus.is())
228     {
229         FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, true);
230         if (pFocusDescriptor != nullptr)
231         {
232             pFocusDescriptor->msCurrentApplicationName = rEventObject.ApplicationName;
233             pFocusDescriptor->msCurrentContextName = rEventObject.ContextName;
234         }
235     }
236 
237     BroadcastEventToSingleContainer(rEventObject, rxEventFocus);
238     if (rxEventFocus.is())
239         BroadcastEventToSingleContainer(rEventObject, nullptr);
240 }
241 
BroadcastEventToSingleContainer(const css::ui::ContextChangeEventObject & rEventObject,const cssu::Reference<cssu::XInterface> & rxEventFocus)242 void ContextChangeEventMultiplexer::BroadcastEventToSingleContainer (
243     const css::ui::ContextChangeEventObject& rEventObject,
244     const cssu::Reference<cssu::XInterface>& rxEventFocus)
245 {
246     FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, false);
247     if (pFocusDescriptor != nullptr)
248     {
249         // Create a copy of the listener container to avoid problems
250         // when one of the called listeners calls add... or remove...
251         ListenerContainer aContainer (pFocusDescriptor->maListeners);
252         for (auto const& listener : aContainer)
253         {
254             listener->notifyContextChangeEvent(rEventObject);
255         }
256     }
257 }
258 
GetFocusDescriptor(const cssu::Reference<cssu::XInterface> & rxEventFocus,const bool bCreateWhenMissing)259 ContextChangeEventMultiplexer::FocusDescriptor* ContextChangeEventMultiplexer::GetFocusDescriptor (
260     const cssu::Reference<cssu::XInterface>& rxEventFocus,
261     const bool bCreateWhenMissing)
262 {
263     ListenerMap::iterator iDescriptor (maListeners.find(rxEventFocus));
264     if (iDescriptor == maListeners.end() && bCreateWhenMissing)
265     {
266         // Listen for the focus being disposed.
267         Reference<lang::XComponent> xComponent (rxEventFocus, UNO_QUERY);
268         if (xComponent.is())
269             xComponent->addEventListener(this);
270 
271         // Create a new listener container for the event focus.
272         iDescriptor = maListeners.emplace(
273                 rxEventFocus,
274                 FocusDescriptor()).first;
275     }
276     if (iDescriptor != maListeners.end())
277         return &iDescriptor->second;
278     else
279         return nullptr;
280 }
281 
getImplementationName()282 OUString SAL_CALL ContextChangeEventMultiplexer::getImplementationName()
283 {
284     return "org.apache.openoffice.comp.framework.ContextChangeEventMultiplexer";
285 }
286 
supportsService(const OUString & rsServiceName)287 sal_Bool SAL_CALL ContextChangeEventMultiplexer::supportsService ( const OUString& rsServiceName)
288 {
289     return cppu::supportsService(this, rsServiceName);
290 }
291 
getSupportedServiceNames()292 css::uno::Sequence<OUString> SAL_CALL ContextChangeEventMultiplexer::getSupportedServiceNames()
293 {
294     // it's a singleton, not a service
295     return css::uno::Sequence<OUString>();
296 }
297 
disposing(const css::lang::EventObject & rEvent)298 void SAL_CALL ContextChangeEventMultiplexer::disposing ( const css::lang::EventObject& rEvent)
299 {
300     ListenerMap::iterator iDescriptor (maListeners.find(rEvent.Source));
301 
302     if (iDescriptor == maListeners.end())
303     {
304         OSL_ASSERT(iDescriptor != maListeners.end());
305         return;
306     }
307 
308     // Should we notify the remaining listeners?
309 
310     maListeners.erase(iDescriptor);
311 }
312 
313 struct Instance {
Instance__anon1a9b6e3e0111::Instance314     explicit Instance():
315         instance(static_cast<cppu::OWeakObject *>(
316                     new ContextChangeEventMultiplexer()))
317     {
318     }
319 
320     css::uno::Reference<css::uno::XInterface> instance;
321 };
322 
323 struct Singleton:
324     public rtl::Static<Instance, Singleton>
325 {};
326 
327 }
328 
329 namespace framework {
330 
331 // right now we assume there's one matching listener
GetFirstListenerWith_ImplImpl(uno::Reference<uno::XInterface> const & xEventFocus,std::function<bool (uno::Reference<ui::XContextChangeEventListener> const &)> const & rPredicate)332 static uno::Reference<ui::XContextChangeEventListener> GetFirstListenerWith_ImplImpl(
333     uno::Reference<uno::XInterface> const& xEventFocus,
334     std::function<bool (uno::Reference<ui::XContextChangeEventListener> const&)> const& rPredicate)
335 {
336     assert(xEventFocus.is()); // in current usage it's a bug if the XController is null here
337     uno::Reference<ui::XContextChangeEventListener> xRet;
338 
339     ContextChangeEventMultiplexer *const pMultiplexer(
340         dynamic_cast<ContextChangeEventMultiplexer *>(Singleton::get().instance.get()));
341     assert(pMultiplexer);
342 
343     ContextChangeEventMultiplexer::FocusDescriptor const*const pFocusDescriptor(
344         pMultiplexer->GetFocusDescriptor(xEventFocus, false));
345     if (!pFocusDescriptor)
346         return xRet;
347 
348     for (auto & xListener : pFocusDescriptor->maListeners)
349     {
350         if (rPredicate(xListener))
351         {
352             assert(!xRet.is()); // generalize this if it is used for more than 1:1 mapping?
353             xRet = xListener;
354         }
355     }
356     return xRet;
357 }
358 
359 namespace {
360 
361 struct Hook
362 {
Hookframework::__anon1a9b6e3e0211::Hook363     Hook() { g_pGetMultiplexerListener = &GetFirstListenerWith_ImplImpl; }
~Hookframework::__anon1a9b6e3e0211::Hook364     ~Hook() { g_pGetMultiplexerListener = nullptr; }
365 };
366 
367 static Hook g_hook;
368 
369 }
370 
371 }
372 
373 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
org_apache_openoffice_comp_framework_ContextChangeEventMultiplexer_get_implementation(css::uno::XComponentContext *,css::uno::Sequence<css::uno::Any> const &)374 org_apache_openoffice_comp_framework_ContextChangeEventMultiplexer_get_implementation(
375     css::uno::XComponentContext *,
376     css::uno::Sequence<css::uno::Any> const &)
377 {
378     return cppu::acquire(static_cast<cppu::OWeakObject *>(
379                 Singleton::get().instance.get()));
380 }
381 
382 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
383