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