diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /framework/source/accelerators | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'framework/source/accelerators')
-rw-r--r-- | framework/source/accelerators/acceleratorcache.cxx | 120 | ||||
-rw-r--r-- | framework/source/accelerators/acceleratorconfiguration.cxx | 1326 | ||||
-rw-r--r-- | framework/source/accelerators/documentacceleratorconfiguration.cxx | 196 | ||||
-rw-r--r-- | framework/source/accelerators/globalacceleratorconfiguration.cxx | 124 | ||||
-rw-r--r-- | framework/source/accelerators/keymapping.cxx | 208 | ||||
-rw-r--r-- | framework/source/accelerators/moduleacceleratorconfiguration.cxx | 157 | ||||
-rw-r--r-- | framework/source/accelerators/presethandler.cxx | 727 | ||||
-rw-r--r-- | framework/source/accelerators/storageholder.cxx | 447 |
8 files changed, 3305 insertions, 0 deletions
diff --git a/framework/source/accelerators/acceleratorcache.cxx b/framework/source/accelerators/acceleratorcache.cxx new file mode 100644 index 000000000..98596a895 --- /dev/null +++ b/framework/source/accelerators/acceleratorcache.cxx @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <accelerators/acceleratorcache.hxx> + +#include <com/sun/star/container/NoSuchElementException.hpp> + +#include <vcl/svapp.hxx> + +namespace framework +{ +bool AcceleratorCache::hasKey(const css::awt::KeyEvent& aKey) const +{ + SolarMutexGuard g; + return (m_lKey2Commands.find(aKey) != m_lKey2Commands.end()); +} + +bool AcceleratorCache::hasCommand(const OUString& sCommand) const +{ + SolarMutexGuard g; + return (m_lCommand2Keys.find(sCommand) != m_lCommand2Keys.end()); +} + +AcceleratorCache::TKeyList AcceleratorCache::getAllKeys() const +{ + SolarMutexGuard g; + TKeyList lKeys; + lKeys.reserve(m_lKey2Commands.size()); + + for (auto const& key2Command : m_lKey2Commands) + { + lKeys.push_back(key2Command.first); + } + + return lKeys; +} + +void AcceleratorCache::setKeyCommandPair(const css::awt::KeyEvent& aKey, const OUString& sCommand) +{ + SolarMutexGuard g; + + // register command for the specified key + m_lKey2Commands[aKey] = sCommand; + + // update optimized structure to bind multiple keys to one command + TKeyList& rKeyList = m_lCommand2Keys[sCommand]; + rKeyList.push_back(aKey); +} + +AcceleratorCache::TKeyList AcceleratorCache::getKeysByCommand(const OUString& sCommand) const +{ + SolarMutexGuard g; + TCommand2Keys::const_iterator pCommand = m_lCommand2Keys.find(sCommand); + if (pCommand == m_lCommand2Keys.end()) + throw css::container::NoSuchElementException(); + return pCommand->second; +} + +OUString AcceleratorCache::getCommandByKey(const css::awt::KeyEvent& aKey) const +{ + SolarMutexGuard g; + TKey2Commands::const_iterator pKey = m_lKey2Commands.find(aKey); + if (pKey == m_lKey2Commands.end()) + throw css::container::NoSuchElementException(); + return pKey->second; +} + +void AcceleratorCache::removeKey(const css::awt::KeyEvent& aKey) +{ + SolarMutexGuard g; + + // check if key exists + TKey2Commands::const_iterator pKey = m_lKey2Commands.find(aKey); + if (pKey == m_lKey2Commands.end()) + return; + + // get its registered command + // Because we must know its place inside the optimized + // structure, which bind keys to commands, too! + OUString sCommand = pKey->second; + pKey = m_lKey2Commands.end(); // nobody should use an undefined value .-) + + // remove key from primary list + m_lKey2Commands.erase(aKey); + + // remove key from optimized command list + m_lCommand2Keys.erase(sCommand); +} + +void AcceleratorCache::removeCommand(const OUString& sCommand) +{ + SolarMutexGuard g; + + const TKeyList& lKeys = getKeysByCommand(sCommand); + for (auto const& lKey : lKeys) + { + removeKey(lKey); + } + m_lCommand2Keys.erase(sCommand); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/accelerators/acceleratorconfiguration.cxx b/framework/source/accelerators/acceleratorconfiguration.cxx new file mode 100644 index 000000000..ad08f375a --- /dev/null +++ b/framework/source/accelerators/acceleratorconfiguration.cxx @@ -0,0 +1,1326 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <utility> + +#include <accelerators/acceleratorconfiguration.hxx> +#include <accelerators/keymapping.hxx> +#include <accelerators/presethandler.hxx> + +#include <xml/saxnamespacefilter.hxx> +#include <xml/acceleratorconfigurationreader.hxx> +#include <xml/acceleratorconfigurationwriter.hxx> + +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/io/XTruncate.hpp> + +#include <vcl/svapp.hxx> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/awt/KeyEvent.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <comphelper/configurationhelper.hxx> +#include <comphelper/sequence.hxx> +#include <officecfg/Setup.hxx> +#include <unotools/configpaths.hxx> +#include <svtools/acceleratorexecute.hxx> +#include <sal/log.hxx> +#include <rtl/ustrbuf.hxx> +#include <o3tl/string_view.hxx> + +constexpr OUStringLiteral PRESET_DEFAULT = u"default"; +constexpr OUStringLiteral TARGET_CURRENT = u"current"; + +namespace framework +{ + constexpr OUStringLiteral CFG_ENTRY_SECONDARY = u"SecondaryKeys"; + constexpr OUStringLiteral CFG_PROP_COMMAND = u"Command"; + + static OUString lcl_getKeyString(const css::awt::KeyEvent& aKeyEvent) + { + const sal_Int32 nBeginIndex = 4; // "KEY_" is the prefix of an identifier... + OUString sKey(KeyMapping::get().mapCodeToIdentifier(aKeyEvent.KeyCode)); + if (sKey.getLength() < nBeginIndex) // dead key + return OUString(); + OUStringBuffer sKeyBuffer(sKey.subView(nBeginIndex)); + + if ( (aKeyEvent.Modifiers & css::awt::KeyModifier::SHIFT) == css::awt::KeyModifier::SHIFT ) + sKeyBuffer.append("_SHIFT"); + if ( (aKeyEvent.Modifiers & css::awt::KeyModifier::MOD1 ) == css::awt::KeyModifier::MOD1 ) + sKeyBuffer.append("_MOD1"); + if ( (aKeyEvent.Modifiers & css::awt::KeyModifier::MOD2 ) == css::awt::KeyModifier::MOD2 ) + sKeyBuffer.append("_MOD2"); + if ( (aKeyEvent.Modifiers & css::awt::KeyModifier::MOD3 ) == css::awt::KeyModifier::MOD3 ) + sKeyBuffer.append("_MOD3"); + + return sKeyBuffer.makeStringAndClear(); + } + +XMLBasedAcceleratorConfiguration::XMLBasedAcceleratorConfiguration(const css::uno::Reference< css::uno::XComponentContext >& xContext) + : m_xContext (xContext ) + , m_aPresetHandler(xContext ) +{ +} + +XMLBasedAcceleratorConfiguration::~XMLBasedAcceleratorConfiguration() +{ + SAL_WARN_IF(m_pWriteCache, "fwk.accelerators", "XMLBasedAcceleratorConfiguration::~XMLBasedAcceleratorConfiguration(): Changes not flushed. Ignore it ..."); +} + +css::uno::Sequence< css::awt::KeyEvent > SAL_CALL XMLBasedAcceleratorConfiguration::getAllKeyEvents() +{ + SolarMutexGuard g; + AcceleratorCache& rCache = impl_getCFG(); + AcceleratorCache::TKeyList lKeys = rCache.getAllKeys(); + return comphelper::containerToSequence(lKeys); +} + +OUString SAL_CALL XMLBasedAcceleratorConfiguration::getCommandByKeyEvent(const css::awt::KeyEvent& aKeyEvent) +{ + SolarMutexGuard g; + AcceleratorCache& rCache = impl_getCFG(); + if (!rCache.hasKey(aKeyEvent)) + throw css::container::NoSuchElementException( + OUString(), + static_cast< ::cppu::OWeakObject* >(this)); + return rCache.getCommandByKey(aKeyEvent); +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::setKeyEvent(const css::awt::KeyEvent& aKeyEvent, + const OUString& sCommand ) +{ + if ( + (aKeyEvent.KeyCode == 0) && + (aKeyEvent.KeyChar == 0) && + (aKeyEvent.KeyFunc == 0) && + (aKeyEvent.Modifiers == 0) + ) + throw css::lang::IllegalArgumentException( + "Such key event seems not to be supported by any operating system.", + static_cast< ::cppu::OWeakObject* >(this), + 0); + + if (sCommand.isEmpty()) + throw css::lang::IllegalArgumentException( + "Empty command strings are not allowed here.", + static_cast< ::cppu::OWeakObject* >(this), + 1); + + SolarMutexGuard g; + AcceleratorCache& rCache = impl_getCFG(true); // sal_True => force getting of a writeable cache! + rCache.setKeyCommandPair(aKeyEvent, sCommand); +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::removeKeyEvent(const css::awt::KeyEvent& aKeyEvent) +{ + SolarMutexGuard g; + AcceleratorCache& rCache = impl_getCFG(true); // true => force using of a writeable cache + if (!rCache.hasKey(aKeyEvent)) + throw css::container::NoSuchElementException( + OUString(), + static_cast< ::cppu::OWeakObject* >(this)); + rCache.removeKey(aKeyEvent); +} + +css::uno::Sequence< css::awt::KeyEvent > SAL_CALL XMLBasedAcceleratorConfiguration::getKeyEventsByCommand(const OUString& sCommand) +{ + if (sCommand.isEmpty()) + throw css::lang::IllegalArgumentException( + "Empty command strings are not allowed here.", + static_cast< ::cppu::OWeakObject* >(this), + 1); + + SolarMutexGuard g; + AcceleratorCache& rCache = impl_getCFG(); + if (!rCache.hasCommand(sCommand)) + throw css::container::NoSuchElementException( + OUString(), + static_cast< ::cppu::OWeakObject* >(this)); + + AcceleratorCache::TKeyList lKeys = rCache.getKeysByCommand(sCommand); + return comphelper::containerToSequence(lKeys); +} + +css::uno::Sequence< css::uno::Any > SAL_CALL XMLBasedAcceleratorConfiguration::getPreferredKeyEventsForCommandList(const css::uno::Sequence< OUString >& lCommandList) +{ + SolarMutexGuard g; + + sal_Int32 i = 0; + sal_Int32 c = lCommandList.getLength(); + css::uno::Sequence< css::uno::Any > lPreferredOnes (c); // don't pack list! + AcceleratorCache& rCache = impl_getCFG(); + + auto lPreferredOnesRange = asNonConstRange(lPreferredOnes); + for (i=0; i<c; ++i) + { + const OUString& rCommand = lCommandList[i]; + if (rCommand.isEmpty()) + throw css::lang::IllegalArgumentException( + "Empty command strings are not allowed here.", + static_cast< ::cppu::OWeakObject* >(this), + static_cast<sal_Int16>(i)); + + if (!rCache.hasCommand(rCommand)) + continue; + + AcceleratorCache::TKeyList lKeys = rCache.getKeysByCommand(rCommand); + if ( lKeys.empty() ) + continue; + + css::uno::Any& rAny = lPreferredOnesRange[i]; + rAny <<= *(lKeys.begin()); + } + + return lPreferredOnes; +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::removeCommandFromAllKeyEvents(const OUString& sCommand) +{ + if (sCommand.isEmpty()) + throw css::lang::IllegalArgumentException( + "Empty command strings are not allowed here.", + static_cast< ::cppu::OWeakObject* >(this), + 0); + + SolarMutexGuard g; + AcceleratorCache& rCache = impl_getCFG(true); // sal_True => force getting of a writeable cache! + if (!rCache.hasCommand(sCommand)) + throw css::container::NoSuchElementException( + "Command does not exists inside this container.", + static_cast< ::cppu::OWeakObject* >(this)); + rCache.removeCommand(sCommand); +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::reload() +{ + css::uno::Reference< css::io::XStream > xStream; + css::uno::Reference< css::io::XStream > xStreamNoLang; + { + SolarMutexGuard g; + xStream = m_aPresetHandler.openTarget(TARGET_CURRENT, + css::embed::ElementModes::READ); + try + { + xStreamNoLang = m_aPresetHandler.openPreset(PRESET_DEFAULT); + } + catch(const css::io::IOException&) {} // does not have to exist + } + + css::uno::Reference< css::io::XInputStream > xIn; + if (xStream.is()) + xIn = xStream->getInputStream(); + if (!xIn.is()) + throw css::io::IOException( + "Could not open accelerator configuration for reading.", + static_cast< ::cppu::OWeakObject* >(this)); + + // impl_ts_load() does not clear the cache + { + SolarMutexGuard g; + m_aReadCache = AcceleratorCache(); + } + + impl_ts_load(xIn); + + // Load also the general language independent default accelerators + // (ignoring the already defined accelerators) + if (xStreamNoLang.is()) + { + xIn = xStreamNoLang->getInputStream(); + if (xIn.is()) + impl_ts_load(xIn); + } +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::store() +{ + css::uno::Reference< css::io::XStream > xStream; + { + SolarMutexGuard g; + xStream = m_aPresetHandler.openTarget(TARGET_CURRENT, + css::embed::ElementModes::READWRITE); // open or create! + } + + css::uno::Reference< css::io::XOutputStream > xOut; + if (xStream.is()) + xOut = xStream->getOutputStream(); + + if (!xOut.is()) + throw css::io::IOException( + "Could not open accelerator configuration for saving.", + static_cast< ::cppu::OWeakObject* >(this)); + + impl_ts_save(xOut); + + xOut.clear(); + xStream.clear(); + + m_aPresetHandler.commitUserChanges(); +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::storeToStorage(const css::uno::Reference< css::embed::XStorage >& xStorage) +{ + // no fallback from read/write to readonly! + css::uno::Reference< css::io::XStream > xStream = xStorage->openStreamElement(TARGET_CURRENT, css::embed::ElementModes::READWRITE); + + css::uno::Reference< css::io::XOutputStream > xOut; + if (xStream.is()) + xOut = xStream->getOutputStream(); + + if (!xOut.is()) + throw css::io::IOException( + "Could not open accelerator configuration for saving.", + static_cast< ::cppu::OWeakObject* >(this)); + + impl_ts_save(xOut); + + // TODO inform listener about success, so it can flush the root and sub storage of this stream! +} + +sal_Bool SAL_CALL XMLBasedAcceleratorConfiguration::isModified() +{ + SolarMutexGuard g; + return (m_pWriteCache != nullptr); +} + +sal_Bool SAL_CALL XMLBasedAcceleratorConfiguration::isReadOnly() +{ + css::uno::Reference< css::io::XStream > xStream; + { + SolarMutexGuard g; + xStream = m_aPresetHandler.openTarget(TARGET_CURRENT, + css::embed::ElementModes::READWRITE); // open or create! + } + + css::uno::Reference< css::io::XOutputStream > xOut; + if (xStream.is()) + xOut = xStream->getOutputStream(); + return !(xOut.is()); +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::setStorage(const css::uno::Reference< css::embed::XStorage >& /*xStorage*/) +{ + SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::setStorage(): implement this HACK .-)"); +} + +sal_Bool SAL_CALL XMLBasedAcceleratorConfiguration::hasStorage() +{ + SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::hasStorage(): implement this HACK .-)"); + return false; +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::addConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& /*xListener*/) +{ + SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::addConfigurationListener(): implement me"); +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::removeConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& /*xListener*/) +{ + SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::removeConfigurationListener(): implement me"); +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::reset() +{ + { + SolarMutexGuard g; + m_aPresetHandler.copyPresetToTarget(PRESET_DEFAULT, TARGET_CURRENT); + } + + reload(); +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::addResetListener(const css::uno::Reference< css::form::XResetListener >& /*xListener*/) +{ + SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::addResetListener(): implement me"); +} + +void SAL_CALL XMLBasedAcceleratorConfiguration::removeResetListener(const css::uno::Reference< css::form::XResetListener >& /*xListener*/) +{ + SAL_INFO("fwk.accelerators", "XMLBasedAcceleratorConfiguration::removeResetListener(): implement me"); +} + +// IStorageListener +void XMLBasedAcceleratorConfiguration::changesOccurred() +{ + reload(); +} + +void XMLBasedAcceleratorConfiguration::impl_ts_load(const css::uno::Reference< css::io::XInputStream >& xStream) +{ + css::uno::Reference< css::uno::XComponentContext > xContext; + { + SolarMutexGuard g; + xContext = m_xContext; + m_pWriteCache.reset(); + } + + css::uno::Reference< css::io::XSeekable > xSeek(xStream, css::uno::UNO_QUERY); + if (xSeek.is()) + xSeek->seek(0); + + SolarMutexGuard g; + + // create the parser queue + // Note: Use special filter object between parser and reader + // to get filtered xml with right namespaces ... + // Use further a temp cache for reading! + rtl::Reference<AcceleratorConfigurationReader> pReader = new AcceleratorConfigurationReader(m_aReadCache); + rtl::Reference<SaxNamespaceFilter> pFilter = new SaxNamespaceFilter(pReader); + + // connect parser, filter and stream + css::uno::Reference< css::xml::sax::XParser > xParser = css::xml::sax::Parser::create(xContext); + xParser->setDocumentHandler(pFilter); + + css::xml::sax::InputSource aSource; + aSource.aInputStream = xStream; + + // TODO think about error handling + xParser->parseStream(aSource); +} + +void XMLBasedAcceleratorConfiguration::impl_ts_save(const css::uno::Reference< css::io::XOutputStream >& xStream) +{ + bool bChanged; + AcceleratorCache aCache; + css::uno::Reference< css::uno::XComponentContext > xContext; + { + SolarMutexGuard g; + bChanged = (m_pWriteCache != nullptr); + if (bChanged) + aCache = *m_pWriteCache; + else + aCache = m_aReadCache; + xContext = m_xContext; + } + + css::uno::Reference< css::io::XTruncate > xClearable(xStream, css::uno::UNO_QUERY_THROW); + xClearable->truncate(); + + // TODO can be removed if seek(0) is done by truncate() automatically! + css::uno::Reference< css::io::XSeekable > xSeek(xStream, css::uno::UNO_QUERY); + if (xSeek.is()) + xSeek->seek(0); + + // combine writer/cache/stream etcpp. + css::uno::Reference< css::xml::sax::XWriter > xWriter = css::xml::sax::Writer::create(xContext); + xWriter->setOutputStream(xStream); + + // write into the stream + css::uno::Reference< css::xml::sax::XDocumentHandler > xHandler(xWriter, css::uno::UNO_QUERY_THROW); + AcceleratorConfigurationWriter aWriter(aCache, xHandler); + aWriter.flush(); + + SolarMutexGuard g; + // take over all changes into the readonly cache ... + // and forget the copy-on-write copied cache + if (bChanged) + { + m_aReadCache = *m_pWriteCache; + m_pWriteCache.reset(); + } +} + +AcceleratorCache& XMLBasedAcceleratorConfiguration::impl_getCFG(bool bWriteAccessRequested) +{ + SolarMutexGuard g; + + //create copy of our readonly-cache, if write access is forced ... but + //not still possible! + if ( bWriteAccessRequested && !m_pWriteCache ) + { + m_pWriteCache.reset(new AcceleratorCache(m_aReadCache)); + } + + // in case, we have a writeable cache, we use it for reading too! + // Otherwise the API user can't find its own changes... + if (m_pWriteCache) + return *m_pWriteCache; + else + return m_aReadCache; +} + +OUString XMLBasedAcceleratorConfiguration::impl_ts_getLocale() const +{ + OUString sISOLocale = officecfg::Setup::L10N::ooLocale::get(); + + if (sISOLocale.isEmpty()) + return "en-US"; + return sISOLocale; +} + +/******************************************************************************* +* +* XCU based accelerator configuration +* +*******************************************************************************/ + +XCUBasedAcceleratorConfiguration::XCUBasedAcceleratorConfiguration(css::uno::Reference< css::uno::XComponentContext > xContext) + : m_xContext (std::move(xContext )) +{ + m_xCfg.set( + ::comphelper::ConfigurationHelper::openConfig( m_xContext, "org.openoffice.Office.Accelerators", ::comphelper::EConfigurationModes::AllLocales ), + css::uno::UNO_QUERY ); +} + +XCUBasedAcceleratorConfiguration::~XCUBasedAcceleratorConfiguration() +{ +} + +css::uno::Sequence< css::awt::KeyEvent > SAL_CALL XCUBasedAcceleratorConfiguration::getAllKeyEvents() +{ + SolarMutexGuard g; + + AcceleratorCache::TKeyList lKeys = impl_getCFG(true).getAllKeys(); //get keys from PrimaryKeys set + + AcceleratorCache::TKeyList lSecondaryKeys = impl_getCFG(false).getAllKeys(); //get keys from SecondaryKeys set + lKeys.reserve(lKeys.size()+lSecondaryKeys.size()); + for (auto const& secondaryKey : lSecondaryKeys) + lKeys.push_back(secondaryKey); + + return comphelper::containerToSequence(lKeys); +} + +OUString SAL_CALL XCUBasedAcceleratorConfiguration::getCommandByKeyEvent(const css::awt::KeyEvent& aKeyEvent) +{ + SolarMutexGuard g; + + AcceleratorCache& rPrimaryCache = impl_getCFG(true ); + AcceleratorCache& rSecondaryCache = impl_getCFG(false); + + if (!rPrimaryCache.hasKey(aKeyEvent) && !rSecondaryCache.hasKey(aKeyEvent)) + throw css::container::NoSuchElementException( + OUString(), + static_cast< ::cppu::OWeakObject* >(this)); + + if (rPrimaryCache.hasKey(aKeyEvent)) + return rPrimaryCache.getCommandByKey(aKeyEvent); + else + return rSecondaryCache.getCommandByKey(aKeyEvent); +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::setKeyEvent(const css::awt::KeyEvent& aKeyEvent, + const OUString& sCommand ) +{ + SAL_INFO( "fwk.accelerators", "XCUBasedAcceleratorConfiguration::setKeyEvent" ); + + if ( + (aKeyEvent.KeyCode == 0) && + (aKeyEvent.KeyChar == 0) && + (aKeyEvent.KeyFunc == 0) && + (aKeyEvent.Modifiers == 0) + ) + throw css::lang::IllegalArgumentException( + "Such key event seems not to be supported by any operating system.", + static_cast< ::cppu::OWeakObject* >(this), + 0); + + if (sCommand.isEmpty()) + throw css::lang::IllegalArgumentException( + "Empty command strings are not allowed here.", + static_cast< ::cppu::OWeakObject* >(this), + 1); + + SolarMutexGuard g; + + AcceleratorCache& rPrimaryCache = impl_getCFG(true, true ); // sal_True => force getting of a writeable cache! + AcceleratorCache& rSecondaryCache = impl_getCFG(false, true); // sal_True => force getting of a writeable cache! + + if ( rPrimaryCache.hasKey(aKeyEvent) ) + { + OUString sOriginalCommand = rPrimaryCache.getCommandByKey(aKeyEvent); + if ( sCommand != sOriginalCommand ) + { + if (rSecondaryCache.hasCommand(sOriginalCommand)) + { + AcceleratorCache::TKeyList lSecondaryKeys = rSecondaryCache.getKeysByCommand(sOriginalCommand); + rSecondaryCache.removeKey(lSecondaryKeys[0]); + rPrimaryCache.setKeyCommandPair(lSecondaryKeys[0], sOriginalCommand); + } + + if (rPrimaryCache.hasCommand(sCommand)) + { + AcceleratorCache::TKeyList lPrimaryKeys = rPrimaryCache.getKeysByCommand(sCommand); + rPrimaryCache.removeKey(lPrimaryKeys[0]); + rSecondaryCache.setKeyCommandPair(lPrimaryKeys[0], sCommand); + } + + rPrimaryCache.setKeyCommandPair(aKeyEvent, sCommand); + } + } + + else if ( rSecondaryCache.hasKey(aKeyEvent) ) + { + OUString sOriginalCommand = rSecondaryCache.getCommandByKey(aKeyEvent); + if (sCommand != sOriginalCommand) + { + if (rPrimaryCache.hasCommand(sCommand)) + { + AcceleratorCache::TKeyList lPrimaryKeys = rPrimaryCache.getKeysByCommand(sCommand); + rPrimaryCache.removeKey(lPrimaryKeys[0]); + rSecondaryCache.setKeyCommandPair(lPrimaryKeys[0], sCommand); + } + + rSecondaryCache.removeKey(aKeyEvent); + rPrimaryCache.setKeyCommandPair(aKeyEvent, sCommand); + } + } + + else + { + if (rPrimaryCache.hasCommand(sCommand)) + { + AcceleratorCache::TKeyList lPrimaryKeys = rPrimaryCache.getKeysByCommand(sCommand); + rPrimaryCache.removeKey(lPrimaryKeys[0]); + rSecondaryCache.setKeyCommandPair(lPrimaryKeys[0], sCommand); + } + + rPrimaryCache.setKeyCommandPair(aKeyEvent, sCommand); + } +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::removeKeyEvent(const css::awt::KeyEvent& aKeyEvent) +{ + SolarMutexGuard g; + + AcceleratorCache& rPrimaryCache = impl_getCFG(true, true ); + AcceleratorCache& rSecondaryCache = impl_getCFG(false, true); + + if (!rPrimaryCache.hasKey(aKeyEvent) && !rSecondaryCache.hasKey(aKeyEvent)) + throw css::container::NoSuchElementException( + OUString(), + static_cast< ::cppu::OWeakObject* >(this)); + + if (rPrimaryCache.hasKey(aKeyEvent)) + { + OUString sDelCommand = rPrimaryCache.getCommandByKey(aKeyEvent); + if (!sDelCommand.isEmpty()) + { + OUString sOriginalCommand = rPrimaryCache.getCommandByKey(aKeyEvent); + if (rSecondaryCache.hasCommand(sOriginalCommand)) + { + AcceleratorCache::TKeyList lSecondaryKeys = rSecondaryCache.getKeysByCommand(sOriginalCommand); + rSecondaryCache.removeKey(lSecondaryKeys[0]); + rPrimaryCache.setKeyCommandPair(lSecondaryKeys[0], sOriginalCommand); + } + + rPrimaryCache.removeKey(aKeyEvent); + } + + } + else + { + OUString sDelCommand = rSecondaryCache.getCommandByKey(aKeyEvent); + if (!sDelCommand.isEmpty()) + rSecondaryCache.removeKey(aKeyEvent); + } +} + +css::uno::Sequence< css::awt::KeyEvent > SAL_CALL XCUBasedAcceleratorConfiguration::getKeyEventsByCommand(const OUString& sCommand) +{ + if (sCommand.isEmpty()) + throw css::lang::IllegalArgumentException( + "Empty command strings are not allowed here.", + static_cast< ::cppu::OWeakObject* >(this), + 1); + + SolarMutexGuard g; + + AcceleratorCache& rPrimaryCache = impl_getCFG(true ); + AcceleratorCache& rSecondaryCache = impl_getCFG(false); + + if (!rPrimaryCache.hasCommand(sCommand) && !rSecondaryCache.hasCommand(sCommand)) + throw css::container::NoSuchElementException( + OUString(), + static_cast< ::cppu::OWeakObject* >(this)); + + AcceleratorCache::TKeyList lKeys = rPrimaryCache.getKeysByCommand(sCommand); + + AcceleratorCache::TKeyList lSecondaryKeys = rSecondaryCache.getKeysByCommand(sCommand); + for (auto const& secondaryKey : lSecondaryKeys) + lKeys.push_back(secondaryKey); + + return comphelper::containerToSequence(lKeys); +} + +static AcceleratorCache::TKeyList::const_iterator lcl_getPreferredKey(const AcceleratorCache::TKeyList& lKeys) +{ + return std::find_if(lKeys.begin(), lKeys.end(), [](const css::awt::KeyEvent& rAWTKey) { + return !::svt::AcceleratorExecute::st_AWTKey2VCLKey(rAWTKey).GetName().isEmpty(); }); +} + +css::uno::Sequence< css::uno::Any > SAL_CALL XCUBasedAcceleratorConfiguration::getPreferredKeyEventsForCommandList(const css::uno::Sequence< OUString >& lCommandList) +{ + SolarMutexGuard g; + + sal_Int32 i = 0; + sal_Int32 c = lCommandList.getLength(); + css::uno::Sequence< css::uno::Any > lPreferredOnes (c); // don't pack list! + AcceleratorCache& rCache = impl_getCFG(true); + + auto lPreferredOnesRange = asNonConstRange(lPreferredOnes); + for (i=0; i<c; ++i) + { + const OUString& rCommand = lCommandList[i]; + if (rCommand.isEmpty()) + throw css::lang::IllegalArgumentException( + "Empty command strings are not allowed here.", + static_cast< ::cppu::OWeakObject* >(this), + static_cast<sal_Int16>(i)); + + if (!rCache.hasCommand(rCommand)) + continue; + + AcceleratorCache::TKeyList lKeys = rCache.getKeysByCommand(rCommand); + if ( lKeys.empty() ) + continue; + + AcceleratorCache::TKeyList::const_iterator pPreferredKey = lcl_getPreferredKey(lKeys); + if (pPreferredKey != lKeys.end ()) + { + css::uno::Any& rAny = lPreferredOnesRange[i]; + rAny <<= *pPreferredKey; + } + } + + return lPreferredOnes; +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::removeCommandFromAllKeyEvents(const OUString& sCommand) +{ + if (sCommand.isEmpty()) + throw css::lang::IllegalArgumentException( + "Empty command strings are not allowed here.", + static_cast< ::cppu::OWeakObject* >(this), + 0); + + SolarMutexGuard g; + + AcceleratorCache& rPrimaryCache = impl_getCFG(true, true ); + AcceleratorCache& rSecondaryCache = impl_getCFG(false, true); + + if (!rPrimaryCache.hasCommand(sCommand) && !rSecondaryCache.hasCommand(sCommand)) + throw css::container::NoSuchElementException( + "Command does not exists inside this container.", + static_cast< ::cppu::OWeakObject* >(this)); + + if (rPrimaryCache.hasCommand(sCommand)) + rPrimaryCache.removeCommand(sCommand); + if (rSecondaryCache.hasCommand(sCommand)) + rSecondaryCache.removeCommand(sCommand); +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::reload() +{ + SAL_INFO( "fwk.accelerators", "XCUBasedAcceleratorConfiguration::reload()" ); + + SolarMutexGuard g; + + bool bPreferred; + css::uno::Reference< css::container::XNameAccess > xAccess; + + bPreferred = true; + m_aPrimaryReadCache = AcceleratorCache(); + m_pPrimaryWriteCache.reset(); + m_xCfg->getByName(CFG_ENTRY_PRIMARY) >>= xAccess; + impl_ts_load(bPreferred, xAccess); // load the preferred keys + + bPreferred = false; + m_aSecondaryReadCache = AcceleratorCache(); + m_pSecondaryWriteCache.reset(); + m_xCfg->getByName(CFG_ENTRY_SECONDARY) >>= xAccess; + impl_ts_load(bPreferred, xAccess); // load the secondary keys +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::store() +{ + SAL_INFO( "fwk.accelerators", "XCUBasedAcceleratorConfiguration::store()" ); + + SolarMutexGuard g; + + bool bPreferred; + + bPreferred = true; + // on-demand creation of the primary write cache + impl_getCFG(bPreferred, true); + impl_ts_save(bPreferred); + + bPreferred = false; + // on-demand creation of the secondary write cache + impl_getCFG(bPreferred, true); + impl_ts_save(bPreferred); +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::storeToStorage(const css::uno::Reference< css::embed::XStorage >& xStorage) +{ + // use m_aCache + old AcceleratorXMLWriter to store data directly on storage given as parameter ... + if (!xStorage.is()) + return; + + tools::Long nOpenModes = css::embed::ElementModes::READWRITE; + css::uno::Reference< css::embed::XStorage > xAcceleratorTypeStorage = xStorage->openStorageElement("accelerator", nOpenModes); + if (!xAcceleratorTypeStorage.is()) + return; + + css::uno::Reference< css::io::XStream > xStream = xAcceleratorTypeStorage->openStreamElement("current", nOpenModes); + css::uno::Reference< css::io::XOutputStream > xOut; + if (xStream.is()) + xOut = xStream->getOutputStream(); + if (!xOut.is()) + throw css::io::IOException( + "Could not open accelerator configuration for saving.", + static_cast< ::cppu::OWeakObject* >(this)); + + // the original m_aCache has been split into primary cache and secondary cache... + // we should merge them before storing to storage + AcceleratorCache aCache; + { + SolarMutexGuard g; + + if (m_pPrimaryWriteCache != nullptr) + aCache = *m_pPrimaryWriteCache; + else + aCache = m_aPrimaryReadCache; + + AcceleratorCache::TKeyList lKeys; + if (m_pSecondaryWriteCache!=nullptr) + { + lKeys = m_pSecondaryWriteCache->getAllKeys(); + for (auto const& lKey : lKeys) + aCache.setKeyCommandPair(lKey, m_pSecondaryWriteCache->getCommandByKey(lKey)); + } + else + { + lKeys = m_aSecondaryReadCache.getAllKeys(); + for (auto const& lKey : lKeys) + aCache.setKeyCommandPair(lKey, m_aSecondaryReadCache.getCommandByKey(lKey)); + } + } + + css::uno::Reference< css::io::XTruncate > xClearable(xOut, css::uno::UNO_QUERY_THROW); + xClearable->truncate(); + css::uno::Reference< css::io::XSeekable > xSeek(xOut, css::uno::UNO_QUERY); + if (xSeek.is()) + xSeek->seek(0); + + css::uno::Reference< css::xml::sax::XWriter > xWriter = css::xml::sax::Writer::create(m_xContext); + xWriter->setOutputStream(xOut); + + // write into the stream + css::uno::Reference< css::xml::sax::XDocumentHandler > xHandler(xWriter, css::uno::UNO_QUERY_THROW); + AcceleratorConfigurationWriter aWriter(aCache, xHandler); + aWriter.flush(); +} + +sal_Bool SAL_CALL XCUBasedAcceleratorConfiguration::isModified() +{ + return false; +} + +sal_Bool SAL_CALL XCUBasedAcceleratorConfiguration::isReadOnly() +{ + return false; +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::setStorage(const css::uno::Reference< css::embed::XStorage >& /*xStorage*/) +{ + SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::setStorage(): implement this HACK .-)"); +} + +sal_Bool SAL_CALL XCUBasedAcceleratorConfiguration::hasStorage() +{ + SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::hasStorage(): implement this HACK .-)"); + return false; +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::addConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& /*xListener*/) +{ + SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::addConfigurationListener(): implement me"); +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::removeConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& /*xListener*/) +{ + SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::removeConfigurationListener(): implement me"); +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::reset() +{ + css::uno::Reference< css::container::XNamed > xNamed(m_xCfg, css::uno::UNO_QUERY); + OUString sConfig = xNamed->getName(); + if ( sConfig == "Global" ) + { + m_xCfg.set( + ::comphelper::ConfigurationHelper::openConfig( m_xContext, CFG_ENTRY_GLOBAL, ::comphelper::EConfigurationModes::AllLocales ), + css::uno::UNO_QUERY ); + XCUBasedAcceleratorConfiguration::reload(); + } + else if ( sConfig == "Modules" ) + { + m_xCfg.set( + ::comphelper::ConfigurationHelper::openConfig( m_xContext, CFG_ENTRY_MODULES, ::comphelper::EConfigurationModes::AllLocales ), + css::uno::UNO_QUERY ); + XCUBasedAcceleratorConfiguration::reload(); + } +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::addResetListener(const css::uno::Reference< css::form::XResetListener >& /*xListener*/) +{ + SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::addResetListener(): implement me"); +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::removeResetListener(const css::uno::Reference< css::form::XResetListener >& /*xListener*/) +{ + SAL_INFO("fwk.accelerators", "XCUBasedAcceleratorConfiguration::removeResetListener(): implement me"); +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::changesOccurred(const css::util::ChangesEvent& aEvent) +{ + SAL_INFO( "fwk.accelerators", "XCUBasedAcceleratorConfiguration::changesOccurred()" ); + + css::uno::Reference< css::container::XHierarchicalNameAccess > xHAccess; + aEvent.Base >>= xHAccess; + if (! xHAccess.is ()) + return; + + css::util::ChangesEvent aReceivedEvents( aEvent ); + const sal_Int32 c = aReceivedEvents.Changes.getLength(); + for (sal_Int32 i=0; i<c; ++i) + { + const css::util::ElementChange& aChange = aReceivedEvents.Changes[i]; + + // Only path of form "PrimaryKeys/Modules/Module['<module_name>']/Key['<command_url>']/Command[<locale>]" will + // be interesting for use. Sometimes short path values are given also by the broadcaster ... but they must be ignored :-) + // So we try to split the path into 3 parts (module isn't important here, because we already know it ... because + // these instance is bound to a specific module configuration ... or it''s the global configuration where no module is given at all. + + OUString sOrgPath; + OUString sPath; + OUString sKey; + + aChange.Accessor >>= sOrgPath; + sPath = sOrgPath; + OUString sPrimarySecondary = ::utl::extractFirstFromConfigurationPath(sPath, &sPath); + OUString sGlobalModules = ::utl::extractFirstFromConfigurationPath(sPath, &sPath); + + if ( sGlobalModules == CFG_ENTRY_GLOBAL ) + { + sKey = ::utl::extractFirstFromConfigurationPath(sPath, &sPath); + if ( !sKey.isEmpty() && !sPath.isEmpty() ) + reloadChanged(sPrimarySecondary, sGlobalModules, OUString(), sKey); + } + else if ( sGlobalModules == CFG_ENTRY_MODULES ) + { + OUString sModule = ::utl::extractFirstFromConfigurationPath(sPath, &sPath); + sKey = ::utl::extractFirstFromConfigurationPath(sPath, &sPath); + + if ( !sKey.isEmpty() && !sPath.isEmpty() ) + { + reloadChanged(sPrimarySecondary, sGlobalModules, sModule, sKey); + } + } + } +} + +void SAL_CALL XCUBasedAcceleratorConfiguration::disposing(const css::lang::EventObject& /*aSource*/) +{ +} + +void XCUBasedAcceleratorConfiguration::impl_ts_load( bool bPreferred, const css::uno::Reference< css::container::XNameAccess >& xCfg ) +{ + AcceleratorCache aReadCache; + css::uno::Reference< css::container::XNameAccess > xAccess; + if ( m_sGlobalOrModules == "Global" ) + xCfg->getByName(CFG_ENTRY_GLOBAL) >>= xAccess; + else if ( m_sGlobalOrModules == "Modules" ) + { + css::uno::Reference< css::container::XNameAccess > xModules; + xCfg->getByName(CFG_ENTRY_MODULES) >>= xModules; + xModules->getByName(m_sModuleCFG) >>= xAccess; + } + + const OUString sIsoLang = impl_ts_getLocale(); + static const OUStringLiteral sDefaultLocale(u"en-US"); + + css::uno::Reference< css::container::XNameAccess > xKey; + css::uno::Reference< css::container::XNameAccess > xCommand; + if (xAccess.is()) + { + css::uno::Sequence< OUString > lKeys = xAccess->getElementNames(); + sal_Int32 nKeys = lKeys.getLength(); + for ( sal_Int32 i=0; i<nKeys; ++i ) + { + OUString sKey = lKeys[i]; + xAccess->getByName(sKey) >>= xKey; + xKey->getByName(CFG_PROP_COMMAND) >>= xCommand; + + const css::uno::Sequence< OUString > lLocales = xCommand->getElementNames(); + ::std::vector< OUString > aLocales { lLocales.begin(), lLocales.end() }; + + OUString sLocale; + for (auto const& locale : aLocales) + { + if ( locale == sIsoLang ) + { + sLocale = locale; + break; + } + } + + if (sLocale.isEmpty()) + { + for (auto const& locale : aLocales) + { + if ( locale == sDefaultLocale ) + { + sLocale = locale; + break; + } + } + + if (sLocale.isEmpty()) + continue; + } + + OUString sCommand; + xCommand->getByName(sLocale) >>= sCommand; + if (sCommand.isEmpty()) + continue; + + css::awt::KeyEvent aKeyEvent; + + sal_Int32 nIndex = 0; + std::u16string_view sKeyCommand = o3tl::getToken(sKey, 0, '_', nIndex); + aKeyEvent.KeyCode = KeyMapping::get().mapIdentifierToCode(OUString::Concat("KEY_") + sKeyCommand); + + const sal_Int32 nToken = 4; + bool bValid = true; + sal_Int32 k; + for (k = 0; k < nToken; ++k) + { + if (nIndex < 0) + break; + + std::u16string_view sToken = o3tl::getToken(sKey, 0, '_', nIndex); + if (sToken.empty()) + { + bValid = false; + break; + } + + if ( sToken == u"SHIFT" ) + aKeyEvent.Modifiers |= css::awt::KeyModifier::SHIFT; + else if ( sToken == u"MOD1" ) + aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD1; + else if ( sToken == u"MOD2" ) + aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD2; + else if ( sToken == u"MOD3" ) + aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD3; + else + { + bValid = false; + break; + } + } + + if ( !aReadCache.hasKey(aKeyEvent) && bValid && k<nToken) + aReadCache.setKeyCommandPair(aKeyEvent, sCommand); + } + } + + if (bPreferred) + m_aPrimaryReadCache = std::move(aReadCache); + else + m_aSecondaryReadCache = std::move(aReadCache); +} + +void XCUBasedAcceleratorConfiguration::impl_ts_save(bool bPreferred) +{ + if (bPreferred) + { + AcceleratorCache::TKeyList lPrimaryReadKeys = m_aPrimaryReadCache.getAllKeys(); + AcceleratorCache::TKeyList lPrimaryWriteKeys = m_pPrimaryWriteCache->getAllKeys(); + + for (auto const& primaryReadKey : lPrimaryReadKeys) + { + if (!m_pPrimaryWriteCache->hasKey(primaryReadKey)) + removeKeyFromConfiguration(primaryReadKey, true); + } + + for (auto const& primaryWriteKey : lPrimaryWriteKeys) + { + OUString sCommand = m_pPrimaryWriteCache->getCommandByKey(primaryWriteKey); + if (!m_aPrimaryReadCache.hasKey(primaryWriteKey)) + { + insertKeyToConfiguration(primaryWriteKey, sCommand, true); + } + else + { + OUString sReadCommand = m_aPrimaryReadCache.getCommandByKey(primaryWriteKey); + if (sReadCommand != sCommand) + insertKeyToConfiguration(primaryWriteKey, sCommand, true); + } + } + + // take over all changes into the original container + SolarMutexGuard g; + // coverity[check_after_deref] - confusing but correct + if (m_pPrimaryWriteCache) + { + m_aPrimaryReadCache = *m_pPrimaryWriteCache; + m_pPrimaryWriteCache.reset(); + } + } + + else + { + AcceleratorCache::TKeyList lSecondaryReadKeys = m_aSecondaryReadCache.getAllKeys(); + AcceleratorCache::TKeyList lSecondaryWriteKeys = m_pSecondaryWriteCache->getAllKeys(); + + for (auto const& secondaryReadKey : lSecondaryReadKeys) + { + if (!m_pSecondaryWriteCache->hasKey(secondaryReadKey)) + removeKeyFromConfiguration(secondaryReadKey, false); + } + + for (auto const& secondaryWriteKey : lSecondaryWriteKeys) + { + OUString sCommand = m_pSecondaryWriteCache->getCommandByKey(secondaryWriteKey); + if (!m_aSecondaryReadCache.hasKey(secondaryWriteKey)) + { + insertKeyToConfiguration(secondaryWriteKey, sCommand, false); + } + else + { + OUString sReadCommand = m_aSecondaryReadCache.getCommandByKey(secondaryWriteKey); + if (sReadCommand != sCommand) + insertKeyToConfiguration(secondaryWriteKey, sCommand, false); + } + } + + // take over all changes into the original container + SolarMutexGuard g; + // coverity[check_after_deref] - confusing but correct + if (m_pSecondaryWriteCache) + { + m_aSecondaryReadCache = *m_pSecondaryWriteCache; + m_pSecondaryWriteCache.reset(); + } + } + + ::comphelper::ConfigurationHelper::flush(m_xCfg); +} + +void XCUBasedAcceleratorConfiguration::insertKeyToConfiguration( const css::awt::KeyEvent& aKeyEvent, const OUString& sCommand, const bool bPreferred ) +{ + css::uno::Reference< css::container::XNameAccess > xAccess; + css::uno::Reference< css::container::XNameContainer > xContainer; + css::uno::Reference< css::lang::XSingleServiceFactory > xFac; + css::uno::Reference< css::uno::XInterface > xInst; + + if ( bPreferred ) + m_xCfg->getByName(CFG_ENTRY_PRIMARY) >>= xAccess; + else + m_xCfg->getByName(CFG_ENTRY_SECONDARY) >>= xAccess; + + if ( m_sGlobalOrModules == CFG_ENTRY_GLOBAL ) + xAccess->getByName(CFG_ENTRY_GLOBAL) >>= xContainer; + else if ( m_sGlobalOrModules == CFG_ENTRY_MODULES ) + { + css::uno::Reference< css::container::XNameContainer > xModules; + xAccess->getByName(CFG_ENTRY_MODULES) >>= xModules; + if ( !xModules->hasByName(m_sModuleCFG) ) + { + xFac.set(xModules, css::uno::UNO_QUERY); + xInst = xFac->createInstance(); + xModules->insertByName(m_sModuleCFG, css::uno::Any(xInst)); + } + xModules->getByName(m_sModuleCFG) >>= xContainer; + } + + const OUString sKey = lcl_getKeyString(aKeyEvent); + css::uno::Reference< css::container::XNameAccess > xKey; + css::uno::Reference< css::container::XNameContainer > xCommand; + if ( !xContainer->hasByName(sKey) ) + { + xFac.set(xContainer, css::uno::UNO_QUERY); + xInst = xFac->createInstance(); + xContainer->insertByName(sKey, css::uno::Any(xInst)); + } + xContainer->getByName(sKey) >>= xKey; + + xKey->getByName(CFG_PROP_COMMAND) >>= xCommand; + OUString sLocale = impl_ts_getLocale(); + if ( !xCommand->hasByName(sLocale) ) + xCommand->insertByName(sLocale, css::uno::Any(sCommand)); + else + xCommand->replaceByName(sLocale, css::uno::Any(sCommand)); +} + +void XCUBasedAcceleratorConfiguration::removeKeyFromConfiguration( const css::awt::KeyEvent& aKeyEvent, const bool bPreferred ) +{ + css::uno::Reference< css::container::XNameAccess > xAccess; + css::uno::Reference< css::container::XNameContainer > xContainer; + + if ( bPreferred ) + m_xCfg->getByName(CFG_ENTRY_PRIMARY) >>= xAccess; + else + m_xCfg->getByName(CFG_ENTRY_SECONDARY) >>= xAccess; + + if ( m_sGlobalOrModules == CFG_ENTRY_GLOBAL ) + xAccess->getByName(CFG_ENTRY_GLOBAL) >>= xContainer; + else if ( m_sGlobalOrModules == CFG_ENTRY_MODULES ) + { + css::uno::Reference< css::container::XNameAccess > xModules; + xAccess->getByName(CFG_ENTRY_MODULES) >>= xModules; + if ( !xModules->hasByName(m_sModuleCFG) ) + return; + xModules->getByName(m_sModuleCFG) >>= xContainer; + } + + const OUString sKey = lcl_getKeyString(aKeyEvent); + xContainer->removeByName(sKey); +} + +void XCUBasedAcceleratorConfiguration::reloadChanged( const OUString& sPrimarySecondary, std::u16string_view sGlobalModules, const OUString& sModule, const OUString& sKey ) +{ + css::uno::Reference< css::container::XNameAccess > xAccess; + css::uno::Reference< css::container::XNameContainer > xContainer; + + m_xCfg->getByName(sPrimarySecondary) >>= xAccess; + if ( sGlobalModules == CFG_ENTRY_GLOBAL ) + xAccess->getByName(CFG_ENTRY_GLOBAL) >>= xContainer; + else + { + css::uno::Reference< css::container::XNameAccess > xModules; + xAccess->getByName(CFG_ENTRY_MODULES) >>= xModules; + if ( !xModules->hasByName(sModule) ) + return; + xModules->getByName(sModule) >>= xContainer; + } + + css::awt::KeyEvent aKeyEvent; + + sal_Int32 nIndex = 0; + std::u16string_view sKeyIdentifier = o3tl::getToken(sKey, 0, '_', nIndex); + aKeyEvent.KeyCode = KeyMapping::get().mapIdentifierToCode(OUString::Concat("KEY_") + sKeyIdentifier); + + const int nToken = 4; + for (sal_Int32 i = 0; i < nToken; ++i) + { + if ( nIndex < 0 ) + break; + + std::u16string_view sToken = o3tl::getToken(sKey, 0, '_', nIndex); + if ( sToken == u"SHIFT" ) + aKeyEvent.Modifiers |= css::awt::KeyModifier::SHIFT; + else if ( sToken == u"MOD1" ) + aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD1; + else if ( sToken == u"MOD2" ) + aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD2; + else if ( sToken == u"MOD3" ) + aKeyEvent.Modifiers |= css::awt::KeyModifier::MOD3; + } + + css::uno::Reference< css::container::XNameAccess > xKey; + css::uno::Reference< css::container::XNameAccess > xCommand; + OUString sCommand; + + if (xContainer->hasByName(sKey)) + { + OUString sLocale = impl_ts_getLocale(); + xContainer->getByName(sKey) >>= xKey; + xKey->getByName(CFG_PROP_COMMAND) >>= xCommand; + xCommand->getByName(sLocale) >>= sCommand; + } + + if ( sPrimarySecondary == CFG_ENTRY_PRIMARY ) + { + if (sCommand.isEmpty()) + m_aPrimaryReadCache.removeKey(aKeyEvent); + else + m_aPrimaryReadCache.setKeyCommandPair(aKeyEvent, sCommand); + } + else if ( sPrimarySecondary == CFG_ENTRY_SECONDARY ) + { + if (sCommand.isEmpty()) + m_aSecondaryReadCache.removeKey(aKeyEvent); + else + m_aSecondaryReadCache.setKeyCommandPair(aKeyEvent, sCommand); + } +} + +AcceleratorCache& XCUBasedAcceleratorConfiguration::impl_getCFG(bool bPreferred, bool bWriteAccessRequested) +{ + SolarMutexGuard g; + + if (bPreferred) + { + //create copy of our readonly-cache, if write access is forced ... but + //not still possible! + if ( bWriteAccessRequested && !m_pPrimaryWriteCache ) + { + m_pPrimaryWriteCache.reset(new AcceleratorCache(m_aPrimaryReadCache)); + } + + // in case, we have a writeable cache, we use it for reading too! + // Otherwise the API user can't find its own changes... + if (m_pPrimaryWriteCache) + return *m_pPrimaryWriteCache; + else + return m_aPrimaryReadCache; + } + + else + { + //create copy of our readonly-cache, if write access is forced ... but + //not still possible! + if ( bWriteAccessRequested && !m_pSecondaryWriteCache ) + { + m_pSecondaryWriteCache.reset(new AcceleratorCache(m_aSecondaryReadCache)); + } + + // in case, we have a writeable cache, we use it for reading too! + // Otherwise the API user can't find its own changes... + if (m_pSecondaryWriteCache) + return *m_pSecondaryWriteCache; + else + return m_aSecondaryReadCache; + } +} + +OUString XCUBasedAcceleratorConfiguration::impl_ts_getLocale() const +{ + OUString sISOLocale = officecfg::Setup::L10N::ooLocale::get(); + + if (sISOLocale.isEmpty()) + return "en-US"; + return sISOLocale; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/accelerators/documentacceleratorconfiguration.cxx b/framework/source/accelerators/documentacceleratorconfiguration.cxx new file mode 100644 index 000000000..c86895f0d --- /dev/null +++ b/framework/source/accelerators/documentacceleratorconfiguration.cxx @@ -0,0 +1,196 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <accelerators/acceleratorconfiguration.hxx> +#include <accelerators/presethandler.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <vcl/svapp.hxx> + +using namespace framework; + +constexpr OUStringLiteral RESOURCETYPE_ACCELERATOR = u"accelerator"; + +namespace { + +/** + implements a read/write access to a document + based accelerator configuration. + */ + +typedef ::cppu::ImplInheritanceHelper< + XMLBasedAcceleratorConfiguration, + css::lang::XServiceInfo> DocumentAcceleratorConfiguration_BASE; + +class DocumentAcceleratorConfiguration : public DocumentAcceleratorConfiguration_BASE +{ +private: + + /** points to the root storage of the outside document, + where we can read/save our configuration data. */ + css::uno::Reference< css::embed::XStorage > m_xDocumentRoot; + +public: + + /** initialize this instance and fill the internal cache. + + @param xSMGR + reference to a uno service manager, which is used internally. + */ + DocumentAcceleratorConfiguration( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + const css::uno::Sequence< css::uno::Any >& lArguments); + + virtual ~DocumentAcceleratorConfiguration() override; + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.DocumentAcceleratorConfiguration"; + } + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { + return cppu::supportsService(this, ServiceName); + } + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + return {"com.sun.star.ui.DocumentAcceleratorConfiguration"}; + } + + // XUIConfigurationStorage + virtual void SAL_CALL setStorage(const css::uno::Reference< css::embed::XStorage >& xStorage) override; + + virtual sal_Bool SAL_CALL hasStorage() override; + + /** read all data into the cache. */ + void fillCache(); +}; + +DocumentAcceleratorConfiguration::DocumentAcceleratorConfiguration( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + const css::uno::Sequence< css::uno::Any >& lArguments) + : DocumentAcceleratorConfiguration_BASE(xContext) +{ + SolarMutexGuard g; + css::uno::Reference<css::embed::XStorage> xRoot; + if (lArguments.getLength() == 1 && (lArguments[0] >>= xRoot)) + { + m_xDocumentRoot = xRoot; + } + else + { + ::comphelper::SequenceAsHashMap lArgs(lArguments); + m_xDocumentRoot = lArgs.getUnpackedValueOrDefault( + "DocumentRoot", + css::uno::Reference< css::embed::XStorage >()); + } +} + +DocumentAcceleratorConfiguration::~DocumentAcceleratorConfiguration() +{ + m_aPresetHandler.removeStorageListener(this); +} + +void SAL_CALL DocumentAcceleratorConfiguration::setStorage(const css::uno::Reference< css::embed::XStorage >& xStorage) +{ + // Attention! xStorage must be accepted too, if it's NULL ! + + bool bForgetOldStorages; + { + SolarMutexGuard g; + bForgetOldStorages = m_xDocumentRoot.is(); + m_xDocumentRoot = xStorage; + } + + if (bForgetOldStorages) + /* forget all currently cached data AND(!) forget all currently used storages. */ + m_aPresetHandler.forgetCachedStorages(); + + if (xStorage.is()) + fillCache(); +} + +sal_Bool SAL_CALL DocumentAcceleratorConfiguration::hasStorage() +{ + SolarMutexGuard g; + return m_xDocumentRoot.is(); +} + +void DocumentAcceleratorConfiguration::fillCache() +{ + css::uno::Reference< css::embed::XStorage > xDocumentRoot; + { + SolarMutexGuard g; + xDocumentRoot = m_xDocumentRoot; + } + + // Sometimes we must live without a document root. + // E.g. if the document is readonly ... + if (!xDocumentRoot.is()) + return; + + // get current office locale ... but don't cache it. + // Otherwise we must be listener on the configuration layer + // which seems to superfluous for this small implementation .-) + LanguageTag aLanguageTag( impl_ts_getLocale()); + + // May be the current document does not contain any + // accelerator config? Handle it gracefully :-) + try + { + // Note: The used preset class is threadsafe by itself ... and live if we live! + // We do not need any mutex here. + + // open the folder, where the configuration exists + m_aPresetHandler.connectToResource( + PresetHandler::E_DOCUMENT, + RESOURCETYPE_ACCELERATOR, + u"", + xDocumentRoot, + aLanguageTag); + + DocumentAcceleratorConfiguration::reload(); + m_aPresetHandler.addStorageListener(this); + } + catch(const css::uno::Exception&) + {} +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_DocumentAcceleratorConfiguration_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &arguments) +{ + rtl::Reference<DocumentAcceleratorConfiguration> inst = new DocumentAcceleratorConfiguration(context, arguments); + css::uno::XInterface *acquired_inst = cppu::acquire(inst.get()); + + inst->fillCache(); + + return acquired_inst; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/accelerators/globalacceleratorconfiguration.cxx b/framework/source/accelerators/globalacceleratorconfiguration.cxx new file mode 100644 index 000000000..e54a05a03 --- /dev/null +++ b/framework/source/accelerators/globalacceleratorconfiguration.cxx @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <accelerators/acceleratorconfiguration.hxx> +#include <accelerators/keymapping.hxx> +#include <helper/mischelper.hxx> + +#include <com/sun/star/util/XChangesNotifier.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/ref.hxx> + +using namespace framework; + +namespace { + +/** + implements a read/write access to the global + accelerator configuration. + */ +typedef ::cppu::ImplInheritanceHelper< + XCUBasedAcceleratorConfiguration, + css::lang::XServiceInfo > GlobalAcceleratorConfiguration_BASE; +class GlobalAcceleratorConfiguration : public GlobalAcceleratorConfiguration_BASE +{ +public: + + /** initialize this instance and fill the internal cache. + + @param xSMGR + reference to a uno service manager, which is used internally. + */ + explicit GlobalAcceleratorConfiguration(const css::uno::Reference< css::uno::XComponentContext >& xContext); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.GlobalAcceleratorConfiguration"; + } + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { + return cppu::supportsService(this, ServiceName); + } + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + return {"com.sun.star.ui.GlobalAcceleratorConfiguration"}; + } + + /// This has to be called after when the instance is acquire()'d. + void fillCache(); + +private: + + /** helper to listen for configuration changes without ownership cycle problems */ + css::uno::Reference< css::util::XChangesListener > m_xCfgListener; +}; + +GlobalAcceleratorConfiguration::GlobalAcceleratorConfiguration(const css::uno::Reference< css::uno::XComponentContext >& xContext) + : GlobalAcceleratorConfiguration_BASE(xContext) +{ + // force keyboard string registration. + KeyMapping::get(); +} + +void GlobalAcceleratorConfiguration::fillCache() +{ + /** read all data into the cache. */ + +#if 0 + // get current office locale ... but don't cache it. + // Otherwise we must be listener on the configuration layer + // which seems to superfluous for this small implementation .-) + // XXX: what is this good for? it was a comphelper::Locale but unused + LanguageTag aLanguageTag(m_sLocale); +#endif + + // May be there exists no accelerator config? Handle it gracefully :-) + try + { + m_sGlobalOrModules = CFG_ENTRY_GLOBAL; + XCUBasedAcceleratorConfiguration::reload(); + + css::uno::Reference< css::util::XChangesNotifier > xBroadcaster(m_xCfg, css::uno::UNO_QUERY_THROW); + m_xCfgListener = new WeakChangesListener(this); + xBroadcaster->addChangesListener(m_xCfgListener); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + {} +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_GlobalAcceleratorConfiguration_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + rtl::Reference<GlobalAcceleratorConfiguration> xGAC = new GlobalAcceleratorConfiguration(context); + xGAC->fillCache(); + return cppu::acquire(xGAC.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/accelerators/keymapping.cxx b/framework/source/accelerators/keymapping.cxx new file mode 100644 index 000000000..a0d33e7b4 --- /dev/null +++ b/framework/source/accelerators/keymapping.cxx @@ -0,0 +1,208 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <accelerators/keymapping.hxx> + +#include <com/sun/star/awt/Key.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <o3tl/string_view.hxx> + +namespace framework +{ + +// helper + +KeyMapping::KeyIdentifierInfo const KeyMapping::KeyIdentifierMap[] = +{ + {css::awt::Key::NUM0 , "KEY_0" }, + {css::awt::Key::NUM1 , "KEY_1" }, + {css::awt::Key::NUM2 , "KEY_2" }, + {css::awt::Key::NUM3 , "KEY_3" }, + {css::awt::Key::NUM4 , "KEY_4" }, + {css::awt::Key::NUM5 , "KEY_5" }, + {css::awt::Key::NUM6 , "KEY_6" }, + {css::awt::Key::NUM7 , "KEY_7" }, + {css::awt::Key::NUM8 , "KEY_8" }, + {css::awt::Key::NUM9 , "KEY_9" }, + {css::awt::Key::A , "KEY_A" }, + {css::awt::Key::B , "KEY_B" }, + {css::awt::Key::C , "KEY_C" }, + {css::awt::Key::D , "KEY_D" }, + {css::awt::Key::E , "KEY_E" }, + {css::awt::Key::F , "KEY_F" }, + {css::awt::Key::G , "KEY_G" }, + {css::awt::Key::H , "KEY_H" }, + {css::awt::Key::I , "KEY_I" }, + {css::awt::Key::J , "KEY_J" }, + {css::awt::Key::K , "KEY_K" }, + {css::awt::Key::L , "KEY_L" }, + {css::awt::Key::M , "KEY_M" }, + {css::awt::Key::N , "KEY_N" }, + {css::awt::Key::O , "KEY_O" }, + {css::awt::Key::P , "KEY_P" }, + {css::awt::Key::Q , "KEY_Q" }, + {css::awt::Key::R , "KEY_R" }, + {css::awt::Key::S , "KEY_S" }, + {css::awt::Key::T , "KEY_T" }, + {css::awt::Key::U , "KEY_U" }, + {css::awt::Key::V , "KEY_V" }, + {css::awt::Key::W , "KEY_W" }, + {css::awt::Key::X , "KEY_X" }, + {css::awt::Key::Y , "KEY_Y" }, + {css::awt::Key::Z , "KEY_Z" }, + {css::awt::Key::F1 , "KEY_F1" }, + {css::awt::Key::F2 , "KEY_F2" }, + {css::awt::Key::F3 , "KEY_F3" }, + {css::awt::Key::F4 , "KEY_F4" }, + {css::awt::Key::F5 , "KEY_F5" }, + {css::awt::Key::F6 , "KEY_F6" }, + {css::awt::Key::F7 , "KEY_F7" }, + {css::awt::Key::F8 , "KEY_F8" }, + {css::awt::Key::F9 , "KEY_F9" }, + {css::awt::Key::F10 , "KEY_F10" }, + {css::awt::Key::F11 , "KEY_F11" }, + {css::awt::Key::F12 , "KEY_F12" }, + {css::awt::Key::F13 , "KEY_F13" }, + {css::awt::Key::F14 , "KEY_F14" }, + {css::awt::Key::F15 , "KEY_F15" }, + {css::awt::Key::F16 , "KEY_F16" }, + {css::awt::Key::F17 , "KEY_F17" }, + {css::awt::Key::F18 , "KEY_F18" }, + {css::awt::Key::F19 , "KEY_F19" }, + {css::awt::Key::F20 , "KEY_F20" }, + {css::awt::Key::F21 , "KEY_F21" }, + {css::awt::Key::F22 , "KEY_F22" }, + {css::awt::Key::F23 , "KEY_F23" }, + {css::awt::Key::F24 , "KEY_F24" }, + {css::awt::Key::F25 , "KEY_F25" }, + {css::awt::Key::F26 , "KEY_F26" }, + {css::awt::Key::DOWN , "KEY_DOWN" }, + {css::awt::Key::UP , "KEY_UP" }, + {css::awt::Key::LEFT , "KEY_LEFT" }, + {css::awt::Key::RIGHT , "KEY_RIGHT" }, + {css::awt::Key::HOME , "KEY_HOME" }, + {css::awt::Key::END , "KEY_END" }, + {css::awt::Key::PAGEUP , "KEY_PAGEUP" }, + {css::awt::Key::PAGEDOWN , "KEY_PAGEDOWN" }, + {css::awt::Key::RETURN , "KEY_RETURN" }, + {css::awt::Key::ESCAPE , "KEY_ESCAPE" }, + {css::awt::Key::TAB , "KEY_TAB" }, + {css::awt::Key::BACKSPACE , "KEY_BACKSPACE" }, + {css::awt::Key::SPACE , "KEY_SPACE" }, + {css::awt::Key::INSERT , "KEY_INSERT" }, + {css::awt::Key::DELETE , "KEY_DELETE" }, + {css::awt::Key::ADD , "KEY_ADD" }, + {css::awt::Key::SUBTRACT , "KEY_SUBTRACT" }, + {css::awt::Key::MULTIPLY , "KEY_MULTIPLY" }, + {css::awt::Key::DIVIDE , "KEY_DIVIDE" }, + {css::awt::Key::POINT , "KEY_POINT" }, + {css::awt::Key::COMMA , "KEY_COMMA" }, + {css::awt::Key::LESS , "KEY_LESS" }, + {css::awt::Key::GREATER , "KEY_GREATER" }, + {css::awt::Key::EQUAL , "KEY_EQUAL" }, + {css::awt::Key::OPEN , "KEY_OPEN" }, + {css::awt::Key::CUT , "KEY_CUT" }, + {css::awt::Key::COPY , "KEY_COPY" }, + {css::awt::Key::PASTE , "KEY_PASTE" }, + {css::awt::Key::UNDO , "KEY_UNDO" }, + {css::awt::Key::REPEAT , "KEY_REPEAT" }, + {css::awt::Key::FIND , "KEY_FIND" }, + {css::awt::Key::PROPERTIES , "KEY_PROPERTIES" }, + {css::awt::Key::FRONT , "KEY_FRONT" }, + {css::awt::Key::CONTEXTMENU , "KEY_CONTEXTMENU"}, + {css::awt::Key::HELP , "KEY_HELP" }, + {css::awt::Key::MENU , "KEY_MENU" }, + {css::awt::Key::HANGUL_HANJA , "KEY_HANGUL_HANJA"}, + {css::awt::Key::DECIMAL , "KEY_DECIMAL" }, + {css::awt::Key::TILDE , "KEY_TILDE" }, + {css::awt::Key::QUOTELEFT , "KEY_QUOTELEFT" }, + {css::awt::Key::BRACKETLEFT , "KEY_BRACKETLEFT" }, + {css::awt::Key::BRACKETRIGHT , "KEY_BRACKETRIGHT" }, + {css::awt::Key::SEMICOLON , "KEY_SEMICOLON" }, + {css::awt::Key::QUOTERIGHT , "KEY_QUOTERIGHT" }, + {0 , "" } // mark the end of this array! +}; + +KeyMapping::KeyMapping() +{ + sal_Int32 i = 0; + while(KeyIdentifierMap[i].Code != 0) + { + OUString sIdentifier = OUString::createFromAscii(KeyIdentifierMap[i].Identifier); + sal_Int16 nCode = KeyIdentifierMap[i].Code; + + m_lIdentifierHash[sIdentifier] = nCode; + m_lCodeHash [nCode] = sIdentifier; + + ++i; + } +} + +KeyMapping & KeyMapping::get() { + static KeyMapping KEYS; + return KEYS; +} + +sal_uInt16 KeyMapping::mapIdentifierToCode(const OUString& sIdentifier) +{ + Identifier2CodeHash::const_iterator pIt = m_lIdentifierHash.find(sIdentifier); + if (pIt != m_lIdentifierHash.end()) + return pIt->second; + + // It's not well known identifier - but may be a pure key code formatted as string... + // Check and convert it! + sal_uInt16 nCode = 0; + if (!KeyMapping::impl_st_interpretIdentifierAsPureKeyCode(sIdentifier, nCode)) + throw css::lang::IllegalArgumentException( + "Can not map given identifier to a valid key code value.", + css::uno::Reference< css::uno::XInterface >(), + 0); + + return nCode; +} + +OUString KeyMapping::mapCodeToIdentifier(sal_uInt16 nCode) +{ + Code2IdentifierHash::const_iterator pIt = m_lCodeHash.find(nCode); + if (pIt != m_lCodeHash.end()) + return pIt->second; + + // If we have no well known identifier - use the pure code value! + return OUString::number(nCode); +} + +bool KeyMapping::impl_st_interpretIdentifierAsPureKeyCode(std::u16string_view sIdentifier, + sal_uInt16& rCode ) +{ + sal_Int32 nCode = o3tl::toInt32(sIdentifier); + if (nCode > 0) + { + rCode = static_cast<sal_uInt16>(nCode); + return true; + } + + // 0 is normally an error of the called method toInt32() ... + // But we must be aware, that the identifier is "0"! + rCode = 0; + return sIdentifier == u"0"; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/accelerators/moduleacceleratorconfiguration.cxx b/framework/source/accelerators/moduleacceleratorconfiguration.cxx new file mode 100644 index 000000000..c8ce39fec --- /dev/null +++ b/framework/source/accelerators/moduleacceleratorconfiguration.cxx @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <accelerators/acceleratorconfiguration.hxx> +#include <helper/mischelper.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <vcl/svapp.hxx> + +#include <com/sun/star/util/XChangesNotifier.hpp> + +#include <cppuhelper/implbase.hxx> + +using namespace framework; + +namespace { + +/** + implements a read/write access to a module + dependent accelerator configuration. + */ +typedef ::cppu::ImplInheritanceHelper< + XCUBasedAcceleratorConfiguration, + css::lang::XServiceInfo > ModuleAcceleratorConfiguration_BASE; + +class ModuleAcceleratorConfiguration : public ModuleAcceleratorConfiguration_BASE +{ +private: + /** identify the application module, where this accelerator + configuration cache should work on. */ + OUString m_sModule; + +public: + + /** initialize this instance and fill the internal cache. + + @param xSMGR + reference to a uno service manager, which is used internally. + */ + ModuleAcceleratorConfiguration( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + const css::uno::Sequence< css::uno::Any >& lArguments); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.ModuleAcceleratorConfiguration"; + } + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { + return cppu::supportsService(this, ServiceName); + } + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + return {"com.sun.star.ui.ModuleAcceleratorConfiguration"}; + } + + /// This has to be called after when the instance is acquire()'d. + void fillCache(); + +private: + /** helper to listen for configuration changes without ownership cycle problems */ + css::uno::Reference< css::util::XChangesListener > m_xCfgListener; +}; + +ModuleAcceleratorConfiguration::ModuleAcceleratorConfiguration( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + const css::uno::Sequence< css::uno::Any >& lArguments) + : ModuleAcceleratorConfiguration_BASE(xContext) +{ + SolarMutexGuard g; + + OUString sModule; + if (lArguments.getLength() == 1 && (lArguments[0] >>= sModule)) + { + m_sModule = sModule; + } else + { + ::comphelper::SequenceAsHashMap lArgs(lArguments); + m_sModule = lArgs.getUnpackedValueOrDefault("ModuleIdentifier", OUString()); + // OUString sLocale = lArgs.getUnpackedValueOrDefault("Locale", OUString("x-default")); + } + + if (m_sModule.isEmpty()) + throw css::uno::RuntimeException( + "The module dependent accelerator configuration service was initialized with an empty module identifier!", + static_cast< ::cppu::OWeakObject* >(this)); +} + +void ModuleAcceleratorConfiguration::fillCache() +{ + { + SolarMutexGuard g; + m_sModuleCFG = m_sModule; + } + +#if 0 + // get current office locale ... but don't cache it. + // Otherwise we must be listener on the configuration layer + // which seems to superfluous for this small implementation .-) + // XXX: what is this good for? it was a comphelper::Locale but unused + LanguageTag aLanguageTag(m_sLocale); +#endif + + // May be the current app module does not have any + // accelerator config? Handle it gracefully :-) + try + { + m_sGlobalOrModules = CFG_ENTRY_MODULES; + XCUBasedAcceleratorConfiguration::reload(); + + css::uno::Reference< css::util::XChangesNotifier > xBroadcaster(m_xCfg, css::uno::UNO_QUERY_THROW); + m_xCfgListener = new WeakChangesListener(this); + xBroadcaster->addChangesListener(m_xCfgListener); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + {} +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ModuleAcceleratorConfiguration_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &arguments) +{ + rtl::Reference<ModuleAcceleratorConfiguration> inst = new ModuleAcceleratorConfiguration(context, arguments); + css::uno::XInterface *acquired_inst = cppu::acquire(inst.get()); + + inst->fillCache(); + + return acquired_inst; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/accelerators/presethandler.cxx b/framework/source/accelerators/presethandler.cxx new file mode 100644 index 000000000..7bd487684 --- /dev/null +++ b/framework/source/accelerators/presethandler.cxx @@ -0,0 +1,727 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <accelerators/presethandler.hxx> + +#include <classes/fwkresid.hxx> + +#include <strings.hrc> + +#include <com/sun/star/configuration/CorruptedUIConfigurationException.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/FileSystemStorageFactory.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/util/thePathSettings.hpp> + +#include <utility> +#include <vcl/svapp.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <rtl/ustrbuf.hxx> +#include <osl/diagnose.h> +#include <i18nlangtag/languagetag.hxx> + +const ::sal_Int32 ID_CORRUPT_UICONFIG_SHARE = 1; +const ::sal_Int32 ID_CORRUPT_UICONFIG_USER = 2; +const ::sal_Int32 ID_CORRUPT_UICONFIG_GENERAL = 3; + +namespace framework +{ + +namespace { + +/** @short because a concurrent access to the same storage from different implementations + isn't supported, we have to share it with others. + + @descr This struct is allegedly shared and must be used within a + synchronized section. But it isn't. + */ +struct TSharedStorages final +{ + StorageHolder m_lStoragesShare; + StorageHolder m_lStoragesUser; + + TSharedStorages() + {}; +}; + +/** @short provides access to the: + a) shared root storages + b) shared "inbetween" storages + of the share and user layer. */ +struct SharedStorages: public rtl::Static<TSharedStorages, SharedStorages> {}; + +} + +PresetHandler::PresetHandler(css::uno::Reference< css::uno::XComponentContext > xContext) + : m_xContext(std::move(xContext)) + , m_eConfigType(E_GLOBAL) +{ +} + +PresetHandler::PresetHandler(const PresetHandler& rCopy) +{ + m_xContext = rCopy.m_xContext; + m_eConfigType = rCopy.m_eConfigType; + m_xWorkingStorageShare = rCopy.m_xWorkingStorageShare; + m_xWorkingStorageNoLang = rCopy.m_xWorkingStorageNoLang; + m_xWorkingStorageUser = rCopy.m_xWorkingStorageUser; + m_lDocumentStorages = rCopy.m_lDocumentStorages; + m_sRelPathShare = rCopy.m_sRelPathShare; + m_sRelPathUser = rCopy.m_sRelPathUser; +} + +PresetHandler::~PresetHandler() +{ + m_xWorkingStorageShare.clear(); + m_xWorkingStorageNoLang.clear(); + m_xWorkingStorageUser.clear(); + + /* #i46497# + Don't call forgetCachedStorages() here for shared storages. + Because we opened different sub storages by using openPath(). + And every already open path was reused and referenced (means it's + ref count was increased!) + So now we have to release our ref counts to these shared storages + only ... and not to free all used storages. + Otherwise we will disconnect all other open configuration access + objects which base on these storages. + */ + auto & sharedStorages = SharedStorages::get(); + sharedStorages.m_lStoragesShare.closePath(m_sRelPathShare); + sharedStorages.m_lStoragesUser.closePath (m_sRelPathUser ); + + /* On the other side closePath() is not needed for our special handled + document storage. Because it's not shared with others ... so we can + free it. + */ + m_lDocumentStorages.forgetCachedStorages(); +} + +void PresetHandler::forgetCachedStorages() +{ + SolarMutexGuard g; + + if (m_eConfigType == E_DOCUMENT) + { + m_xWorkingStorageShare.clear(); + m_xWorkingStorageNoLang.clear(); + m_xWorkingStorageUser.clear(); + } + + m_lDocumentStorages.forgetCachedStorages(); +} + +namespace { + +OUString lcl_getLocalizedMessage(::sal_Int32 nID) +{ + OUString sMessage("Unknown error."); + + switch(nID) + { + case ID_CORRUPT_UICONFIG_SHARE : + sMessage = FwkResId(STR_CORRUPT_UICFG_SHARE); + + break; + + case ID_CORRUPT_UICONFIG_USER : + sMessage = FwkResId(STR_CORRUPT_UICFG_USER); + break; + + case ID_CORRUPT_UICONFIG_GENERAL : + sMessage = FwkResId(STR_CORRUPT_UICFG_GENERAL); + break; + } + + return sMessage; +} + +void lcl_throwCorruptedUIConfigurationException( + css::uno::Any const & exception, sal_Int32 id) +{ + css::uno::Exception e; + bool ok = (exception >>= e); + OSL_ASSERT(ok); + throw css::configuration::CorruptedUIConfigurationException( + lcl_getLocalizedMessage(id), + css::uno::Reference< css::uno::XInterface >(), + exception.getValueTypeName() + ": \"" + e.Message + "\""); +} + +} + +css::uno::Reference< css::embed::XStorage > PresetHandler::getOrCreateRootStorageShare() +{ + auto & sharedStorages = SharedStorages::get(); + css::uno::Reference< css::embed::XStorage > xRoot = sharedStorages.m_lStoragesShare.getRootStorage(); + if (xRoot.is()) + return xRoot; + + css::uno::Reference< css::uno::XComponentContext > xContext; + { + SolarMutexGuard g; + xContext = m_xContext; + } + + css::uno::Reference< css::util::XPathSettings > xPathSettings = + css::util::thePathSettings::get( xContext ); + + OUString sShareLayer = xPathSettings->getBasePathShareLayer(); + + // "UIConfig" is a "multi path" ... use first part only here! + sal_Int32 nPos = sShareLayer.indexOf(';'); + if (nPos > 0) + sShareLayer = sShareLayer.copy(0, nPos); + + // Note: May be an user uses URLs without a final slash! Check it ... + nPos = sShareLayer.lastIndexOf('/'); + if (nPos != sShareLayer.getLength()-1) + sShareLayer += "/"; + + sShareLayer += "soffice.cfg"; + /* + // TODO remove me! + // Attention: This is temp. workaround ... We create a temp. storage file + // based of a system directory. This must be used so, till the storage implementation + // can work on directories too. + */ + css::uno::Sequence< css::uno::Any > lArgs{ + css::uno::Any(sShareLayer), + css::uno::Any(css::embed::ElementModes::READ | css::embed::ElementModes::NOCREATE) + }; + + css::uno::Reference< css::lang::XSingleServiceFactory > xStorageFactory = css::embed::FileSystemStorageFactory::create( xContext ); + css::uno::Reference< css::embed::XStorage > xStorage; + + try + { + xStorage.set(xStorageFactory->createInstanceWithArguments(lArgs), css::uno::UNO_QUERY_THROW); + } + catch(const css::uno::Exception&) + { + css::uno::Any ex(cppu::getCaughtException()); + lcl_throwCorruptedUIConfigurationException( + ex, ID_CORRUPT_UICONFIG_SHARE); + } + + sharedStorages.m_lStoragesShare.setRootStorage(xStorage); + + return xStorage; +} + +css::uno::Reference< css::embed::XStorage > PresetHandler::getOrCreateRootStorageUser() +{ + auto & sharedStorages = SharedStorages::get(); + css::uno::Reference< css::embed::XStorage > xRoot = sharedStorages.m_lStoragesUser.getRootStorage(); + if (xRoot.is()) + return xRoot; + + css::uno::Reference< css::uno::XComponentContext > xContext; + { + SolarMutexGuard g; + xContext = m_xContext; + } + + css::uno::Reference< css::util::XPathSettings > xPathSettings = + css::util::thePathSettings::get( xContext ); + + OUString sUserLayer = xPathSettings->getBasePathUserLayer(); + + // Note: May be an user uses URLs without a final slash! Check it ... + sal_Int32 nPos = sUserLayer.lastIndexOf('/'); + if (nPos != sUserLayer.getLength()-1) + sUserLayer += "/"; + + sUserLayer += "soffice.cfg"; // storage file + + css::uno::Sequence< css::uno::Any > lArgs{ css::uno::Any(sUserLayer), + css::uno::Any(css::embed::ElementModes::READWRITE) }; + + css::uno::Reference< css::lang::XSingleServiceFactory > xStorageFactory = css::embed::FileSystemStorageFactory::create( xContext ); + css::uno::Reference< css::embed::XStorage > xStorage; + + try + { + xStorage.set(xStorageFactory->createInstanceWithArguments(lArgs), css::uno::UNO_QUERY_THROW); + } + catch(const css::uno::Exception&) + { + css::uno::Any ex(cppu::getCaughtException()); + lcl_throwCorruptedUIConfigurationException( + ex, ID_CORRUPT_UICONFIG_USER); + } + + sharedStorages.m_lStoragesUser.setRootStorage(xStorage); + + return xStorage; +} + +css::uno::Reference< css::embed::XStorage > PresetHandler::getWorkingStorageUser() const +{ + SolarMutexGuard g; + return m_xWorkingStorageUser; +} + +css::uno::Reference< css::embed::XStorage > PresetHandler::getParentStorageShare() +{ + css::uno::Reference< css::embed::XStorage > xWorking; + { + SolarMutexGuard g; + xWorking = m_xWorkingStorageShare; + } + + return SharedStorages::get().m_lStoragesShare.getParentStorage(xWorking); +} + +css::uno::Reference< css::embed::XStorage > PresetHandler::getParentStorageUser() +{ + css::uno::Reference< css::embed::XStorage > xWorking; + { + SolarMutexGuard g; + xWorking = m_xWorkingStorageUser; + } + + return SharedStorages::get().m_lStoragesUser.getParentStorage(xWorking); +} + +void PresetHandler::connectToResource( PresetHandler::EConfigType eConfigType , + std::u16string_view sResource , + std::u16string_view sModule , + const css::uno::Reference< css::embed::XStorage >& xDocumentRoot, + const LanguageTag& rLanguageTag ) +{ + // TODO free all current open storages! + + { + SolarMutexGuard g; + m_eConfigType = eConfigType; + } + + css::uno::Reference< css::embed::XStorage > xShare; + css::uno::Reference< css::embed::XStorage > xNoLang; + css::uno::Reference< css::embed::XStorage > xUser; + + // special case for documents + // use outside root storage, if we run in E_DOCUMENT mode! + if (eConfigType == E_DOCUMENT) + { + if (!xDocumentRoot.is()) + throw css::uno::RuntimeException( + "There is valid root storage, where the UI configuration can work on."); + m_lDocumentStorages.setRootStorage(xDocumentRoot); + xShare = xDocumentRoot; + xUser = xDocumentRoot; + } + else + { + xShare = getOrCreateRootStorageShare(); + xUser = getOrCreateRootStorageUser(); + } + + // #...# + try + { + + // a) inside share layer we should not create any new structures... We have to use + // existing ones only! + // b) inside user layer we can (SOFT mode!) but sometimes we should not (HARD mode!) + // create new empty structures. We should prefer using of any existing structure. + sal_Int32 eShareMode = (css::embed::ElementModes::READ | css::embed::ElementModes::NOCREATE); + sal_Int32 eUserMode = css::embed::ElementModes::READWRITE; + + OUStringBuffer sRelPathBuf(1024); + OUString sRelPathShare; + OUString sRelPathUser; + switch(eConfigType) + { + case E_GLOBAL : + { + sRelPathShare = OUString::Concat("global/") + sResource; + sRelPathUser = sRelPathShare; + + xShare = impl_openPathIgnoringErrors(sRelPathShare, eShareMode, true ); + xUser = impl_openPathIgnoringErrors(sRelPathUser , eUserMode , false); + } + break; + + case E_MODULES : + { + sRelPathShare = OUString::Concat("modules/") + sModule + "/" + sResource; + sRelPathUser = sRelPathShare; + + xShare = impl_openPathIgnoringErrors(sRelPathShare, eShareMode, true ); + xUser = impl_openPathIgnoringErrors(sRelPathUser , eUserMode , false); + } + break; + + case E_DOCUMENT : + { + // A document does not have a share layer in real. + // It has one layer only, and this one should be opened READ_WRITE. + // So we open the user layer here only and set the share layer equals to it .-) + + sRelPathBuf.append(sResource); + sRelPathUser = sRelPathBuf.makeStringAndClear(); + sRelPathShare = sRelPathUser; + + try + { + xUser = m_lDocumentStorages.openPath(sRelPathUser , eUserMode ); + xShare = xUser; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { xShare.clear(); xUser.clear(); } + } + break; + } + + // Non-localized global share + xNoLang = xShare; + + if ( + (rLanguageTag != LanguageTag(LANGUAGE_USER_PRIV_NOTRANSLATE)) && // localized level? + (eConfigType != E_DOCUMENT ) // no localization in document mode! + ) + { + // First try to find the right localized set inside share layer. + // Fallbacks are allowed there. + OUString aShareLocale( rLanguageTag.getBcp47()); + OUString sLocalizedSharePath(sRelPathShare); + bool bAllowFallbacks = true; + xShare = impl_openLocalizedPathIgnoringErrors(sLocalizedSharePath, eShareMode, true , aShareLocale, bAllowFallbacks); + + // The try to locate the right sub dir inside user layer ... without using fallbacks! + // Normally the corresponding sub dir should be created matching the specified locale. + // Because we allow creation of storages inside user layer by default. + OUString aUserLocale( rLanguageTag.getBcp47()); + OUString sLocalizedUserPath(sRelPathUser); + bAllowFallbacks = false; + xUser = impl_openLocalizedPathIgnoringErrors(sLocalizedUserPath, eUserMode, false, aUserLocale, bAllowFallbacks); + + sRelPathShare = sLocalizedSharePath; + sRelPathUser = sLocalizedUserPath; + } + + { + SolarMutexGuard g; + m_xWorkingStorageShare = xShare; + m_xWorkingStorageNoLang= xNoLang; + m_xWorkingStorageUser = xUser; + m_sRelPathShare = sRelPathShare; + m_sRelPathUser = sRelPathUser; + } + + } + catch(const css::uno::Exception&) + { + css::uno::Any ex(cppu::getCaughtException()); + lcl_throwCorruptedUIConfigurationException( + ex, ID_CORRUPT_UICONFIG_GENERAL); + } +} + +void PresetHandler::copyPresetToTarget(std::u16string_view sPreset, + std::u16string_view sTarget) +{ + // don't check our preset list, if element exists + // We try to open it and forward all errors to the user! + + css::uno::Reference< css::embed::XStorage > xWorkingShare; + css::uno::Reference< css::embed::XStorage > xWorkingNoLang; + css::uno::Reference< css::embed::XStorage > xWorkingUser; + { + SolarMutexGuard g; + xWorkingShare = m_xWorkingStorageShare; + xWorkingNoLang= m_xWorkingStorageNoLang; + xWorkingUser = m_xWorkingStorageUser; + } + + // e.g. module without any config data ?! + if ( + (!xWorkingShare.is()) || + (!xWorkingUser.is() ) + ) + { + return; + } + + OUString sPresetFile = OUString::Concat(sPreset) + ".xml"; + OUString sTargetFile = OUString::Concat(sTarget) + ".xml"; + + // remove existing elements before you try to copy the preset to that location ... + // Otherwise w will get an ElementExistException inside copyElementTo()! + css::uno::Reference< css::container::XNameAccess > xCheckingUser(xWorkingUser, css::uno::UNO_QUERY_THROW); + if (xCheckingUser->hasByName(sTargetFile)) + xWorkingUser->removeElement(sTargetFile); + + xWorkingShare->copyElementTo(sPresetFile, xWorkingUser, sTargetFile); + + // If our storages work in transacted mode, we have + // to commit all changes from bottom to top! + commitUserChanges(); +} + +css::uno::Reference< css::io::XStream > PresetHandler::openPreset(std::u16string_view sPreset) +{ + css::uno::Reference< css::embed::XStorage > xFolder; + { + SolarMutexGuard g; + xFolder = m_xWorkingStorageNoLang; + } + + // e.g. module without any config data ?! + if (!xFolder.is()) + return css::uno::Reference< css::io::XStream >(); + + OUString sFile = OUString::Concat(sPreset) + ".xml"; + + // inform user about errors (use original exceptions!) + css::uno::Reference< css::io::XStream > xStream = xFolder->openStreamElement(sFile, css::embed::ElementModes::READ); + return xStream; +} + +css::uno::Reference< css::io::XStream > PresetHandler::openTarget( + std::u16string_view sTarget, sal_Int32 const nMode) +{ + css::uno::Reference< css::embed::XStorage > xFolder; + { + SolarMutexGuard g; + xFolder = m_xWorkingStorageUser; + } + + // e.g. module without any config data ?! + if (!xFolder.is()) + return css::uno::Reference< css::io::XStream >(); + + OUString const sFile(OUString::Concat(sTarget) + ".xml"); + + return xFolder->openStreamElement(sFile, nMode); +} + +void PresetHandler::commitUserChanges() +{ + css::uno::Reference< css::embed::XStorage > xWorking; + EConfigType eCfgType; + { + SolarMutexGuard g; + xWorking = m_xWorkingStorageUser; + eCfgType = m_eConfigType; + } + + // e.g. module without any config data ?! + if (!xWorking.is()) + return; + + OUString sPath; + + switch(eCfgType) + { + case E_GLOBAL : + case E_MODULES : + { + auto & sharedStorages = SharedStorages::get(); + sPath = sharedStorages.m_lStoragesUser.getPathOfStorage(xWorking); + sharedStorages.m_lStoragesUser.commitPath(sPath); + sharedStorages.m_lStoragesUser.notifyPath(sPath); + } + break; + + case E_DOCUMENT : + { + sPath = m_lDocumentStorages.getPathOfStorage(xWorking); + m_lDocumentStorages.commitPath(sPath); + m_lDocumentStorages.notifyPath(sPath); + } + break; + } +} + +void PresetHandler::addStorageListener(XMLBasedAcceleratorConfiguration* pListener) +{ + OUString sRelPath; + EConfigType eCfgType; + { + SolarMutexGuard g; + sRelPath = m_sRelPathUser; // use user path ... because we don't work directly on the share layer! + eCfgType = m_eConfigType; + } + + if (sRelPath.isEmpty()) + return; + + switch(eCfgType) + { + case E_GLOBAL : + case E_MODULES : + { + SharedStorages::get().m_lStoragesUser.addStorageListener(pListener, sRelPath); + } + break; + + case E_DOCUMENT : + { + m_lDocumentStorages.addStorageListener(pListener, sRelPath); + } + break; + } +} + +void PresetHandler::removeStorageListener(XMLBasedAcceleratorConfiguration* pListener) +{ + OUString sRelPath; + EConfigType eCfgType; + { + SolarMutexGuard g; + sRelPath = m_sRelPathUser; // use user path ... because we don't work directly on the share layer! + eCfgType = m_eConfigType; + } + + if (sRelPath.isEmpty()) + return; + + switch(eCfgType) + { + case E_GLOBAL : + case E_MODULES : + { + SharedStorages::get().m_lStoragesUser.removeStorageListener(pListener, sRelPath); + } + break; + + case E_DOCUMENT : + { + m_lDocumentStorages.removeStorageListener(pListener, sRelPath); + } + break; + } +} + +css::uno::Reference< css::embed::XStorage > PresetHandler::impl_openPathIgnoringErrors(const OUString& sPath , + sal_Int32 eMode , + bool bShare) +{ + css::uno::Reference< css::embed::XStorage > xPath; + try + { + if (bShare) + xPath = SharedStorages::get().m_lStoragesShare.openPath(sPath, eMode); + else + xPath = SharedStorages::get().m_lStoragesUser.openPath(sPath, eMode); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { xPath.clear(); } + return xPath; +} + +::std::vector< OUString >::const_iterator PresetHandler::impl_findMatchingLocalizedValue( + const ::std::vector< OUString >& lLocalizedValues, + OUString& rLanguageTag, + bool bAllowFallbacks ) +{ + ::std::vector< OUString >::const_iterator pFound = lLocalizedValues.end(); + if (bAllowFallbacks) + { + pFound = LanguageTag::getFallback(lLocalizedValues, rLanguageTag); + // if we found a valid locale ... take it over to our in/out parameter + // rLanguageTag + if (pFound != lLocalizedValues.end()) + { + rLanguageTag = *pFound; + } + } + else + { + pFound = std::find(lLocalizedValues.begin(), lLocalizedValues.end(), rLanguageTag); + } + + return pFound; +} + +css::uno::Reference< css::embed::XStorage > PresetHandler::impl_openLocalizedPathIgnoringErrors( + OUString& sPath , + sal_Int32 eMode , + bool bShare , + OUString& rLanguageTag , + bool bAllowFallback) +{ + css::uno::Reference< css::embed::XStorage > xPath = impl_openPathIgnoringErrors(sPath, eMode, bShare); + ::std::vector< OUString > lSubFolders = impl_getSubFolderNames(xPath); + ::std::vector< OUString >::const_iterator pLocaleFolder = impl_findMatchingLocalizedValue(lSubFolders, rLanguageTag, bAllowFallback); + + // no fallback ... creation not allowed => no storage + if ( + (pLocaleFolder == lSubFolders.end() ) && + ((eMode & css::embed::ElementModes::NOCREATE) == css::embed::ElementModes::NOCREATE) + ) + return css::uno::Reference< css::embed::XStorage >(); + + // it doesn't matter, if there is a locale fallback or not + // If creation of storages is allowed, we do it anyway. + // Otherwise we have no acc config at all, which can make other trouble. + OUString sLocalizedPath = sPath + "/"; + if (pLocaleFolder != lSubFolders.end()) + sLocalizedPath += *pLocaleFolder; + else + sLocalizedPath += rLanguageTag; + + css::uno::Reference< css::embed::XStorage > xLocalePath = impl_openPathIgnoringErrors(sLocalizedPath, eMode, bShare); + + if (xLocalePath.is()) + sPath = sLocalizedPath; + else + sPath.clear(); + + return xLocalePath; +} + +::std::vector< OUString > PresetHandler::impl_getSubFolderNames(const css::uno::Reference< css::embed::XStorage >& xFolder) +{ + if (!xFolder.is()) + return ::std::vector< OUString >(); + + ::std::vector< OUString > lSubFolders; + const css::uno::Sequence< OUString > lNames = xFolder->getElementNames(); + const OUString* pNames = lNames.getConstArray(); + sal_Int32 c = lNames.getLength(); + sal_Int32 i = 0; + + for (i=0; i<c; ++i) + { + try + { + if (xFolder->isStorageElement(pNames[i])) + lSubFolders.push_back(pNames[i]); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + {} + } + + return lSubFolders; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/accelerators/storageholder.cxx b/framework/source/accelerators/storageholder.cxx new file mode 100644 index 000000000..c0a6106d7 --- /dev/null +++ b/framework/source/accelerators/storageholder.cxx @@ -0,0 +1,447 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <accelerators/storageholder.hxx> +#include <accelerators/acceleratorconfiguration.hxx> +#include <sal/log.hxx> + +#include <com/sun/star/embed/ElementModes.hpp> + +#include <com/sun/star/embed/XTransactedObject.hpp> + +#include <rtl/ustrbuf.hxx> +#include <o3tl/string_view.hxx> + +#include <algorithm> + +constexpr OUStringLiteral PATH_SEPARATOR = u"/"; +#define PATH_SEPARATOR_UNICODE u'/' + +namespace framework +{ + +StorageHolder::StorageHolder() +{ +} + +StorageHolder::~StorageHolder() +{ + // TODO implement me + // dispose/clear etcpp. +} + +void StorageHolder::forgetCachedStorages() +{ + std::unique_lock g(m_mutex); + for (auto & lStorage : m_lStorages) + { + TStorageInfo& rInfo = lStorage.second; + // TODO think about listener ! + rInfo.Storage.clear(); + } + m_lStorages.clear(); +} + +void StorageHolder::setRootStorage(const css::uno::Reference< css::embed::XStorage >& xRoot) +{ + std::unique_lock g(m_mutex); + m_xRoot = xRoot; +} + +css::uno::Reference< css::embed::XStorage > StorageHolder::getRootStorage() const +{ + std::unique_lock g(m_mutex); + return m_xRoot; +} + +css::uno::Reference< css::embed::XStorage > StorageHolder::openPath(const OUString& sPath , + sal_Int32 nOpenMode) +{ + OUString sNormedPath = StorageHolder::impl_st_normPath(sPath); + std::vector<OUString> lFolders = StorageHolder::impl_st_parsePath(sNormedPath); + + // SAFE -> ---------------------------------- + std::unique_lock aReadLock(m_mutex); + css::uno::Reference< css::embed::XStorage > xParent = m_xRoot; + aReadLock.unlock(); + // <- SAFE ---------------------------------- + + css::uno::Reference< css::embed::XStorage > xChild; + OUString sRelPath; + + for (auto const& lFolder : lFolders) + { + OUString sCheckPath (sRelPath + lFolder + PATH_SEPARATOR); + + // SAFE -> ------------------------------ + aReadLock.lock(); + + // If we found an already open storage ... we must increase + // its use count. Otherwise it will may be closed too early :-) + TPath2StorageInfo::iterator pCheck = m_lStorages.find(sCheckPath); + TStorageInfo* pInfo = nullptr; + if (pCheck != m_lStorages.end()) + { + pInfo = &(pCheck->second); + ++(pInfo->UseCount); + xChild = pInfo->Storage; + + aReadLock.unlock(); + // <- SAFE ------------------------------ + } + else + { + aReadLock.unlock(); + // <- SAFE ------------------------------ + + try + { + xChild = StorageHolder::openSubStorageWithFallback(xParent, lFolder, nOpenMode); // TODO think about delegating fallback decision to our own caller! + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { + /* TODO URGENT! + in case we found some "already existing storages" on the path before and increased its UseCount ... + and now we will get an exception on creating a new sub storage ... + we must decrease all UseCounts, which was touched before. Otherwise these storages can't be closed! + + Idea: Using of another structure member "PossibleUseCount" as vector of unique numbers. + Every thread use another unique number to identify all "owned candidates". + A flush method with the same unique number force increasing of the "UseCount" variable then + inside a synchronized block ... + */ + throw; + } + + std::unique_lock g(m_mutex); + pInfo = &(m_lStorages[sCheckPath]); + pInfo->Storage = xChild; + pInfo->UseCount = 1; + } + + xParent = xChild; + sRelPath += lFolder + PATH_SEPARATOR; + } + + // TODO think about return last storage as working storage ... but don't caching it inside this holder! + // => otherwise the same storage is may be commit more than once. + + return xChild; +} + +StorageHolder::TStorageList StorageHolder::getAllPathStorages(const OUString& sPath) +{ + OUString sNormedPath = StorageHolder::impl_st_normPath(sPath); + std::vector<OUString> lFolders = StorageHolder::impl_st_parsePath(sNormedPath); + + StorageHolder::TStorageList lStoragesOfPath; + OUString sRelPath; + + std::unique_lock g(m_mutex); + + for (auto const& lFolder : lFolders) + { + OUString sCheckPath (sRelPath + lFolder + PATH_SEPARATOR); + + TPath2StorageInfo::iterator pCheck = m_lStorages.find(sCheckPath); + if (pCheck == m_lStorages.end()) + { + // at least one path element was not found + // Seems that this path isn't open ... + lStoragesOfPath.clear(); + return lStoragesOfPath; + } + + TStorageInfo& rInfo = pCheck->second; + lStoragesOfPath.push_back(rInfo.Storage); + + sRelPath += lFolder + PATH_SEPARATOR; + } + + return lStoragesOfPath; +} + +void StorageHolder::commitPath(const OUString& sPath) +{ + StorageHolder::TStorageList lStorages = getAllPathStorages(sPath); + + css::uno::Reference< css::embed::XTransactedObject > xCommit; + StorageHolder::TStorageList::reverse_iterator pIt; + for ( pIt = lStorages.rbegin(); // order of commit is important ... otherwise changes are not recognized! + pIt != lStorages.rend(); + ++pIt ) + { + xCommit.set(*pIt, css::uno::UNO_QUERY); + if (!xCommit.is()) + continue; + xCommit->commit(); + } + + // SAFE -> ------------------------------ + { + std::unique_lock aReadLock(m_mutex); + xCommit.set(m_xRoot, css::uno::UNO_QUERY); + } + // <- SAFE ------------------------------ + + if (xCommit.is()) + xCommit->commit(); +} + +void StorageHolder::closePath(const OUString& rPath) +{ + OUString sNormedPath = StorageHolder::impl_st_normPath(rPath); + std::vector<OUString> lFolders = StorageHolder::impl_st_parsePath(sNormedPath); + + /* convert list of paths in the following way: + [0] = "path_1" => "path_1 + [1] = "path_2" => "path_1/path_2" + [2] = "path_3" => "path_1/path_2/path_3" + */ + OUString sParentPath; + for (auto & lFolder : lFolders) + { + OUString sCurrentRelPath(sParentPath + lFolder + PATH_SEPARATOR); + lFolder = sCurrentRelPath; + sParentPath = sCurrentRelPath; + } + + std::unique_lock g(m_mutex); + + std::vector<OUString>::reverse_iterator pIt2; + for ( pIt2 = lFolders.rbegin(); + pIt2 != lFolders.rend(); + ++pIt2 ) + { + OUString sPath = *pIt2; + TPath2StorageInfo::iterator pPath = m_lStorages.find(sPath); + if (pPath == m_lStorages.end()) + continue; // ??? + + TStorageInfo& rInfo = pPath->second; + --rInfo.UseCount; + if (rInfo.UseCount < 1) + { + rInfo.Storage.clear(); + m_lStorages.erase(pPath); + } + } +} + +void StorageHolder::notifyPath(const OUString& sPath) +{ + OUString sNormedPath = StorageHolder::impl_st_normPath(sPath); + + std::unique_lock g(m_mutex); + + TPath2StorageInfo::iterator pIt1 = m_lStorages.find(sNormedPath); + if (pIt1 == m_lStorages.end()) + return; + + TStorageInfo& rInfo = pIt1->second; + for (auto const& listener : rInfo.Listener) + { + if (listener) + listener->changesOccurred(); + } +} + +void StorageHolder::addStorageListener( XMLBasedAcceleratorConfiguration* pListener, + const OUString& sPath ) +{ + OUString sNormedPath = StorageHolder::impl_st_normPath(sPath); + + std::unique_lock g(m_mutex); + + TPath2StorageInfo::iterator pIt1 = m_lStorages.find(sNormedPath); + if (pIt1 == m_lStorages.end()) + return; + + TStorageInfo& rInfo = pIt1->second; + TStorageListenerList::iterator pIt2 = ::std::find(rInfo.Listener.begin(), rInfo.Listener.end(), pListener); + if (pIt2 == rInfo.Listener.end()) + rInfo.Listener.push_back(pListener); +} + +void StorageHolder::removeStorageListener( XMLBasedAcceleratorConfiguration* pListener, + const OUString& sPath ) +{ + OUString sNormedPath = StorageHolder::impl_st_normPath(sPath); + + std::unique_lock g(m_mutex); + + TPath2StorageInfo::iterator pIt1 = m_lStorages.find(sNormedPath); + if (pIt1 == m_lStorages.end()) + return; + + TStorageInfo& rInfo = pIt1->second; + TStorageListenerList::iterator pIt2 = ::std::find(rInfo.Listener.begin(), rInfo.Listener.end(), pListener); + if (pIt2 != rInfo.Listener.end()) + rInfo.Listener.erase(pIt2); +} + +OUString StorageHolder::getPathOfStorage(const css::uno::Reference< css::embed::XStorage >& xStorage) +{ + std::unique_lock g(m_mutex); + + for (auto const& lStorage : m_lStorages) + { + const TStorageInfo& rInfo = lStorage.second; + if (rInfo.Storage == xStorage) + return lStorage.first; + } + + return OUString(); +} + +css::uno::Reference< css::embed::XStorage > StorageHolder::getParentStorage(const css::uno::Reference< css::embed::XStorage >& xChild) +{ + OUString sChildPath = getPathOfStorage(xChild); + return getParentStorage(sChildPath); +} + +css::uno::Reference< css::embed::XStorage > StorageHolder::getParentStorage(const OUString& sChildPath) +{ + // normed path = "a/b/c/" ... we search for "a/b/" + OUString sNormedPath = StorageHolder::impl_st_normPath(sChildPath); + std::vector<OUString> lFolders = StorageHolder::impl_st_parsePath(sNormedPath); + sal_Int32 c = lFolders.size(); + + // a) "" => - => no parent + // b) "a/b/c/" => "a/b/" => return storage "a/b/" + // c) "a/" => "" => return root ! + + // a) + if (c < 1) + return css::uno::Reference< css::embed::XStorage >(); + + // SAFE -> ---------------------------------- + { + std::unique_lock aReadLock(m_mutex); + + // b) + if (c < 2) + return m_xRoot; + + // c) + OUStringBuffer sParentPath(64); + sal_Int32 i = 0; + for (i = 0; i < c - 1; ++i) + { + sParentPath.append(lFolders[i] + PATH_SEPARATOR); + } + + auto pParent = m_lStorages.find(sParentPath.makeStringAndClear()); + if (pParent != m_lStorages.end()) + return pParent->second.Storage; + } + // <- SAFE ---------------------------------- + + // ? + SAL_INFO("fwk", "StorageHolder::getParentStorage(): Unexpected situation. Cached storage item seems to be wrong."); + return css::uno::Reference< css::embed::XStorage >(); +} + +StorageHolder& StorageHolder::operator=(const StorageHolder& rCopy) +{ + std::unique_lock g(m_mutex); + m_xRoot = rCopy.m_xRoot; + m_lStorages = rCopy.m_lStorages; + return *this; +} + +css::uno::Reference< css::embed::XStorage > StorageHolder::openSubStorageWithFallback(const css::uno::Reference< css::embed::XStorage >& xBaseStorage , + const OUString& sSubStorage , + sal_Int32 eOpenMode) +{ + // a) try it first with user specified open mode + // ignore errors ... but save it for later use! + try + { + css::uno::Reference< css::embed::XStorage > xSubStorage = xBaseStorage->openStorageElement(sSubStorage, eOpenMode); + if (xSubStorage.is()) + return xSubStorage; + } + catch(const css::uno::RuntimeException&) + { + throw; + } + catch(const css::uno::Exception&) + { + // b) readonly already tried? => forward last error! + if ((eOpenMode & css::embed::ElementModes::WRITE) != css::embed::ElementModes::WRITE) // fallback possible ? + throw; + } + + // b) readonly already tried, throw error + if ((eOpenMode & css::embed::ElementModes::WRITE) != css::embed::ElementModes::WRITE) // fallback possible ? + throw css::uno::Exception(); + + // c) try it readonly + // don't catch exception here! Outside code wish to know, if operation failed or not. + // Otherwise they work on NULL references ... + sal_Int32 eNewMode = (eOpenMode & ~css::embed::ElementModes::WRITE); + css::uno::Reference< css::embed::XStorage > xSubStorage = xBaseStorage->openStorageElement(sSubStorage, eNewMode); + if (xSubStorage.is()) + return xSubStorage; + + // d) no chance! + SAL_INFO("fwk", "openSubStorageWithFallback(): Unexpected situation! Got no exception for missing storage ..."); + return css::uno::Reference< css::embed::XStorage >(); +} + +OUString StorageHolder::impl_st_normPath(const OUString& sPath) +{ + // path must start without "/" but end with "/"! + + OUString sNormedPath = sPath; + + // "/bla" => "bla" && "/" => "" (!) + (void)sNormedPath.startsWith(PATH_SEPARATOR, &sNormedPath); + + // "/" => "" || "" => "" ? + if (sNormedPath.isEmpty()) + return OUString(); + + // "bla" => "bla/" + if (sNormedPath.lastIndexOf(PATH_SEPARATOR_UNICODE) != (sNormedPath.getLength()-1)) + sNormedPath += PATH_SEPARATOR; + + return sNormedPath; +} + +std::vector<OUString> StorageHolder::impl_st_parsePath(std::u16string_view sPath) +{ + std::vector<OUString> lToken; + sal_Int32 i = 0; + while (true) + { + OUString sToken( o3tl::getToken(sPath, 0, PATH_SEPARATOR_UNICODE, i) ); + if (i < 0) + break; + lToken.push_back(sToken); + } + return lToken; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |