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 <sal/config.h>
21 
22 #include <utility>
23 
24 #include <accelerators/acceleratorconfiguration.hxx>
25 #include <accelerators/keymapping.hxx>
26 #include <accelerators/presethandler.hxx>
27 
28 #include <xml/saxnamespacefilter.hxx>
29 #include <xml/acceleratorconfigurationreader.hxx>
30 #include <xml/acceleratorconfigurationwriter.hxx>
31 
32 #include <com/sun/star/xml/sax/Parser.hpp>
33 #include <com/sun/star/xml/sax/InputSource.hpp>
34 #include <com/sun/star/xml/sax/Writer.hpp>
35 #include <com/sun/star/io/IOException.hpp>
36 #include <com/sun/star/embed/ElementModes.hpp>
37 #include <com/sun/star/io/XSeekable.hpp>
38 #include <com/sun/star/io/XTruncate.hpp>
39 
40 #include <vcl/svapp.hxx>
41 #include <com/sun/star/container/XNamed.hpp>
42 #include <com/sun/star/container/XNameContainer.hpp>
43 #include <com/sun/star/container/XHierarchicalNameAccess.hpp>
44 #include <com/sun/star/awt/KeyEvent.hpp>
45 #include <com/sun/star/awt/KeyModifier.hpp>
46 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
47 #include <comphelper/configurationhelper.hxx>
48 #include <comphelper/sequence.hxx>
49 #include <officecfg/Setup.hxx>
50 #include <unotools/configpaths.hxx>
51 #include <svtools/acceleratorexecute.hxx>
52 #include <sal/log.hxx>
53 #include <rtl/ustrbuf.hxx>
54 #include <o3tl/string_view.hxx>
55 
56 constexpr OUString PRESET_DEFAULT = u"default"_ustr;
57 constexpr OUString TARGET_CURRENT = u"current"_ustr;
58 
59 namespace framework
60 {
61     constexpr OUString CFG_ENTRY_SECONDARY = u"SecondaryKeys"_ustr;
62     constexpr OUString CFG_PROP_COMMAND = u"Command"_ustr;
63 
lcl_getKeyString(const css::awt::KeyEvent & aKeyEvent)64     static OUString lcl_getKeyString(const css::awt::KeyEvent& aKeyEvent)
65     {
66         const sal_Int32 nBeginIndex = 4; // "KEY_" is the prefix of an identifier...
67         OUString sKey(KeyMapping::get().mapCodeToIdentifier(aKeyEvent.KeyCode));
68         if (sKey.getLength() < nBeginIndex) // dead key
69             return OUString();
70         OUStringBuffer sKeyBuffer(sKey.subView(nBeginIndex));
71 
72         if ( (aKeyEvent.Modifiers & css::awt::KeyModifier::SHIFT) == css::awt::KeyModifier::SHIFT )
73             sKeyBuffer.append("_SHIFT");
74         if ( (aKeyEvent.Modifiers & css::awt::KeyModifier::MOD1 ) == css::awt::KeyModifier::MOD1  )
75             sKeyBuffer.append("_MOD1");
76         if ( (aKeyEvent.Modifiers & css::awt::KeyModifier::MOD2 ) == css::awt::KeyModifier::MOD2  )
77             sKeyBuffer.append("_MOD2");
78         if ( (aKeyEvent.Modifiers & css::awt::KeyModifier::MOD3 ) == css::awt::KeyModifier::MOD3  )
79             sKeyBuffer.append("_MOD3");
80 
81         return sKeyBuffer.makeStringAndClear();
82     }
83 
XMLBasedAcceleratorConfiguration(const css::uno::Reference<css::uno::XComponentContext> & xContext)84 XMLBasedAcceleratorConfiguration::XMLBasedAcceleratorConfiguration(const css::uno::Reference< css::uno::XComponentContext >& xContext)
85     : m_xContext      (xContext                     )
86     , m_aPresetHandler(xContext                     )
87 {
88 }
89 
~XMLBasedAcceleratorConfiguration()90 XMLBasedAcceleratorConfiguration::~XMLBasedAcceleratorConfiguration()
91 {
92     SAL_WARN_IF(m_pWriteCache, "fwk.accelerators", "XMLBasedAcceleratorConfiguration::~XMLBasedAcceleratorConfiguration(): Changes not flushed. Ignore it ...");
93 }
94 
getAllKeyEvents()95 css::uno::Sequence< css::awt::KeyEvent > SAL_CALL XMLBasedAcceleratorConfiguration::getAllKeyEvents()
96 {
97     SolarMutexGuard g;
98     AcceleratorCache&          rCache = impl_getCFG();
99     AcceleratorCache::TKeyList lKeys  = rCache.getAllKeys();
100     return comphelper::containerToSequence(lKeys);
101 }
102 
getCommandByKeyEvent(const css::awt::KeyEvent & aKeyEvent)103 OUString SAL_CALL XMLBasedAcceleratorConfiguration::getCommandByKeyEvent(const css::awt::KeyEvent& aKeyEvent)
104 {
105     SolarMutexGuard g;
106     AcceleratorCache& rCache = impl_getCFG();
107     if (!rCache.hasKey(aKeyEvent))
108         throw css::container::NoSuchElementException(
109                 OUString(),
110                 static_cast< ::cppu::OWeakObject* >(this));
111     return rCache.getCommandByKey(aKeyEvent);
112 }
113 
setKeyEvent(const css::awt::KeyEvent & aKeyEvent,const OUString & sCommand)114 void SAL_CALL XMLBasedAcceleratorConfiguration::setKeyEvent(const css::awt::KeyEvent& aKeyEvent,
115                                                     const OUString&    sCommand )
116 {
117     if (
118         (aKeyEvent.KeyCode   == 0) &&
119         (aKeyEvent.KeyChar   == 0) &&
120         (aKeyEvent.KeyFunc   == 0) &&
121         (aKeyEvent.Modifiers == 0)
122         )
123         throw css::lang::IllegalArgumentException(
124             u"Such key event seems not to be supported by any operating system."_ustr,
125             static_cast< ::cppu::OWeakObject* >(this),
126             0);
127 
128     if (sCommand.isEmpty())
129         throw css::lang::IllegalArgumentException(
130             u"Empty command strings are not allowed here."_ustr,
131             static_cast< ::cppu::OWeakObject* >(this),
132             1);
133 
134     SolarMutexGuard g;
135     AcceleratorCache& rCache = impl_getCFG(true); // sal_True => force getting of a writeable cache!
136     rCache.setKeyCommandPair(aKeyEvent, sCommand);
137 }
138 
removeKeyEvent(const css::awt::KeyEvent & aKeyEvent)139 void SAL_CALL XMLBasedAcceleratorConfiguration::removeKeyEvent(const css::awt::KeyEvent& aKeyEvent)
140 {
141     SolarMutexGuard g;
142     AcceleratorCache& rCache = impl_getCFG(true); // true => force using of a writeable cache
143     if (!rCache.hasKey(aKeyEvent))
144         throw css::container::NoSuchElementException(
145         OUString(),
146         static_cast< ::cppu::OWeakObject* >(this));
147     rCache.removeKey(aKeyEvent);
148 }
149 
getKeyEventsByCommand(const OUString & sCommand)150 css::uno::Sequence< css::awt::KeyEvent > SAL_CALL XMLBasedAcceleratorConfiguration::getKeyEventsByCommand(const OUString& sCommand)
151 {
152     if (sCommand.isEmpty())
153         throw css::lang::IllegalArgumentException(
154                 u"Empty command strings are not allowed here."_ustr,
155                 static_cast< ::cppu::OWeakObject* >(this),
156                 1);
157 
158     SolarMutexGuard g;
159     AcceleratorCache& rCache = impl_getCFG();
160     if (!rCache.hasCommand(sCommand))
161         throw css::container::NoSuchElementException(
162                 OUString(),
163                 static_cast< ::cppu::OWeakObject* >(this));
164 
165     AcceleratorCache::TKeyList lKeys  = rCache.getKeysByCommand(sCommand);
166     return comphelper::containerToSequence(lKeys);
167 }
168 
getPreferredKeyEventsForCommandList(const css::uno::Sequence<OUString> & lCommandList)169 css::uno::Sequence< css::uno::Any > SAL_CALL XMLBasedAcceleratorConfiguration::getPreferredKeyEventsForCommandList(const css::uno::Sequence< OUString >& lCommandList)
170 {
171     SolarMutexGuard g;
172 
173     sal_Int32                           i              = 0;
174     sal_Int32                           c              = lCommandList.getLength();
175     css::uno::Sequence< css::uno::Any > lPreferredOnes (c); // don't pack list!
176     AcceleratorCache&                   rCache         = impl_getCFG();
177 
178     auto lPreferredOnesRange = asNonConstRange(lPreferredOnes);
179     for (i=0; i<c; ++i)
180     {
181         const OUString& rCommand = lCommandList[i];
182         if (rCommand.isEmpty())
183             throw css::lang::IllegalArgumentException(
184                     u"Empty command strings are not allowed here."_ustr,
185                     static_cast< ::cppu::OWeakObject* >(this),
186                     static_cast<sal_Int16>(i));
187 
188         if (!rCache.hasCommand(rCommand))
189             continue;
190 
191         AcceleratorCache::TKeyList lKeys = rCache.getKeysByCommand(rCommand);
192         if ( lKeys.empty() )
193             continue;
194 
195         css::uno::Any& rAny = lPreferredOnesRange[i];
196         rAny <<= *(lKeys.begin());
197     }
198 
199     return lPreferredOnes;
200 }
201 
removeCommandFromAllKeyEvents(const OUString & sCommand)202 void SAL_CALL XMLBasedAcceleratorConfiguration::removeCommandFromAllKeyEvents(const OUString& sCommand)
203 {
204     if (sCommand.isEmpty())
205         throw css::lang::IllegalArgumentException(
206                 u"Empty command strings are not allowed here."_ustr,
207                 static_cast< ::cppu::OWeakObject* >(this),
208                 0);
209 
210     SolarMutexGuard g;
211     AcceleratorCache& rCache = impl_getCFG(true); // sal_True => force getting of a writeable cache!
212     if (!rCache.hasCommand(sCommand))
213         throw css::container::NoSuchElementException(
214                 u"Command does not exists inside this container."_ustr,
215                 static_cast< ::cppu::OWeakObject* >(this));
216     rCache.removeCommand(sCommand);
217 }
218 
reload()219 void SAL_CALL XMLBasedAcceleratorConfiguration::reload()
220 {
221     css::uno::Reference< css::io::XStream > xStream;
222     css::uno::Reference< css::io::XStream > xStreamNoLang;
223     {
224         SolarMutexGuard g;
225         xStream = m_aPresetHandler.openTarget(TARGET_CURRENT,
226                 css::embed::ElementModes::READ);
227         try
228         {
229             xStreamNoLang = m_aPresetHandler.openPreset(PRESET_DEFAULT);
230         }
231         catch(const css::io::IOException&) {} // does not have to exist
232     }
233 
234     css::uno::Reference< css::io::XInputStream > xIn;
235     if (xStream.is())
236         xIn = xStream->getInputStream();
237     if (!xIn.is())
238         throw css::io::IOException(
239             u"Could not open accelerator configuration for reading."_ustr,
240             static_cast< ::cppu::OWeakObject* >(this));
241 
242     // impl_ts_load() does not clear the cache
243     {
244         SolarMutexGuard g;
245         m_aReadCache = AcceleratorCache();
246     }
247 
248     impl_ts_load(xIn);
249 
250     // Load also the general language independent default accelerators
251     // (ignoring the already defined accelerators)
252     if (xStreamNoLang.is())
253     {
254         xIn = xStreamNoLang->getInputStream();
255         if (xIn.is())
256             impl_ts_load(xIn);
257     }
258 }
259 
store()260 void SAL_CALL XMLBasedAcceleratorConfiguration::store()
261 {
262     css::uno::Reference< css::io::XStream > xStream;
263     {
264         SolarMutexGuard g;
265         xStream = m_aPresetHandler.openTarget(TARGET_CURRENT,
266                css::embed::ElementModes::READWRITE); // open or create!
267     }
268 
269     css::uno::Reference< css::io::XOutputStream > xOut;
270     if (xStream.is())
271         xOut = xStream->getOutputStream();
272 
273     if (!xOut.is())
274         throw css::io::IOException(
275             u"Could not open accelerator configuration for saving."_ustr,
276             static_cast< ::cppu::OWeakObject* >(this));
277 
278     impl_ts_save(xOut);
279 
280     xOut.clear();
281     xStream.clear();
282 
283     m_aPresetHandler.commitUserChanges();
284 }
285 
storeToStorage(const css::uno::Reference<css::embed::XStorage> & xStorage)286 void SAL_CALL XMLBasedAcceleratorConfiguration::storeToStorage(const css::uno::Reference< css::embed::XStorage >& xStorage)
287 {
288     // no fallback from read/write to readonly!
289     css::uno::Reference< css::io::XStream > xStream = xStorage->openStreamElement(TARGET_CURRENT, css::embed::ElementModes::READWRITE);
290 
291     css::uno::Reference< css::io::XOutputStream > xOut;
292     if (xStream.is())
293         xOut = xStream->getOutputStream();
294 
295     if (!xOut.is())
296         throw css::io::IOException(
297                 u"Could not open accelerator configuration for saving."_ustr,
298                 static_cast< ::cppu::OWeakObject* >(this));
299 
300     impl_ts_save(xOut);
301 
302     // TODO inform listener about success, so it can flush the root and sub storage of this stream!
303 }
304 
isModified()305 sal_Bool SAL_CALL XMLBasedAcceleratorConfiguration::isModified()
306 {
307     SolarMutexGuard g;
308     return (m_pWriteCache != nullptr);
309 }
310 
isReadOnly()311 sal_Bool SAL_CALL XMLBasedAcceleratorConfiguration::isReadOnly()
312 {
313     css::uno::Reference< css::io::XStream > xStream;
314     {
315         SolarMutexGuard g;
316         xStream = m_aPresetHandler.openTarget(TARGET_CURRENT,
317                 css::embed::ElementModes::READWRITE); // open or create!
318     }
319 
320     css::uno::Reference< css::io::XOutputStream > xOut;
321     if (xStream.is())
322         xOut = xStream->getOutputStream();
323     return !(xOut.is());
324 }
325 
setStorage(const css::uno::Reference<css::embed::XStorage> &)326 void SAL_CALL XMLBasedAcceleratorConfiguration::setStorage(const css::uno::Reference< css::embed::XStorage >& /*xStorage*/)
327 {
328     SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::setStorage(): implement this HACK .-)");
329 }
330 
hasStorage()331 sal_Bool SAL_CALL XMLBasedAcceleratorConfiguration::hasStorage()
332 {
333     SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::hasStorage(): implement this HACK .-)");
334     return false;
335 }
336 
addConfigurationListener(const css::uno::Reference<css::ui::XUIConfigurationListener> &)337 void SAL_CALL XMLBasedAcceleratorConfiguration::addConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& /*xListener*/)
338 {
339     SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::addConfigurationListener(): implement me");
340 }
341 
removeConfigurationListener(const css::uno::Reference<css::ui::XUIConfigurationListener> &)342 void SAL_CALL XMLBasedAcceleratorConfiguration::removeConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& /*xListener*/)
343 {
344     SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::removeConfigurationListener(): implement me");
345 }
346 
reset()347 void SAL_CALL XMLBasedAcceleratorConfiguration::reset()
348 {
349     {
350         SolarMutexGuard g;
351         m_aPresetHandler.copyPresetToTarget(PRESET_DEFAULT, TARGET_CURRENT);
352     }
353 
354     reload();
355 }
356 
addResetListener(const css::uno::Reference<css::form::XResetListener> &)357 void SAL_CALL XMLBasedAcceleratorConfiguration::addResetListener(const css::uno::Reference< css::form::XResetListener >& /*xListener*/)
358 {
359     SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::addResetListener(): implement me");
360 }
361 
removeResetListener(const css::uno::Reference<css::form::XResetListener> &)362 void SAL_CALL XMLBasedAcceleratorConfiguration::removeResetListener(const css::uno::Reference< css::form::XResetListener >& /*xListener*/)
363 {
364     SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::removeResetListener(): implement me");
365 }
366 
367 // IStorageListener
changesOccurred()368 void XMLBasedAcceleratorConfiguration::changesOccurred()
369 {
370     reload();
371 }
372 
impl_ts_load(const css::uno::Reference<css::io::XInputStream> & xStream)373 void XMLBasedAcceleratorConfiguration::impl_ts_load(const css::uno::Reference< css::io::XInputStream >& xStream)
374 {
375     css::uno::Reference< css::uno::XComponentContext > xContext;
376     {
377         SolarMutexGuard g;
378         xContext = m_xContext;
379         m_pWriteCache.reset();
380     }
381 
382     css::uno::Reference< css::io::XSeekable > xSeek(xStream, css::uno::UNO_QUERY);
383     if (xSeek.is())
384         xSeek->seek(0);
385 
386     SolarMutexGuard g;
387 
388     // create the parser queue
389     // Note: Use special filter object between parser and reader
390     // to get filtered xml with right namespaces ...
391     // Use further a temp cache for reading!
392     rtl::Reference<AcceleratorConfigurationReader> pReader = new AcceleratorConfigurationReader(m_aReadCache);
393     rtl::Reference<SaxNamespaceFilter> pFilter = new SaxNamespaceFilter(pReader);
394 
395     // connect parser, filter and stream
396     css::uno::Reference< css::xml::sax::XParser > xParser = css::xml::sax::Parser::create(xContext);
397     xParser->setDocumentHandler(pFilter);
398 
399     css::xml::sax::InputSource aSource;
400     aSource.aInputStream = xStream;
401 
402     // TODO think about error handling
403     xParser->parseStream(aSource);
404 }
405 
impl_ts_save(const css::uno::Reference<css::io::XOutputStream> & xStream)406 void XMLBasedAcceleratorConfiguration::impl_ts_save(const css::uno::Reference< css::io::XOutputStream >& xStream)
407 {
408     bool bChanged;
409     AcceleratorCache aCache;
410     css::uno::Reference< css::uno::XComponentContext > xContext;
411     {
412         SolarMutexGuard g;
413         bChanged = (m_pWriteCache != nullptr);
414         if (bChanged)
415             aCache = *m_pWriteCache;
416         else
417             aCache = m_aReadCache;
418         xContext = m_xContext;
419     }
420 
421     css::uno::Reference< css::io::XTruncate > xClearable(xStream, css::uno::UNO_QUERY_THROW);
422     xClearable->truncate();
423 
424     // TODO can be removed if seek(0) is done by truncate() automatically!
425     css::uno::Reference< css::io::XSeekable > xSeek(xStream, css::uno::UNO_QUERY);
426     if (xSeek.is())
427         xSeek->seek(0);
428 
429     // combine writer/cache/stream etcpp.
430     css::uno::Reference< css::xml::sax::XWriter > xWriter = css::xml::sax::Writer::create(xContext);
431     xWriter->setOutputStream(xStream);
432 
433     // write into the stream
434     css::uno::Reference< css::xml::sax::XDocumentHandler > xHandler(xWriter, css::uno::UNO_QUERY_THROW);
435     AcceleratorConfigurationWriter aWriter(aCache, xHandler);
436     aWriter.flush();
437 
438     SolarMutexGuard g;
439     // take over all changes into the readonly cache ...
440     // and forget the copy-on-write copied cache
441     if (bChanged)
442     {
443         m_aReadCache = *m_pWriteCache;
444         m_pWriteCache.reset();
445     }
446 }
447 
impl_getCFG(bool bWriteAccessRequested)448 AcceleratorCache& XMLBasedAcceleratorConfiguration::impl_getCFG(bool bWriteAccessRequested)
449 {
450     SolarMutexGuard g;
451 
452     //create copy of our readonly-cache, if write access is forced ... but
453     //not still possible!
454     if ( bWriteAccessRequested && !m_pWriteCache )
455     {
456         m_pWriteCache.reset(new AcceleratorCache(m_aReadCache));
457     }
458 
459     // in case, we have a writeable cache, we use it for reading too!
460     // Otherwise the API user can't find its own changes...
461     if (m_pWriteCache)
462         return *m_pWriteCache;
463     else
464         return m_aReadCache;
465 }
466 
467 // static
impl_ts_getLocale()468 OUString XMLBasedAcceleratorConfiguration::impl_ts_getLocale()
469 {
470     OUString sISOLocale = officecfg::Setup::L10N::ooLocale::get();
471 
472     if (sISOLocale.isEmpty())
473         return u"en-US"_ustr;
474     return sISOLocale;
475 }
476 
477 /*******************************************************************************
478 *
479 * XCU based accelerator configuration
480 *
481 *******************************************************************************/
482 
XCUBasedAcceleratorConfiguration(css::uno::Reference<css::uno::XComponentContext> xContext)483 XCUBasedAcceleratorConfiguration::XCUBasedAcceleratorConfiguration(css::uno::Reference< css::uno::XComponentContext > xContext)
484                                 : m_xContext      (std::move(xContext                     ))
485 {
486     m_xCfg.set(
487              ::comphelper::ConfigurationHelper::openConfig( m_xContext, u"org.openoffice.Office.Accelerators"_ustr, ::comphelper::EConfigurationModes::AllLocales ),
488              css::uno::UNO_QUERY );
489 }
490 
~XCUBasedAcceleratorConfiguration()491 XCUBasedAcceleratorConfiguration::~XCUBasedAcceleratorConfiguration()
492 {
493 }
494 
getAllKeyEvents()495 css::uno::Sequence< css::awt::KeyEvent > SAL_CALL XCUBasedAcceleratorConfiguration::getAllKeyEvents()
496 {
497     SolarMutexGuard g;
498 
499     AcceleratorCache::TKeyList lKeys  = impl_getCFG(true).getAllKeys(); //get keys from PrimaryKeys set
500 
501     AcceleratorCache::TKeyList lSecondaryKeys = impl_getCFG(false).getAllKeys(); //get keys from SecondaryKeys set
502     lKeys.reserve(lKeys.size()+lSecondaryKeys.size());
503     for (auto const& secondaryKey : lSecondaryKeys)
504         lKeys.push_back(secondaryKey);
505 
506     return comphelper::containerToSequence(lKeys);
507 }
508 
getCommandByKeyEvent(const css::awt::KeyEvent & aKeyEvent)509 OUString SAL_CALL XCUBasedAcceleratorConfiguration::getCommandByKeyEvent(const css::awt::KeyEvent& aKeyEvent)
510 {
511     SolarMutexGuard g;
512 
513     AcceleratorCache& rPrimaryCache   = impl_getCFG(true );
514     AcceleratorCache& rSecondaryCache = impl_getCFG(false);
515 
516     if (!rPrimaryCache.hasKey(aKeyEvent) && !rSecondaryCache.hasKey(aKeyEvent))
517         throw css::container::NoSuchElementException(
518         OUString(),
519         static_cast< ::cppu::OWeakObject* >(this));
520 
521     if (rPrimaryCache.hasKey(aKeyEvent))
522         return rPrimaryCache.getCommandByKey(aKeyEvent);
523     else
524         return rSecondaryCache.getCommandByKey(aKeyEvent);
525 }
526 
setKeyEvent(const css::awt::KeyEvent & aKeyEvent,const OUString & sCommand)527 void SAL_CALL XCUBasedAcceleratorConfiguration::setKeyEvent(const css::awt::KeyEvent& aKeyEvent,
528                                                     const OUString&    sCommand )
529 {
530     SAL_INFO( "fwk.accelerators", "XCUBasedAcceleratorConfiguration::setKeyEvent" );
531 
532     if (
533         (aKeyEvent.KeyCode   == 0) &&
534         (aKeyEvent.KeyChar   == 0) &&
535         (aKeyEvent.KeyFunc   == 0) &&
536         (aKeyEvent.Modifiers == 0)
537         )
538         throw css::lang::IllegalArgumentException(
539                 u"Such key event seems not to be supported by any operating system."_ustr,
540                 static_cast< ::cppu::OWeakObject* >(this),
541                 0);
542 
543     if (sCommand.isEmpty())
544                 throw css::lang::IllegalArgumentException(
545                     u"Empty command strings are not allowed here."_ustr,
546                     static_cast< ::cppu::OWeakObject* >(this),
547                     1);
548 
549     SolarMutexGuard g;
550 
551     AcceleratorCache& rPrimaryCache   = impl_getCFG(true, true ); // sal_True => force getting of a writeable cache!
552     AcceleratorCache& rSecondaryCache = impl_getCFG(false, true); // sal_True => force getting of a writeable cache!
553 
554     if ( rPrimaryCache.hasKey(aKeyEvent) )
555     {
556         OUString sOriginalCommand = rPrimaryCache.getCommandByKey(aKeyEvent);
557         if ( sCommand != sOriginalCommand )
558         {
559             if (rSecondaryCache.hasCommand(sOriginalCommand))
560             {
561                 AcceleratorCache::TKeyList lSecondaryKeys = rSecondaryCache.getKeysByCommand(sOriginalCommand);
562                 rSecondaryCache.removeKey(lSecondaryKeys[0]);
563                 rPrimaryCache.setKeyCommandPair(lSecondaryKeys[0], sOriginalCommand);
564             }
565 
566             if (rPrimaryCache.hasCommand(sCommand))
567             {
568                 AcceleratorCache::TKeyList lPrimaryKeys = rPrimaryCache.getKeysByCommand(sCommand);
569                 rPrimaryCache.removeKey(lPrimaryKeys[0]);
570                 rSecondaryCache.setKeyCommandPair(lPrimaryKeys[0], sCommand);
571             }
572 
573             rPrimaryCache.setKeyCommandPair(aKeyEvent, sCommand);
574         }
575     }
576 
577     else if ( rSecondaryCache.hasKey(aKeyEvent) )
578     {
579         OUString sOriginalCommand = rSecondaryCache.getCommandByKey(aKeyEvent);
580         if (sCommand != sOriginalCommand)
581         {
582             if (rPrimaryCache.hasCommand(sCommand))
583             {
584                 AcceleratorCache::TKeyList lPrimaryKeys = rPrimaryCache.getKeysByCommand(sCommand);
585                 rPrimaryCache.removeKey(lPrimaryKeys[0]);
586                 rSecondaryCache.setKeyCommandPair(lPrimaryKeys[0], sCommand);
587             }
588 
589             rSecondaryCache.removeKey(aKeyEvent);
590             rPrimaryCache.setKeyCommandPair(aKeyEvent, sCommand);
591         }
592     }
593 
594     else
595     {
596         if (rPrimaryCache.hasCommand(sCommand))
597         {
598             AcceleratorCache::TKeyList lPrimaryKeys = rPrimaryCache.getKeysByCommand(sCommand);
599             rPrimaryCache.removeKey(lPrimaryKeys[0]);
600             rSecondaryCache.setKeyCommandPair(lPrimaryKeys[0], sCommand);
601         }
602 
603         rPrimaryCache.setKeyCommandPair(aKeyEvent, sCommand);
604     }
605 }
606 
removeKeyEvent(const css::awt::KeyEvent & aKeyEvent)607 void SAL_CALL XCUBasedAcceleratorConfiguration::removeKeyEvent(const css::awt::KeyEvent& aKeyEvent)
608 {
609     SolarMutexGuard g;
610 
611     AcceleratorCache& rPrimaryCache   = impl_getCFG(true, true );
612     AcceleratorCache& rSecondaryCache = impl_getCFG(false, true);
613 
614     if (!rPrimaryCache.hasKey(aKeyEvent) && !rSecondaryCache.hasKey(aKeyEvent))
615         throw css::container::NoSuchElementException(
616         OUString(),
617         static_cast< ::cppu::OWeakObject* >(this));
618 
619     if (rPrimaryCache.hasKey(aKeyEvent))
620     {
621         OUString sOriginalCommand = rPrimaryCache.getCommandByKey(aKeyEvent);
622         if (!sOriginalCommand.isEmpty())
623         {
624             if (rSecondaryCache.hasCommand(sOriginalCommand))
625             {
626                 AcceleratorCache::TKeyList lSecondaryKeys = rSecondaryCache.getKeysByCommand(sOriginalCommand);
627                 rSecondaryCache.removeKey(lSecondaryKeys[0]);
628                 rPrimaryCache.setKeyCommandPair(lSecondaryKeys[0], sOriginalCommand);
629             }
630 
631             rPrimaryCache.removeKey(aKeyEvent);
632         }
633 
634     }
635     else
636     {
637         OUString sDelCommand = rSecondaryCache.getCommandByKey(aKeyEvent);
638         if (!sDelCommand.isEmpty())
639             rSecondaryCache.removeKey(aKeyEvent);
640     }
641 }
642 
getKeyEventsByCommand(const OUString & sCommand)643 css::uno::Sequence< css::awt::KeyEvent > SAL_CALL XCUBasedAcceleratorConfiguration::getKeyEventsByCommand(const OUString& sCommand)
644 {
645     if (sCommand.isEmpty())
646         throw css::lang::IllegalArgumentException(
647                 u"Empty command strings are not allowed here."_ustr,
648                 static_cast< ::cppu::OWeakObject* >(this),
649                 1);
650 
651     SolarMutexGuard g;
652 
653     AcceleratorCache& rPrimaryCache   = impl_getCFG(true );
654     AcceleratorCache& rSecondaryCache = impl_getCFG(false);
655 
656     if (!rPrimaryCache.hasCommand(sCommand) && !rSecondaryCache.hasCommand(sCommand))
657         throw css::container::NoSuchElementException(
658                 OUString(),
659                 static_cast< ::cppu::OWeakObject* >(this));
660 
661     AcceleratorCache::TKeyList lKeys  = rPrimaryCache.getKeysByCommand(sCommand);
662 
663     AcceleratorCache::TKeyList lSecondaryKeys = rSecondaryCache.getKeysByCommand(sCommand);
664     for (auto const& secondaryKey : lSecondaryKeys)
665         lKeys.push_back(secondaryKey);
666 
667     return comphelper::containerToSequence(lKeys);
668 }
669 
lcl_getPreferredKey(const AcceleratorCache::TKeyList & lKeys)670 static AcceleratorCache::TKeyList::const_iterator lcl_getPreferredKey(const AcceleratorCache::TKeyList& lKeys)
671 {
672     return std::find_if(lKeys.begin(), lKeys.end(), [](const css::awt::KeyEvent& rAWTKey) {
673         return !::svt::AcceleratorExecute::st_AWTKey2VCLKey(rAWTKey).GetName().isEmpty(); });
674 }
675 
getPreferredKeyEventsForCommandList(const css::uno::Sequence<OUString> & lCommandList)676 css::uno::Sequence< css::uno::Any > SAL_CALL XCUBasedAcceleratorConfiguration::getPreferredKeyEventsForCommandList(const css::uno::Sequence< OUString >& lCommandList)
677 {
678     SolarMutexGuard g;
679 
680     sal_Int32                           i              = 0;
681     sal_Int32                           c              = lCommandList.getLength();
682     css::uno::Sequence< css::uno::Any > lPreferredOnes (c); // don't pack list!
683     AcceleratorCache&                   rCache         = impl_getCFG(true);
684 
685     auto lPreferredOnesRange = asNonConstRange(lPreferredOnes);
686     for (i=0; i<c; ++i)
687     {
688         const OUString& rCommand = lCommandList[i];
689         if (rCommand.isEmpty())
690             throw css::lang::IllegalArgumentException(
691                     u"Empty command strings are not allowed here."_ustr,
692                     static_cast< ::cppu::OWeakObject* >(this),
693                     static_cast<sal_Int16>(i));
694 
695         if (!rCache.hasCommand(rCommand))
696             continue;
697 
698         AcceleratorCache::TKeyList lKeys = rCache.getKeysByCommand(rCommand);
699         if ( lKeys.empty() )
700             continue;
701 
702         AcceleratorCache::TKeyList::const_iterator pPreferredKey = lcl_getPreferredKey(lKeys);
703         if (pPreferredKey != lKeys.end ())
704         {
705             css::uno::Any& rAny = lPreferredOnesRange[i];
706             rAny <<= *pPreferredKey;
707         }
708     }
709 
710     return lPreferredOnes;
711 }
712 
removeCommandFromAllKeyEvents(const OUString & sCommand)713 void SAL_CALL XCUBasedAcceleratorConfiguration::removeCommandFromAllKeyEvents(const OUString& sCommand)
714 {
715     if (sCommand.isEmpty())
716         throw css::lang::IllegalArgumentException(
717                 u"Empty command strings are not allowed here."_ustr,
718                 static_cast< ::cppu::OWeakObject* >(this),
719                 0);
720 
721     SolarMutexGuard g;
722 
723     AcceleratorCache& rPrimaryCache   = impl_getCFG(true, true );
724     AcceleratorCache& rSecondaryCache = impl_getCFG(false, true);
725 
726     if (!rPrimaryCache.hasCommand(sCommand) && !rSecondaryCache.hasCommand(sCommand))
727         throw css::container::NoSuchElementException(
728                 u"Command does not exists inside this container."_ustr,
729                 static_cast< ::cppu::OWeakObject* >(this));
730 
731     if (rPrimaryCache.hasCommand(sCommand))
732         rPrimaryCache.removeCommand(sCommand);
733     if (rSecondaryCache.hasCommand(sCommand))
734         rSecondaryCache.removeCommand(sCommand);
735 }
736 
reload()737 void SAL_CALL XCUBasedAcceleratorConfiguration::reload()
738 {
739     SAL_INFO( "fwk.accelerators", "XCUBasedAcceleratorConfiguration::reload()" );
740 
741     SolarMutexGuard g;
742 
743     bool bPreferred;
744     css::uno::Reference< css::container::XNameAccess > xAccess;
745 
746     bPreferred = true;
747     m_aPrimaryReadCache = AcceleratorCache();
748     m_pPrimaryWriteCache.reset();
749     m_xCfg->getByName(CFG_ENTRY_PRIMARY) >>= xAccess;
750     impl_ts_load(bPreferred, xAccess); // load the preferred keys
751 
752     bPreferred = false;
753     m_aSecondaryReadCache = AcceleratorCache();
754     m_pSecondaryWriteCache.reset();
755     m_xCfg->getByName(CFG_ENTRY_SECONDARY) >>= xAccess;
756     impl_ts_load(bPreferred, xAccess); // load the secondary keys
757 }
758 
store()759 void SAL_CALL XCUBasedAcceleratorConfiguration::store()
760 {
761     SAL_INFO( "fwk.accelerators", "XCUBasedAcceleratorConfiguration::store()" );
762 
763     SolarMutexGuard g;
764 
765     bool bPreferred;
766 
767     bPreferred = true;
768     // on-demand creation of the primary write cache
769     impl_getCFG(bPreferred, true);
770     impl_ts_save(bPreferred);
771 
772     bPreferred = false;
773     // on-demand creation of the secondary write cache
774     impl_getCFG(bPreferred, true);
775     impl_ts_save(bPreferred);
776 }
777 
storeToStorage(const css::uno::Reference<css::embed::XStorage> & xStorage)778 void SAL_CALL XCUBasedAcceleratorConfiguration::storeToStorage(const css::uno::Reference< css::embed::XStorage >& xStorage)
779 {
780     // use m_aCache + old AcceleratorXMLWriter to store data directly on storage given as parameter ...
781     if (!xStorage.is())
782         return;
783 
784     tools::Long nOpenModes = css::embed::ElementModes::READWRITE;
785     css::uno::Reference< css::embed::XStorage > xAcceleratorTypeStorage = xStorage->openStorageElement(u"accelerator"_ustr, nOpenModes);
786     if (!xAcceleratorTypeStorage.is())
787         return;
788 
789     css::uno::Reference< css::io::XStream > xStream = xAcceleratorTypeStorage->openStreamElement(u"current"_ustr, nOpenModes);
790     css::uno::Reference< css::io::XOutputStream > xOut;
791     if (xStream.is())
792         xOut = xStream->getOutputStream();
793     if (!xOut.is())
794         throw css::io::IOException(
795             u"Could not open accelerator configuration for saving."_ustr,
796             static_cast< ::cppu::OWeakObject* >(this));
797 
798     // the original m_aCache has been split into primary cache and secondary cache...
799     // we should merge them before storing to storage
800     AcceleratorCache aCache;
801     {
802         SolarMutexGuard g;
803 
804         if (m_pPrimaryWriteCache != nullptr)
805             aCache = *m_pPrimaryWriteCache;
806         else
807             aCache = m_aPrimaryReadCache;
808 
809         AcceleratorCache::TKeyList lKeys;
810         if (m_pSecondaryWriteCache!=nullptr)
811         {
812             lKeys = m_pSecondaryWriteCache->getAllKeys();
813             for (auto const& lKey : lKeys)
814                 aCache.setKeyCommandPair(lKey, m_pSecondaryWriteCache->getCommandByKey(lKey));
815         }
816         else
817         {
818             lKeys = m_aSecondaryReadCache.getAllKeys();
819             for (auto const& lKey : lKeys)
820                 aCache.setKeyCommandPair(lKey, m_aSecondaryReadCache.getCommandByKey(lKey));
821         }
822     }
823 
824     css::uno::Reference< css::io::XTruncate > xClearable(xOut, css::uno::UNO_QUERY_THROW);
825     xClearable->truncate();
826     css::uno::Reference< css::io::XSeekable > xSeek(xOut, css::uno::UNO_QUERY);
827     if (xSeek.is())
828         xSeek->seek(0);
829 
830     css::uno::Reference< css::xml::sax::XWriter > xWriter = css::xml::sax::Writer::create(m_xContext);
831     xWriter->setOutputStream(xOut);
832 
833     // write into the stream
834     css::uno::Reference< css::xml::sax::XDocumentHandler > xHandler(xWriter, css::uno::UNO_QUERY_THROW);
835     AcceleratorConfigurationWriter aWriter(aCache, xHandler);
836     aWriter.flush();
837 }
838 
isModified()839 sal_Bool SAL_CALL XCUBasedAcceleratorConfiguration::isModified()
840 {
841     return false;
842 }
843 
isReadOnly()844 sal_Bool SAL_CALL XCUBasedAcceleratorConfiguration::isReadOnly()
845 {
846     return false;
847 }
848 
setStorage(const css::uno::Reference<css::embed::XStorage> &)849 void SAL_CALL XCUBasedAcceleratorConfiguration::setStorage(const css::uno::Reference< css::embed::XStorage >& /*xStorage*/)
850 {
851     SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::setStorage(): implement this HACK .-)");
852 }
853 
hasStorage()854 sal_Bool SAL_CALL XCUBasedAcceleratorConfiguration::hasStorage()
855 {
856     SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::hasStorage(): implement this HACK .-)");
857     return false;
858 }
859 
addConfigurationListener(const css::uno::Reference<css::ui::XUIConfigurationListener> &)860 void SAL_CALL XCUBasedAcceleratorConfiguration::addConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& /*xListener*/)
861 {
862     SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::addConfigurationListener(): implement me");
863 }
864 
removeConfigurationListener(const css::uno::Reference<css::ui::XUIConfigurationListener> &)865 void SAL_CALL XCUBasedAcceleratorConfiguration::removeConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& /*xListener*/)
866 {
867     SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::removeConfigurationListener(): implement me");
868 }
869 
reset()870 void SAL_CALL XCUBasedAcceleratorConfiguration::reset()
871 {
872     css::uno::Reference< css::container::XNamed > xNamed(m_xCfg, css::uno::UNO_QUERY);
873     OUString sConfig = xNamed->getName();
874     if ( sConfig == "Global" )
875     {
876         m_xCfg.set(
877             ::comphelper::ConfigurationHelper::openConfig( m_xContext, CFG_ENTRY_GLOBAL, ::comphelper::EConfigurationModes::AllLocales ),
878             css::uno::UNO_QUERY );
879         XCUBasedAcceleratorConfiguration::reload();
880     }
881     else if ( sConfig == "Modules" )
882     {
883         m_xCfg.set(
884             ::comphelper::ConfigurationHelper::openConfig( m_xContext, CFG_ENTRY_MODULES, ::comphelper::EConfigurationModes::AllLocales ),
885             css::uno::UNO_QUERY );
886         XCUBasedAcceleratorConfiguration::reload();
887     }
888 }
889 
addResetListener(const css::uno::Reference<css::form::XResetListener> &)890 void SAL_CALL XCUBasedAcceleratorConfiguration::addResetListener(const css::uno::Reference< css::form::XResetListener >& /*xListener*/)
891 {
892     SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::addResetListener(): implement me");
893 }
894 
removeResetListener(const css::uno::Reference<css::form::XResetListener> &)895 void SAL_CALL XCUBasedAcceleratorConfiguration::removeResetListener(const css::uno::Reference< css::form::XResetListener >& /*xListener*/)
896 {
897     SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::removeResetListener(): implement me");
898 }
899 
changesOccurred(const css::util::ChangesEvent & aEvent)900 void SAL_CALL XCUBasedAcceleratorConfiguration::changesOccurred(const css::util::ChangesEvent& aEvent)
901 {
902     SAL_INFO( "fwk.accelerators", "XCUBasedAcceleratorConfiguration::changesOccurred()" );
903 
904     css::uno::Reference< css::container::XHierarchicalNameAccess > xHAccess;
905     aEvent.Base >>= xHAccess;
906     if (! xHAccess.is ())
907         return;
908 
909     css::util::ChangesEvent aReceivedEvents( aEvent );
910     const sal_Int32 c = aReceivedEvents.Changes.getLength();
911     for (sal_Int32 i=0; i<c; ++i)
912     {
913         const css::util::ElementChange& aChange  =   aReceivedEvents.Changes[i];
914 
915         // Only path of form "PrimaryKeys/Modules/Module['<module_name>']/Key['<command_url>']/Command[<locale>]" will
916         // be interesting for use. Sometimes short path values are given also by the broadcaster ... but they must be ignored :-)
917         // So we try to split the path into 3 parts (module isn't important here, because we already know it ... because
918         // these instance is bound to a specific module configuration ... or it''s the global configuration where no module is given at all.
919 
920         OUString sOrgPath;
921         OUString sPath;
922         OUString sKey;
923 
924         aChange.Accessor >>= sOrgPath;
925         sPath              = sOrgPath;
926         OUString sPrimarySecondary = ::utl::extractFirstFromConfigurationPath(sPath, &sPath);
927         OUString sGlobalModules = ::utl::extractFirstFromConfigurationPath(sPath, &sPath);
928 
929         if ( sGlobalModules == CFG_ENTRY_GLOBAL )
930         {
931             sKey = ::utl::extractFirstFromConfigurationPath(sPath, &sPath);
932             if ( !sKey.isEmpty() && !sPath.isEmpty() )
933                 reloadChanged(sPrimarySecondary, sGlobalModules, OUString(), sKey);
934         }
935         else if ( sGlobalModules == CFG_ENTRY_MODULES )
936         {
937             OUString sModule = ::utl::extractFirstFromConfigurationPath(sPath, &sPath);
938             sKey = ::utl::extractFirstFromConfigurationPath(sPath, &sPath);
939 
940             if ( !sKey.isEmpty() && !sPath.isEmpty() )
941             {
942                 reloadChanged(sPrimarySecondary, sGlobalModules, sModule, sKey);
943             }
944         }
945     }
946 }
947 
disposing(const css::lang::EventObject &)948 void SAL_CALL XCUBasedAcceleratorConfiguration::disposing(const css::lang::EventObject& /*aSource*/)
949 {
950 }
951 
impl_ts_load(bool bPreferred,const css::uno::Reference<css::container::XNameAccess> & xCfg)952 void XCUBasedAcceleratorConfiguration::impl_ts_load( bool bPreferred, const css::uno::Reference< css::container::XNameAccess >& xCfg )
953 {
954     AcceleratorCache aReadCache;
955     css::uno::Reference< css::container::XNameAccess > xAccess;
956     if ( m_sGlobalOrModules == "Global" )
957         xCfg->getByName(CFG_ENTRY_GLOBAL) >>= xAccess;
958     else if ( m_sGlobalOrModules == "Modules" )
959     {
960         css::uno::Reference< css::container::XNameAccess > xModules;
961         xCfg->getByName(CFG_ENTRY_MODULES) >>= xModules;
962         xModules->getByName(m_sModuleCFG) >>= xAccess;
963     }
964 
965     const OUString sIsoLang       = impl_ts_getLocale();
966     static constexpr OUStringLiteral sDefaultLocale(u"en-US");
967 
968     css::uno::Reference< css::container::XNameAccess > xKey;
969     css::uno::Reference< css::container::XNameAccess > xCommand;
970     if (xAccess.is())
971     {
972         css::uno::Sequence< OUString > lKeys = xAccess->getElementNames();
973         sal_Int32 nKeys = lKeys.getLength();
974         for ( sal_Int32 i=0; i<nKeys; ++i )
975         {
976             OUString sKey = lKeys[i];
977             xAccess->getByName(sKey) >>= xKey;
978             xKey->getByName(CFG_PROP_COMMAND) >>= xCommand;
979 
980             const css::uno::Sequence< OUString > lLocales = xCommand->getElementNames();
981             ::std::vector< OUString > aLocales { lLocales.begin(), lLocales.end() };
982 
983             OUString sLocale;
984             for (auto const& locale : aLocales)
985             {
986                 if ( locale == sIsoLang )
987                 {
988                     sLocale = locale;
989                     break;
990                 }
991             }
992 
993             if (sLocale.isEmpty())
994             {
995                 for (auto const& locale : aLocales)
996                 {
997                     if ( locale == sDefaultLocale )
998                     {
999                         sLocale = locale;
1000                         break;
1001                     }
1002                 }
1003 
1004                 if (sLocale.isEmpty())
1005                     continue;
1006             }
1007 
1008             OUString sCommand;
1009             xCommand->getByName(sLocale) >>= sCommand;
1010             if (sCommand.isEmpty())
1011                 continue;
1012 
1013             css::awt::KeyEvent aKeyEvent;
1014 
1015             sal_Int32 nIndex = 0;
1016             std::u16string_view sKeyCommand = o3tl::getToken(sKey, 0, '_', nIndex);
1017             aKeyEvent.KeyCode = KeyMapping::get().mapIdentifierToCode(OUString::Concat("KEY_") + sKeyCommand);
1018 
1019             const sal_Int32 nToken = 4;
1020             bool bValid = true;
1021             sal_Int32 k;
1022             for (k = 0; k < nToken; ++k)
1023             {
1024                 if (nIndex < 0)
1025                     break;
1026 
1027                 std::u16string_view sToken = o3tl::getToken(sKey, 0, '_', nIndex);
1028                 if (sToken.empty())
1029                 {
1030                     bValid = false;
1031                     break;
1032                 }
1033 
1034                 if ( sToken == u"SHIFT" )
1035                     aKeyEvent.Modifiers |= css::awt::KeyModifier::SHIFT;
1036                 else if ( sToken == u"MOD1" )
1037                     aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD1;
1038                 else if ( sToken == u"MOD2" )
1039                     aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD2;
1040                 else if ( sToken == u"MOD3" )
1041                     aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD3;
1042                 else
1043                 {
1044                     bValid = false;
1045                     break;
1046                 }
1047             }
1048 
1049             if ( !aReadCache.hasKey(aKeyEvent) && bValid && k<nToken)
1050                 aReadCache.setKeyCommandPair(aKeyEvent, sCommand);
1051         }
1052     }
1053 
1054     if (bPreferred)
1055         m_aPrimaryReadCache = std::move(aReadCache);
1056     else
1057         m_aSecondaryReadCache = std::move(aReadCache);
1058 }
1059 
impl_ts_save(bool bPreferred)1060 void XCUBasedAcceleratorConfiguration::impl_ts_save(bool bPreferred)
1061 {
1062     if (bPreferred)
1063     {
1064         AcceleratorCache::TKeyList lPrimaryReadKeys  = m_aPrimaryReadCache.getAllKeys();
1065         AcceleratorCache::TKeyList lPrimaryWriteKeys = m_pPrimaryWriteCache->getAllKeys();
1066 
1067         for (auto const& primaryReadKey : lPrimaryReadKeys)
1068         {
1069             if (!m_pPrimaryWriteCache->hasKey(primaryReadKey))
1070                 removeKeyFromConfiguration(primaryReadKey, true);
1071         }
1072 
1073         for (auto const& primaryWriteKey : lPrimaryWriteKeys)
1074         {
1075             OUString sCommand = m_pPrimaryWriteCache->getCommandByKey(primaryWriteKey);
1076             if (!m_aPrimaryReadCache.hasKey(primaryWriteKey))
1077             {
1078                 insertKeyToConfiguration(primaryWriteKey, sCommand, true);
1079             }
1080             else
1081             {
1082                 OUString sReadCommand = m_aPrimaryReadCache.getCommandByKey(primaryWriteKey);
1083                 if (sReadCommand != sCommand)
1084                     insertKeyToConfiguration(primaryWriteKey, sCommand, true);
1085             }
1086         }
1087 
1088         // take over all changes into the original container
1089         SolarMutexGuard g;
1090         // coverity[check_after_deref] - confusing but correct
1091         if (m_pPrimaryWriteCache)
1092         {
1093             m_aPrimaryReadCache = *m_pPrimaryWriteCache;
1094             m_pPrimaryWriteCache.reset();
1095         }
1096     }
1097 
1098     else
1099     {
1100         AcceleratorCache::TKeyList lSecondaryReadKeys  = m_aSecondaryReadCache.getAllKeys();
1101         AcceleratorCache::TKeyList lSecondaryWriteKeys = m_pSecondaryWriteCache->getAllKeys();
1102 
1103         for (auto const& secondaryReadKey : lSecondaryReadKeys)
1104         {
1105             if (!m_pSecondaryWriteCache->hasKey(secondaryReadKey))
1106                 removeKeyFromConfiguration(secondaryReadKey, false);
1107         }
1108 
1109         for (auto const& secondaryWriteKey : lSecondaryWriteKeys)
1110         {
1111             OUString sCommand = m_pSecondaryWriteCache->getCommandByKey(secondaryWriteKey);
1112             if (!m_aSecondaryReadCache.hasKey(secondaryWriteKey))
1113             {
1114                 insertKeyToConfiguration(secondaryWriteKey, sCommand, false);
1115             }
1116             else
1117             {
1118                 OUString sReadCommand = m_aSecondaryReadCache.getCommandByKey(secondaryWriteKey);
1119                 if (sReadCommand != sCommand)
1120                     insertKeyToConfiguration(secondaryWriteKey, sCommand, false);
1121             }
1122         }
1123 
1124         // take over all changes into the original container
1125         SolarMutexGuard g;
1126         // coverity[check_after_deref] - confusing but correct
1127         if (m_pSecondaryWriteCache)
1128         {
1129             m_aSecondaryReadCache = *m_pSecondaryWriteCache;
1130             m_pSecondaryWriteCache.reset();
1131         }
1132     }
1133 
1134     ::comphelper::ConfigurationHelper::flush(m_xCfg);
1135 }
1136 
insertKeyToConfiguration(const css::awt::KeyEvent & aKeyEvent,const OUString & sCommand,const bool bPreferred)1137 void XCUBasedAcceleratorConfiguration::insertKeyToConfiguration( const css::awt::KeyEvent& aKeyEvent, const OUString& sCommand, const bool bPreferred )
1138 {
1139     css::uno::Reference< css::container::XNameAccess > xAccess;
1140     css::uno::Reference< css::container::XNameContainer > xContainer;
1141     css::uno::Reference< css::lang::XSingleServiceFactory > xFac;
1142     css::uno::Reference< css::uno::XInterface > xInst;
1143 
1144     if ( bPreferred )
1145         m_xCfg->getByName(CFG_ENTRY_PRIMARY) >>= xAccess;
1146     else
1147         m_xCfg->getByName(CFG_ENTRY_SECONDARY) >>= xAccess;
1148 
1149     if ( m_sGlobalOrModules == CFG_ENTRY_GLOBAL )
1150         xAccess->getByName(CFG_ENTRY_GLOBAL) >>= xContainer;
1151     else if ( m_sGlobalOrModules == CFG_ENTRY_MODULES )
1152     {
1153         css::uno::Reference< css::container::XNameContainer > xModules;
1154         xAccess->getByName(CFG_ENTRY_MODULES) >>= xModules;
1155         if ( !xModules->hasByName(m_sModuleCFG) )
1156         {
1157             xFac.set(xModules, css::uno::UNO_QUERY);
1158             xInst = xFac->createInstance();
1159             xModules->insertByName(m_sModuleCFG, css::uno::Any(xInst));
1160         }
1161         xModules->getByName(m_sModuleCFG) >>= xContainer;
1162     }
1163 
1164     const OUString sKey = lcl_getKeyString(aKeyEvent);
1165     css::uno::Reference< css::container::XNameAccess > xKey;
1166     css::uno::Reference< css::container::XNameContainer > xCommand;
1167     if ( !xContainer->hasByName(sKey) )
1168     {
1169         xFac.set(xContainer, css::uno::UNO_QUERY);
1170         xInst = xFac->createInstance();
1171         xContainer->insertByName(sKey, css::uno::Any(xInst));
1172     }
1173     xContainer->getByName(sKey) >>= xKey;
1174 
1175     xKey->getByName(CFG_PROP_COMMAND) >>= xCommand;
1176     OUString sLocale = impl_ts_getLocale();
1177     if ( !xCommand->hasByName(sLocale) )
1178         xCommand->insertByName(sLocale, css::uno::Any(sCommand));
1179     else
1180         xCommand->replaceByName(sLocale, css::uno::Any(sCommand));
1181 }
1182 
removeKeyFromConfiguration(const css::awt::KeyEvent & aKeyEvent,const bool bPreferred)1183 void XCUBasedAcceleratorConfiguration::removeKeyFromConfiguration( const css::awt::KeyEvent& aKeyEvent, const bool bPreferred )
1184 {
1185     css::uno::Reference< css::container::XNameAccess > xAccess;
1186     css::uno::Reference< css::container::XNameContainer > xContainer;
1187 
1188     if ( bPreferred )
1189         m_xCfg->getByName(CFG_ENTRY_PRIMARY) >>= xAccess;
1190     else
1191         m_xCfg->getByName(CFG_ENTRY_SECONDARY) >>= xAccess;
1192 
1193     if ( m_sGlobalOrModules == CFG_ENTRY_GLOBAL )
1194         xAccess->getByName(CFG_ENTRY_GLOBAL) >>= xContainer;
1195     else if ( m_sGlobalOrModules == CFG_ENTRY_MODULES )
1196     {
1197         css::uno::Reference< css::container::XNameAccess > xModules;
1198         xAccess->getByName(CFG_ENTRY_MODULES) >>= xModules;
1199         if ( !xModules->hasByName(m_sModuleCFG) )
1200             return;
1201         xModules->getByName(m_sModuleCFG) >>= xContainer;
1202     }
1203 
1204     const OUString sKey = lcl_getKeyString(aKeyEvent);
1205     xContainer->removeByName(sKey);
1206 }
1207 
reloadChanged(const OUString & sPrimarySecondary,std::u16string_view sGlobalModules,const OUString & sModule,const OUString & sKey)1208 void XCUBasedAcceleratorConfiguration::reloadChanged( const OUString& sPrimarySecondary, std::u16string_view sGlobalModules, const OUString& sModule, const OUString& sKey )
1209 {
1210     css::uno::Reference< css::container::XNameAccess > xAccess;
1211     css::uno::Reference< css::container::XNameContainer > xContainer;
1212 
1213     m_xCfg->getByName(sPrimarySecondary) >>= xAccess;
1214     if ( sGlobalModules == CFG_ENTRY_GLOBAL )
1215         xAccess->getByName(CFG_ENTRY_GLOBAL) >>= xContainer;
1216     else
1217     {
1218         css::uno::Reference< css::container::XNameAccess > xModules;
1219         xAccess->getByName(CFG_ENTRY_MODULES) >>= xModules;
1220         if ( !xModules->hasByName(sModule) )
1221             return;
1222         xModules->getByName(sModule) >>= xContainer;
1223     }
1224 
1225     css::awt::KeyEvent aKeyEvent;
1226 
1227     sal_Int32 nIndex = 0;
1228     std::u16string_view sKeyIdentifier = o3tl::getToken(sKey, 0, '_', nIndex);
1229     aKeyEvent.KeyCode = KeyMapping::get().mapIdentifierToCode(OUString::Concat("KEY_") + sKeyIdentifier);
1230 
1231     const int nToken = 4;
1232     for (sal_Int32 i = 0; i < nToken; ++i)
1233     {
1234         if ( nIndex < 0 )
1235             break;
1236 
1237         std::u16string_view sToken = o3tl::getToken(sKey, 0, '_', nIndex);
1238         if ( sToken == u"SHIFT" )
1239             aKeyEvent.Modifiers |= css::awt::KeyModifier::SHIFT;
1240         else if ( sToken == u"MOD1" )
1241             aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD1;
1242         else if ( sToken == u"MOD2" )
1243             aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD2;
1244         else if ( sToken == u"MOD3" )
1245              aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD3;
1246     }
1247 
1248     css::uno::Reference< css::container::XNameAccess > xKey;
1249     css::uno::Reference< css::container::XNameAccess > xCommand;
1250     OUString sCommand;
1251 
1252     if (xContainer->hasByName(sKey))
1253     {
1254         OUString sLocale = impl_ts_getLocale();
1255         xContainer->getByName(sKey)    >>= xKey;
1256         xKey->getByName(CFG_PROP_COMMAND)  >>= xCommand;
1257         xCommand->getByName(sLocale)       >>= sCommand;
1258     }
1259 
1260     if ( sPrimarySecondary == CFG_ENTRY_PRIMARY )
1261     {
1262         if (sCommand.isEmpty())
1263             m_aPrimaryReadCache.removeKey(aKeyEvent);
1264         else
1265             m_aPrimaryReadCache.setKeyCommandPair(aKeyEvent, sCommand);
1266     }
1267     else if ( sPrimarySecondary == CFG_ENTRY_SECONDARY )
1268     {
1269         if (sCommand.isEmpty())
1270             m_aSecondaryReadCache.removeKey(aKeyEvent);
1271         else
1272             m_aSecondaryReadCache.setKeyCommandPair(aKeyEvent, sCommand);
1273     }
1274 }
1275 
impl_getCFG(bool bPreferred,bool bWriteAccessRequested)1276 AcceleratorCache& XCUBasedAcceleratorConfiguration::impl_getCFG(bool bPreferred, bool bWriteAccessRequested)
1277 {
1278     SolarMutexGuard g;
1279 
1280     if (bPreferred)
1281     {
1282         //create copy of our readonly-cache, if write access is forced ... but
1283         //not still possible!
1284         if ( bWriteAccessRequested && !m_pPrimaryWriteCache )
1285         {
1286             m_pPrimaryWriteCache.reset(new AcceleratorCache(m_aPrimaryReadCache));
1287         }
1288 
1289         // in case, we have a writeable cache, we use it for reading too!
1290         // Otherwise the API user can't find its own changes...
1291         if (m_pPrimaryWriteCache)
1292             return *m_pPrimaryWriteCache;
1293         else
1294             return m_aPrimaryReadCache;
1295     }
1296 
1297     else
1298     {
1299         //create copy of our readonly-cache, if write access is forced ... but
1300         //not still possible!
1301         if ( bWriteAccessRequested && !m_pSecondaryWriteCache )
1302         {
1303             m_pSecondaryWriteCache.reset(new AcceleratorCache(m_aSecondaryReadCache));
1304         }
1305 
1306         // in case, we have a writeable cache, we use it for reading too!
1307         // Otherwise the API user can't find its own changes...
1308         if (m_pSecondaryWriteCache)
1309             return *m_pSecondaryWriteCache;
1310         else
1311             return m_aSecondaryReadCache;
1312     }
1313 }
1314 
1315 // static
impl_ts_getLocale()1316 OUString XCUBasedAcceleratorConfiguration::impl_ts_getLocale()
1317 {
1318     OUString sISOLocale = officecfg::Setup::L10N::ooLocale::get();
1319 
1320     if (sISOLocale.isEmpty())
1321         return u"en-US"_ustr;
1322     return sISOLocale;
1323 }
1324 
1325 } // namespace framework
1326 
1327 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1328