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 | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.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 '')
187 files changed, 79443 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: */ diff --git a/framework/source/classes/framecontainer.cxx b/framework/source/classes/framecontainer.cxx new file mode 100644 index 000000000..a23e3633e --- /dev/null +++ b/framework/source/classes/framecontainer.cxx @@ -0,0 +1,293 @@ +/* -*- 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 <framework/framecontainer.hxx> + +#include <com/sun/star/frame/FrameSearchFlag.hpp> + +#include <vcl/svapp.hxx> +#include <comphelper/sequence.hxx> +#include <sal/log.hxx> + +namespace framework +{ +/**-*************************************************************************************************************** + @short initialize an empty container + @descr The container will be empty then - special features (e.g. the async quit mechanism) are disabled. + + @threadsafe not necessary - it's not a singleton + *****************************************************************************************************************/ +FrameContainer::FrameContainer() +/*DEPRECATEME + , m_bAsyncQuit ( sal_False ) // default must be "disabled"! + , m_aAsyncCall ( LINK( this, FrameContainer, implts_asyncQuit ) ) +*/ +{ +} + +/**-*************************************************************************************************************** + @short deinitialize may a filled container + @descr Special features (if the currently are running) will be disabled and we free all used other resources. + + @threadsafe not necessary - it's not a singleton + *****************************************************************************************************************/ +FrameContainer::~FrameContainer() +{ + // Don't forget to free memory! + m_aContainer.clear(); + m_xActiveFrame.clear(); +} + +/**-*************************************************************************************************************** + @short append a new frame to the container + @descr We accept the incoming frame only, if it is a valid reference and doesn't exist already. + + @param xFrame + frame, which should be added to this container + Must be a valid reference. + + @threadsafe yes + *****************************************************************************************************************/ +void FrameContainer::append(const css::uno::Reference<css::frame::XFrame>& xFrame) +{ + if (xFrame.is() && !exist(xFrame)) + { + SolarMutexGuard g; + m_aContainer.push_back(xFrame); + } +} + +/**-*************************************************************************************************************** + @short remove a frame from the container + @descr In case we remove the last frame and our internal special feature (the async quit mechanism) + was enabled by the desktop instance, we start it. + + @param xFrame + frame, which should be deleted from this container + Must be a valid reference. + + @threadsafe yes + *****************************************************************************************************************/ +void FrameContainer::remove(const css::uno::Reference<css::frame::XFrame>& xFrame) +{ + SolarMutexGuard g; + + TFrameContainer::iterator aSearchedItem + = ::std::find(m_aContainer.begin(), m_aContainer.end(), xFrame); + if (aSearchedItem != m_aContainer.end()) + { + m_aContainer.erase(aSearchedItem); + + // If removed frame was the current active frame - reset state variable. + if (m_xActiveFrame == xFrame) + m_xActiveFrame.clear(); + } +} + +/**-*************************************************************************************************************** + @short check if the given frame currently exist inside the container + @param xFrame + reference to the queried frame + + @return <TRUE/> if frame is part of this container + <FALSE/> otherwise + + @threadsafe yes + *****************************************************************************************************************/ +bool FrameContainer::exist(const css::uno::Reference<css::frame::XFrame>& xFrame) const +{ + SolarMutexGuard g; + return (::std::find(m_aContainer.begin(), m_aContainer.end(), xFrame) != m_aContainer.end()); +} + +/**-*************************************************************************************************************** + @short delete all existing items of the container + @threadsafe yes + *****************************************************************************************************************/ +void FrameContainer::clear() +{ + SolarMutexGuard g; + // Clear the container ... + m_aContainer.clear(); + // ... and don't forget to reset the active frame. + // It's a reference to a valid container-item. + // But no container item => no active frame! + m_xActiveFrame.clear(); +} + +/**-*************************************************************************************************************** + @short returns count of all current existing frames + @deprecated This value can't be guaranteed for multithreading environments. + So it will be marked as deprecated and should be replaced by "getAllElements()". + + @return the count of existing container items + + @threadsafe yes + *****************************************************************************************************************/ +sal_uInt32 FrameContainer::getCount() const +{ + SolarMutexGuard g; + return static_cast<sal_uInt32>(m_aContainer.size()); +} + +/**-*************************************************************************************************************** + @short returns one item of this container + @deprecated This value can't be guaranteed for multithreading environments. + So it will be marked as deprecated and should be replaced by "getAllElements()". + + @param nIndex + a value between 0 and (getCount()-1) to address one container item + + @return a reference to a frame inside the container, which match with given index + + @threadsafe yes + *****************************************************************************************************************/ +css::uno::Reference<css::frame::XFrame> FrameContainer::operator[](sal_uInt32 nIndex) const +{ + css::uno::Reference<css::frame::XFrame> xFrame; + try + { + // Get element form container WITH automatic test of ranges! + // If index not valid, an out_of_range exception is thrown. + SolarMutexGuard g; + xFrame = m_aContainer.at(nIndex); + } + catch (const std::out_of_range&) + { + // The index is not valid for current container-content - we must handle this case! + // We can return the default value ... + SAL_INFO("fwk", "FrameContainer::operator[]: Exception caught: std::out_of_range"); + } + return xFrame; +} + +/**-*************************************************************************************************************** + @short returns a snapshot of all currently existing frames inside this container + @descr Should be used to replace the deprecated functions getCount()/operator[]! + + @return a list of all frame references inside this container + + @threadsafe yes + *****************************************************************************************************************/ +css::uno::Sequence<css::uno::Reference<css::frame::XFrame>> FrameContainer::getAllElements() const +{ + SolarMutexGuard g; + return comphelper::containerToSequence(m_aContainer); +} + +/**-*************************************************************************************************************** + @short set the given frame as the new active one inside this container + @descr We accept this frame only, if it's already a part of this container. + + @param xFrame + reference to the new active frame + Must be a valid reference and already part of this container. + + @threadsafe yes + *****************************************************************************************************************/ +void FrameContainer::setActive(const css::uno::Reference<css::frame::XFrame>& xFrame) +{ + if (!xFrame.is() || exist(xFrame)) + { + SolarMutexGuard g; + m_xActiveFrame = xFrame; + } +} + +/**-*************************************************************************************************************** + @short return the current active frame of this container + @descr Value can be null in case the frame was removed from the container and nobody + from outside decide which of all others should be the new one... + + @return a reference to the current active frame + Value can be NULL! + + @threadsafe yes + *****************************************************************************************************************/ +css::uno::Reference<css::frame::XFrame> FrameContainer::getActive() const +{ + SolarMutexGuard g; + return m_xActiveFrame; +} + +/**-*************************************************************************************************************** + @short implements a simple search based on current container items + @descr It can be used for findFrame() and implements a deep down search. + + @param sName + target name, which is searched + + @return reference to the found frame or NULL if not. + + @threadsafe yes + *****************************************************************************************************************/ +css::uno::Reference<css::frame::XFrame> +FrameContainer::searchOnAllChildrens(const OUString& sName) const +{ + SolarMutexGuard g; + // Step over all child frames. But if direct child isn't the right one search on his children first - before + // you go to next direct child of this container! + css::uno::Reference<css::frame::XFrame> xSearchedFrame; + for (auto const& container : m_aContainer) + { + if (container->getName() == sName) + { + xSearchedFrame = container; + break; + } + else + { + xSearchedFrame = container->findFrame(sName, css::frame::FrameSearchFlag::CHILDREN); + if (xSearchedFrame.is()) + break; + } + } + return xSearchedFrame; +} + +/**-*************************************************************************************************************** + @short implements a simple search based on current container items + @descr It can be used for findFrame() and search on members of this container only! + + @param sName + target name, which is searched + + @return reference to the found frame or NULL if not. + + @threadsafe yes + *****************************************************************************************************************/ +css::uno::Reference<css::frame::XFrame> +FrameContainer::searchOnDirectChildrens(std::u16string_view sName) const +{ + SolarMutexGuard g; + css::uno::Reference<css::frame::XFrame> xSearchedFrame; + for (auto const& container : m_aContainer) + { + if (container->getName() == sName) + { + xSearchedFrame = container; + break; + } + } + return xSearchedFrame; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/classes/taskcreator.cxx b/framework/source/classes/taskcreator.cxx new file mode 100644 index 000000000..151322e1e --- /dev/null +++ b/framework/source/classes/taskcreator.cxx @@ -0,0 +1,110 @@ +/* -*- 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 <classes/taskcreator.hxx> +#include <services.h> +#include <taskcreatordefs.hxx> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/TaskCreator.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <utility> + +namespace framework{ + +/*-**************************************************************************************************** + @short initialize instance with necessary information + @descr We need a valid uno service manager to create or instantiate new services. + All other information to create frames or tasks come in on right interface methods. + + @param xContext + points to the valid uno service manager +*//*-*****************************************************************************************************/ +TaskCreator::TaskCreator( css::uno::Reference< css::uno::XComponentContext > xContext ) + : m_xContext (std::move( xContext )) +{ +} + +/*-**************************************************************************************************** + @short deinitialize instance + @descr We should release all used resource which are not needed any longer. +*//*-*****************************************************************************************************/ +TaskCreator::~TaskCreator() +{ +} + +/*-**************************************************************************************************** + TODO document me +*//*-*****************************************************************************************************/ +css::uno::Reference< css::frame::XFrame > TaskCreator::createTask( const OUString& sName, const utl::MediaDescriptor& rDescriptor ) +{ + css::uno::Reference< css::lang::XSingleServiceFactory > xCreator; + + try + { + xCreator.set( m_xContext->getServiceManager()->createInstanceWithContext(IMPLEMENTATIONNAME_FWK_TASKCREATOR, m_xContext), css::uno::UNO_QUERY_THROW); + } + catch(const css::uno::Exception&) + {} + + // no catch here ... without a task creator service we can't open ANY document window within the office. + // That's IMHO not a good idea. Then we should accept the stacktrace showing us the real problem. + // BTW: The used fallback creator service (IMPLEMENTATIONNAME_FWK_TASKCREATOR) is implemented in the same + // library then these class here ... Why we should not be able to create it ? + if ( ! xCreator.is()) + xCreator = css::frame::TaskCreator::create(m_xContext); + + css::uno::Sequence< css::uno::Any > lArgs(6); + auto plArgs = lArgs.getArray(); + css::beans::NamedValue aArg; + + aArg.Name = ARGUMENT_PARENTFRAME; + aArg.Value <<= css::uno::Reference< css::frame::XFrame >( css::frame::Desktop::create( m_xContext ), css::uno::UNO_QUERY_THROW); + plArgs[0] <<= aArg; + + aArg.Name = ARGUMENT_CREATETOPWINDOW; + aArg.Value <<= true; + plArgs[1] <<= aArg; + + aArg.Name = ARGUMENT_MAKEVISIBLE; + aArg.Value <<= false; + plArgs[2] <<= aArg; + + aArg.Name = ARGUMENT_SUPPORTPERSISTENTWINDOWSTATE; + aArg.Value <<= true; + plArgs[3] <<= aArg; + + aArg.Name = ARGUMENT_FRAMENAME; + aArg.Value <<= sName; + plArgs[4] <<= aArg; + + bool bHidden + = rDescriptor.getUnpackedValueOrDefault("HiddenForConversion", false); + aArg.Name = "HiddenForConversion"; + aArg.Value <<= bHidden; + plArgs[5] <<= aArg; + + css::uno::Reference< css::frame::XFrame > xTask(xCreator->createInstanceWithArguments(lArgs), css::uno::UNO_QUERY_THROW); + return xTask; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/closedispatcher.cxx b/framework/source/dispatch/closedispatcher.cxx new file mode 100644 index 000000000..a6ff39d45 --- /dev/null +++ b/framework/source/dispatch/closedispatcher.cxx @@ -0,0 +1,612 @@ +/* -*- 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 <dispatch/closedispatcher.hxx> +#include <pattern/frame.hxx> +#include <framework/framelistanalyzer.hxx> +#include <services.h> + +#include <com/sun/star/bridge/BridgeFactory.hpp> +#include <com/sun/star/bridge/XBridgeFactory2.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/CommandGroup.hpp> +#include <com/sun/star/frame/StartModule.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/document/XActionLockable.hpp> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <toolkit/helper/vclunohelper.hxx> + +#include <osl/diagnose.h> +#include <utility> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> +#include <vcl/syswin.hxx> +#include <unotools/moduleoptions.hxx> +#include <o3tl/string_view.hxx> + +using namespace com::sun::star; + +namespace framework{ + +#ifdef fpf + #error "Who uses \"fpf\" as define. It will overwrite my namespace alias ..." +#endif +namespace fpf = ::framework::pattern::frame; + +constexpr OUStringLiteral URL_CLOSEDOC = u".uno:CloseDoc"; +constexpr OUStringLiteral URL_CLOSEWIN = u".uno:CloseWin"; +const char URL_CLOSEFRAME[] = ".uno:CloseFrame"; + +CloseDispatcher::CloseDispatcher(css::uno::Reference< css::uno::XComponentContext > xContext , + const css::uno::Reference< css::frame::XFrame >& xFrame , + std::u16string_view sTarget) + : m_xContext(std::move(xContext)) + , m_aAsyncCallback( + new vcl::EventPoster(LINK(this, CloseDispatcher, impl_asyncCallback))) + , m_eOperation(E_CLOSE_DOC) + , m_pSysWindow(nullptr) +{ + uno::Reference<frame::XFrame> xTarget = static_impl_searchRightTargetFrame(xFrame, sTarget); + m_xCloseFrame = xTarget; + + // Try to retrieve the system window instance of the closing frame. + uno::Reference<awt::XWindow> xWindow = xTarget->getContainerWindow(); + if (xWindow.is()) + { + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + if (pWindow->IsSystemWindow()) + m_pSysWindow = dynamic_cast<SystemWindow*>(pWindow.get()); + } +} + +CloseDispatcher::~CloseDispatcher() +{ + SolarMutexGuard g; + m_aAsyncCallback.reset(); + m_pSysWindow.reset(); +} + +void SAL_CALL CloseDispatcher::dispatch(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments) +{ + dispatchWithNotification(aURL, lArguments, css::uno::Reference< css::frame::XDispatchResultListener >()); +} + +css::uno::Sequence< sal_Int16 > SAL_CALL CloseDispatcher::getSupportedCommandGroups() +{ + return css::uno::Sequence< sal_Int16 >{css::frame::CommandGroup::VIEW, css::frame::CommandGroup::DOCUMENT}; +} + +css::uno::Sequence< css::frame::DispatchInformation > SAL_CALL CloseDispatcher::getConfigurableDispatchInformation(sal_Int16 nCommandGroup) +{ + if (nCommandGroup == css::frame::CommandGroup::VIEW) + { + /* Attention: Don't add .uno:CloseFrame here. Because it's not really + a configurable feature ... and further it does not have + a valid UIName entry inside the GenericCommands.xcu ... */ + css::uno::Sequence< css::frame::DispatchInformation > lViewInfos{ + { URL_CLOSEWIN, css::frame::CommandGroup::VIEW } + }; + return lViewInfos; + } + else if (nCommandGroup == css::frame::CommandGroup::DOCUMENT) + { + css::uno::Sequence< css::frame::DispatchInformation > lDocInfos{ + { URL_CLOSEDOC, css::frame::CommandGroup::DOCUMENT } + }; + return lDocInfos; + } + + return css::uno::Sequence< css::frame::DispatchInformation >(); +} + +void SAL_CALL CloseDispatcher::addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/, + const css::util::URL& /*aURL*/ ) +{ +} + +void SAL_CALL CloseDispatcher::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/, + const css::util::URL& /*aURL*/ ) +{ +} + +void SAL_CALL CloseDispatcher::dispatchWithNotification(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + // SAFE -> ---------------------------------- + SolarMutexClearableGuard aWriteLock; + + // This reference indicates, that we were already called before and + // our asynchronous process was not finished yet. + // We have to reject double calls. Otherwise we risk, + // that we try to close an already closed resource... + // And it is no problem to do nothing then. The UI user will try it again, if + // non of these jobs was successful. + if (m_xSelfHold.is()) + { + aWriteLock.clear(); + // <- SAFE ------------------------------ + + implts_notifyResultListener( + xListener, + css::frame::DispatchResultState::DONTKNOW, + css::uno::Any()); + return; + } + + // First we have to check, if this dispatcher is used right. Means if valid URLs are used. + // If not - we have to break this operation. But an optional listener must be informed. + // BTW: We save the information about the requested operation. Because + // we need it later. + if ( aURL.Complete == URL_CLOSEDOC ) + m_eOperation = E_CLOSE_DOC; + else if ( aURL.Complete == URL_CLOSEWIN ) + m_eOperation = E_CLOSE_WIN; + else if ( aURL.Complete == URL_CLOSEFRAME ) + m_eOperation = E_CLOSE_FRAME; + else + { + aWriteLock.clear(); + // <- SAFE ------------------------------ + + implts_notifyResultListener( + xListener, + css::frame::DispatchResultState::FAILURE, + css::uno::Any()); + return; + } + + if (m_pSysWindow && m_pSysWindow->GetCloseHdl().IsSet()) + { + // The closing frame has its own close handler. Call it instead. + m_pSysWindow->GetCloseHdl().Call(*m_pSysWindow); + + aWriteLock.clear(); + // <- SAFE ------------------------------ + + implts_notifyResultListener( + xListener, + css::frame::DispatchResultState::SUCCESS, + css::uno::Any()); + + return; + } + + // OK - URLs are the right ones. + // But we can't execute synchronously :-) + // May we are called from a generic key-input handler, + // which isn't aware that this call kill its own environment... + // Do it asynchronous everytimes! + + // But don't forget to hold ourselves alive. + // We are called back from an environment, which doesn't know a uno reference. + // They call us back by using our c++ interface. + + m_xResultListener = xListener; + m_xSelfHold.set(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY); + + aWriteLock.clear(); + // <- SAFE ---------------------------------- + + bool bIsSynchron = false; + for (const css::beans::PropertyValue& rArg : lArguments ) + { + if ( rArg.Name == "SynchronMode" ) + { + rArg.Value >>= bIsSynchron; + break; + } + } + + if ( bIsSynchron ) + impl_asyncCallback(nullptr); + else + { + SolarMutexGuard g; + m_aAsyncCallback->Post(); + } +} + +/** + @short asynchronous callback + @descr We start all actions inside this object asynchronous + (see comments there). + Now we do the following: + - close all views to the same document, if needed and possible + - make the current frame empty + ! This step is necessary to handle errors during closing the + document inside the frame. May the document shows a dialog and + the user ignore it. Then the state of the office can be changed + during we try to close frame and document. + - check the environment (means count open frames - excluding our + current one) + - decide then, if we must close this frame only, establish the backing mode + or shutdown the whole application. +*/ +IMPL_LINK_NOARG(CloseDispatcher, impl_asyncCallback, LinkParamNone*, void) +{ + try + { + + // Allow calling of XController->suspend() everytimes. + // Dispatch is an UI functionality. We implement such dispatch object here. + // And further XController->suspend() was designed to bring an UI ... + bool bControllerSuspended = false; + + bool bCloseAllViewsToo; + EOperation eOperation; + css::uno::Reference< css::uno::XComponentContext > xContext; + css::uno::Reference< css::frame::XFrame > xCloseFrame; + css::uno::Reference< css::frame::XDispatchResultListener > xListener; + { + SolarMutexGuard g; + + // Closing of all views, related to the same document, is allowed + // only if the dispatched URL was ".uno:CloseDoc"! + bCloseAllViewsToo = (m_eOperation == E_CLOSE_DOC); + + eOperation = m_eOperation; + xContext = m_xContext; + xCloseFrame.set(m_xCloseFrame.get(), css::uno::UNO_QUERY); + xListener = m_xResultListener; + } + + // frame already dead ?! + // Nothing to do ! + if (! xCloseFrame.is()) + return; + + bool bCloseFrame = false; + bool bEstablishBackingMode = false; + bool bTerminateApp = false; + + // Analyze the environment a first time. + // If we found some special cases, we can + // make some decisions earlier! + css::uno::Reference< css::frame::XFramesSupplier > xDesktop( css::frame::Desktop::create(xContext), css::uno::UNO_QUERY_THROW); + FrameListAnalyzer aCheck1(xDesktop, xCloseFrame, FrameAnalyzerFlags::Help | FrameAnalyzerFlags::BackingComponent); + + // Check for existing UNO connections. + // NOTE: There is a race between checking this and connections being created/destroyed before + // we close the frame / terminate the app. + css::uno::Reference<css::bridge::XBridgeFactory2> bridgeFac( css::bridge::BridgeFactory::create(xContext) ); + bool bHasActiveConnections = bridgeFac->getExistingBridges().hasElements(); + + // a) If the current frame (where the close dispatch was requested for) does not have + // any parent frame ... it will close this frame only. Such frame isn't part of the + // global desktop tree ... and such frames are used as "implementation details" only. + // E.g. the live previews of our wizards doing such things. And then the owner of the frame + // is responsible for closing the application or accepting closing of the application + // by others. + if ( ! xCloseFrame->getCreator().is()) + bCloseFrame = true; + + // b) The help window can't disagree with any request. + // Because it doesn't implement a controller - it uses a window only. + // Further it can't be the last open frame - if we do all other things + // right inside this CloseDispatcher implementation. + // => close it! + else if (aCheck1.m_bReferenceIsHelp) + bCloseFrame = true; + + // c) If we are already in "backing mode", we terminate the application, if no active UNO connections are found. + // If there is an active UNO connection, we only close the frame and leave the application alive. + // It doesn't matter, how many other frames (can be the help or hidden frames only) are open then. + else if (aCheck1.m_bReferenceIsBacking) { + if (bHasActiveConnections) + bCloseFrame = true; + else + bTerminateApp = true; + } + + // d) Otherwise we have to: close all views to the same document, close the + // document inside our own frame and decide then again, what has to be done! + else + { + if (implts_prepareFrameForClosing(m_xCloseFrame, bCloseAllViewsToo, bControllerSuspended)) + { + // OK; this frame is empty now. + // Check the environment again to decide, what is the next step. + FrameListAnalyzer aCheck2(xDesktop, xCloseFrame, FrameAnalyzerFlags::All); + + // c1) there is as minimum 1 frame open, which is visible and contains a document + // different from our one. And it's not the help! + // => close our frame only - nothing else. + if (!aCheck2.m_lOtherVisibleFrames.empty()) + bCloseFrame = true; + else + + // c2) if we close the current view ... but not all other views + // to the same document, we must close the current frame only! + // Because implts_closeView() suspended this view only - does not + // close the frame. + if ( + (!bCloseAllViewsToo ) && + (!aCheck2.m_lModelFrames.empty()) + ) + bCloseFrame = true; + + else + // c3) there is no other (visible) frame open ... + // The help module will be ignored everytimes! + // But we have to decide if we must terminate the + // application or establish the backing mode now. + // And that depends from the dispatched URL ... + { + if (eOperation == E_CLOSE_FRAME) + { + if (bHasActiveConnections) + bCloseFrame = true; + else + bTerminateApp = true; + } + else if( SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::STARTMODULE) ) + bEstablishBackingMode = true; + else if (bHasActiveConnections) + bCloseFrame = true; + else + bTerminateApp = true; + } + } + } + + // Do it now ... + bool bSuccess = false; + if (bCloseFrame) + bSuccess = implts_closeFrame(); + else if (bEstablishBackingMode) + #if defined MACOSX + { + // on mac close down, quickstarter keeps the process alive + // however if someone has shut down the quickstarter + // behave as any other platform + + bool bQuickstarterRunning = false; + // get quickstart service + try + { + css::uno::Reference< css::beans::XFastPropertySet > xSet( xContext->getServiceManager()->createInstanceWithContext(IMPLEMENTATIONNAME_QUICKLAUNCHER, xContext), css::uno::UNO_QUERY_THROW ); + css::uno::Any aVal( xSet->getFastPropertyValue( 0 ) ); + bool bState = false; + if( aVal >>= bState ) + bQuickstarterRunning = bState; + } + catch( const css::uno::Exception& ) + { + } + bSuccess = bQuickstarterRunning ? implts_terminateApplication() : implts_establishBackingMode(); + } + #else + bSuccess = implts_establishBackingMode(); + #endif + else if (bTerminateApp) + bSuccess = implts_terminateApplication(); + + if ( ! bSuccess && bControllerSuspended ) + { + css::uno::Reference< css::frame::XController > xController = xCloseFrame->getController(); + if (xController.is()) + xController->suspend(false); + } + + // inform listener + sal_Int16 nState = css::frame::DispatchResultState::FAILURE; + if (bSuccess) + nState = css::frame::DispatchResultState::SUCCESS; + implts_notifyResultListener(xListener, nState, css::uno::Any()); + + SolarMutexGuard g; + // This method was called asynchronous from our main thread by using a pointer. + // We reached this method only, by using a reference to ourself :-) + // Further this member is used to detect still running and not yet finished + // asynchronous operations. So it's time now to release this reference. + // But hold it temp alive. Otherwise we die before we can finish this method really :-)) + css::uno::Reference< css::uno::XInterface > xTempHold = m_xSelfHold; + m_xSelfHold.clear(); + m_xResultListener.clear(); + } + catch(const css::lang::DisposedException&) + { + } +} + +bool CloseDispatcher::implts_prepareFrameForClosing(const css::uno::Reference< css::frame::XFrame >& xFrame, + bool bCloseAllOtherViewsToo, + bool& bControllerSuspended ) +{ + // Frame already dead ... so this view is closed ... is closed ... is ... .-) + if (! xFrame.is()) + return true; + + // Close all views to the same document ... if forced to do so. + // But don't touch our own frame here! + // We must do so ... because the may be following controller->suspend() + // will show the "save/discard/cancel" dialog for the last view only! + if (bCloseAllOtherViewsToo) + { + css::uno::Reference< css::uno::XComponentContext > xContext; + { + SolarMutexGuard g; + xContext = m_xContext; + } + + css::uno::Reference< css::frame::XFramesSupplier > xDesktop( css::frame::Desktop::create( xContext ), css::uno::UNO_QUERY_THROW); + FrameListAnalyzer aCheck(xDesktop, xFrame, FrameAnalyzerFlags::All); + + size_t c = aCheck.m_lModelFrames.size(); + size_t i = 0; + for (i=0; i<c; ++i) + { + if (!fpf::closeIt(aCheck.m_lModelFrames[i])) + return false; + } + } + + // Inform user about modified documents or still running jobs (e.g. printing). + { + css::uno::Reference< css::frame::XController > xController = xFrame->getController(); + if (xController.is()) // some views don't uses a controller .-( (e.g. the help window) + { + bControllerSuspended = xController->suspend(true); + if (! bControllerSuspended) + return false; + } + } + + // don't remove the component really by e.g. calling setComponent(null, null). + // It's enough to suspend the controller. + // If we close the frame later this controller doesn't show the same dialog again. + return true; +} + +bool CloseDispatcher::implts_closeFrame() +{ + css::uno::Reference< css::frame::XFrame > xFrame; + { + SolarMutexGuard g; + xFrame.set(m_xCloseFrame.get(), css::uno::UNO_QUERY); + } + + // frame already dead ? => so it's closed ... it's closed ... + if ( ! xFrame.is() ) + return true; + + // don't deliver ownership; our "UI user" will try it again if it failed. + // OK - he will get an empty frame then. But normally an empty frame + // should be closeable always :-) + if (!fpf::closeIt(xFrame)) + return false; + + { + SolarMutexGuard g; + m_xCloseFrame.clear(); + } + + return true; +} + +bool CloseDispatcher::implts_establishBackingMode() +{ + css::uno::Reference< css::uno::XComponentContext > xContext; + css::uno::Reference< css::frame::XFrame > xFrame; + { + SolarMutexGuard g; + xContext = m_xContext; + xFrame.set(m_xCloseFrame.get(), css::uno::UNO_QUERY); + } + + if (!xFrame.is()) + return false; + + css::uno::Reference < css::document::XActionLockable > xLock( xFrame, css::uno::UNO_QUERY ); + if ( xLock.is() && xLock->isActionLocked() ) + return false; + + css::uno::Reference< css::awt::XWindow > xContainerWindow = xFrame->getContainerWindow(); + + css::uno::Reference< css::frame::XController > xStartModule = css::frame::StartModule::createWithParentWindow( + xContext, xContainerWindow); + + // Attention: You MUST(!) call setComponent() before you call attachFrame(). + css::uno::Reference< css::awt::XWindow > xBackingWin(xStartModule, css::uno::UNO_QUERY); + xFrame->setComponent(xBackingWin, xStartModule); + xStartModule->attachFrame(xFrame); + xContainerWindow->setVisible(true); + + return true; +} + +bool CloseDispatcher::implts_terminateApplication() +{ + css::uno::Reference< css::uno::XComponentContext > xContext; + { + SolarMutexGuard g; + xContext = m_xContext; + } + + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( xContext ); + + return xDesktop->terminate(); +} + +void CloseDispatcher::implts_notifyResultListener(const css::uno::Reference< css::frame::XDispatchResultListener >& xListener, + sal_Int16 nState , + const css::uno::Any& aResult ) +{ + if (!xListener.is()) + return; + + css::frame::DispatchResultEvent aEvent( + css::uno::Reference< css::uno::XInterface >(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY), + nState, + aResult); + + xListener->dispatchFinished(aEvent); +} + +css::uno::Reference< css::frame::XFrame > CloseDispatcher::static_impl_searchRightTargetFrame(const css::uno::Reference< css::frame::XFrame >& xFrame , + std::u16string_view sTarget) +{ + if (o3tl::equalsIgnoreAsciiCase(sTarget, u"_self")) + return xFrame; + + OSL_ENSURE(sTarget.empty(), "CloseDispatch used for unexpected target. Magic things will happen now .-)"); + + css::uno::Reference< css::frame::XFrame > xTarget = xFrame; + while(true) + { + // a) top frames will be closed + if (xTarget->isTop()) + return xTarget; + + // b) even child frame containing top level windows (e.g. query designer of database) will be closed + css::uno::Reference< css::awt::XWindow > xWindow = xTarget->getContainerWindow(); + css::uno::Reference< css::awt::XTopWindow > xTopWindowCheck(xWindow, css::uno::UNO_QUERY); + if (xTopWindowCheck.is()) + { + // b1) Note: Toolkit interface XTopWindow sometimes is used by real VCL-child-windows also .-) + // Be sure that these window is really a "top system window". + // Attention ! Checking Window->GetParent() isn't the right approach here. + // Because sometimes VCL create "implicit border windows" as parents even we created + // a simple XWindow using the toolkit only .-( + SolarMutexGuard aSolarLock; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->IsSystemWindow() ) + return xTarget; + } + + // c) try to find better results on parent frame + // If no parent frame exists (because this frame is used outside the desktop tree) + // the given frame must be used directly. + css::uno::Reference< css::frame::XFrame > xParent = xTarget->getCreator(); + if ( ! xParent.is()) + return xTarget; + + // c1) check parent frame inside next loop ... + xTarget = xParent; + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/dispatchdisabler.cxx b/framework/source/dispatch/dispatchdisabler.cxx new file mode 100644 index 000000000..2341d0dd6 --- /dev/null +++ b/framework/source/dispatch/dispatchdisabler.cxx @@ -0,0 +1,170 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <services.h> +#include <dispatch/dispatchdisabler.hxx> + +#include <com/sun/star/frame/DispatchDescriptor.hpp> +#include <cppuhelper/supportsservice.hxx> + +using namespace css; +using namespace framework; + +DispatchDisabler::DispatchDisabler(const uno::Reference< uno::XComponentContext >& ) +{ +} + +// XInitialization +void SAL_CALL DispatchDisabler::initialize( const uno::Sequence< uno::Any >& aArguments ) +{ + uno::Sequence< OUString > aDisabledURLs; + if( aArguments.hasElements() && + ( aArguments[0] >>= aDisabledURLs ) ) + { + for( OUString const & url : std::as_const(aDisabledURLs) ) + maDisabledURLs.insert(url); + } +} + +// XDispatchProvider +uno::Reference< frame::XDispatch > SAL_CALL +DispatchDisabler::queryDispatch( const util::URL& rURL, + const OUString& rTargetFrameName, + ::sal_Int32 nSearchFlags ) +{ + // If present - disabled. + if( maDisabledURLs.find(rURL.Complete) != maDisabledURLs.end() || + !mxSlave.is() ) + return uno::Reference< frame::XDispatch >(); + else + return mxSlave->queryDispatch(rURL, rTargetFrameName, nSearchFlags); +} + +uno::Sequence< uno::Reference< frame::XDispatch > > SAL_CALL +DispatchDisabler::queryDispatches( const uno::Sequence< frame::DispatchDescriptor >& rRequests ) +{ + uno::Sequence< uno::Reference< frame::XDispatch > > aResult(rRequests.getLength()); + auto aResultRange = asNonConstRange(aResult); + for( sal_Int32 i = 0; i < rRequests.getLength(); ++i ) + aResultRange[i] = queryDispatch(rRequests[i].FeatureURL, + rRequests[i].FrameName, + rRequests[i].SearchFlags); + return aResult; +} + +// XDispatchProviderInterceptor +uno::Reference< frame::XDispatchProvider > SAL_CALL +DispatchDisabler::getSlaveDispatchProvider() +{ + return mxSlave; +} + +void SAL_CALL DispatchDisabler::setSlaveDispatchProvider( const uno::Reference< frame::XDispatchProvider >& xNewDispatchProvider ) +{ + mxSlave = xNewDispatchProvider; +} + +uno::Reference< frame::XDispatchProvider > SAL_CALL +DispatchDisabler::getMasterDispatchProvider() +{ + return mxMaster; +} +void SAL_CALL +DispatchDisabler::setMasterDispatchProvider( const uno::Reference< frame::XDispatchProvider >& xNewSupplier ) +{ + mxMaster = xNewSupplier; +} + +// XInterceptorInfo +uno::Sequence< OUString > SAL_CALL + DispatchDisabler::getInterceptedURLs() +{ + uno::Sequence< OUString > aDisabledURLs(maDisabledURLs.size()); + auto aDisabledURLsRange = asNonConstRange(aDisabledURLs); + sal_Int32 n = 0; + for (auto const& disabledURL : maDisabledURLs) + aDisabledURLsRange[n++] = disabledURL; + return aDisabledURLs; +} + +// XElementAccess +uno::Type SAL_CALL DispatchDisabler::getElementType() +{ + uno::Type aModuleType = cppu::UnoType<OUString>::get(); + return aModuleType; +} + +::sal_Bool SAL_CALL DispatchDisabler::hasElements() +{ + return !maDisabledURLs.empty(); +} + +// XNameAccess +uno::Any SAL_CALL DispatchDisabler::getByName( const OUString& ) +{ + return uno::Any(); +} + +uno::Sequence< OUString > SAL_CALL DispatchDisabler::getElementNames() +{ + return getInterceptedURLs(); +} + +sal_Bool SAL_CALL DispatchDisabler::hasByName( const OUString& rName ) +{ + return maDisabledURLs.find(rName) != maDisabledURLs.end(); +} + +// XNameReplace +void SAL_CALL DispatchDisabler::replaceByName( const OUString& rName, const uno::Any& aElement ) +{ + removeByName( rName ); + insertByName( rName, aElement ); +} + +// XNameContainer +void DispatchDisabler::insertByName( const OUString& rName, const uno::Any& ) +{ + maDisabledURLs.insert(rName); +} + +void DispatchDisabler::removeByName( const OUString& rName ) +{ + auto it = maDisabledURLs.find(rName); + if( it != maDisabledURLs.end() ) + maDisabledURLs.erase(it); +} + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL DispatchDisabler::getImplementationName() +{ + return "com.sun.star.comp.framework.services.DispatchDisabler"; +} + +sal_Bool SAL_CALL DispatchDisabler::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL DispatchDisabler::getSupportedServiceNames() +{ + return { "com.sun.star.frame.DispatchDisabler" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_DispatchDisabler_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::DispatchDisabler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/dispatchinformationprovider.cxx b/framework/source/dispatch/dispatchinformationprovider.cxx new file mode 100644 index 000000000..a5a0bd698 --- /dev/null +++ b/framework/source/dispatch/dispatchinformationprovider.cxx @@ -0,0 +1,130 @@ +/* -*- 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 <dispatch/dispatchinformationprovider.hxx> +#include <dispatch/closedispatcher.hxx> + +#include <com/sun/star/frame/AppDispatchProvider.hpp> + +#include <comphelper/sequence.hxx> + +#include <unordered_map> +#include <utility> + +namespace framework{ + +DispatchInformationProvider::DispatchInformationProvider(css::uno::Reference< css::uno::XComponentContext > xContext , + const css::uno::Reference< css::frame::XFrame >& xFrame) + : m_xContext (std::move(xContext )) + , m_xFrame (xFrame ) +{ +} + +DispatchInformationProvider::~DispatchInformationProvider() +{ +} + +css::uno::Sequence< sal_Int16 > SAL_CALL DispatchInformationProvider::getSupportedCommandGroups() +{ + css::uno::Sequence< css::uno::Reference< css::frame::XDispatchInformationProvider > > lProvider = implts_getAllSubProvider(); + sal_Int32 c1 = lProvider.getLength(); + sal_Int32 i1 = 0; + + ::std::vector< sal_Int16 > lGroups; + + for (i1=0; i1<c1; ++i1) + { + // ignore controller, which doesn't implement the right interface + css::uno::Reference< css::frame::XDispatchInformationProvider > xProvider = lProvider[i1]; + if (!xProvider.is()) + continue; + + const css::uno::Sequence< sal_Int16 > lProviderGroups = xProvider->getSupportedCommandGroups(); + sal_Int32 c2 = lProviderGroups.getLength(); + sal_Int32 i2 = 0; + for (i2=0; i2<c2; ++i2) + { + const sal_Int16& rGroup = lProviderGroups[i2]; + ::std::vector< sal_Int16 >::const_iterator pGroup = + ::std::find(lGroups.begin(), lGroups.end(), rGroup); + if (pGroup == lGroups.end()) + lGroups.push_back(rGroup); + } + } + + return ::comphelper::containerToSequence(lGroups); +} + +css::uno::Sequence< css::frame::DispatchInformation > SAL_CALL DispatchInformationProvider::getConfigurableDispatchInformation(sal_Int16 nCommandGroup) +{ + css::uno::Sequence< css::uno::Reference< css::frame::XDispatchInformationProvider > > lProvider = implts_getAllSubProvider(); + sal_Int32 c1 = lProvider.getLength(); + sal_Int32 i1 = 0; + + std::unordered_map<OUString, css::frame::DispatchInformation> lInfos; + + for (i1=0; i1<c1; ++i1) + { + try + { + // ignore controller, which doesn't implement the right interface + css::uno::Reference< css::frame::XDispatchInformationProvider > xProvider = lProvider[i1]; + if (!xProvider.is()) + continue; + + const css::uno::Sequence< css::frame::DispatchInformation > lProviderInfos = xProvider->getConfigurableDispatchInformation(nCommandGroup); + sal_Int32 c2 = lProviderInfos.getLength(); + sal_Int32 i2 = 0; + for (i2=0; i2<c2; ++i2) + { + const css::frame::DispatchInformation& rInfo = lProviderInfos[i2]; + auto pInfo = lInfos.find(rInfo.Command); + if (pInfo == lInfos.end()) + lInfos[rInfo.Command] = rInfo; + } + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { continue; } + } + + return comphelper::mapValuesToSequence(lInfos); +} + +css::uno::Sequence< css::uno::Reference< css::frame::XDispatchInformationProvider > > DispatchInformationProvider::implts_getAllSubProvider() +{ + css::uno::Reference< css::frame::XFrame > xFrame(m_xFrame); + if (!xFrame.is()) + return css::uno::Sequence< css::uno::Reference< css::frame::XDispatchInformationProvider > >(); + + rtl::Reference<CloseDispatcher> xCloser = new CloseDispatcher(m_xContext, xFrame, u"_self"); // explicit "_self" ... not "" ... see implementation of close dispatcher itself! + + css::uno::Reference< css::frame::XDispatchInformationProvider > xController (xFrame->getController() , css::uno::UNO_QUERY); + css::uno::Reference< css::frame::XDispatchInformationProvider > xAppDispatcher = css::frame::AppDispatchProvider::create(m_xContext); + css::uno::Sequence< css::uno::Reference< css::frame::XDispatchInformationProvider > > lProvider{ + xController, xCloser, xAppDispatcher + }; + + return lProvider; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/dispatchprovider.cxx b/framework/source/dispatch/dispatchprovider.cxx new file mode 100644 index 000000000..8328e9c42 --- /dev/null +++ b/framework/source/dispatch/dispatchprovider.cxx @@ -0,0 +1,585 @@ +/* -*- 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 <dispatch/dispatchprovider.hxx> +#include <loadenv/loadenv.hxx> +#include <dispatch/loaddispatcher.hxx> +#include <dispatch/closedispatcher.hxx> +#include <dispatch/startmoduledispatcher.hxx> + +#include <pattern/window.hxx> +#include <targets.h> +#include "isstartmoduledispatch.hxx" + +#include <com/sun/star/frame/XDesktop.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/util/XCacheInfo.hpp> + +#include <rtl/ustring.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> +#include <framework/dispatchhelper.hxx> + +namespace framework{ + +/** + @short standard ctor/dtor + @descr These initialize a new instance of this class with needed information for work. + We hold a weakreference to our owner frame which start dispatches at us. + We can't use a normal reference because he hold a reference of us too ... + nobody can die so ...! + + @seealso using at owner + + @param rxContext + reference to servicemanager to create new services. + @param xFrame + reference to our owner frame. +*/ +DispatchProvider::DispatchProvider( css::uno::Reference< css::uno::XComponentContext > xContext , + const css::uno::Reference< css::frame::XFrame >& xFrame ) + : m_xContext (std::move( xContext )) + , m_xFrame ( xFrame ) +{ +} + +/** + @short protected(!) dtor for deinitializing + @descr We made it protected to prevent using of us as base class instead as a member. + */ +DispatchProvider::~DispatchProvider() +{ +} + +/** + @interface XDispatchProvider + @short search a dispatcher for given URL + @descr If no interceptor is set on owner, we search for right frame and dispatch URL to it. + If no frame was found, we do nothing. + But we don't do it directly here. We detect the type of our owner frame and calls + specialized queryDispatch() helper dependen from that. Because a Desktop handle some + requests in another way then a normal frame. + + @param aURL + URL to dispatch. + @param sTargetFrameName + name of searched frame. + @param nSearchFlags + flags for searching. + @return A reference to a dispatch object for this URL (if someone was found!). + + @threadsafe yes +*/ +css::uno::Reference< css::frame::XDispatch > SAL_CALL DispatchProvider::queryDispatch( const css::util::URL& aURL , + const OUString& sTargetFrameName , + sal_Int32 nSearchFlags ) +{ + css::uno::Reference< css::frame::XDispatch > xDispatcher; + + css::uno::Reference< css::frame::XFrame > xOwner(m_xFrame); + + css::uno::Reference< css::frame::XDesktop > xDesktopCheck( xOwner, css::uno::UNO_QUERY ); + + if (xDesktopCheck.is()) + xDispatcher = implts_queryDesktopDispatch(xOwner, aURL, sTargetFrameName, nSearchFlags); + else + xDispatcher = implts_queryFrameDispatch(xOwner, aURL, sTargetFrameName, nSearchFlags); + + return xDispatcher; +} + +/** + @interface XDispatchProvider + @short do the same like queryDispatch() ... but handle multiple dispatches at the same time + @descr It's an optimism. User give us a list of queries ... and we return a list of dispatcher. + If one of given queries couldn't be solved to a real existing dispatcher ... + we return a list with empty references in it! Order of both lists will be retained! + + @seealso method queryDispatch() + + @param lDescriptions + a list of all dispatch parameters for multiple requests + @return A reference a list of dispatch objects for these URLs - may with some <NULL/> values inside. + + @threadsafe yes +*/ +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL DispatchProvider::queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptions ) +{ + // Create return list - which must have same size then the given descriptor + // It's not allowed to pack it! + sal_Int32 nCount = lDescriptions.getLength(); + css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > lDispatcher( nCount ); + auto lDispatcherRange = asNonConstRange(lDispatcher); + // Step over all descriptors and try to get any dispatcher for it. + for( sal_Int32 i=0; i<nCount; ++i ) + { + lDispatcherRange[i] = queryDispatch( lDescriptions[i].FeatureURL , + lDescriptions[i].FrameName , + lDescriptions[i].SearchFlags ); + } + + return lDispatcher; +} + +/** + @short helper for queryDispatch() + @descr Every member of the frame tree (frame, desktop) must handle such request + in another way. So we implement different specialized methods for everyone. + + @threadsafe yes + */ +css::uno::Reference< css::frame::XDispatch > DispatchProvider::implts_queryDesktopDispatch( const css::uno::Reference< css::frame::XFrame >& xDesktop , + const css::util::URL& aURL , + const OUString& sTargetFrameName , + sal_Int32 nSearchFlags ) +{ + css::uno::Reference< css::frame::XDispatch > xDispatcher; + + // ignore wrong requests which are not supported + if ( + (sTargetFrameName==SPECIALTARGET_PARENT ) || // we have no parent by definition + (sTargetFrameName==SPECIALTARGET_BEAMER ) // beamer frames are allowed as child of tasks only - + // and they exist more than ones. We have no idea which our sub tasks is the right one + ) + { + return nullptr; + } + + // I) handle special cases which not right for using findFrame() first + + // I.I) "_blank" + // It's not the right place to create a new task here - because we are queried for a dispatch object + // only, which can handle such request. Such dispatcher should create the required task on demand. + // Normally the functionality for "_blank" is provided by findFrame() - but that would create it directly + // here. that's why we must "intercept" here. + + if (sTargetFrameName==SPECIALTARGET_BLANK) + { + if (implts_isLoadableContent(aURL)) + xDispatcher = implts_getOrCreateDispatchHelper( E_BLANKDISPATCHER, xDesktop ); + } + + // I.II) "_default" + // This is a combination of search an empty task for recycling - or create a new one. + + else if (sTargetFrameName==SPECIALTARGET_DEFAULT) + { + if (implts_isLoadableContent(aURL)) + xDispatcher = implts_getOrCreateDispatchHelper( E_DEFAULTDISPATCHER, xDesktop ); + + if (isStartModuleDispatch(aURL)) + xDispatcher = implts_getOrCreateDispatchHelper( E_STARTMODULEDISPATCHER, xDesktop ); + } + + // I.III) "_self", "", "_top" + // The desktop can't load any document - but he can handle some special protocols like "uno", "slot" ... + // Why is "top" here handled too? Because the desktop is the topest frame. Normally it's superfluous + // to use this target - but we can handle it in the same manner then "_self". + + else if ( + (sTargetFrameName==SPECIALTARGET_SELF) || + (sTargetFrameName==SPECIALTARGET_TOP ) || + (sTargetFrameName.isEmpty()) + ) + { + xDispatcher = implts_searchProtocolHandler(aURL); + } + + // I.IV) no further special targets exist + // Now we have to search for the right target frame by calling findFrame() - but should provide our code + // against creation of a new task if no frame could be found. + // I said it before - it's allowed for dispatch() only. + + else + { + sal_Int32 nRightFlags = nSearchFlags & ~css::frame::FrameSearchFlag::CREATE; + + // try to find any existing target and ask him for his dispatcher + css::uno::Reference< css::frame::XFrame > xFoundFrame = xDesktop->findFrame(sTargetFrameName, nRightFlags); + if (xFoundFrame.is()) + { + css::uno::Reference< css::frame::XDispatchProvider > xProvider( xFoundFrame, css::uno::UNO_QUERY ); + xDispatcher = xProvider->queryDispatch(aURL,SPECIALTARGET_SELF,0); + } + // if it couldn't be found - but creation was allowed + // use special dispatcher for creation or forwarding to the browser + else if (nSearchFlags & css::frame::FrameSearchFlag::CREATE) + xDispatcher = implts_getOrCreateDispatchHelper( E_CREATEDISPATCHER, xDesktop, sTargetFrameName, nSearchFlags ); + } + + return xDispatcher; +} + +css::uno::Reference< css::frame::XDispatch > DispatchProvider::implts_queryFrameDispatch( const css::uno::Reference< css::frame::XFrame >& xFrame , + const css::util::URL& aURL , + const OUString& sTargetFrameName , + sal_Int32 nSearchFlags ) +{ + css::uno::Reference< css::frame::XDispatch > xDispatcher; + + // 0) Some URLs are dispatched in a generic way (e.g. by the menu) using the default target "". + // But they are specified to use her own fix target. Detect such URLs here and use the correct target. + + // I) handle special cases which not right for using findFrame() first + + // I.I) "_blank", "_default" + // It's not the right place to create a new task here. Only the desktop can do that. + // Normally the functionality for "_blank" is provided by findFrame() - but that would create it directly + // here. that's why we must "intercept" here. + + if ( + (sTargetFrameName==SPECIALTARGET_BLANK ) || + (sTargetFrameName==SPECIALTARGET_DEFAULT) + ) + { + css::uno::Reference< css::frame::XDispatchProvider > xParent( xFrame->getCreator(), css::uno::UNO_QUERY ); + if (xParent.is()) + xDispatcher = xParent->queryDispatch(aURL, sTargetFrameName, 0); // it's a special target - ignore search flags + } + + // I.II) "_beamer" + // Special sub frame of a top frame only. Search or create it. ... OK it's currently a little bit HACKI. + // Only the sfx (means the controller) can create it. + + else if (sTargetFrameName==SPECIALTARGET_BEAMER) + { + css::uno::Reference< css::frame::XDispatchProvider > xBeamer( xFrame->findFrame( SPECIALTARGET_BEAMER, css::frame::FrameSearchFlag::CHILDREN | css::frame::FrameSearchFlag::SELF ), css::uno::UNO_QUERY ); + if (xBeamer.is()) + { + xDispatcher = xBeamer->queryDispatch(aURL, SPECIALTARGET_SELF, 0); + } + else + { + css::uno::Reference< css::frame::XDispatchProvider > xController( xFrame->getController(), css::uno::UNO_QUERY ); + if (xController.is()) + // force using of special target - but use original search flags + // May the caller used the CREATE flag or not! + xDispatcher = xController->queryDispatch(aURL, SPECIALTARGET_BEAMER, nSearchFlags); + } + } + + // I.IV) "_parent" + // Our parent frame (if it exist) should handle this URL. + + else if (sTargetFrameName==SPECIALTARGET_PARENT) + { + css::uno::Reference< css::frame::XDispatchProvider > xParent( xFrame->getCreator(), css::uno::UNO_QUERY ); + if (xParent.is()) + // SELF => we must address the parent directly... and not his parent or any other parent! + xDispatcher = xParent->queryDispatch(aURL, SPECIALTARGET_SELF, 0); + } + + // I.V) "_top" + // This request must be forwarded to any parent frame, till we reach a top frame. + // If no parent exist, we can handle itself. + + else if (sTargetFrameName==SPECIALTARGET_TOP) + { + if (xFrame->isTop()) + { + // If we are this top frame itself (means our owner frame) + // we should call ourself recursiv with a better target "_self". + // So we can share the same code! (see reaction for "_self" inside this method too.) + xDispatcher = queryDispatch(aURL,SPECIALTARGET_SELF,0); + } + else + { + css::uno::Reference< css::frame::XDispatchProvider > xParent( xFrame->getCreator(), css::uno::UNO_QUERY ); + // Normally if isTop() returned sal_False ... the parent frame MUST(!) exist ... + // But it seems to be better to check that here to prevent us against an access violation. + if (xParent.is()) + xDispatcher = xParent->queryDispatch(aURL, SPECIALTARGET_TOP, 0); + } + } + + // I.VI) "_self", "" + // Our owner frame should handle this URL. But we can't do it for all of them. + // So we ask the internal set controller first. If he disagree we try to find a registered + // protocol handler. If this failed too - we check for a loadable content and in case of true + // we load it into the frame by returning specialized dispatch object. + + else if ( + (sTargetFrameName==SPECIALTARGET_SELF) || + (sTargetFrameName.isEmpty()) + ) + { + // There exist a hard coded interception for special URLs. + if ( aURL.Complete == ".uno:CloseDoc" || aURL.Complete == ".uno:CloseWin" ) + { + css::uno::Reference< css::frame::XDispatchProvider > xParent( xFrame->getCreator(), css::uno::UNO_QUERY ); + // In case the frame is not a top one, is not based on system window and has a parent, + // the parent frame should be queried for the correct dispatcher. + // See i93473 + if ( + !WindowHelper::isTopWindow(xFrame->getContainerWindow()) && + !VCLUnoHelper::GetWindow(xFrame->getContainerWindow())->IsSystemWindow() && + xParent.is() + ) + xDispatcher = xParent->queryDispatch(aURL, SPECIALTARGET_SELF, 0); + else + xDispatcher = implts_getOrCreateDispatchHelper( E_CLOSEDISPATCHER, xFrame ); + } + else if ( aURL.Complete == ".uno:CloseFrame" ) + xDispatcher = implts_getOrCreateDispatchHelper( E_CLOSEDISPATCHER, xFrame ); + + if ( ! xDispatcher.is()) + { + // Ask our controller for his agreement for these dispatched URL ... + // because some URLs are internal and can be handled faster by SFX - which most is the current controller! + // But in case of e.g. the bibliography not all queries will be handled successfully here. + css::uno::Reference< css::frame::XDispatchProvider > xController( xFrame->getController(), css::uno::UNO_QUERY ); + if (xController.is()) + xDispatcher = xController->queryDispatch(aURL, SPECIALTARGET_SELF, 0); + } + + // If controller has no fun to dispatch these URL - we must search another right dispatcher. + // Search for any registered protocol handler first. + if (!xDispatcher.is()) + xDispatcher = implts_searchProtocolHandler(aURL); + + // Not for controller - not for protocol handler + // It should be a loadable content - may be a file. Check it ... + // This check is necessary to found out, that + // support for some protocols isn't installed by user. May be + // "ftp" isn't available. So we suppress creation of our self dispatcher. + // The result will be clear. He can't handle it - but he would try it. + if ( + ( ! xDispatcher.is() ) && + ( implts_isLoadableContent(aURL) ) + ) + { + xDispatcher = implts_getOrCreateDispatchHelper( E_SELFDISPATCHER, xFrame ); + } + } + + // I.VII) no further special handlings exist + // Now we have to search for the right target frame by calling findFrame() - but should provide our code + // against creation of a new task if no frame could be found. + // I said it before - it's allowed for dispatch() only. + + else + { + sal_Int32 nRightFlags = nSearchFlags & ~css::frame::FrameSearchFlag::CREATE; + + // try to find any existing target and ask him for his dispatcher + css::uno::Reference< css::frame::XFrame > xFoundFrame = xFrame->findFrame(sTargetFrameName, nRightFlags); + if (xFoundFrame.is()) + { + // Attention: Found target is our own owner frame! + // Don't ask him for his dispatcher. We know it already - it's our self dispatch helper. + // Otherwise we can start a never ending recursiv call. Why? + // Somewhere called our owner frame - he called some interceptor objects - and may by this dispatch provider + // is called. If wa use queryDispatch() on our owner frame again - we start this call stack again ... and again. + if (xFoundFrame==xFrame) + xDispatcher = implts_getOrCreateDispatchHelper( E_SELFDISPATCHER, xFrame ); + else + { + css::uno::Reference< css::frame::XDispatchProvider > xProvider( xFoundFrame, css::uno::UNO_QUERY ); + xDispatcher = xProvider->queryDispatch(aURL,SPECIALTARGET_SELF,0); + } + } + else + // if it couldn't be found - but creation was allowed + // forward request to the desktop. + // Note: The given target name must be used to set the name on new created task! + // Don't forward request by changing it to a special one e.g _blank. + // Use the CREATE flag only to prevent call against further searches. + // We already know it - the target must be created new. + if (nSearchFlags & css::frame::FrameSearchFlag::CREATE) + { + css::uno::Reference< css::frame::XDispatchProvider > xParent( xFrame->getCreator(), css::uno::UNO_QUERY ); + if (xParent.is()) + xDispatcher = xParent->queryDispatch(aURL, sTargetFrameName, css::frame::FrameSearchFlag::CREATE); + } + } + + return xDispatcher; +} + +/** + @short search for a registered protocol handler and ask him for a dispatch object + @descr We search a suitable handler inside our cfg package org.openoffice.Office.ProtocolHandler. + If we found anyone, we create and initialize it. Initialize means: we set our owner frame on it + as context information. He can use it or leave it. Of course - we are aware of handler implementations, + which doesn't support initialization. It's an optional feature. + + @param aURL + the dispatch URL for which may a handler is registered + + @return A dispatch object if a handler was found and agree with the given URL or <NULL/> otherwise. + + @threadsafe yes +*/ +css::uno::Reference< css::frame::XDispatch > DispatchProvider::implts_searchProtocolHandler( const css::util::URL& aURL ) +{ + css::uno::Reference< css::frame::XDispatch > xDispatcher; + ProtocolHandler aHandler; + + // This member is threadsafe by himself and lives if we live - we don't need any mutex here. + if (m_aProtocolHandlerCache.search(aURL,&aHandler)) + { + css::uno::Reference< css::frame::XDispatchProvider > xHandler; + { + SolarMutexGuard g; + + // create it + bool bInitialize = true; + try + { + // Only create the protocol handler instance once, the creation is expensive. + auto it = m_aProtocolHandlers.find(aHandler.m_sUNOName); + if (it == m_aProtocolHandlers.end()) + { + xHandler.set( + css::uno::Reference<css::lang::XMultiServiceFactory>(m_xContext->getServiceManager(), css::uno::UNO_QUERY_THROW) + ->createInstance(aHandler.m_sUNOName), + css::uno::UNO_QUERY); + + // Check if the handler explicitly requested to avoid caching. + css::uno::Reference<css::util::XCacheInfo> xCacheInfo(xHandler, css::uno::UNO_QUERY); + if (!xCacheInfo.is() || xCacheInfo->isCachingAllowed()) + { + m_aProtocolHandlers.emplace(aHandler.m_sUNOName, xHandler); + } + } + else + { + xHandler = it->second; + bInitialize = false; + } + } + catch(const css::uno::Exception&) {} + + // look if initialization is necessary + css::uno::Reference< css::lang::XInitialization > xInit( xHandler, css::uno::UNO_QUERY ); + if (xInit.is() && bInitialize) + { + css::uno::Reference< css::frame::XFrame > xOwner( m_xFrame.get(), css::uno::UNO_QUERY ); + SAL_WARN_IF(!xOwner.is(), "fwk", "DispatchProvider::implts_searchProtocolHandler(): Couldn't get reference to my owner frame. So I can't set may needed context information for this protocol handler."); + if (xOwner.is()) + { + try + { + // but do it only, if all context information is OK + css::uno::Sequence< css::uno::Any > lContext{ css::uno::Any(xOwner) }; + xInit->initialize(lContext); + } + catch(const css::uno::Exception&) {} + } + } + } + + // ask for his (sub)dispatcher for the given URL + if (xHandler.is()) + xDispatcher = xHandler->queryDispatch(aURL,SPECIALTARGET_SELF,0); + } + + return xDispatcher; +} + +/** + @short get or create new dispatch helper + @descr Sometimes we need some helper implementations to support dispatching of special URLs or commands. + But it's not a good idea to hold these services for the whole life time of this provider instance. + We should create it on demand... + That's why we implement this method. It return an already existing helper or create a new one otherwise. + + @attention The parameter sTarget and nSearchFlags are defaulted to "" and 0! + Mostly it depends from the parameter eHelper is they are required or not. + + @param eHelper + specify the requested dispatch helper + @param xOwner + the target of possible dispatch() call on created dispatch helper + @param sTarget + the target parameter of the original queryDispatch() request + @param nSearchFlags + the flags parameter of the original queryDispatch() request + @return A reference to a dispatch helper. + + @threadsafe yes +*/ +css::uno::Reference< css::frame::XDispatch > DispatchProvider::implts_getOrCreateDispatchHelper( EDispatchHelper eHelper , + const css::uno::Reference< css::frame::XFrame >& xOwner , + const OUString& sTarget , + sal_Int32 nSearchFlags) +{ + css::uno::Reference< css::frame::XDispatch > xDispatchHelper; + + switch (eHelper) + { + case E_CREATEDISPATCHER : + xDispatchHelper = new LoadDispatcher(m_xContext, xOwner, sTarget, nSearchFlags); + break; + + case E_BLANKDISPATCHER : + { + if (xOwner.is()) + xDispatchHelper = new LoadDispatcher(m_xContext, xOwner, SPECIALTARGET_BLANK, 0); + } + break; + + case E_DEFAULTDISPATCHER : + { + if (xOwner.is()) + xDispatchHelper = new LoadDispatcher(m_xContext, xOwner, SPECIALTARGET_DEFAULT, 0); + } + break; + + case E_SELFDISPATCHER : + xDispatchHelper = new LoadDispatcher(m_xContext, xOwner, SPECIALTARGET_SELF, 0); + break; + + case E_CLOSEDISPATCHER : + xDispatchHelper = new CloseDispatcher( m_xContext, xOwner, sTarget ); + break; + + case E_STARTMODULEDISPATCHER : + xDispatchHelper = new StartModuleDispatcher( m_xContext ); + break; + } + + return xDispatchHelper; +} + +/** + @short check URL for support by our used loader or handler + @descr If we must return our own dispatch helper implementations (self, blank, create dispatcher!) + we should be sure, that URL describe any loadable content. Otherwise slot/uno URLs + will be detected... but there exist nothing for real loading into a target frame! + + @param aURL + URL which should be "detected" + @return <TRUE/> if somewhere could handle that - <FALSE/> otherwise. + + @threadsafe yes +*/ +bool DispatchProvider::implts_isLoadableContent( const css::util::URL& aURL ) +{ + LoadEnv::EContentType eType = LoadEnv::classifyContent(aURL.Complete, css::uno::Sequence< css::beans::PropertyValue >()); + return ( eType == LoadEnv::E_CAN_BE_LOADED ); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/interceptionhelper.cxx b/framework/source/dispatch/interceptionhelper.cxx new file mode 100644 index 000000000..57ab28ee4 --- /dev/null +++ b/framework/source/dispatch/interceptionhelper.cxx @@ -0,0 +1,270 @@ +/* -*- 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 <dispatch/interceptionhelper.hxx> + +#include <com/sun/star/frame/XInterceptorInfo.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <osl/diagnose.h> +#include <utility> +#include <vcl/svapp.hxx> +#include <tools/diagnose_ex.h> + +using namespace com::sun::star; + +namespace framework{ + +InterceptionHelper::InterceptionHelper(const css::uno::Reference< css::frame::XFrame >& xOwner, + css::uno::Reference< css::frame::XDispatchProvider > xSlave) + : m_xOwnerWeak (xOwner ) + , m_xSlave (std::move(xSlave )) +{ +} + +InterceptionHelper::~InterceptionHelper() +{ +} + +css::uno::Reference< css::frame::XDispatch > SAL_CALL InterceptionHelper::queryDispatch(const css::util::URL& aURL , + const OUString& sTargetFrameName, + sal_Int32 nSearchFlags ) +{ + css::uno::Reference<css::frame::XDispatchProvider> xInterceptor; + // SAFE { + { + SolarMutexGuard aReadLock; + + // a) first search an interceptor, which match to this URL by its URL pattern registration + // Note: if it return NULL - it does not mean an empty interceptor list automatically! + InterceptorList::const_iterator pIt = m_lInterceptionRegs.findByPattern(aURL.Complete); + if (pIt != m_lInterceptionRegs.end()) + xInterceptor = pIt->xInterceptor; + + // b) No match by registration - but a valid interceptor list. + // Find first interceptor w/o pattern, so we need to query it + if (!xInterceptor.is()) + { + for (auto const& lInterceptionReg : m_lInterceptionRegs) + { + if (!lInterceptionReg.lURLPattern.hasElements()) + { + // no pattern -> need to ask this guy! + xInterceptor = lInterceptionReg.xInterceptor; + break; + } + } + // if we didn't find any non-pattern interceptor, there's no-one + // registered for this command url (we already searched for matching + // patterns above) + } + // c) No registered interceptor => use our direct slave. + // This helper exist by design and must be valid everytimes ... + // But to be more feature proof - we should check that .-) + if (!xInterceptor.is() && m_xSlave.is()) + xInterceptor = m_xSlave; + } + // } SAFE + + css::uno::Reference< css::frame::XDispatch > xReturn; + if (xInterceptor.is()) + xReturn = xInterceptor->queryDispatch(aURL, sTargetFrameName, nSearchFlags); + return xReturn; +} + +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL InterceptionHelper::queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptor ) +{ + sal_Int32 c = lDescriptor.getLength(); + css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > lDispatches (c); + css::uno::Reference< css::frame::XDispatch >* pDispatches = lDispatches.getArray(); + const css::frame::DispatchDescriptor* pDescriptor = lDescriptor.getConstArray(); + + for (sal_Int32 i=0; i<c; ++i) + pDispatches[i] = queryDispatch(pDescriptor[i].FeatureURL, pDescriptor[i].FrameName, pDescriptor[i].SearchFlags); + + return lDispatches; +} + +void SAL_CALL InterceptionHelper::registerDispatchProviderInterceptor(const css::uno::Reference< css::frame::XDispatchProviderInterceptor >& xInterceptor) +{ + // reject incorrect calls of this interface method + css::uno::Reference< css::frame::XDispatchProvider > xThis(this); + if (!xInterceptor.is()) + throw css::uno::RuntimeException("NULL references not allowed as in parameter", xThis); + + // Fill a new info structure for new interceptor. + // Save his reference and try to get an additional URL/pattern list from him. + // If no list exist register these interceptor for all dispatch events with "*"! + InterceptorInfo aInfo; + + aInfo.xInterceptor = xInterceptor; + css::uno::Reference< css::frame::XInterceptorInfo > xInfo(xInterceptor, css::uno::UNO_QUERY); + if (xInfo.is()) + aInfo.lURLPattern = xInfo->getInterceptedURLs(); + else + aInfo.lURLPattern = { "*" }; + + // SAFE { + SolarMutexClearableGuard aWriteLock; + + // a) no interceptor at all - set this instance as master for given interceptor + // and set our slave as its slave - and put this interceptor to the list. + // Its place there doesn't matter. Because this list is currently empty. + if (m_lInterceptionRegs.empty()) + { + xInterceptor->setMasterDispatchProvider(xThis ); + xInterceptor->setSlaveDispatchProvider (m_xSlave); + m_lInterceptionRegs.push_back(aInfo); + } + + // b) OK - there is at least one interceptor already registered. + // It's slave and it's master must be valid references ... + // because we created it. + + // insert it before any other existing interceptor - means at the beginning of our list. + else + { + css::uno::Reference< css::frame::XDispatchProvider > xSlaveD = m_lInterceptionRegs.begin()->xInterceptor; + css::uno::Reference< css::frame::XDispatchProviderInterceptor > xSlaveI (xSlaveD , css::uno::UNO_QUERY); + + xInterceptor->setMasterDispatchProvider(xThis ); + xInterceptor->setSlaveDispatchProvider (xSlaveD ); + xSlaveI->setMasterDispatchProvider (aInfo.xInterceptor); + + m_lInterceptionRegs.push_front(aInfo); + } + + css::uno::Reference< css::frame::XFrame > xOwner(m_xOwnerWeak.get(), css::uno::UNO_QUERY); + + aWriteLock.clear(); + // } SAFE + + // Don't forget to send a frame action event "context changed". + // Any cached dispatch objects must be validated now! + if (xOwner.is()) + xOwner->contextChanged(); +} + +void SAL_CALL InterceptionHelper::releaseDispatchProviderInterceptor(const css::uno::Reference< css::frame::XDispatchProviderInterceptor >& xInterceptor) +{ + // reject wrong calling of this interface method + css::uno::Reference< css::frame::XDispatchProvider > xThis(this); + if (!xInterceptor.is()) + throw css::uno::RuntimeException("NULL references not allowed as in parameter", xThis); + + // SAFE { + SolarMutexClearableGuard aWriteLock; + + // search this interceptor ... + // If it could be located inside cache - + // use its slave/master relations to update the interception list; + // set empty references for it as new master and slave; + // and release it from out cache. + InterceptorList::iterator pIt = m_lInterceptionRegs.findByReference(xInterceptor); + if (pIt != m_lInterceptionRegs.end()) + { + css::uno::Reference< css::frame::XDispatchProvider > xSlaveD = xInterceptor->getSlaveDispatchProvider(); + css::uno::Reference< css::frame::XDispatchProvider > xMasterD = xInterceptor->getMasterDispatchProvider(); + css::uno::Reference< css::frame::XDispatchProviderInterceptor > xSlaveI (xSlaveD , css::uno::UNO_QUERY); + css::uno::Reference< css::frame::XDispatchProviderInterceptor > xMasterI (xMasterD , css::uno::UNO_QUERY); + + if (xMasterI.is()) + xMasterI->setSlaveDispatchProvider(xSlaveD); + + if (xSlaveI.is()) + { + try + { + xSlaveI->setMasterDispatchProvider(xMasterD); + } + catch (const lang::DisposedException&) + { + TOOLS_WARN_EXCEPTION("fwk.dispatch", + "InterceptionHelper::releaseDispatchProviderInterceptor: " + "xSlaveI is disposed: "); + } + } + + xInterceptor->setSlaveDispatchProvider (css::uno::Reference< css::frame::XDispatchProvider >()); + xInterceptor->setMasterDispatchProvider(css::uno::Reference< css::frame::XDispatchProvider >()); + + m_lInterceptionRegs.erase(pIt); + } + + css::uno::Reference< css::frame::XFrame > xOwner(m_xOwnerWeak.get(), css::uno::UNO_QUERY); + + aWriteLock.clear(); + // } SAFE + + // Don't forget to send a frame action event "context changed". + // Any cached dispatch objects must be validated now! + if (xOwner.is()) + xOwner->contextChanged(); +} + +#define FORCE_DESTRUCTION_OF_INTERCEPTION_CHAIN +void SAL_CALL InterceptionHelper::disposing(const css::lang::EventObject& aEvent) +{ + #ifdef FORCE_DESTRUCTION_OF_INTERCEPTION_CHAIN + // SAFE -> + SolarMutexResettableGuard aReadLock; + + // check call... we accept such disposing calls only from our owner frame. + css::uno::Reference< css::frame::XFrame > xOwner(m_xOwnerWeak.get(), css::uno::UNO_QUERY); + if (aEvent.Source != xOwner) + return; + + // Because every interceptor hold at least one reference to us ... and we destruct this list + // of interception objects ... we should hold ourself alive .-) + css::uno::Reference< css::frame::XDispatchProvider > xThis(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY_THROW); + + // We need a full copy of all currently registered interceptor objects. + // Otherwise we can't iterate over this vector without the risk, that our iterator will be invalid. + // Because this vetor will be influenced by every deregistered interceptor. + InterceptionHelper::InterceptorList aCopy = m_lInterceptionRegs; + + aReadLock.clear(); + // <- SAFE + + for (auto & elem : aCopy) + { + if (elem.xInterceptor.is()) + { + css::uno::Reference< css::frame::XDispatchProviderInterceptor > xInterceptor(elem.xInterceptor, css::uno::UNO_QUERY_THROW); + releaseDispatchProviderInterceptor(xInterceptor); + elem.xInterceptor.clear(); + } + } + + aCopy.clear(); + + #if OSL_DEBUG_LEVEL > 0 + // SAFE -> + aReadLock.reset(); + if (!m_lInterceptionRegs.empty() ) + OSL_FAIL("There are some pending interceptor objects, which seems to be registered during (!) the destruction of a frame."); + aReadLock.clear(); + // <- SAFE + #endif // ODL_DEBUG_LEVEL>0 + + #endif // FORCE_DESTRUCTION_OF_INTERCEPTION_CHAIN +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/isstartmoduledispatch.hxx b/framework/source/dispatch/isstartmoduledispatch.hxx new file mode 100644 index 000000000..a968a36f6 --- /dev/null +++ b/framework/source/dispatch/isstartmoduledispatch.hxx @@ -0,0 +1,32 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/util/URL.hpp> + +namespace framework +{ +inline bool isStartModuleDispatch(css::util::URL const& url) +{ + return url.Complete == ".uno:ShowStartModule"; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/loaddispatcher.cxx b/framework/source/dispatch/loaddispatcher.cxx new file mode 100644 index 000000000..04ea5cf5c --- /dev/null +++ b/framework/source/dispatch/loaddispatcher.cxx @@ -0,0 +1,148 @@ +/* -*- 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 <dispatch/loaddispatcher.hxx> +#include <loadenv/loadenvexception.hxx> +#include <sal/log.hxx> + +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <utility> + +namespace framework{ + +LoadDispatcher::LoadDispatcher(const css::uno::Reference< css::uno::XComponentContext >& xContext , + const css::uno::Reference< css::frame::XFrame >& xOwnerFrame , + OUString sTargetName , + sal_Int32 nSearchFlags) + : m_xOwnerFrame (xOwnerFrame ) + , m_sTarget (std::move(sTargetName )) + , m_nSearchFlags(nSearchFlags) + , m_aLoader (xContext ) +{ +} + +LoadDispatcher::~LoadDispatcher() +{ +} + +void SAL_CALL LoadDispatcher::dispatchWithNotification(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + impl_dispatch( aURL, lArguments, xListener ); +} + +void SAL_CALL LoadDispatcher::dispatch(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments) +{ + impl_dispatch( aURL, lArguments, css::uno::Reference< css::frame::XDispatchResultListener >() ); +} + +css::uno::Any SAL_CALL LoadDispatcher::dispatchWithReturnValue( const css::util::URL& rURL, + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) +{ + return impl_dispatch( rURL, lArguments, css::uno::Reference< css::frame::XDispatchResultListener >()); +} + +void SAL_CALL LoadDispatcher::addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/, + const css::util::URL& /*aURL*/ ) +{ +} + +void SAL_CALL LoadDispatcher::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/, + const css::util::URL& /*aURL*/ ) +{ +} + +css::uno::Any LoadDispatcher::impl_dispatch( const css::util::URL& rURL, + const css::uno::Sequence< css::beans::PropertyValue >& lArguments, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + // Attention: May be nobody outside hold such temp. dispatch object alive (because + // the container in which we resist isn't implemented threadsafe but updated by a timer + // and clear our reference...) we should hold us self alive! + css::uno::Reference< css::uno::XInterface > xThis(static_cast< css::frame::XNotifyingDispatch* >(this), css::uno::UNO_QUERY); + + osl::MutexGuard g(m_mutex); + + // We are the only client of this load env object... but + // may a dispatch request before is still in progress (?!). + // Then we should wait a little bit and block this new request. + // In case we run into the timeout, we should reject this new request + // and return "FAILED" as result. Otherwise we can start this new operation. + if (!m_aLoader.waitWhileLoading(2000)) // => 2 sec. + { + if (xListener.is()) + xListener->dispatchFinished( + css::frame::DispatchResultEvent(xThis, css::frame::DispatchResultState::DONTKNOW, css::uno::Any())); // DONTKNOW? ... not really started ... not really failed :-) + } + + css::uno::Reference< css::frame::XFrame > xBaseFrame(m_xOwnerFrame.get(), css::uno::UNO_QUERY); + if (!xBaseFrame.is() && xListener.is()) + xListener->dispatchFinished( + css::frame::DispatchResultEvent(xThis, css::frame::DispatchResultState::FAILURE, css::uno::Any())); + + // OK ... now the internal loader seems to be usable for new requests + // and our owner frame seems to be valid for such operations. + // Initialize it with all new but needed properties and start the loading. + css::uno::Reference< css::lang::XComponent > xComponent; + try + { + m_aLoader.startLoading( rURL.Complete, lArguments, xBaseFrame, m_sTarget, m_nSearchFlags, LoadEnvFeatures::AllowContentHandler | LoadEnvFeatures::WorkWithUI); + m_aLoader.waitWhileLoading(); // wait for ever! + xComponent = m_aLoader.getTargetComponent(); + + // TODO thinking about asynchronous operations and listener support + } + catch(const LoadEnvException& e) + { + SAL_WARN( + "fwk.dispatch", + "caught LoadEnvException " << +e.m_nID << " \"" << e.m_sMessage + << "\"" + << (e.m_exOriginal.has<css::uno::Exception>() + ? (", " + e.m_exOriginal.getValueTypeName() + " \"" + + e.m_exOriginal.get<css::uno::Exception>().Message + + "\"") + : OUString()) + << " while dispatching <" << rURL.Complete << ">"); + xComponent.clear(); + } + + if (xListener.is()) + { + if (xComponent.is()) + xListener->dispatchFinished( + css::frame::DispatchResultEvent(xThis, css::frame::DispatchResultState::SUCCESS, css::uno::Any())); + else + xListener->dispatchFinished( + css::frame::DispatchResultEvent(xThis, css::frame::DispatchResultState::FAILURE, css::uno::Any())); + } + + // return the model - like loadComponentFromURL() + css::uno::Any aRet; + if ( xComponent.is () ) + aRet <<= xComponent; + + return aRet; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/mailtodispatcher.cxx b/framework/source/dispatch/mailtodispatcher.cxx new file mode 100644 index 000000000..9149b9b40 --- /dev/null +++ b/framework/source/dispatch/mailtodispatcher.cxx @@ -0,0 +1,233 @@ +/* -*- 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 <dispatch/mailtodispatcher.hxx> +#include <services.h> + +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <com/sun/star/system/SystemShellExecuteException.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <utility> + +namespace framework{ + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL MailToDispatcher::getImplementationName() +{ + return "com.sun.star.comp.framework.MailToDispatcher"; +} + +sal_Bool SAL_CALL MailToDispatcher::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL MailToDispatcher::getSupportedServiceNames() +{ + return { SERVICENAME_PROTOCOLHANDLER }; +} + + +/** + @short standard ctor + @descr This initializes a new instance of this class with needed information for work. + + @param rxContext + reference to uno servicemanager for creation of new services +*/ +MailToDispatcher::MailToDispatcher( css::uno::Reference< css::uno::XComponentContext > xContext ) + : m_xContext (std::move( xContext )) +{ +} + +/** + @short standard dtor +*/ +MailToDispatcher::~MailToDispatcher() +{ +} + +/** + @short decide if this dispatch implementation can be used for requested URL or not + @descr A protocol handler is registered for a URL pattern inside configuration and will + be asked by the generic dispatch mechanism inside framework, if he can handle this + special URL which match his registration. He can agree by returning of a valid dispatch + instance or disagree by returning <NULL/>. + We don't create new dispatch instances here really - we return THIS as result to handle it + at the same implementation. +*/ +css::uno::Reference< css::frame::XDispatch > SAL_CALL MailToDispatcher::queryDispatch( const css::util::URL& aURL , + const OUString& /*sTarget*/ , + sal_Int32 /*nFlags*/ ) +{ + css::uno::Reference< css::frame::XDispatch > xDispatcher; + if (aURL.Complete.startsWith("mailto:")) + xDispatcher = this; + return xDispatcher; +} + +/** + @short do the same like dispatch() but for multiple requests at the same time +*/ +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL MailToDispatcher::queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptor ) +{ + sal_Int32 nCount = lDescriptor.getLength(); + css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > lDispatcher( nCount ); + auto lDispatcherRange = asNonConstRange(lDispatcher); + for( sal_Int32 i=0; i<nCount; ++i ) + { + lDispatcherRange[i] = queryDispatch( + lDescriptor[i].FeatureURL, + lDescriptor[i].FrameName, + lDescriptor[i].SearchFlags); + } + return lDispatcher; +} + +/** + @short dispatch URL with arguments + @descr We use threadsafe internal method to do so. It returns a state value - but we ignore it. + Because we don't support status listener notifications here. Status events are not guaranteed - + and we call another service internally which doesn't return any notifications too. + + @param aURL + mail URL which should be executed + @param lArguments + list of optional arguments for this mail request +*/ +void SAL_CALL MailToDispatcher::dispatch( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& /*lArguments*/ ) +{ + // dispatch() is an [oneway] call ... and may our user release his reference to us immediately. + // So we should hold us self alive till this call ends. + css::uno::Reference< css::frame::XNotifyingDispatch > xSelfHold(this); + implts_dispatch(aURL); + // No notification for status listener! +} + +/** + @short dispatch with guaranteed notifications about success + @descr We use threadsafe internal method to do so. Return state of this function will be used + for notification if an optional listener is given. + + @param aURL + mail URL which should be executed + @param lArguments + list of optional arguments for this mail request + @param xListener + reference to a valid listener for state events +*/ +void SAL_CALL MailToDispatcher::dispatchWithNotification( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& /*lArguments*/, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + // This class was designed to die by reference. And if user release his reference to us immediately after calling this method + // we can run into some problems. So we hold us self alive till this method ends. + // Another reason: We can use this reference as source of sending event at the end too. + css::uno::Reference< css::frame::XNotifyingDispatch > xThis(this); + + bool bState = implts_dispatch(aURL); + if (xListener.is()) + { + css::frame::DispatchResultEvent aEvent; + if (bState) + aEvent.State = css::frame::DispatchResultState::SUCCESS; + else + aEvent.State = css::frame::DispatchResultState::FAILURE; + aEvent.Source = xThis; + + xListener->dispatchFinished( aEvent ); + } +} + +/** + @short threadsafe helper for dispatch calls + @descr We support two interfaces for the same process - dispatch URLs. That the reason for this internal + function. It implements the real dispatch operation and returns a state value which inform caller + about success. He can notify listener then by using this return value. + + @param aURL + mail URL which should be executed + + @return <TRUE/> if dispatch could be started successfully + Note: Our internal used shell executor doesn't return any state value - so we must + believe that call was successful. + <FALSE/> if necessary resource couldn't be created or an exception was thrown. +*/ +bool MailToDispatcher::implts_dispatch( const css::util::URL& aURL ) +{ + bool bSuccess = false; + + css::uno::Reference< css::system::XSystemShellExecute > xSystemShellExecute = css::system::SystemShellExecute::create( m_xContext ); + + try + { + // start mail client + // Because there is no notification about success - we use case of + // no detected exception as SUCCESS - FAILED otherwise. + xSystemShellExecute->execute( aURL.Complete, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY ); + bSuccess = true; + } + catch (const css::lang::IllegalArgumentException&) + { + } + catch (const css::system::SystemShellExecuteException&) + { + } + + return bSuccess; +} + +/** + @short add/remove listener for state events + @descr Because we use an external process to forward such mail URLs, and this process doesn't + return any notifications about success or failed state - we don't support such status + listener. We have no status to send. + + @param xListener + reference to a valid listener for state events + @param aURL + URL about listener will be informed, if something occurred +*/ +void SAL_CALL MailToDispatcher::addStatusListener( const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/ , + const css::util::URL& /*aURL*/ ) +{ + // not supported yet +} + +void SAL_CALL MailToDispatcher::removeStatusListener( const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/ , + const css::util::URL& /*aURL*/ ) +{ + // not supported yet +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_MailToDispatcher_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::MailToDispatcher(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/oxt_handler.cxx b/framework/source/dispatch/oxt_handler.cxx new file mode 100644 index 000000000..df956c7ee --- /dev/null +++ b/framework/source/dispatch/oxt_handler.cxx @@ -0,0 +1,175 @@ +/* -*- 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 <dispatch/oxt_handler.hxx> +#include <unotools/mediadescriptor.hxx> + +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/task/XJobExecutor.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <tools/long.hxx> +#include <utility> + +namespace framework{ + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL Oxt_Handler::getImplementationName() +{ + return "com.sun.star.comp.framework.OXTFileHandler"; +} + +sal_Bool SAL_CALL Oxt_Handler::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL Oxt_Handler::getSupportedServiceNames() +{ + return { "com.sun.star.frame.ContentHandler" }; +} + + +/*-************************************************************************************************************ + @short standard ctor + @descr These initialize a new instance of this class with needed information for work. + + @seealso using at owner + + @param "xFactory", reference to service manager for creation of new services + @onerror Show an assertion and do nothing else. + @threadsafe yes +*//*-*************************************************************************************************************/ +Oxt_Handler::Oxt_Handler( css::uno::Reference< css::uno::XComponentContext > xContext ) + : m_xContext (std::move( xContext )) +{ +} + +/*-************************************************************************************************************ + @short standard dtor +*//*-*************************************************************************************************************/ +Oxt_Handler::~Oxt_Handler() +{ +} + +/*-************************************************************************************************************ + @interface css::frame::XDispatch + + @short try to load audio file + @descr This method try to load given audio file by URL and play it. We use vcl/Sound class to do that. + Playing of sound is asynchron every time. + + @attention We must hold us alive by ourself ... because we use async. vcl sound player ... but playing is started + in async interface call "dispatch()" too. And caller forget us immediately. But then our uno ref count + will decreased to 0 and will die. The only solution is to use own reference to our implementation. + But we do it for really started jobs only and release it during call back of vcl. + + @seealso class vcl/Sound + @seealso method implts_PlayerNotify() + + @param "aURL" , URL to dispatch. + @param "lArguments", list of optional arguments. + @onerror We do nothing. + @threadsafe yes +*//*-*************************************************************************************************************/ +void SAL_CALL Oxt_Handler::dispatchWithNotification( const css::util::URL& aURL, + const css::uno::Sequence< css::beans::PropertyValue >& /*lArguments*/, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + std::unique_lock g(m_mutex); + + css::uno::Sequence< css::uno::Any > lParams{ css::uno::Any(aURL.Main) }; + + css::uno::Reference< css::uno::XInterface > xService = m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext( "com.sun.star.deployment.ui.PackageManagerDialog", lParams, m_xContext ); + css::uno::Reference< css::task::XJobExecutor > xExecutable( xService, css::uno::UNO_QUERY ); + if ( xExecutable.is() ) + xExecutable->trigger( OUString() ); + + if ( xListener.is() ) + { + css::frame::DispatchResultEvent aEvent; + aEvent.State = css::frame::DispatchResultState::SUCCESS; + xListener->dispatchFinished( aEvent ); + } +} + +void SAL_CALL Oxt_Handler::dispatch( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) +{ + dispatchWithNotification( aURL, lArguments, css::uno::Reference< css::frame::XDispatchResultListener >() ); +} + +/*-************************************************************************************************************ + @interface css::document::XExtendedFilterDetection + + @short try to detect file (given as argument included in "lDescriptor") + @descr We try to detect, if given file could be handled by this class and is a well known one. + If it is - we return right internal type name - otherwise we return nothing! + So call can search for another detect service and ask him too. + + @attention a) We don't need any mutex here ... because we don't use any member! + b) Don't use internal player instance "m_pPlayer" to detect given sound file! + It's not necessary to do that ... and we can use temp. variable to do the same. + This way is easy - we don't must synchronize it with currently played sounds! + Another reason to do so ... We are a listener on our internal ma_Player object. + If you would call "IsSoundFile()" on this instance, he would call us back and + we make some unnecessary things ... + @param "lDescriptor", description of file to detect + @return Internal type name which match this file ... or nothing if it is unknown. + + @onerror We return nothing. + @threadsafe yes +*//*-*************************************************************************************************************/ +OUString SAL_CALL Oxt_Handler::detect( css::uno::Sequence< css::beans::PropertyValue >& lDescriptor ) +{ + // Our default is "nothing". So we can return it, if detection failed or file type is really unknown. + OUString sTypeName; + + // Analyze given descriptor to find filename or input stream or... + utl::MediaDescriptor aDescriptor( lDescriptor ); + OUString sURL = aDescriptor.getUnpackedValueOrDefault( utl::MediaDescriptor::PROP_URL, OUString() ); + + tools::Long nLength = sURL.getLength(); + if ( ( nLength > 4 ) && sURL.matchIgnoreAsciiCase( ".oxt", nLength-4 ) ) + { + // "IsSoundFile" differs between different "wav" and "au" file versions... + // couldn't return this information... because: it use the OS to detect it! + // I think we can than following ones: + // a) look for given extension of url to map our type decision HARD CODED!!! + // b) return preferred type every time... it's easy :-) + sTypeName = "oxt_OpenOffice_Extension"; + aDescriptor[utl::MediaDescriptor::PROP_TYPENAME] <<= sTypeName; + aDescriptor >> lDescriptor; + } + + // Return our decision. + return sTypeName; +} + +} // namespace framework + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_Oxt_Handler_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::Oxt_Handler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/popupmenudispatcher.cxx b/framework/source/dispatch/popupmenudispatcher.cxx new file mode 100644 index 000000000..3220ceece --- /dev/null +++ b/framework/source/dispatch/popupmenudispatcher.cxx @@ -0,0 +1,269 @@ +/* -*- 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 <dispatch/popupmenudispatcher.hxx> +#include <services.h> +#include <properties.h> + +#include <com/sun/star/frame/XLayoutManager2.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/ui/XUIElement.hpp> + +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +namespace framework{ + +using namespace ::com::sun::star; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; +using namespace ::cppu; +using namespace ::osl; + +PopupMenuDispatcher::PopupMenuDispatcher( + uno::Reference< XComponentContext > xContext ) + : m_xContext (std::move( xContext )) + , m_bAlreadyDisposed ( false ) + , m_bActivateListener ( false ) +{ +} + +PopupMenuDispatcher::~PopupMenuDispatcher() +{ + // Warn programmer if he forgot to dispose this instance. + // We must release all our references ... + // and a dtor isn't the best place to do that! +} + +OUString SAL_CALL PopupMenuDispatcher::getImplementationName() +{ + return "com.sun.star.comp.framework.PopupMenuControllerDispatcher"; +} + +sal_Bool SAL_CALL PopupMenuDispatcher::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL PopupMenuDispatcher::getSupportedServiceNames() +{ + return { SERVICENAME_PROTOCOLHANDLER }; +} + +void SAL_CALL PopupMenuDispatcher::initialize( const css::uno::Sequence< css::uno::Any >& lArguments ) +{ + css::uno::Reference< css::frame::XFrame > xFrame; + + SolarMutexGuard g; + for (int a=0; a<lArguments.getLength(); ++a) + { + if (a==0) + { + lArguments[a] >>= xFrame; + m_xWeakFrame = xFrame; + + m_bActivateListener = true; + uno::Reference< css::frame::XFrameActionListener > xFrameActionListener(this); + xFrame->addFrameActionListener( xFrameActionListener ); + } + } +} + +css::uno::Reference< css::frame::XDispatch > +SAL_CALL PopupMenuDispatcher::queryDispatch( + const css::util::URL& rURL , + const OUString& sTarget , + sal_Int32 nFlags ) +{ + if ( !rURL.Complete.startsWith( "vnd.sun.star.popup:" ) ) + return {}; + + // --- SAFE --- + SolarMutexClearableGuard aGuard; + impl_RetrievePopupControllerQuery(); + if ( !m_xUriRefFactory.is() ) + m_xUriRefFactory = css::uri::UriReferenceFactory::create( m_xContext ); + + css::uno::Reference< css::container::XNameAccess > xPopupCtrlQuery( m_xPopupCtrlQuery ); + aGuard.clear(); + // --- SAFE --- + + if ( !xPopupCtrlQuery.is() ) + return {}; + + css::uno::Reference< css::frame::XDispatch > xDispatch; + + try + { + // Just use the main part of the URL for popup menu controllers + sal_Int32 nSchemePart( 0 ); + OUString aBaseURL( "vnd.sun.star.popup:" ); + OUString aURL( rURL.Complete ); + + nSchemePart = aURL.indexOf( ':' ); + if (( nSchemePart > 0 ) && + ( aURL.getLength() > ( nSchemePart+1 ))) + { + sal_Int32 nQueryPart = aURL.indexOf( '?', nSchemePart ); + if ( nQueryPart > 0 ) + aBaseURL += aURL.subView( nSchemePart+1, nQueryPart-(nSchemePart+1) ); + else if ( nQueryPart == -1 ) + aBaseURL += aURL.subView( nSchemePart+1 ); + } + + css::uno::Reference< css::frame::XDispatchProvider > xDispatchProvider; + + // Find popup menu controller using the base URL + xPopupCtrlQuery->getByName( aBaseURL ) >>= xDispatchProvider; + + // Ask popup menu dispatch provider for dispatch object + if ( xDispatchProvider.is() ) + xDispatch = xDispatchProvider->queryDispatch( rURL, sTarget, nFlags ); + } + catch ( const RuntimeException& ) + { + throw; + } + catch ( const Exception& ) + { + } + return xDispatch; +} + +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL +PopupMenuDispatcher::queryDispatches( + const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptor ) +{ + sal_Int32 nCount = lDescriptor.getLength(); + css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > lDispatcher( nCount ); + auto lDispatcherRange = asNonConstRange(lDispatcher); + for( sal_Int32 i=0; i<nCount; ++i ) + { + lDispatcherRange[i] = queryDispatch( + lDescriptor[i].FeatureURL, + lDescriptor[i].FrameName, + lDescriptor[i].SearchFlags); + } + return lDispatcher; +} + +void SAL_CALL PopupMenuDispatcher::dispatch( const URL& /*aURL*/, const Sequence< PropertyValue >& /*seqProperties*/ ) +{ +} + +void SAL_CALL PopupMenuDispatcher::addStatusListener( const uno::Reference< XStatusListener >& /*xControl*/, + const URL& /*aURL*/ ) +{ +} + +void SAL_CALL PopupMenuDispatcher::removeStatusListener( const uno::Reference< XStatusListener >& /*xControl*/, + const URL& /*aURL*/ ) +{ +} + +void SAL_CALL PopupMenuDispatcher::frameAction( const FrameActionEvent& aEvent ) +{ + SolarMutexGuard g; + if (( aEvent.Action == css::frame::FrameAction_COMPONENT_DETACHING ) || + ( aEvent.Action == css::frame::FrameAction_COMPONENT_ATTACHED )) + { + // Reset query reference to requery it again next time + m_xPopupCtrlQuery.clear(); + } +} + +void SAL_CALL PopupMenuDispatcher::disposing( const EventObject& ) +{ + SolarMutexGuard g; + // Safe impossible cases + SAL_WARN_IF( m_bAlreadyDisposed, "fwk", "MenuDispatcher::disposing(): Object already disposed .. don't call it again!" ); + + if( m_bAlreadyDisposed ) + return; + + m_bAlreadyDisposed = true; + + if ( m_bActivateListener ) + { + uno::Reference< XFrame > xFrame( m_xWeakFrame.get(), UNO_QUERY ); + if ( xFrame.is() ) + { + xFrame->removeFrameActionListener( uno::Reference< XFrameActionListener >(this) ); + m_bActivateListener = false; + } + } + + // Forget our factory. + m_xContext.clear(); +} + +void PopupMenuDispatcher::impl_RetrievePopupControllerQuery() +{ + if ( m_xPopupCtrlQuery.is() ) + return; + + css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager; + css::uno::Reference< css::frame::XFrame > xFrame( m_xWeakFrame ); + + if ( !xFrame.is() ) + return; + + css::uno::Reference< css::beans::XPropertySet > xPropSet( xFrame, css::uno::UNO_QUERY ); + if ( !xPropSet.is() ) + return; + + try + { + xPropSet->getPropertyValue( FRAME_PROPNAME_ASCII_LAYOUTMANAGER ) >>= xLayoutManager; + + if ( xLayoutManager.is() ) + { + css::uno::Reference< css::ui::XUIElement > xMenuBar = xLayoutManager->getElement( "private:resource/menubar/menubar" ); + + m_xPopupCtrlQuery.set( xMenuBar, css::uno::UNO_QUERY ); + } + } + catch ( const css::uno::RuntimeException& ) + { + throw; + } + catch ( const css::uno::Exception& ) + { + } +} + +} // namespace framework + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_PopupMenuDispatcher_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::PopupMenuDispatcher(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/servicehandler.cxx b/framework/source/dispatch/servicehandler.cxx new file mode 100644 index 000000000..14dea0603 --- /dev/null +++ b/framework/source/dispatch/servicehandler.cxx @@ -0,0 +1,260 @@ +/* -*- 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 <dispatch/servicehandler.hxx> +#include <services.h> + +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/task/XJobExecutor.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +#include <tools/diagnose_ex.h> +#include <cppuhelper/supportsservice.hxx> +#include <utility> + +namespace framework{ + +constexpr OUStringLiteral PROTOCOL_VALUE = u"service:"; + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL ServiceHandler::getImplementationName() +{ + return "com.sun.star.comp.framework.ServiceHandler"; +} + +sal_Bool SAL_CALL ServiceHandler::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL ServiceHandler::getSupportedServiceNames() +{ + return { SERVICENAME_PROTOCOLHANDLER }; +} + + +/** + @short standard ctor + @descr This initializes a new instance of this class with needed information for work. + + @param xFactory + reference to uno servicemanager for creation of new services +*/ +ServiceHandler::ServiceHandler( css::uno::Reference< css::uno::XComponentContext > xContext ) + : m_xContext (std::move( xContext )) +{ +} + +/** + @short standard dtor +*/ +ServiceHandler::~ServiceHandler() +{ +} + +/** + @short decide if this dispatch implementation can be used for requested URL or not + @descr A protocol handler is registered for a URL pattern inside configuration and will + be asked by the generic dispatch mechanism inside framework, if he can handle this + special URL which match his registration. He can agree by returning of a valid dispatch + instance or disagree by returning <NULL/>. + We don't create new dispatch instances here really - we return THIS as result to handle it + at the same implementation. +*/ +css::uno::Reference< css::frame::XDispatch > SAL_CALL ServiceHandler::queryDispatch( const css::util::URL& aURL , + const OUString& /*sTarget*/ , + sal_Int32 /*nFlags*/ ) +{ + css::uno::Reference< css::frame::XDispatch > xDispatcher; + if (aURL.Complete.startsWith(PROTOCOL_VALUE)) + xDispatcher = this; + return xDispatcher; +} + +/** + @short do the same like dispatch() but for multiple requests at the same time +*/ +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL ServiceHandler::queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptor ) +{ + sal_Int32 nCount = lDescriptor.getLength(); + css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > lDispatcher( nCount ); + auto lDispatcherRange = asNonConstRange(lDispatcher); + for( sal_Int32 i=0; i<nCount; ++i ) + { + lDispatcherRange[i] = queryDispatch( + lDescriptor[i].FeatureURL, + lDescriptor[i].FrameName, + lDescriptor[i].SearchFlags); + } + return lDispatcher; +} + +/** + @short dispatch URL with arguments + @descr We use threadsafe internal method to do so. It returns a state value - but we ignore it. + Because we don't support status listener notifications here. + + @param aURL + uno URL which should be executed + @param lArguments + list of optional arguments for this request +*/ +void SAL_CALL ServiceHandler::dispatch( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& /*lArguments*/ ) +{ + // dispatch() is an [oneway] call ... and may our user release his reference to us immediately. + // So we should hold us self alive till this call ends. + css::uno::Reference< css::frame::XNotifyingDispatch > xSelfHold(this); + implts_dispatch(aURL); + // No notification for status listener! +} + +/** + @short dispatch with guaranteed notifications about success + @descr We use threadsafe internal method to do so. Return state of this function will be used + for notification if an optional listener is given. + + @param aURL + uno URL which should be executed + @param lArguments + list of optional arguments for this request + @param xListener + optional listener for state events +*/ +void SAL_CALL ServiceHandler::dispatchWithNotification( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& /*lArguments*/, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + // This class was designed to die by reference. And if user release his reference to us immediately after calling this method + // we can run into some problems. So we hold us self alive till this method ends. + // Another reason: We can use this reference as source of sending event at the end too. + css::uno::Reference< css::frame::XNotifyingDispatch > xThis(this); + + css::uno::Reference< css::uno::XInterface > xService = implts_dispatch(aURL); + if (xListener.is()) + { + css::frame::DispatchResultEvent aEvent; + if (xService.is()) + aEvent.State = css::frame::DispatchResultState::SUCCESS; + else + aEvent.State = css::frame::DispatchResultState::FAILURE; + aEvent.Result <<= xService; // may NULL for state=FAILED! + aEvent.Source = xThis; + + xListener->dispatchFinished( aEvent ); + } +} + +/** + @short threadsafe helper for dispatch calls + @descr We support two interfaces for the same process - dispatch URLs. That the reason for this internal + function. It implements the real dispatch operation and returns a state value which inform caller + about success. He can notify listener then by using this return value. + + @param aURL + uno URL which should be executed + + @return <NULL/> if requested service couldn't be created successfully; + a valid reference otherwise. This return value can be used to indicate, + if dispatch was successful. +*/ +css::uno::Reference< css::uno::XInterface > ServiceHandler::implts_dispatch( const css::util::URL& aURL ) +{ + // extract service name and may optional given parameters from given URL + // and use it to create and start the component + OUString sServiceAndArguments = aURL.Complete.copy(PROTOCOL_VALUE.getLength()); + OUString sServiceName; + OUString sArguments; + + sal_Int32 nArgStart = sServiceAndArguments.indexOf('?'); + if (nArgStart!=-1) + { + sServiceName = sServiceAndArguments.copy(0,nArgStart); + ++nArgStart; // ignore '?'! + sArguments = sServiceAndArguments.copy(nArgStart); + } + else + { + sServiceName = sServiceAndArguments; + } + + if (sServiceName.isEmpty()) + return css::uno::Reference< css::uno::XInterface >(); + + // If a service doesn't support an optional job executor interface - he can't get + // any given parameters! + // Because we can't know if we must call createInstanceWithArguments() or XJobExecutor::trigger() ... + + css::uno::Reference< css::uno::XInterface > xService; + try + { + // => a) a service starts running inside his own ctor and we create it only + xService = m_xContext->getServiceManager()->createInstanceWithContext(sServiceName, m_xContext); + // or b) he implements the right interface and starts there (may with optional parameters) + css::uno::Reference< css::task::XJobExecutor > xExecutable(xService, css::uno::UNO_QUERY); + if (xExecutable.is()) + xExecutable->trigger(sArguments); + } + // ignore all errors - inclusive runtime errors! + // E.g. a script based service (written in Python) could not be executed + // because it contains syntax errors, which was detected at runtime... + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("fwk.dispatch", "ignored"); + xService.clear(); + } + + return xService; +} + +/** + @short add/remove listener for state events + @descr We use an internal container to hold such registered listener. This container lives if we live. + And if call pass registration as non breakable transaction - we can accept the request without + any explicit lock. Because we share our mutex with this container. + + @param xListener + reference to a valid listener for state events + @param aURL + URL about listener will be informed, if something occurred +*/ +void SAL_CALL ServiceHandler::addStatusListener( const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/ , + const css::util::URL& /*aURL*/ ) +{ + // not supported yet +} + +void SAL_CALL ServiceHandler::removeStatusListener( const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/ , + const css::util::URL& /*aURL*/ ) +{ + // not supported yet +} + +} // namespace framework + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_ServiceHandler_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::ServiceHandler(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/startmoduledispatcher.cxx b/framework/source/dispatch/startmoduledispatcher.cxx new file mode 100644 index 000000000..39ac70ee1 --- /dev/null +++ b/framework/source/dispatch/startmoduledispatcher.cxx @@ -0,0 +1,147 @@ +/* -*- 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 <dispatch/startmoduledispatcher.hxx> + +#include <framework/framelistanalyzer.hxx> +#include <targets.h> +#include "isstartmoduledispatch.hxx" + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/StartModule.hpp> + +#include <unotools/moduleoptions.hxx> +#include <utility> + +namespace framework{ + +#ifdef fpf + #error "Who uses \"fpf\" as define. It will overwrite my namespace alias ..." +#endif + +StartModuleDispatcher::StartModuleDispatcher(css::uno::Reference< css::uno::XComponentContext > xContext) + : m_xContext (std::move(xContext )) +{ +} + +StartModuleDispatcher::~StartModuleDispatcher() +{ +} + +void SAL_CALL StartModuleDispatcher::dispatch(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments) +{ + dispatchWithNotification(aURL, lArguments, css::uno::Reference< css::frame::XDispatchResultListener >()); +} + +void SAL_CALL StartModuleDispatcher::dispatchWithNotification(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& /*lArguments*/, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + ::sal_Int16 nResult = css::frame::DispatchResultState::DONTKNOW; + if (isStartModuleDispatch(aURL)) + { + nResult = css::frame::DispatchResultState::FAILURE; + if (implts_isBackingModePossible ()) + { + implts_establishBackingMode (); + nResult = css::frame::DispatchResultState::SUCCESS; + } + } + + implts_notifyResultListener(xListener, nResult, css::uno::Any()); +} + +css::uno::Sequence< ::sal_Int16 > SAL_CALL StartModuleDispatcher::getSupportedCommandGroups() +{ + return css::uno::Sequence< ::sal_Int16 >(); +} + +css::uno::Sequence< css::frame::DispatchInformation > SAL_CALL StartModuleDispatcher::getConfigurableDispatchInformation(::sal_Int16 /*nCommandGroup*/) +{ + return css::uno::Sequence< css::frame::DispatchInformation >(); +} + +void SAL_CALL StartModuleDispatcher::addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/, + const css::util::URL& /*aURL*/ ) +{ +} + +void SAL_CALL StartModuleDispatcher::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& /*xListener*/, + const css::util::URL& /*aURL*/ ) +{ +} + +bool StartModuleDispatcher::implts_isBackingModePossible() +{ + if ( ! SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::STARTMODULE)) + return false; + + css::uno::Reference< css::frame::XFramesSupplier > xDesktop = + css::frame::Desktop::create( m_xContext ); + + FrameListAnalyzer aCheck( + xDesktop, + css::uno::Reference< css::frame::XFrame >(), + FrameAnalyzerFlags::Help | FrameAnalyzerFlags::BackingComponent); + + bool bIsPossible = false; + + if ( ! aCheck.m_xBackingComponent.is() + && aCheck.m_lOtherVisibleFrames.empty() ) + { + bIsPossible = true; + } + + return bIsPossible; +} + +void StartModuleDispatcher::implts_establishBackingMode() +{ + css::uno::Reference< css::frame::XDesktop2> xDesktop = css::frame::Desktop::create( m_xContext ); + css::uno::Reference< css::frame::XFrame > xFrame = xDesktop->findFrame(SPECIALTARGET_BLANK, 0); + css::uno::Reference< css::awt::XWindow > xContainerWindow = xFrame->getContainerWindow(); + + css::uno::Reference< css::frame::XController > xStartModule = css::frame::StartModule::createWithParentWindow(m_xContext, xContainerWindow); + css::uno::Reference< css::awt::XWindow > xComponentWindow(xStartModule, css::uno::UNO_QUERY); + xFrame->setComponent(xComponentWindow, xStartModule); + xStartModule->attachFrame(xFrame); + xContainerWindow->setVisible(true); +} + +void StartModuleDispatcher::implts_notifyResultListener(const css::uno::Reference< css::frame::XDispatchResultListener >& xListener, + ::sal_Int16 nState , + const css::uno::Any& aResult ) +{ + if ( ! xListener.is()) + return; + + css::frame::DispatchResultEvent aEvent( + css::uno::Reference< css::uno::XInterface >(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY), + nState, + aResult); + + xListener->dispatchFinished(aEvent); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/systemexec.cxx b/framework/source/dispatch/systemexec.cxx new file mode 100644 index 000000000..c7b377842 --- /dev/null +++ b/framework/source/dispatch/systemexec.cxx @@ -0,0 +1,156 @@ +/* -*- 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 <dispatch/systemexec.hxx> +#include <services.h> + +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <com/sun/star/util/PathSubstitution.hpp> +#include <com/sun/star/util/XStringSubstitution.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <utility> + +namespace framework{ + +constexpr OUStringLiteral PROTOCOL_VALUE = u"systemexecute:"; + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL SystemExec::getImplementationName() +{ + return "com.sun.star.comp.framework.SystemExecute"; +} + +sal_Bool SAL_CALL SystemExec::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL SystemExec::getSupportedServiceNames() +{ + return { SERVICENAME_PROTOCOLHANDLER }; +} + +SystemExec::SystemExec( css::uno::Reference< css::uno::XComponentContext > xContext ) + : m_xContext (std::move( xContext )) +{ +} + +SystemExec::~SystemExec() +{ +} + +css::uno::Reference< css::frame::XDispatch > SAL_CALL SystemExec::queryDispatch( const css::util::URL& aURL , + const OUString&, + sal_Int32 ) +{ + css::uno::Reference< css::frame::XDispatch > xDispatcher; + if (aURL.Complete.startsWith(PROTOCOL_VALUE)) + xDispatcher = this; + return xDispatcher; +} + +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL SystemExec::queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptor ) +{ + sal_Int32 nCount = lDescriptor.getLength(); + css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > lDispatcher( nCount ); + auto lDispatcherRange = asNonConstRange(lDispatcher); + for( sal_Int32 i=0; i<nCount; ++i ) + { + lDispatcherRange[i] = queryDispatch( + lDescriptor[i].FeatureURL, + lDescriptor[i].FrameName, + lDescriptor[i].SearchFlags); + } + return lDispatcher; +} + +void SAL_CALL SystemExec::dispatch( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) +{ + dispatchWithNotification(aURL, lArguments, css::uno::Reference< css::frame::XDispatchResultListener >()); +} + +void SAL_CALL SystemExec::dispatchWithNotification( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >&, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + // convert "systemexec:file:///c:/temp/test.html" => "file:///c:/temp/test.html" + sal_Int32 c = aURL.Complete.getLength()-PROTOCOL_VALUE.getLength(); + if (c<1) // we don't check for valid URLs here! The system will show an error message ... + { + impl_notifyResultListener(xListener, css::frame::DispatchResultState::FAILURE); + return; + } + OUString sSystemURLWithVariables = aURL.Complete.copy(PROTOCOL_VALUE.getLength(), c); + + // TODO check security settings ... + + try + { + css::uno::Reference< css::util::XStringSubstitution > xPathSubst( css::util::PathSubstitution::create(m_xContext) ); + + OUString sSystemURL = xPathSubst->substituteVariables(sSystemURLWithVariables, true); // sal_True force an exception if unknown variables exists ! + + css::uno::Reference< css::system::XSystemShellExecute > xShell = css::system::SystemShellExecute::create( m_xContext ); + + xShell->execute(sSystemURL, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY); + impl_notifyResultListener(xListener, css::frame::DispatchResultState::SUCCESS); + } + catch(const css::uno::Exception&) + { + impl_notifyResultListener(xListener, css::frame::DispatchResultState::FAILURE); + } +} + +void SAL_CALL SystemExec::addStatusListener( const css::uno::Reference< css::frame::XStatusListener >&, + const css::util::URL& ) +{ + // not supported yet +} + +void SAL_CALL SystemExec::removeStatusListener( const css::uno::Reference< css::frame::XStatusListener >&, + const css::util::URL& ) +{ + // not supported yet +} + +void SystemExec::impl_notifyResultListener(const css::uno::Reference< css::frame::XDispatchResultListener >& xListener, + const sal_Int16 nState ) +{ + if (xListener.is()) + { + css::frame::DispatchResultEvent aEvent; + aEvent.State = nState; + xListener->dispatchFinished(aEvent); + } +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_SystemExecute_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::SystemExec(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/dispatch/windowcommanddispatch.cxx b/framework/source/dispatch/windowcommanddispatch.cxx new file mode 100644 index 000000000..abad29dc7 --- /dev/null +++ b/framework/source/dispatch/windowcommanddispatch.cxx @@ -0,0 +1,158 @@ +/* -*- 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 <dispatch/windowcommanddispatch.hxx> +#include <targets.h> + +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> + +#include <utility> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> +#include <vcl/commandevent.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +namespace framework{ + +WindowCommandDispatch::WindowCommandDispatch(css::uno::Reference< css::uno::XComponentContext > xContext , + const css::uno::Reference< css::frame::XFrame >& xFrame) + : m_xContext (std::move(xContext )) + , m_xFrame (xFrame ) + , m_xWindow (xFrame->getContainerWindow()) +{ + impl_startListening(); +} + +WindowCommandDispatch::~WindowCommandDispatch() +{ + impl_stopListening(); + m_xContext.clear(); +} + +void WindowCommandDispatch::impl_startListening() +{ + std::unique_lock aReadLock(m_mutex); + css::uno::Reference< css::awt::XWindow > xWindow( m_xWindow.get(), css::uno::UNO_QUERY ); + aReadLock.unlock(); + + if ( ! xWindow.is()) + return; + + { + SolarMutexGuard aSolarLock; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + if ( ! pWindow) + return; + + pWindow->AddEventListener( LINK(this, WindowCommandDispatch, impl_notifyCommand) ); + } +} + +void WindowCommandDispatch::impl_stopListening() +{ + std::unique_lock aReadLock(m_mutex); + css::uno::Reference< css::awt::XWindow > xWindow( m_xWindow.get(), css::uno::UNO_QUERY ); + aReadLock.unlock(); + + if (!xWindow.is()) + return; + + { + SolarMutexGuard aSolarLock; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + if (!pWindow) + return; + + pWindow->RemoveEventListener( LINK(this, WindowCommandDispatch, impl_notifyCommand) ); + + m_xWindow.clear(); + } +} + +IMPL_LINK(WindowCommandDispatch, impl_notifyCommand, VclWindowEvent&, rEvent, void) +{ + if (rEvent.GetId() == VclEventId::ObjectDying) + { + impl_stopListening(); + return; + } + if (rEvent.GetId() != VclEventId::WindowCommand) + return; + + const CommandEvent* pCommand = static_cast<CommandEvent*>(rEvent.GetData()); + if (pCommand->GetCommand() != CommandEventId::ShowDialog) + return; + + const CommandDialogData* pData = pCommand->GetDialogData(); + if ( ! pData) + return; + + const ShowDialogId nCommand = pData->GetDialogId(); + OUString sCommand; + + switch (nCommand) + { + case ShowDialogId::Preferences : + sCommand = ".uno:OptionsTreeDialog"; + break; + + case ShowDialogId::About : + sCommand = ".uno:About"; + break; + + default : + return; + } + + // ignore all errors here. It's clicking a menu entry only ... + // The user will try it again, in case nothing happens .-) + try + { + // SYNCHRONIZED -> + std::unique_lock aReadLock(m_mutex); + css::uno::Reference< css::frame::XDispatchProvider > xProvider(m_xFrame.get(), css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::uno::XComponentContext > xContext = m_xContext; + aReadLock.unlock(); + // <- SYNCHRONIZED + + // check provider ... we know it's weak reference only + if ( ! xProvider.is()) + return; + + css::uno::Reference< css::util::XURLTransformer > xParser(css::util::URLTransformer::create(xContext)); + css::util::URL aCommand; + aCommand.Complete = sCommand; + xParser->parseStrict(aCommand); + + css::uno::Reference< css::frame::XDispatch > xDispatch = xProvider->queryDispatch(aCommand, SPECIALTARGET_SELF, 0); + if (xDispatch.is()) + xDispatch->dispatch(aCommand, css::uno::Sequence< css::beans::PropertyValue >()); + } + catch(const css::uno::Exception&) + {} +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/classes/actiontriggercontainer.cxx b/framework/source/fwe/classes/actiontriggercontainer.cxx new file mode 100644 index 000000000..360223e6e --- /dev/null +++ b/framework/source/fwe/classes/actiontriggercontainer.cxx @@ -0,0 +1,134 @@ +/* -*- 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 <classes/actiontriggercontainer.hxx> +#include <classes/actiontriggerpropertyset.hxx> +#include <classes/actiontriggerseparatorpropertyset.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/typeprovider.hxx> + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; + +namespace framework +{ + +ActionTriggerContainer::ActionTriggerContainer() +{ +} + +ActionTriggerContainer::~ActionTriggerContainer() +{ +} + +// XInterface +Any SAL_CALL ActionTriggerContainer::queryInterface( const Type& aType ) +{ + Any a = ::cppu::queryInterface( + aType , + static_cast< XMultiServiceFactory* >(this), + static_cast< XServiceInfo* >(this), + static_cast< XTypeProvider* >(this)); + + if( a.hasValue() ) + { + return a; + } + + return PropertySetContainer::queryInterface( aType ); +} + +void ActionTriggerContainer::acquire() noexcept +{ + PropertySetContainer::acquire(); +} + +void ActionTriggerContainer::release() noexcept +{ + PropertySetContainer::release(); +} + +// XMultiServiceFactory +Reference< XInterface > SAL_CALL ActionTriggerContainer::createInstance( const OUString& aServiceSpecifier ) +{ + if ( aServiceSpecifier == SERVICENAME_ACTIONTRIGGER ) + return static_cast<OWeakObject *>( new ActionTriggerPropertySet()); + else if ( aServiceSpecifier == SERVICENAME_ACTIONTRIGGERCONTAINER ) + return static_cast<OWeakObject *>( new ActionTriggerContainer()); + else if ( aServiceSpecifier == SERVICENAME_ACTIONTRIGGERSEPARATOR ) + return static_cast<OWeakObject *>( new ActionTriggerSeparatorPropertySet()); + else + throw css::uno::RuntimeException("Unknown service specifier!", static_cast<OWeakObject *>(this) ); +} + +Reference< XInterface > SAL_CALL ActionTriggerContainer::createInstanceWithArguments( const OUString& ServiceSpecifier, const Sequence< Any >& /*Arguments*/ ) +{ + return createInstance( ServiceSpecifier ); +} + +Sequence< OUString > SAL_CALL ActionTriggerContainer::getAvailableServiceNames() +{ + Sequence< OUString > aSeq{ SERVICENAME_ACTIONTRIGGER, + SERVICENAME_ACTIONTRIGGERCONTAINER, + SERVICENAME_ACTIONTRIGGERSEPARATOR }; + + return aSeq; +} + +// XServiceInfo +OUString SAL_CALL ActionTriggerContainer::getImplementationName() +{ + return IMPLEMENTATIONNAME_ACTIONTRIGGERCONTAINER; +} + +sal_Bool SAL_CALL ActionTriggerContainer::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL ActionTriggerContainer::getSupportedServiceNames() +{ + Sequence< OUString > seqServiceNames { SERVICENAME_ACTIONTRIGGERCONTAINER }; + return seqServiceNames; +} + +// XTypeProvider +Sequence< Type > SAL_CALL ActionTriggerContainer::getTypes() +{ + // Create a static typecollection ... + static ::cppu::OTypeCollection ourTypeCollection( + cppu::UnoType<XMultiServiceFactory>::get(), + cppu::UnoType<XIndexContainer>::get(), + cppu::UnoType<XServiceInfo>::get(), + cppu::UnoType<XTypeProvider>::get()); + + return ourTypeCollection.getTypes(); +} + +Sequence< sal_Int8 > SAL_CALL ActionTriggerContainer::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/classes/actiontriggerpropertyset.cxx b/framework/source/fwe/classes/actiontriggerpropertyset.cxx new file mode 100644 index 000000000..4592174df --- /dev/null +++ b/framework/source/fwe/classes/actiontriggerpropertyset.cxx @@ -0,0 +1,371 @@ +/* -*- 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 <classes/actiontriggerpropertyset.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <cppuhelper/proptypehlp.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <vcl/svapp.hxx> + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::lang; +using namespace com::sun::star::awt; + +//struct SAL_DLLPUBLIC_IMPORT ::cppu::OBroadcastHelperVar< OMultiTypeInterfaceContainerHelper, OMultiTypeInterfaceContainerHelper::keyType >; + +namespace { + +// Handles for properties +// (PLEASE SORT THIS FIELD, IF YOU ADD NEW PROPERTIES!) +// We use an enum to define these handles, to use all numbers from 0 to nn and +// if you add someone, you don't must control this! +// But don't forget to change values of follow defines, if you do something with this enum! +enum EPROPERTIES +{ + HANDLE_COMMANDURL, + HANDLE_HELPURL, + HANDLE_IMAGE, + HANDLE_SUBCONTAINER, + HANDLE_TEXT, + PROPERTYCOUNT +}; + +} + +namespace framework +{ + +ActionTriggerPropertySet::ActionTriggerPropertySet() + : OBroadcastHelper ( m_aMutex ) + , OPropertySetHelper ( *static_cast< OBroadcastHelper * >(this) ) +{ +} + +ActionTriggerPropertySet::~ActionTriggerPropertySet() +{ +} + +// XInterface +Any SAL_CALL ActionTriggerPropertySet::queryInterface( const Type& aType ) +{ + Any a = ::cppu::queryInterface( + aType, + static_cast< XServiceInfo* >(this), + static_cast< XTypeProvider* >(this)); + + if( a.hasValue() ) + return a; + else + { + a = OPropertySetHelper::queryInterface( aType ); + + if( a.hasValue() ) + return a; + } + + return OWeakObject::queryInterface( aType ); +} + +void SAL_CALL ActionTriggerPropertySet::acquire() noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL ActionTriggerPropertySet::release() noexcept +{ + OWeakObject::release(); +} + +// XServiceInfo +OUString SAL_CALL ActionTriggerPropertySet::getImplementationName() +{ + return IMPLEMENTATIONNAME_ACTIONTRIGGER; +} + +sal_Bool SAL_CALL ActionTriggerPropertySet::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL ActionTriggerPropertySet::getSupportedServiceNames() +{ + Sequence<OUString> seqServiceNames { SERVICENAME_ACTIONTRIGGER }; + return seqServiceNames; +} + +// XTypeProvider +Sequence< Type > SAL_CALL ActionTriggerPropertySet::getTypes() +{ + // Create a static typecollection ... + static ::cppu::OTypeCollection ourTypeCollection( + cppu::UnoType<XPropertySet>::get(), + cppu::UnoType<XFastPropertySet>::get(), + cppu::UnoType<XMultiPropertySet>::get(), + cppu::UnoType<XServiceInfo>::get(), + cppu::UnoType<XTypeProvider>::get()); + + + return ourTypeCollection.getTypes(); +} + +Sequence< sal_Int8 > SAL_CALL ActionTriggerPropertySet::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +sal_Bool SAL_CALL ActionTriggerPropertySet::convertFastPropertyValue( + Any& aConvertedValue, + Any& aOldValue, + sal_Int32 nHandle, + const Any& aValue ) +{ + // Check, if value of property will changed in method "setFastPropertyValue_NoBroadcast()". + // Return sal_True, if changed - else return sal_False. + // Attention: Method "impl_tryToChangeProperty()" can throw the IllegalArgumentException !!! + // Initialize return value with sal_False !!! + // (Handle can be invalid) + bool bReturn = false; + + switch( nHandle ) + { + case HANDLE_COMMANDURL: + bReturn = impl_tryToChangeProperty( m_aCommandURL, aValue, aOldValue, aConvertedValue ); + break; + + case HANDLE_HELPURL: + bReturn = impl_tryToChangeProperty( m_aHelpURL, aValue, aOldValue, aConvertedValue ); + break; + + case HANDLE_IMAGE: + bReturn = impl_tryToChangeProperty( m_xBitmap, aValue, aOldValue, aConvertedValue ); + break; + + case HANDLE_SUBCONTAINER: + bReturn = impl_tryToChangeProperty( m_xActionTriggerContainer, aValue, aOldValue, aConvertedValue ); + break; + + case HANDLE_TEXT: + bReturn = impl_tryToChangeProperty( m_aText, aValue, aOldValue, aConvertedValue ); + break; + } + + // Return state of operation. + return bReturn; +} + +void SAL_CALL ActionTriggerPropertySet::setFastPropertyValue_NoBroadcast( + sal_Int32 nHandle, const Any& aValue ) +{ + SolarMutexGuard aGuard; + + // Search for right handle ... and try to set property value. + switch( nHandle ) + { + case HANDLE_COMMANDURL: + aValue >>= m_aCommandURL; + break; + + case HANDLE_HELPURL: + aValue >>= m_aHelpURL; + break; + + case HANDLE_IMAGE: + aValue >>= m_xBitmap; + break; + + case HANDLE_SUBCONTAINER: + aValue >>= m_xActionTriggerContainer; + break; + + case HANDLE_TEXT: + aValue >>= m_aText; + break; + } +} + +void SAL_CALL ActionTriggerPropertySet::getFastPropertyValue( + Any& aValue, sal_Int32 nHandle ) const +{ + SolarMutexGuard aGuard; + + // Search for right handle ... and try to get property value. + switch( nHandle ) + { + case HANDLE_COMMANDURL: + aValue <<= m_aCommandURL; + break; + + case HANDLE_HELPURL: + aValue <<= m_aHelpURL; + break; + + case HANDLE_IMAGE: + aValue <<= m_xBitmap; + break; + + case HANDLE_SUBCONTAINER: + aValue <<= m_xActionTriggerContainer; + break; + + case HANDLE_TEXT: + aValue <<= m_aText; + break; + } +} + +::cppu::IPropertyArrayHelper& SAL_CALL ActionTriggerPropertySet::getInfoHelper() +{ + // Define static member to give structure of properties to baseclass "OPropertySetHelper". + // "impl_getStaticPropertyDescriptor" is a non exported and static function, who will define a static propertytable. + // "true" say: Table is sorted by name. + static OPropertyArrayHelper ourInfoHelper( impl_getStaticPropertyDescriptor(), true ); + + return ourInfoHelper; +} + +Reference< XPropertySetInfo > SAL_CALL ActionTriggerPropertySet::getPropertySetInfo() +{ + // Create structure of propertysetinfo for baseclass "OPropertySetHelper". + // (Use method "getInfoHelper()".) + static Reference< XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) ); + + return xInfo; +} + +Sequence< Property > ActionTriggerPropertySet::impl_getStaticPropertyDescriptor() +{ + return + { + Property( "CommandURL" , HANDLE_COMMANDURL , cppu::UnoType<OUString>::get(), PropertyAttribute::TRANSIENT ), + Property( "HelpURL" , HANDLE_HELPURL , cppu::UnoType<OUString>::get(), PropertyAttribute::TRANSIENT ), + Property( "Image" , HANDLE_IMAGE , cppu::UnoType<XBitmap>::get(), PropertyAttribute::TRANSIENT ), + Property( "SubContainer" , HANDLE_SUBCONTAINER , cppu::UnoType<OUString>::get(), PropertyAttribute::TRANSIENT ), + Property( "Text" , HANDLE_TEXT , cppu::UnoType<XInterface>::get(), PropertyAttribute::TRANSIENT ) + }; +} + +bool ActionTriggerPropertySet::impl_tryToChangeProperty( + const OUString& sCurrentValue , + const Any& aNewValue , + Any& aOldValue , + Any& aConvertedValue ) +{ + // Set default return value if method failed. + bool bReturn = false; + // Get new value from any. + // IllegalArgumentException() can be thrown! + OUString sValue; + convertPropertyValue( sValue, aNewValue ); + + // If value change ... + if( sValue != sCurrentValue ) + { + // ... set information of change. + aOldValue <<= sCurrentValue; + aConvertedValue <<= sValue; + // Return OK - "value will be change ..." + bReturn = true; + } + else + { + // ... clear information of return parameter! + aOldValue.clear (); + aConvertedValue.clear (); + // Return NOTHING - "value will not be change ..." + bReturn = false; + } + + return bReturn; +} + +bool ActionTriggerPropertySet::impl_tryToChangeProperty( + const Reference< XBitmap >& aCurrentValue , + const Any& aNewValue , + Any& aOldValue , + Any& aConvertedValue ) +{ + // Set default return value if method failed. + bool bReturn = false; + // Get new value from any. + // IllegalArgumentException() can be thrown! + Reference< XBitmap > aValue; + convertPropertyValue( aValue, aNewValue ); + + // If value change ... + if( aValue != aCurrentValue ) + { + // ... set information of change. + aOldValue <<= aCurrentValue; + aConvertedValue <<= aValue; + // Return OK - "value will be change ..." + bReturn = true; + } + else + { + // ... clear information of return parameter! + aOldValue.clear (); + aConvertedValue.clear (); + // Return NOTHING - "value will not be change ..." + bReturn = false; + } + + return bReturn; +} + +bool ActionTriggerPropertySet::impl_tryToChangeProperty( + const Reference< XInterface >& aCurrentValue , + const Any& aNewValue , + Any& aOldValue , + Any& aConvertedValue ) +{ + // Set default return value if method failed. + bool bReturn = false; + // Get new value from any. + // IllegalArgumentException() can be thrown! + Reference< XInterface > aValue; + convertPropertyValue( aValue, aNewValue ); + + // If value change ... + if( aValue != aCurrentValue ) + { + // ... set information of change. + aOldValue <<= aCurrentValue; + aConvertedValue <<= aValue; + // Return OK - "value will be change ..." + bReturn = true; + } + else + { + // ... clear information of return parameter! + aOldValue.clear (); + aConvertedValue.clear (); + // Return NOTHING - "value will not be change ..." + bReturn = false; + } + + return bReturn; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/classes/actiontriggerseparatorpropertyset.cxx b/framework/source/fwe/classes/actiontriggerseparatorpropertyset.cxx new file mode 100644 index 000000000..104d765f5 --- /dev/null +++ b/framework/source/fwe/classes/actiontriggerseparatorpropertyset.cxx @@ -0,0 +1,245 @@ +/* -*- 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 <classes/actiontriggerseparatorpropertyset.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <cppuhelper/proptypehlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <vcl/svapp.hxx> + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::lang; +using namespace com::sun::star::awt; + +namespace { + +// Handles for properties +// (PLEASE SORT THIS FIELD, IF YOU ADD NEW PROPERTIES!) +// We use an enum to define these handles, to use all numbers from 0 to nn and +// if you add someone, you don't must control this! +// But don't forget to change values of follow defines, if you do something with this enum! +enum EPROPERTIES +{ + HANDLE_TYPE, + PROPERTYCOUNT +}; + +} + +namespace framework +{ + +ActionTriggerSeparatorPropertySet::ActionTriggerSeparatorPropertySet() + : OBroadcastHelper ( m_aMutex ) + , OPropertySetHelper ( *static_cast< OBroadcastHelper * >(this) ) + , m_nSeparatorType( 0 ) +{ +} + +ActionTriggerSeparatorPropertySet::~ActionTriggerSeparatorPropertySet() +{ +} + +// XInterface +Any SAL_CALL ActionTriggerSeparatorPropertySet::queryInterface( const Type& aType ) +{ + Any a = ::cppu::queryInterface( + aType, + static_cast< XServiceInfo* >(this), + static_cast< XTypeProvider* >(this)); + + if( a.hasValue() ) + return a; + else + { + a = OPropertySetHelper::queryInterface( aType ); + + if( a.hasValue() ) + return a; + } + + return OWeakObject::queryInterface( aType ); +} + +void ActionTriggerSeparatorPropertySet::acquire() noexcept +{ + OWeakObject::acquire(); +} + +void ActionTriggerSeparatorPropertySet::release() noexcept +{ + OWeakObject::release(); +} + +// XServiceInfo +OUString SAL_CALL ActionTriggerSeparatorPropertySet::getImplementationName() +{ + return IMPLEMENTATIONNAME_ACTIONTRIGGERSEPARATOR; +} + +sal_Bool SAL_CALL ActionTriggerSeparatorPropertySet::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL ActionTriggerSeparatorPropertySet::getSupportedServiceNames() +{ + Sequence<OUString> seqServiceNames { SERVICENAME_ACTIONTRIGGERSEPARATOR }; + return seqServiceNames; +} + +// XTypeProvider +Sequence< Type > SAL_CALL ActionTriggerSeparatorPropertySet::getTypes() +{ + // Create a static typecollection ... + static ::cppu::OTypeCollection ourTypeCollection( + cppu::UnoType<XPropertySet>::get(), + cppu::UnoType<XFastPropertySet>::get(), + cppu::UnoType<XMultiPropertySet>::get(), + cppu::UnoType<XServiceInfo>::get(), + cppu::UnoType<XTypeProvider>::get()); + + return ourTypeCollection.getTypes(); +} + +Sequence< sal_Int8 > SAL_CALL ActionTriggerSeparatorPropertySet::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +sal_Bool SAL_CALL ActionTriggerSeparatorPropertySet::convertFastPropertyValue( + Any& aConvertedValue, + Any& aOldValue, + sal_Int32 nHandle, + const Any& aValue ) +{ + // Check, if value of property will changed in method "setFastPropertyValue_NoBroadcast()". + // Return sal_True, if changed - else return sal_False. + // Attention: Method "impl_tryToChangeProperty()" can throw the IllegalArgumentException !!! + // Initialize return value with sal_False !!! + // (Handle can be invalid) + bool bReturn = false; + + switch( nHandle ) + { + case HANDLE_TYPE: + bReturn = impl_tryToChangeProperty( m_nSeparatorType, aValue, aOldValue, aConvertedValue ); + break; + } + + // Return state of operation. + return bReturn; +} + +void SAL_CALL ActionTriggerSeparatorPropertySet::setFastPropertyValue_NoBroadcast( + sal_Int32 nHandle, const Any& aValue ) +{ + SolarMutexGuard aGuard; + + // Search for right handle ... and try to set property value. + switch( nHandle ) + { + case HANDLE_TYPE: + aValue >>= m_nSeparatorType; + break; + } +} + +void SAL_CALL ActionTriggerSeparatorPropertySet::getFastPropertyValue( + Any& aValue, sal_Int32 nHandle ) const +{ + SolarMutexGuard aGuard; + + // Search for right handle ... and try to get property value. + switch( nHandle ) + { + case HANDLE_TYPE: + aValue <<= m_nSeparatorType; + break; + } +} + +::cppu::IPropertyArrayHelper& SAL_CALL ActionTriggerSeparatorPropertySet::getInfoHelper() +{ + // Define static member to give structure of properties to baseclass "OPropertySetHelper". + // "impl_getStaticPropertyDescriptor" is a non exported and static function, who will define a static propertytable. + // "true" indicates: Table is sorted by name. + static OPropertyArrayHelper ourInfoHelper( impl_getStaticPropertyDescriptor(), true ); + + return ourInfoHelper; +} + +Reference< XPropertySetInfo > SAL_CALL ActionTriggerSeparatorPropertySet::getPropertySetInfo() +{ + // Create structure of propertysetinfo for baseclass "OPropertySetHelper". + // (Use method "getInfoHelper()".) + static Reference< XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) ); + + return xInfo; +} + +Sequence< Property > ActionTriggerSeparatorPropertySet::impl_getStaticPropertyDescriptor() +{ + return + { + Property( "SeparatorType", HANDLE_TYPE, cppu::UnoType<sal_Int16>::get(), PropertyAttribute::TRANSIENT ) + }; +} + +bool ActionTriggerSeparatorPropertySet::impl_tryToChangeProperty( + sal_Int16 aCurrentValue , + const Any& aNewValue , + Any& aOldValue , + Any& aConvertedValue ) +{ + // Set default return value if method failed. + bool bReturn = false; + // Get new value from any. + // IllegalArgumentException() can be thrown! + sal_Int16 aValue = 0; + convertPropertyValue( aValue, aNewValue ); + + // If value change ... + if( aValue != aCurrentValue ) + { + // ... set information of change. + aOldValue <<= aCurrentValue; + aConvertedValue <<= aValue; + // Return OK - "value will be change ..." + bReturn = true; + } + else + { + // ... clear information of return parameter! + aOldValue.clear (); + aConvertedValue.clear (); + // Return NOTHING - "value will not be change ..." + bReturn = false; + } + + return bReturn; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/classes/addonmenu.cxx b/framework/source/fwe/classes/addonmenu.cxx new file mode 100644 index 000000000..423a157f8 --- /dev/null +++ b/framework/source/fwe/classes/addonmenu.cxx @@ -0,0 +1,299 @@ +/* -*- 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 <addonmenu.hxx> +#include <framework/addonsoptions.hxx> +#include <menuconfiguration.hxx> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <vcl/commandinfoprovider.hxx> +#include <vcl/menu.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::beans; + +namespace framework +{ + +bool AddonMenuManager::HasAddonMenuElements() +{ + return AddonsOptions().HasAddonsMenu(); +} + +// Create the Add-Ons menu +VclPtr<PopupMenu> AddonMenuManager::CreateAddonMenu( const Reference< XFrame >& rFrame ) +{ + AddonsOptions aOptions; + VclPtr<PopupMenu> pAddonMenu; + + const Sequence< Sequence< PropertyValue > >& rAddonMenuEntries = aOptions.GetAddonsMenu(); + if ( rAddonMenuEntries.hasElements() ) + { + sal_uInt16 nUniqueMenuId = ADDONMENU_ITEMID_START; + pAddonMenu = VclPtr<PopupMenu>::Create(); + OUString aModuleIdentifier = vcl::CommandInfoProvider::GetModuleIdentifier( rFrame ); + AddonMenuManager::BuildMenu( pAddonMenu, MENU_APPEND, nUniqueMenuId, rAddonMenuEntries, rFrame, aModuleIdentifier ); + + // Don't return an empty Add-On menu + if ( pAddonMenu->GetItemCount() == 0 ) + { + pAddonMenu.disposeAndClear(); + } + } + + return pAddonMenu; +} + +// Returns the next insert position from nPos. +sal_uInt16 AddonMenuManager::GetNextPos( sal_uInt16 nPos ) +{ + return ( nPos == MENU_APPEND ) ? MENU_APPEND : ( nPos+1 ); +} + +static sal_uInt16 FindMenuId( Menu const * pMenu, std::u16string_view aCommand ) +{ + sal_uInt16 nPos = 0; + OUString aCmd; + for ( nPos = 0; nPos < pMenu->GetItemCount(); nPos++ ) + { + sal_uInt16 nId = pMenu->GetItemId( nPos ); + aCmd = pMenu->GetItemCommand( nId ); + if ( aCmd == aCommand ) + return nId; + } + + return USHRT_MAX; +} + +// Merge the Add-Ons help menu items into the given menu bar at a defined pos +void AddonMenuManager::MergeAddonHelpMenu( const Reference< XFrame >& rFrame, + MenuBar const * pMergeMenuBar ) +{ + if ( !pMergeMenuBar ) + return; + + PopupMenu* pHelpMenu(nullptr); + sal_uInt16 nId = FindMenuId(pMergeMenuBar, u".uno:HelpMenu"); + if ( nId != USHRT_MAX ) + pHelpMenu = pMergeMenuBar->GetPopupMenu( nId ); + + if ( !pHelpMenu ) + return; + + // Add-Ons help menu items should be inserted after the "registration" menu item + sal_uInt16 nItemCount = pHelpMenu->GetItemCount(); + sal_uInt16 nInsSepAfterPos = MENU_APPEND; + sal_uInt16 nUniqueMenuId = ADDONMENU_ITEMID_START; + AddonsOptions aOptions; + + // try to detect the about menu item with the command URL + nId = FindMenuId(pHelpMenu, u".uno:About"); + sal_uInt16 nInsPos = pHelpMenu->GetItemPos( nId ); + + const Sequence< Sequence< PropertyValue > >& rAddonHelpMenuEntries = aOptions.GetAddonsHelpMenu(); + + if ( nInsPos < nItemCount && pHelpMenu->GetItemType( nInsPos ) != MenuItemType::SEPARATOR ) + nInsSepAfterPos = nInsPos; + + OUString aModuleIdentifier = vcl::CommandInfoProvider::GetModuleIdentifier(rFrame); + AddonMenuManager::BuildMenu( pHelpMenu, nInsPos, nUniqueMenuId, rAddonHelpMenuEntries, rFrame, aModuleIdentifier ); + + if ( pHelpMenu->GetItemCount() > nItemCount ) + { + if ( nInsSepAfterPos < MENU_APPEND ) + { + nInsSepAfterPos += ( pHelpMenu->GetItemCount() - nItemCount ); + if ( pHelpMenu->GetItemType( nInsSepAfterPos ) != MenuItemType::SEPARATOR ) + pHelpMenu->InsertSeparator(OString(), nInsSepAfterPos); + } + pHelpMenu->InsertSeparator(OString(), nItemCount); + } +} + +// Merge the addon popup menus into the given menu bar at the provided pos. +void AddonMenuManager::MergeAddonPopupMenus( const Reference< XFrame >& rFrame, + sal_uInt16 nMergeAtPos, + MenuBar* pMergeMenuBar ) +{ + if ( !pMergeMenuBar ) + return; + + AddonsOptions aAddonsOptions; + sal_uInt16 nInsertPos = nMergeAtPos; + + OUString aTitle; + OUString aURL; + OUString aTarget; + OUString aContext; + Sequence< Sequence< PropertyValue > > aAddonSubMenu; + sal_uInt16 nUniqueMenuId = ADDONMENU_ITEMID_START; + + OUString aModuleIdentifier = vcl::CommandInfoProvider::GetModuleIdentifier(rFrame); + + const Sequence< Sequence< PropertyValue > >& rAddonMenuEntries = aAddonsOptions.GetAddonsMenuBarPart(); + for ( const Sequence< PropertyValue >& rEntry : rAddonMenuEntries ) + { + AddonMenuManager::GetMenuEntry( rEntry, + aTitle, + aURL, + aTarget, + aContext, + aAddonSubMenu ); + if ( !aTitle.isEmpty() && + !aURL.isEmpty() && + aAddonSubMenu.hasElements() && + AddonMenuManager::IsCorrectContext( aModuleIdentifier, aContext )) + { + sal_uInt16 nId = nUniqueMenuId++; + VclPtrInstance<PopupMenu> pAddonPopupMenu; + + AddonMenuManager::BuildMenu( pAddonPopupMenu, MENU_APPEND, nUniqueMenuId, aAddonSubMenu, rFrame, aModuleIdentifier ); + + if ( pAddonPopupMenu->GetItemCount() > 0 ) + { + pMergeMenuBar->InsertItem( nId, aTitle, MenuItemBits::NONE, OString(), nInsertPos++ ); + pMergeMenuBar->SetPopupMenu( nId, pAddonPopupMenu ); + + // Store the command URL into the VCL menu bar for later identification + pMergeMenuBar->SetItemCommand( nId, aURL ); + } + else + pAddonPopupMenu.disposeAndClear(); + } + } +} + +// Insert the menu and sub menu entries into pCurrentMenu with the aAddonMenuDefinition provided +void AddonMenuManager::BuildMenu( PopupMenu* pCurrentMenu, + sal_uInt16 nInsPos, + sal_uInt16& nUniqueMenuId, + const Sequence< Sequence< PropertyValue > >& aAddonMenuDefinition, + const Reference< XFrame >& rFrame, + const OUString& rModuleIdentifier ) +{ + Sequence< Sequence< PropertyValue > > aAddonSubMenu; + bool bInsertSeparator = false; + sal_uInt32 i = 0; + sal_uInt32 nElements = 0; + sal_uInt32 nCount = aAddonMenuDefinition.getLength(); + + OUString aTitle; + OUString aURL; + OUString aTarget; + OUString aContext; + + for ( i = 0; i < nCount; ++i ) + { + GetMenuEntry( aAddonMenuDefinition[i], aTitle, aURL, aTarget, aContext, aAddonSubMenu ); + + if ( !IsCorrectContext( rModuleIdentifier, aContext ) || ( aTitle.isEmpty() && aURL.isEmpty() )) + continue; + + if ( aURL == "private:separator" ) + bInsertSeparator = true; + else + { + VclPtr<PopupMenu> pSubMenu; + if ( aAddonSubMenu.hasElements() ) + { + pSubMenu = VclPtr<PopupMenu>::Create(); + AddonMenuManager::BuildMenu( pSubMenu, MENU_APPEND, nUniqueMenuId, aAddonSubMenu, rFrame, rModuleIdentifier ); + + // Don't create a menu item for an empty sub menu + if ( pSubMenu->GetItemCount() == 0 ) + { + pSubMenu.disposeAndClear(); + continue; + } + } + + if ( bInsertSeparator && nElements > 0 ) + { + // Insert a separator only when we insert a new element afterwards and we + // have already one before us + nElements = 0; + bInsertSeparator = false; + pCurrentMenu->InsertSeparator(OString(), nInsPos); + nInsPos = AddonMenuManager::GetNextPos( nInsPos ); + } + + sal_uInt16 nId = nUniqueMenuId++; + pCurrentMenu->InsertItem(nId, aTitle, MenuItemBits::NONE, OString(), nInsPos); + nInsPos = AddonMenuManager::GetNextPos( nInsPos ); + + ++nElements; + + void* nAttributePtr = MenuAttributes::CreateAttribute(aTarget, OUString()); + pCurrentMenu->SetUserValue(nId, nAttributePtr, MenuAttributes::ReleaseAttribute); + pCurrentMenu->SetItemCommand( nId, aURL ); + + if ( pSubMenu ) + pCurrentMenu->SetPopupMenu( nId, pSubMenu ); + } + } +} + +// Retrieve the menu entry property values from a sequence +void AddonMenuManager::GetMenuEntry( const Sequence< PropertyValue >& rAddonMenuEntry, + OUString& rTitle, + OUString& rURL, + OUString& rTarget, + OUString& rContext, + Sequence< Sequence< PropertyValue > >& rAddonSubMenu ) +{ + // Reset submenu parameter + rAddonSubMenu = Sequence< Sequence< PropertyValue > >(); + + for ( const PropertyValue& rEntry : rAddonMenuEntry ) + { + OUString aMenuEntryPropName = rEntry.Name; + if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_URL ) + rEntry.Value >>= rURL; + else if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_TITLE ) + rEntry.Value >>= rTitle; + else if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_TARGET ) + rEntry.Value >>= rTarget; + else if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_SUBMENU ) + rEntry.Value >>= rAddonSubMenu; + else if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_CONTEXT ) + rEntry.Value >>= rContext; + } +} + +// Check if the context string matches the provided xModel context +bool AddonMenuManager::IsCorrectContext( std::u16string_view rModuleIdentifier, std::u16string_view rContext ) +{ + if ( rContext.empty() ) + return true; + + if ( !rModuleIdentifier.empty() ) + { + return rContext.find( rModuleIdentifier ) != std::u16string_view::npos; + } + + return false; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/classes/addonsoptions.cxx b/framework/source/fwe/classes/addonsoptions.cxx new file mode 100644 index 000000000..b97a7eac6 --- /dev/null +++ b/framework/source/fwe/classes/addonsoptions.cxx @@ -0,0 +1,1958 @@ +/* -*- 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 <com/sun/star/beans/PropertyValue.hpp> +#include <framework/addonsoptions.hxx> +#include <o3tl/safeint.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/configitem.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <tools/stream.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <comphelper/getexpandeduri.hxx> +#include <comphelper/processfactory.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/graph.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/svapp.hxx> + +#include <algorithm> +#include <string_view> +#include <unordered_map> +#include <vector> + +// namespaces + +using namespace ::std; +using namespace ::utl; +using namespace ::osl; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star; + +constexpr OUStringLiteral ROOTNODE_ADDONMENU = u"Office.Addons"; +constexpr OUStringLiteral PATHDELIMITER = u"/"; +constexpr OUStringLiteral SEPARATOR_URL = u"private:separator"; + +#define PROPERTYNAME_URL ADDONSMENUITEM_STRING_URL +#define PROPERTYNAME_TITLE ADDONSMENUITEM_STRING_TITLE +#define PROPERTYNAME_TARGET ADDONSMENUITEM_STRING_TARGET +#define PROPERTYNAME_IMAGEIDENTIFIER ADDONSMENUITEM_STRING_IMAGEIDENTIFIER +#define PROPERTYNAME_CONTEXT ADDONSMENUITEM_STRING_CONTEXT +#define PROPERTYNAME_SUBMENU ADDONSMENUITEM_STRING_SUBMENU + +constexpr OUStringLiteral IMAGES_NODENAME = u"UserDefinedImages"; + +// The following order is mandatory. Please add properties at the end! +#define INDEX_URL 0 +#define INDEX_TITLE 1 +#define INDEX_IMAGEIDENTIFIER 2 +#define INDEX_TARGET 3 +#define INDEX_CONTEXT 4 +#define INDEX_SUBMENU 5 +#define INDEX_CONTROLTYPE 6 +#define INDEX_WIDTH 7 +#define INDEX_ALIGN 8 +#define INDEX_AUTOSIZE 9 +#define INDEX_OWNERDRAW 10 +#define INDEX_MANDATORY 11 +#define INDEX_STYLE 12 +#define PROPERTYCOUNT_INDEX 13 + +// The following order is mandatory. Please add properties at the end! +#define PROPERTYCOUNT_MENUITEM 6 +#define OFFSET_MENUITEM_URL 0 +#define OFFSET_MENUITEM_TITLE 1 +#define OFFSET_MENUITEM_IMAGEIDENTIFIER 2 +#define OFFSET_MENUITEM_TARGET 3 +#define OFFSET_MENUITEM_CONTEXT 4 +#define OFFSET_MENUITEM_SUBMENU 5 + +// The following order is mandatory. Please add properties at the end! +#define PROPERTYCOUNT_POPUPMENU 4 +#define OFFSET_POPUPMENU_TITLE 0 +#define OFFSET_POPUPMENU_CONTEXT 1 +#define OFFSET_POPUPMENU_SUBMENU 2 +#define OFFSET_POPUPMENU_URL 3 // Used for property set + +// The following order is mandatory. Please add properties at the end! +#define PROPERTYCOUNT_TOOLBARITEM 7 +#define OFFSET_TOOLBARITEM_URL 0 +#define OFFSET_TOOLBARITEM_TITLE 1 +#define OFFSET_TOOLBARITEM_IMAGEIDENTIFIER 2 +#define OFFSET_TOOLBARITEM_TARGET 3 +#define OFFSET_TOOLBARITEM_CONTEXT 4 +#define OFFSET_TOOLBARITEM_CONTROLTYPE 5 +#define OFFSET_TOOLBARITEM_WIDTH 6 + +// The following order is mandatory. Please add properties at the end! +#define PROPERTYCOUNT_NOTEBOOKBARITEM 8 +#define OFFSET_NOTEBOOKBARITEM_URL 0 +#define OFFSET_NOTEBOOKBARITEM_TITLE 1 +#define OFFSET_NOTEBOOKBARITEM_IMAGEIDENTIFIER 2 +#define OFFSET_NOTEBOOKBARITEM_TARGET 3 +#define OFFSET_NOTEBOOKBARITEM_CONTEXT 4 +#define OFFSET_NOTEBOOKBARITEM_CONTROLTYPE 5 +#define OFFSET_NOTEBOOKBARITEM_WIDTH 6 +#define OFFSET_NOTEBOOKBARITEM_STYLE 7 + +// The following order is mandatory. Please add properties at the end! +#define PROPERTYCOUNT_STATUSBARITEM 8 +#define OFFSET_STATUSBARITEM_URL 0 +#define OFFSET_STATUSBARITEM_TITLE 1 +#define OFFSET_STATUSBARITEM_CONTEXT 2 +#define OFFSET_STATUSBARITEM_ALIGN 3 +#define OFFSET_STATUSBARITEM_AUTOSIZE 4 +#define OFFSET_STATUSBARITEM_OWNERDRAW 5 +#define OFFSET_STATUSBARITEM_MANDATORY 6 +#define OFFSET_STATUSBARITEM_WIDTH 7 + +// The following order is mandatory. Please add properties at the end! +#define PROPERTYCOUNT_IMAGES 8 +#define PROPERTYCOUNT_EMBEDDED_IMAGES 2 +#define OFFSET_IMAGES_SMALL 0 +#define OFFSET_IMAGES_BIG 1 +#define OFFSET_IMAGES_SMALLHC 2 +#define OFFSET_IMAGES_BIGHC 3 +#define OFFSET_IMAGES_SMALL_URL 4 +#define OFFSET_IMAGES_BIG_URL 5 +#define OFFSET_IMAGES_SMALLHC_URL 6 +#define OFFSET_IMAGES_BIGHC_URL 7 + +#define PROPERTYCOUNT_MERGE_MENUBAR 6 +#define OFFSET_MERGEMENU_MERGEPOINT 0 +#define OFFSET_MERGEMENU_MERGECOMMAND 1 +#define OFFSET_MERGEMENU_MERGECOMMANDPARAMETER 2 +#define OFFSET_MERGEMENU_MERGEFALLBACK 3 +#define OFFSET_MERGEMENU_MERGECONTEXT 4 +#define OFFSET_MERGEMENU_MENUITEMS 5 + +#define PROPERTYCOUNT_MERGE_TOOLBAR 7 +#define OFFSET_MERGETOOLBAR_TOOLBAR 0 +#define OFFSET_MERGETOOLBAR_MERGEPOINT 1 +#define OFFSET_MERGETOOLBAR_MERGECOMMAND 2 +#define OFFSET_MERGETOOLBAR_MERGECOMMANDPARAMETER 3 +#define OFFSET_MERGETOOLBAR_MERGEFALLBACK 4 +#define OFFSET_MERGETOOLBAR_MERGECONTEXT 5 +#define OFFSET_MERGETOOLBAR_TOOLBARITEMS 6 + +#define PROPERTYCOUNT_MERGE_NOTEBOOKBAR 7 +#define OFFSET_MERGENOTEBOOKBAR_NOTEBOOKBAR 0 +#define OFFSET_MERGENOTEBOOKBAR_MERGEPOINT 1 +#define OFFSET_MERGENOTEBOOKBAR_MERGECOMMAND 2 +#define OFFSET_MERGENOTEBOOKBAR_MERGECOMMANDPARAMETER 3 +#define OFFSET_MERGENOTEBOOKBAR_MERGEFALLBACK 4 +#define OFFSET_MERGENOTEBOOKBAR_MERGECONTEXT 5 +#define OFFSET_MERGENOTEBOOKBAR_NOTEBOOKBARITEMS 6 + +#define PROPERTYCOUNT_MERGE_STATUSBAR 6 +#define OFFSET_MERGESTATUSBAR_MERGEPOINT 0 +#define OFFSET_MERGESTATUSBAR_MERGECOMMAND 1 +#define OFFSET_MERGESTATUSBAR_MERGECOMMANDPARAMETER 2 +#define OFFSET_MERGESTATUSBAR_MERGEFALLBACK 3 +#define OFFSET_MERGESTATUSBAR_MERGECONTEXT 4 +#define OFFSET_MERGESTATUSBAR_STATUSBARITEMS 5 + +// private declarations! + +/*-**************************************************************************************************************** + @descr struct to hold information about one menu entry. +****************************************************************************************************************-*/ + +namespace framework +{ + +class AddonsOptions_Impl : public ConfigItem +{ + + // public methods + + public: + + // constructor / destructor + + AddonsOptions_Impl(); + virtual ~AddonsOptions_Impl() override; + + // overridden methods of baseclass + + /*-**************************************************************************************************** + @short called for notify of configmanager + @descr This method is called from the ConfigManager before application ends or from the + PropertyChangeListener if the sub tree broadcasts changes. You must update your + internal values. + + @seealso baseclass ConfigItem + + @param "lPropertyNames" is the list of properties which should be updated. + *//*-*****************************************************************************************************/ + + virtual void Notify( const Sequence< OUString >& lPropertyNames ) override; + + // public interface + + /*-**************************************************************************************************** + @short base implementation of public interface for "SvtDynamicMenuOptions"! + @descr These class is used as static member of "SvtDynamicMenuOptions" ... + => The code exist only for one time and isn't duplicated for every instance! + *//*-*****************************************************************************************************/ + + bool HasAddonsMenu () const; + sal_Int32 GetAddonsToolBarCount() const; + sal_Int32 GetAddonsNotebookBarCount() const; + const Sequence< Sequence< PropertyValue > >& GetAddonsMenu () const { return m_aCachedMenuProperties;} + const Sequence< Sequence< PropertyValue > >& GetAddonsMenuBarPart () const { return m_aCachedMenuBarPartProperties;} + const Sequence< Sequence< PropertyValue > >& GetAddonsToolBarPart ( sal_uInt32 nIndex ) const; + const Sequence< Sequence< PropertyValue > >& GetAddonsNotebookBarPart ( sal_uInt32 nIndex ) const; + OUString GetAddonsToolbarResourceName( sal_uInt32 nIndex ) const; + OUString GetAddonsNotebookBarResourceName( sal_uInt32 nIndex ) const; + const Sequence< Sequence< PropertyValue > >& GetAddonsHelpMenu () const { return m_aCachedHelpMenuProperties;} + BitmapEx GetImageFromURL( const OUString& aURL, bool bBig, bool bNoScale ); + const MergeMenuInstructionContainer& GetMergeMenuInstructions() const { return m_aCachedMergeMenuInsContainer;} + bool GetMergeToolbarInstructions( const OUString& rToolbarName, MergeToolbarInstructionContainer& rToolbarInstructions ) const; + bool GetMergeNotebookBarInstructions( const OUString& rNotebookBarName, MergeNotebookBarInstructionContainer& rNotebookBarInstructions ) const; + const MergeStatusbarInstructionContainer& GetMergeStatusbarInstructions() const { return m_aCachedStatusbarMergingInstructions;} + void ReadConfigurationData(); + + private: + enum ImageSize + { + IMGSIZE_SMALL = 0, + IMGSIZE_BIG + }; + + struct OneImageEntry + { + BitmapEx aScaled; ///< cached scaled image + BitmapEx aImage; ///< original un-scaled image + OUString aURL; ///< URL in case it is not loaded yet + }; + + struct ImageEntry + { + // if the image is set, it was embedded in some way, + // otherwise we use the associated URL to load on demand + + // accessed in this order + OneImageEntry aSizeEntry[2]; + ImageEntry() {} + void addImage(ImageSize eSize, const BitmapEx &rImage); + void addImage(ImageSize eSize, const OUString &rURL); + }; + + typedef std::unordered_map< OUString, ImageEntry > ImageManager; + typedef std::unordered_map< OUString, sal_uInt32 > StringToIndexMap; + typedef std::vector< Sequence< Sequence< PropertyValue > > > AddonToolBars; + typedef std::vector< Sequence< Sequence< PropertyValue > > > AddonNotebookBars; + typedef std::unordered_map< OUString, MergeToolbarInstructionContainer > ToolbarMergingInstructions; + typedef std::unordered_map< OUString, MergeNotebookBarInstructionContainer > NotebookBarMergingInstructions; + + /*-**************************************************************************************************** + @short return list of key names of our configuration management which represent our module tree + @descr These methods return the current list of key names! We need it to get needed values from our + configuration management! + @param "nCount" , returns count of menu entries for "new" + @return A list of configuration key names is returned. + *//*-*****************************************************************************************************/ + + void ReadAddonMenuSet( Sequence< Sequence< PropertyValue > >& aAddonMenuSeq ); + void ReadOfficeMenuBarSet( Sequence< Sequence< PropertyValue > >& aAddonOfficeMenuBarSeq ); + void ReadOfficeToolBarSet( AddonToolBars& rAddonOfficeToolBars, std::vector< OUString >& rAddonOfficeToolBarResNames ); + bool ReadToolBarItemSet( const OUString& rToolBarItemSetNodeName, Sequence< Sequence< PropertyValue > >& aAddonOfficeToolBarSeq ); + void ReadOfficeNotebookBarSet( AddonNotebookBars& rAddonOfficeNotebookBars, std::vector< OUString >& rAddonOfficeNotebookBarResNames ); + bool ReadNotebookBarItemSet( const OUString& rNotebookBarItemSetNodeName, Sequence< Sequence< PropertyValue > >& aAddonOfficeNotebookBarSeq ); + + void ReadOfficeHelpSet( Sequence< Sequence< PropertyValue > >& aAddonOfficeHelpMenuSeq ); + void ReadImages( ImageManager& aImageManager ); + void ReadMenuMergeInstructions( MergeMenuInstructionContainer& rContainer ); + void ReadToolbarMergeInstructions( ToolbarMergingInstructions& rToolbarMergeMap ); + void ReadNotebookBarMergeInstructions( NotebookBarMergingInstructions& rNotebookBarMergeMap ); + void ReadStatusbarMergeInstructions( MergeStatusbarInstructionContainer& rContainer ); + + void ReadMergeMenuData( std::u16string_view aMergeAddonInstructionBase, Sequence< Sequence< PropertyValue > >& rMergeMenu ); + void ReadMergeToolbarData( std::u16string_view aMergeAddonInstructionBase, Sequence< Sequence< PropertyValue > >& rMergeToolbarItems ); + void ReadMergeNotebookBarData( std::u16string_view aMergeAddonInstructionBase, Sequence< Sequence< PropertyValue > >& rMergeNotebookBarItems ); + void ReadMergeStatusbarData( std::u16string_view aMergeAddonInstructionBase, Sequence< Sequence< PropertyValue > >& rMergeStatusbar ); + bool ReadMenuItem( std::u16string_view aMenuItemNodeName, Sequence< PropertyValue >& aMenuItem, bool bIgnoreSubMenu = false ); + bool ReadPopupMenu( std::u16string_view aPopupMenuNodeName, Sequence< PropertyValue >& aPopupMenu ); + void AppendPopupMenu( Sequence< PropertyValue >& aTargetPopupMenu, const Sequence< PropertyValue >& rSourcePopupMenu ); + bool ReadToolBarItem( std::u16string_view aToolBarItemNodeName, Sequence< PropertyValue >& aToolBarItem ); + bool ReadNotebookBarItem( std::u16string_view aNotebookBarItemNodeName, Sequence< PropertyValue >& aNotebookBarItem ); + + bool ReadStatusBarItem( std::u16string_view aStatusbarItemNodeName, Sequence< PropertyValue >& aStatusbarItem ); + std::unique_ptr<ImageEntry> ReadImageData( std::u16string_view aImagesNodeName ); + void ReadAndAssociateImages( const OUString& aURL, const OUString& aImageId ); + BitmapEx ReadImageFromURL( const OUString& aURL ); + bool HasAssociatedImages( const OUString& aURL ); + void SubstituteVariables( OUString& aURL ); + + void ReadSubMenuEntries( const Sequence< OUString >& aSubMenuNodeNames, Sequence< Sequence< PropertyValue > >& rSubMenu ); + OUString GeneratePrefixURL(); + + Sequence< OUString > GetPropertyNamesMenuItem( std::u16string_view aPropertyRootNode ) + const; + Sequence< OUString > GetPropertyNamesPopupMenu( std::u16string_view aPropertyRootNode ) + const; + Sequence< OUString > GetPropertyNamesToolBarItem( std::u16string_view aPropertyRootNode ) + const; + Sequence< OUString > GetPropertyNamesNotebookBarItem( std::u16string_view aPropertyRootNode ) const; + + Sequence< OUString > GetPropertyNamesStatusbarItem( std::u16string_view aPropertyRootNode ) const; + Sequence< OUString > GetPropertyNamesImages( std::u16string_view aPropertyRootNode ) const; + bool CreateImageFromSequence( BitmapEx& rImage, Sequence< sal_Int8 >& rBitmapDataSeq ) const; + + DECL_LINK(NotifyEvent, void*, void); + + virtual void ImplCommit() override; + + // private member + + private: + sal_Int32 m_nRootAddonPopupMenuId; + OUString m_aPropNames[PROPERTYCOUNT_INDEX]; + OUString m_aPropImagesNames[PROPERTYCOUNT_IMAGES]; + OUString m_aPropMergeMenuNames[PROPERTYCOUNT_MERGE_MENUBAR]; + OUString m_aPropMergeToolbarNames[PROPERTYCOUNT_MERGE_TOOLBAR]; + OUString m_aPropMergeNotebookBarNames[PROPERTYCOUNT_MERGE_NOTEBOOKBAR]; + OUString m_aPropMergeStatusbarNames[PROPERTYCOUNT_MERGE_STATUSBAR]; + OUString m_aPathDelimiter; + OUString m_aRootAddonPopupMenuURLPrexfix; + Sequence< Sequence< PropertyValue > > m_aCachedMenuProperties; + Sequence< Sequence< PropertyValue > > m_aCachedMenuBarPartProperties; + AddonToolBars m_aCachedToolBarPartProperties; + AddonNotebookBars m_aCachedNotebookBarPartProperties; + std::vector< OUString > m_aCachedToolBarPartResourceNames; + std::vector< OUString > m_aCachedNotebookBarPartResourceNames; + Sequence< Sequence< PropertyValue > > m_aCachedHelpMenuProperties; + ImageManager m_aImageManager; + Sequence< Sequence< PropertyValue > > m_aEmptyAddonToolBar; + Sequence< Sequence< PropertyValue > > m_aEmptyAddonNotebookBar; + MergeMenuInstructionContainer m_aCachedMergeMenuInsContainer; + ToolbarMergingInstructions m_aCachedToolbarMergingInstructions; + NotebookBarMergingInstructions m_aCachedNotebookBarMergingInstructions; + MergeStatusbarInstructionContainer m_aCachedStatusbarMergingInstructions; +}; + +void AddonsOptions_Impl::ImageEntry::addImage(ImageSize eSize, const BitmapEx& rImage) +{ + aSizeEntry[static_cast<int>(eSize)].aImage = rImage; +} + +void AddonsOptions_Impl::ImageEntry::addImage(ImageSize eSize, const OUString &rURL) +{ + aSizeEntry[static_cast<int>(eSize)].aURL = rURL; +} + +// constructor + +AddonsOptions_Impl::AddonsOptions_Impl() + // Init baseclasses first + : ConfigItem( ROOTNODE_ADDONMENU ), + m_nRootAddonPopupMenuId( 0 ), + m_aPathDelimiter( PATHDELIMITER ), + m_aRootAddonPopupMenuURLPrexfix( ADDONSPOPUPMENU_URL_PREFIX_STR ) +{ + // initialize array with fixed property names + m_aPropNames[ INDEX_URL ] = PROPERTYNAME_URL; + m_aPropNames[ INDEX_TITLE ] = PROPERTYNAME_TITLE; + m_aPropNames[ INDEX_TARGET ] = PROPERTYNAME_TARGET; + m_aPropNames[ INDEX_IMAGEIDENTIFIER ] = PROPERTYNAME_IMAGEIDENTIFIER; + m_aPropNames[ INDEX_CONTEXT ] = PROPERTYNAME_CONTEXT; + m_aPropNames[ INDEX_SUBMENU ] = PROPERTYNAME_SUBMENU; // Submenu set! + m_aPropNames[ INDEX_CONTROLTYPE ] = "ControlType"; + m_aPropNames[ INDEX_WIDTH ] = "Width"; + m_aPropNames[ INDEX_ALIGN ] = "Alignment"; + m_aPropNames[ INDEX_AUTOSIZE ] = "AutoSize"; + m_aPropNames[ INDEX_OWNERDRAW ] = "OwnerDraw"; + m_aPropNames[ INDEX_MANDATORY ] = "Mandatory"; + m_aPropNames[ INDEX_STYLE ] = "Style"; + + // initialize array with fixed images property names + m_aPropImagesNames[ OFFSET_IMAGES_SMALL ] = "ImageSmall"; + m_aPropImagesNames[ OFFSET_IMAGES_BIG ] = "ImageBig"; + m_aPropImagesNames[ OFFSET_IMAGES_SMALLHC ] = "ImageSmallHC"; + m_aPropImagesNames[ OFFSET_IMAGES_BIGHC ] = "ImageBigHC"; + m_aPropImagesNames[ OFFSET_IMAGES_SMALL_URL ] = "ImageSmallURL"; + m_aPropImagesNames[ OFFSET_IMAGES_BIG_URL ] = "ImageBigURL"; + m_aPropImagesNames[ OFFSET_IMAGES_SMALLHC_URL ] = "ImageSmallHCURL"; + m_aPropImagesNames[ OFFSET_IMAGES_BIGHC_URL ] = "ImageBigHCURL"; + + // initialize array with fixed merge menu property names + m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MERGEPOINT ] = "MergePoint"; + m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MERGECOMMAND ] = "MergeCommand"; + m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MERGECOMMANDPARAMETER ] = "MergeCommandParameter"; + m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MERGEFALLBACK ] = "MergeFallback"; + m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MERGECONTEXT ] = "MergeContext"; + m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MENUITEMS ] = "MenuItems"; + + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_TOOLBAR ] = "MergeToolBar"; + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_MERGEPOINT ] = "MergePoint"; + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_MERGECOMMAND ] = "MergeCommand"; + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_MERGECOMMANDPARAMETER ] = "MergeCommandParameter"; + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_MERGEFALLBACK ] = "MergeFallback"; + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_MERGECONTEXT ] = "MergeContext"; + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_TOOLBARITEMS ] = "ToolBarItems"; + + m_aPropMergeNotebookBarNames[ OFFSET_MERGENOTEBOOKBAR_NOTEBOOKBAR ] = "MergeNotebookBar"; + m_aPropMergeNotebookBarNames[ OFFSET_MERGENOTEBOOKBAR_MERGEPOINT ] = "MergePoint"; + m_aPropMergeNotebookBarNames[ OFFSET_MERGENOTEBOOKBAR_MERGECOMMAND ] = "MergeCommand"; + m_aPropMergeNotebookBarNames[ OFFSET_MERGENOTEBOOKBAR_MERGECOMMANDPARAMETER ] = "MergeCommandParameter"; + m_aPropMergeNotebookBarNames[ OFFSET_MERGENOTEBOOKBAR_MERGEFALLBACK ] = "MergeFallback"; + m_aPropMergeNotebookBarNames[ OFFSET_MERGENOTEBOOKBAR_MERGECONTEXT ] = "MergeContext"; + m_aPropMergeNotebookBarNames[ OFFSET_MERGENOTEBOOKBAR_NOTEBOOKBARITEMS ] = "NotebookBarItems"; + + m_aPropMergeStatusbarNames[ OFFSET_MERGESTATUSBAR_MERGEPOINT ] = "MergePoint"; + m_aPropMergeStatusbarNames[ OFFSET_MERGESTATUSBAR_MERGECOMMAND ] = "MergeCommand"; + m_aPropMergeStatusbarNames[ OFFSET_MERGESTATUSBAR_MERGECOMMANDPARAMETER ] = "MergeCommandParameter"; + m_aPropMergeStatusbarNames[ OFFSET_MERGESTATUSBAR_MERGEFALLBACK ] = "MergeFallback"; + m_aPropMergeStatusbarNames[ OFFSET_MERGESTATUSBAR_MERGECONTEXT ] = "MergeContext"; + m_aPropMergeStatusbarNames[ OFFSET_MERGESTATUSBAR_STATUSBARITEMS ] = "StatusBarItems"; + + ReadConfigurationData(); + + // Enable notification mechanism of our baseclass. + // We need it to get information about changes outside these class on our used configuration keys! + Sequence<OUString> aNotifySeq { "AddonUI" }; + EnableNotification( aNotifySeq ); +} + +// destructor + +AddonsOptions_Impl::~AddonsOptions_Impl() +{ + assert(!IsModified()); // should have been committed +} + +void AddonsOptions_Impl::ReadConfigurationData() +{ + // reset members to be read again from configuration + m_aCachedMenuProperties = Sequence< Sequence< PropertyValue > >(); + m_aCachedMenuBarPartProperties = Sequence< Sequence< PropertyValue > >(); + m_aCachedToolBarPartProperties = AddonToolBars(); + m_aCachedNotebookBarPartProperties = AddonNotebookBars(); + m_aCachedHelpMenuProperties = Sequence< Sequence< PropertyValue > >(); + m_aCachedToolBarPartResourceNames.clear(); + m_aCachedNotebookBarPartResourceNames.clear(); + m_aImageManager = ImageManager(); + + ReadAddonMenuSet( m_aCachedMenuProperties ); + ReadOfficeMenuBarSet( m_aCachedMenuBarPartProperties ); + ReadOfficeToolBarSet( m_aCachedToolBarPartProperties, m_aCachedToolBarPartResourceNames ); + ReadOfficeNotebookBarSet( m_aCachedNotebookBarPartProperties, m_aCachedNotebookBarPartResourceNames ); + + ReadOfficeHelpSet( m_aCachedHelpMenuProperties ); + ReadImages( m_aImageManager ); + + m_aCachedMergeMenuInsContainer.clear(); + m_aCachedToolbarMergingInstructions.clear(); + m_aCachedNotebookBarMergingInstructions.clear(); + m_aCachedStatusbarMergingInstructions.clear(); + + ReadMenuMergeInstructions( m_aCachedMergeMenuInsContainer ); + ReadToolbarMergeInstructions( m_aCachedToolbarMergingInstructions ); + ReadNotebookBarMergeInstructions( m_aCachedNotebookBarMergingInstructions ); + ReadStatusbarMergeInstructions( m_aCachedStatusbarMergingInstructions ); +} + +// public method + +void AddonsOptions_Impl::Notify( const Sequence< OUString >& /*lPropertyNames*/ ) +{ + Application::PostUserEvent(LINK(this, AddonsOptions_Impl, NotifyEvent)); +} + +// public method + +void AddonsOptions_Impl::ImplCommit() +{ + SAL_WARN("fwk", "AddonsOptions_Impl::ImplCommit(): Not implemented yet!"); +} + +// public method + +bool AddonsOptions_Impl::HasAddonsMenu() const +{ + return m_aCachedMenuProperties.hasElements(); +} + +// public method + +sal_Int32 AddonsOptions_Impl::GetAddonsToolBarCount() const +{ + return m_aCachedToolBarPartProperties.size(); +} + +// public method + +sal_Int32 AddonsOptions_Impl::GetAddonsNotebookBarCount() const +{ + return m_aCachedNotebookBarPartProperties.size(); +} + +// public method + +const Sequence< Sequence< PropertyValue > >& AddonsOptions_Impl::GetAddonsToolBarPart( sal_uInt32 nIndex ) const +{ + if ( /*nIndex >= 0 &&*/ nIndex < m_aCachedToolBarPartProperties.size() ) + return m_aCachedToolBarPartProperties[nIndex]; + else + return m_aEmptyAddonToolBar; +} + +// public method + +const Sequence< Sequence< PropertyValue > >& AddonsOptions_Impl::GetAddonsNotebookBarPart( sal_uInt32 nIndex ) const +{ + if ( /*nIndex >= 0 &&*/ nIndex < m_aCachedNotebookBarPartProperties.size() ) + return m_aCachedNotebookBarPartProperties[nIndex]; + else + return m_aEmptyAddonNotebookBar; +} + +// public method + +OUString AddonsOptions_Impl::GetAddonsToolbarResourceName( sal_uInt32 nIndex ) const +{ + if ( nIndex < m_aCachedToolBarPartResourceNames.size() ) + return m_aCachedToolBarPartResourceNames[nIndex]; + else + return OUString(); +} + +// public method + +OUString AddonsOptions_Impl::GetAddonsNotebookBarResourceName( sal_uInt32 nIndex ) const +{ + if ( nIndex < m_aCachedNotebookBarPartResourceNames.size() ) + return m_aCachedNotebookBarPartResourceNames[nIndex]; + else + return OUString(); +} + +// public method + +bool AddonsOptions_Impl::GetMergeToolbarInstructions( + const OUString& rToolbarName, + MergeToolbarInstructionContainer& rToolbarInstructions ) const +{ + ToolbarMergingInstructions::const_iterator pIter = m_aCachedToolbarMergingInstructions.find( rToolbarName ); + if ( pIter != m_aCachedToolbarMergingInstructions.end() ) + { + rToolbarInstructions = pIter->second; + return true; + } + else + return false; +} + +// public method + +bool AddonsOptions_Impl::GetMergeNotebookBarInstructions( + const OUString& rNotebookBarName, + MergeNotebookBarInstructionContainer& rNotebookBarInstructions ) const +{ + NotebookBarMergingInstructions::const_iterator pIter = m_aCachedNotebookBarMergingInstructions.find( rNotebookBarName ); + if ( pIter != m_aCachedNotebookBarMergingInstructions.end() ) + { + rNotebookBarInstructions = pIter->second; + return true; + } + else + return false; +} + +// public method + +static BitmapEx ScaleImage( const BitmapEx &rImage, bool bBig ) +{ + Size aSize = ToolBox::GetDefaultImageSize(bBig ? ToolBoxButtonSize::Large : ToolBoxButtonSize::Small); + BitmapEx aScaleBmp(rImage); + SAL_INFO("fwk", "Addons: expensive scale image from " + << aScaleBmp.GetSizePixel() << " to " << aSize); + aScaleBmp.Scale(aSize, BmpScaleFlag::BestQuality); + return aScaleBmp; +} + +BitmapEx AddonsOptions_Impl::GetImageFromURL( const OUString& aURL, bool bBig, bool bNoScale ) +{ + BitmapEx aImage; + + SAL_INFO("fwk", "Expensive: Addons GetImageFromURL " << aURL << + " big " << (bBig?"big":"little") << + " scale " << (bNoScale ? "noscale" : "scale")); + + ImageManager::iterator pIter = m_aImageManager.find(aURL); + if ( pIter != m_aImageManager.end() ) + { + ImageSize eSize = bBig ? IMGSIZE_BIG : IMGSIZE_SMALL; + int nIdx = static_cast<int>(eSize); + int nOtherIdx = nIdx ? 0 : 1; + + OneImageEntry& rSizeEntry = pIter->second.aSizeEntry[nIdx]; + OneImageEntry& rOtherEntry = pIter->second.aSizeEntry[nOtherIdx]; + // actually read the image ... + if (rSizeEntry.aImage.IsEmpty()) + rSizeEntry.aImage = ReadImageFromURL(rSizeEntry.aURL); + + if (rSizeEntry.aImage.IsEmpty()) + { // try the other size and scale it + aImage = ScaleImage(ReadImageFromURL(rOtherEntry.aURL), bBig); + rSizeEntry.aImage = aImage; + if (rSizeEntry.aImage.IsEmpty()) + SAL_WARN("fwk", "failed to load addons image " << aURL); + } + + // FIXME: bNoScale is not terribly meaningful or useful + + if (aImage.IsEmpty() && bNoScale) + aImage = rSizeEntry.aImage; + + if (aImage.IsEmpty() && !rSizeEntry.aScaled.IsEmpty()) + aImage = rSizeEntry.aScaled; + + else // scale to the correct size for the theme / toolbox + { + aImage = rSizeEntry.aImage; + if (aImage.IsEmpty()) // use and scale the other if one size is missing + aImage = rOtherEntry.aImage; + + aImage = ScaleImage(aImage, bBig); + rSizeEntry.aScaled = aImage; // cache for next time + } + } + + return aImage; +} + +void AddonsOptions_Impl::ReadAddonMenuSet( Sequence< Sequence< PropertyValue > >& rAddonMenuSeq ) +{ + // Read the AddonMenu set and fill property sequences + OUString aAddonMenuNodeName( "AddonUI/AddonMenu" ); + Sequence< OUString > aAddonMenuNodeSeq = GetNodeNames( aAddonMenuNodeName ); + OUString aAddonMenuItemNode( aAddonMenuNodeName + m_aPathDelimiter ); + + sal_uInt32 nCount = aAddonMenuNodeSeq.getLength(); + sal_uInt32 nIndex = 0; + Sequence< PropertyValue > aMenuItem( PROPERTYCOUNT_MENUITEM ); + auto pMenuItem = aMenuItem.getArray(); + // Init the property value sequence + pMenuItem[ OFFSET_MENUITEM_URL ].Name = m_aPropNames[ INDEX_URL ]; + pMenuItem[ OFFSET_MENUITEM_TITLE ].Name = m_aPropNames[ INDEX_TITLE ]; + pMenuItem[ OFFSET_MENUITEM_TARGET ].Name = m_aPropNames[ INDEX_TARGET ]; + pMenuItem[ OFFSET_MENUITEM_IMAGEIDENTIFIER ].Name = m_aPropNames[ INDEX_IMAGEIDENTIFIER]; + pMenuItem[ OFFSET_MENUITEM_CONTEXT ].Name = m_aPropNames[ INDEX_CONTEXT ]; + pMenuItem[ OFFSET_MENUITEM_SUBMENU ].Name = m_aPropNames[ INDEX_SUBMENU ]; // Submenu set! + + for ( sal_uInt32 n = 0; n < nCount; n++ ) + { + OUString aRootMenuItemNode( aAddonMenuItemNode + aAddonMenuNodeSeq[n] ); + + // Read the MenuItem + if ( ReadMenuItem( aRootMenuItemNode, aMenuItem ) ) + { + // Successfully read a menu item, append to our list + sal_uInt32 nMenuItemCount = rAddonMenuSeq.getLength() + 1; + rAddonMenuSeq.realloc( nMenuItemCount ); + rAddonMenuSeq.getArray()[nIndex++] = aMenuItem; + } + } +} + +void AddonsOptions_Impl::ReadOfficeHelpSet( Sequence< Sequence< PropertyValue > >& rAddonOfficeHelpMenuSeq ) +{ + // Read the AddonMenu set and fill property sequences + OUString aAddonHelpMenuNodeName( "AddonUI/OfficeHelp" ); + Sequence< OUString > aAddonHelpMenuNodeSeq = GetNodeNames( aAddonHelpMenuNodeName ); + OUString aAddonHelpMenuItemNode( aAddonHelpMenuNodeName + m_aPathDelimiter ); + + sal_uInt32 nCount = aAddonHelpMenuNodeSeq.getLength(); + sal_uInt32 nIndex = 0; + Sequence< PropertyValue > aMenuItem( PROPERTYCOUNT_MENUITEM ); + auto pMenuItem = aMenuItem.getArray(); + // Init the property value sequence + pMenuItem[ OFFSET_MENUITEM_URL ].Name = m_aPropNames[ INDEX_URL ]; + pMenuItem[ OFFSET_MENUITEM_TITLE ].Name = m_aPropNames[ INDEX_TITLE ]; + pMenuItem[ OFFSET_MENUITEM_TARGET ].Name = m_aPropNames[ INDEX_TARGET ]; + pMenuItem[ OFFSET_MENUITEM_IMAGEIDENTIFIER ].Name = m_aPropNames[ INDEX_IMAGEIDENTIFIER]; + pMenuItem[ OFFSET_MENUITEM_CONTEXT ].Name = m_aPropNames[ INDEX_CONTEXT ]; + pMenuItem[ OFFSET_MENUITEM_SUBMENU ].Name = m_aPropNames[ INDEX_SUBMENU ]; // Submenu set! + + for ( sal_uInt32 n = 0; n < nCount; n++ ) + { + OUString aRootMenuItemNode( aAddonHelpMenuItemNode + aAddonHelpMenuNodeSeq[n] ); + + // Read the MenuItem + if ( ReadMenuItem( aRootMenuItemNode, aMenuItem, true ) ) + { + // Successfully read a menu item, append to our list + sal_uInt32 nMenuItemCount = rAddonOfficeHelpMenuSeq.getLength() + 1; + rAddonOfficeHelpMenuSeq.realloc( nMenuItemCount ); + rAddonOfficeHelpMenuSeq.getArray()[nIndex++] = aMenuItem; + } + } +} + +void AddonsOptions_Impl::ReadOfficeMenuBarSet( Sequence< Sequence< PropertyValue > >& rAddonOfficeMenuBarSeq ) +{ + // Read the OfficeMenuBar set and fill property sequences + OUString aAddonMenuBarNodeName( "AddonUI/OfficeMenuBar" ); + Sequence< OUString > aAddonMenuBarNodeSeq = GetNodeNames( aAddonMenuBarNodeName ); + OUString aAddonMenuBarNode( aAddonMenuBarNodeName + m_aPathDelimiter ); + + sal_uInt32 nCount = aAddonMenuBarNodeSeq.getLength(); + sal_uInt32 nIndex = 0; + Sequence< PropertyValue > aPopupMenu( PROPERTYCOUNT_POPUPMENU ); + auto pPopupMenu = aPopupMenu.getArray(); + // Init the property value sequence + pPopupMenu[ OFFSET_POPUPMENU_TITLE ].Name = m_aPropNames[ INDEX_TITLE ]; + pPopupMenu[ OFFSET_POPUPMENU_CONTEXT ].Name = m_aPropNames[ INDEX_CONTEXT]; + pPopupMenu[ OFFSET_POPUPMENU_SUBMENU ].Name = m_aPropNames[ INDEX_SUBMENU]; + pPopupMenu[ OFFSET_POPUPMENU_URL ].Name = m_aPropNames[ INDEX_URL ]; + + StringToIndexMap aTitleToIndexMap; + auto pAddonOfficeMenuBarSeq = rAddonOfficeMenuBarSeq.getArray(); + for ( sal_uInt32 n = 0; n < nCount; n++ ) + { + OUString aPopupMenuNode( aAddonMenuBarNode + aAddonMenuBarNodeSeq[n] ); + + // Read the MenuItem + if ( ReadPopupMenu( aPopupMenuNode, aPopupMenu ) ) + { + // Successfully read a popup menu, append to our list + OUString aPopupTitle; + if ( aPopupMenu[OFFSET_POPUPMENU_TITLE].Value >>= aPopupTitle ) + { + StringToIndexMap::const_iterator pIter = aTitleToIndexMap.find( aPopupTitle ); + if ( pIter != aTitleToIndexMap.end() ) + { + // title already there => concat both popup menus + Sequence< PropertyValue >& rOldPopupMenu = pAddonOfficeMenuBarSeq[pIter->second]; + AppendPopupMenu( rOldPopupMenu, aPopupMenu ); + } + else + { + // not found + sal_uInt32 nMenuItemCount = rAddonOfficeMenuBarSeq.getLength() + 1; + rAddonOfficeMenuBarSeq.realloc( nMenuItemCount ); + pAddonOfficeMenuBarSeq = rAddonOfficeMenuBarSeq.getArray(); + pAddonOfficeMenuBarSeq[nIndex] = aPopupMenu; + aTitleToIndexMap.emplace( aPopupTitle, nIndex ); + ++nIndex; + } + } + } + } +} + +void AddonsOptions_Impl::ReadOfficeToolBarSet( AddonToolBars& rAddonOfficeToolBars, std::vector< OUString >& rAddonOfficeToolBarResNames ) +{ + // Read the OfficeToolBar set and fill property sequences + OUString aAddonToolBarNodeName( "AddonUI/OfficeToolBar" ); + Sequence< OUString > aAddonToolBarNodeSeq = GetNodeNames( aAddonToolBarNodeName ); + OUString aAddonToolBarNode( aAddonToolBarNodeName + m_aPathDelimiter ); + + sal_uInt32 nCount = aAddonToolBarNodeSeq.getLength(); + + for ( sal_uInt32 n = 0; n < nCount; n++ ) + { + OUString aToolBarItemNode( aAddonToolBarNode + aAddonToolBarNodeSeq[n] ); + rAddonOfficeToolBarResNames.push_back( aAddonToolBarNodeSeq[n] ); + rAddonOfficeToolBars.push_back( m_aEmptyAddonToolBar ); + ReadToolBarItemSet( aToolBarItemNode, rAddonOfficeToolBars[n] ); + } +} + +bool AddonsOptions_Impl::ReadToolBarItemSet( const OUString& rToolBarItemSetNodeName, Sequence< Sequence< PropertyValue > >& rAddonOfficeToolBarSeq ) +{ + sal_uInt32 nToolBarItemCount = rAddonOfficeToolBarSeq.getLength(); + OUString aAddonToolBarItemSetNode( rToolBarItemSetNodeName + m_aPathDelimiter ); + Sequence< OUString > aAddonToolBarItemSetNodeSeq = GetNodeNames( rToolBarItemSetNodeName ); + Sequence< PropertyValue > aToolBarItem( PROPERTYCOUNT_TOOLBARITEM ); + auto pToolBarItem = aToolBarItem.getArray(); + // Init the property value sequence + pToolBarItem[ OFFSET_TOOLBARITEM_URL ].Name = m_aPropNames[ INDEX_URL ]; + pToolBarItem[ OFFSET_TOOLBARITEM_TITLE ].Name = m_aPropNames[ INDEX_TITLE ]; + pToolBarItem[ OFFSET_TOOLBARITEM_IMAGEIDENTIFIER ].Name = m_aPropNames[ INDEX_IMAGEIDENTIFIER]; + pToolBarItem[ OFFSET_TOOLBARITEM_TARGET ].Name = m_aPropNames[ INDEX_TARGET ]; + pToolBarItem[ OFFSET_TOOLBARITEM_CONTEXT ].Name = m_aPropNames[ INDEX_CONTEXT ]; + pToolBarItem[ OFFSET_TOOLBARITEM_CONTROLTYPE ].Name = m_aPropNames[ INDEX_CONTROLTYPE ]; + pToolBarItem[ OFFSET_TOOLBARITEM_WIDTH ].Name = m_aPropNames[ INDEX_WIDTH ]; + + sal_uInt32 nCount = aAddonToolBarItemSetNodeSeq.getLength(); + for ( sal_uInt32 n = 0; n < nCount; n++ ) + { + OUString aToolBarItemNode( aAddonToolBarItemSetNode + aAddonToolBarItemSetNodeSeq[n] ); + + // Read the ToolBarItem + if ( ReadToolBarItem( aToolBarItemNode, aToolBarItem ) ) + { + // Successfully read a toolbar item, append to our list + sal_uInt32 nAddonCount = rAddonOfficeToolBarSeq.getLength(); + rAddonOfficeToolBarSeq.realloc( nAddonCount+1 ); + rAddonOfficeToolBarSeq.getArray()[nAddonCount] = aToolBarItem; + } + } + + return ( o3tl::make_unsigned(rAddonOfficeToolBarSeq.getLength()) > nToolBarItemCount ); +} + +void AddonsOptions_Impl::ReadOfficeNotebookBarSet( + AddonNotebookBars& rAddonOfficeNotebookBars, + std::vector<OUString>& rAddonOfficeNotebookBarResNames) +{ + // Read the OfficeToolBar set and fill property sequences + OUString aAddonNotebookBarNodeName("AddonUI/OfficeNotebookBar"); + Sequence<OUString> aAddonNotebookBarNodeSeq = GetNodeNames(aAddonNotebookBarNodeName); + OUString aAddonNotebookBarNode(aAddonNotebookBarNodeName + m_aPathDelimiter); + + sal_uInt32 nCount = aAddonNotebookBarNodeSeq.getLength(); + + for (sal_uInt32 n = 0; n < nCount; n++) + { + OUString aNotebookBarItemNode(aAddonNotebookBarNode + aAddonNotebookBarNodeSeq[n]); + rAddonOfficeNotebookBarResNames.push_back(aAddonNotebookBarNodeSeq[n]); + rAddonOfficeNotebookBars.push_back(m_aEmptyAddonNotebookBar); + ReadNotebookBarItemSet(aNotebookBarItemNode, rAddonOfficeNotebookBars[n]); + } +} + +bool AddonsOptions_Impl::ReadNotebookBarItemSet( + const OUString& rNotebookBarItemSetNodeName, + Sequence<Sequence<PropertyValue>>& rAddonOfficeNotebookBarSeq) +{ + sal_uInt32 nNotebookBarItemCount = rAddonOfficeNotebookBarSeq.getLength(); + OUString aAddonNotebookBarItemSetNode(rNotebookBarItemSetNodeName + m_aPathDelimiter); + Sequence<OUString> aAddonNotebookBarItemSetNodeSeq = GetNodeNames(rNotebookBarItemSetNodeName); + Sequence<PropertyValue> aNotebookBarItem(PROPERTYCOUNT_NOTEBOOKBARITEM); + auto pNotebookBarItem = aNotebookBarItem.getArray(); + // Init the property value sequence + pNotebookBarItem[OFFSET_NOTEBOOKBARITEM_URL].Name = m_aPropNames[INDEX_URL]; + pNotebookBarItem[OFFSET_NOTEBOOKBARITEM_TITLE].Name = m_aPropNames[INDEX_TITLE]; + pNotebookBarItem[OFFSET_NOTEBOOKBARITEM_IMAGEIDENTIFIER].Name + = m_aPropNames[INDEX_IMAGEIDENTIFIER]; + pNotebookBarItem[OFFSET_NOTEBOOKBARITEM_TARGET].Name = m_aPropNames[INDEX_TARGET]; + pNotebookBarItem[OFFSET_NOTEBOOKBARITEM_CONTEXT].Name = m_aPropNames[INDEX_CONTEXT]; + pNotebookBarItem[OFFSET_NOTEBOOKBARITEM_CONTROLTYPE].Name = m_aPropNames[INDEX_CONTROLTYPE]; + pNotebookBarItem[OFFSET_NOTEBOOKBARITEM_WIDTH].Name = m_aPropNames[INDEX_WIDTH]; + pNotebookBarItem[OFFSET_NOTEBOOKBARITEM_STYLE].Name = m_aPropNames[INDEX_STYLE]; + + sal_uInt32 nCount = aAddonNotebookBarItemSetNodeSeq.getLength(); + for (sal_uInt32 n = 0; n < nCount; n++) + { + OUString aNotebookBarItemNode(aAddonNotebookBarItemSetNode + + aAddonNotebookBarItemSetNodeSeq[n]); + // Read the NotebookBarItem + if (ReadNotebookBarItem(aNotebookBarItemNode, aNotebookBarItem)) + { + // Successfully read a toolbar item, append to our list + sal_uInt32 nAddonCount = rAddonOfficeNotebookBarSeq.getLength(); + rAddonOfficeNotebookBarSeq.realloc(nAddonCount + 1); + rAddonOfficeNotebookBarSeq.getArray()[nAddonCount] = aNotebookBarItem; + } + } + + return (o3tl::make_unsigned(rAddonOfficeNotebookBarSeq.getLength()) + > nNotebookBarItemCount); +} + +void AddonsOptions_Impl::ReadImages( ImageManager& aImageManager ) +{ + // Read the user-defined Images set and fill image manager + OUString aAddonImagesNodeName( "AddonUI/Images" ); + Sequence< OUString > aAddonImagesNodeSeq = GetNodeNames( aAddonImagesNodeName ); + OUString aAddonImagesNode( aAddonImagesNodeName + m_aPathDelimiter ); + + sal_uInt32 nCount = aAddonImagesNodeSeq.getLength(); + + // Init the property value sequence + OUString aURL; + + for ( sal_uInt32 n = 0; n < nCount; n++ ) + { + OUString aImagesItemNode( aAddonImagesNode + aAddonImagesNodeSeq[n] ); + + // Create sequence for data access + Sequence< OUString > aAddonImageItemNodePropNames = { aImagesItemNode + + m_aPathDelimiter + + m_aPropNames[ OFFSET_MENUITEM_URL ] }; + + Sequence< Any > aAddonImageItemNodeValues = GetProperties( aAddonImageItemNodePropNames ); + + // An user-defined image entry must have a URL. As "ImageIdentifier" has a higher priority + // we also check if we already have an images association. + if (( aAddonImageItemNodeValues[0] >>= aURL ) && + !aURL.isEmpty() && + !HasAssociatedImages( aURL )) + { + OUString aImagesUserDefinedItemNode = aImagesItemNode + + m_aPathDelimiter + + IMAGES_NODENAME + + m_aPathDelimiter; + + // Read a user-defined images data + std::unique_ptr<ImageEntry> pImageEntry = ReadImageData( aImagesUserDefinedItemNode ); + if ( pImageEntry ) + { + // Successfully read a user-defined images item, put it into our image manager + aImageManager.emplace( aURL, std::move(*pImageEntry) ); + } + } + } +} + +OUString AddonsOptions_Impl::GeneratePrefixURL() +{ + // Create a unique prefixed Add-On popup menu URL so it can be identified later as a runtime popup menu. + OUString aPopupMenuURL; + OUStringBuffer aBuf( m_aRootAddonPopupMenuURLPrexfix.getLength() + 3 ); + aBuf.append( m_aRootAddonPopupMenuURLPrexfix ); + aBuf.append( ++m_nRootAddonPopupMenuId ); + aPopupMenuURL = aBuf.makeStringAndClear(); + return aPopupMenuURL; +} + +void AddonsOptions_Impl::ReadMenuMergeInstructions( MergeMenuInstructionContainer& aContainer ) +{ + static const OUStringLiteral aMenuMergeRootName( u"AddonUI/OfficeMenuBarMerging/" ); + + Sequence< OUString > aAddonMergeNodesSeq = GetNodeNames( aMenuMergeRootName ); + + sal_uInt32 nCount = aAddonMergeNodesSeq.getLength(); + + // Init the property value sequence + Sequence< OUString > aNodePropNames( 5 ); + auto pNodePropNames = aNodePropNames.getArray(); + + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + OUString aMergeAddonInstructions( aMenuMergeRootName + aAddonMergeNodesSeq[i] ); + + Sequence< OUString > aAddonInstMergeNodesSeq = GetNodeNames( aMergeAddonInstructions ); + sal_uInt32 nCountAddons = aAddonInstMergeNodesSeq.getLength(); + + for ( sal_uInt32 j = 0; j < nCountAddons; j++ ) + { + OUString aMergeAddonInstructionBase = aMergeAddonInstructions + + m_aPathDelimiter + + aAddonInstMergeNodesSeq[j] + + m_aPathDelimiter; + + // Create sequence for data access + pNodePropNames[0] = aMergeAddonInstructionBase + + m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MERGEPOINT ]; + + pNodePropNames[1] = aMergeAddonInstructionBase + + m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MERGECOMMAND ]; + + pNodePropNames[2] = aMergeAddonInstructionBase + + m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MERGECOMMANDPARAMETER ]; + + pNodePropNames[3] = aMergeAddonInstructionBase + + m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MERGEFALLBACK ]; + + pNodePropNames[4] = aMergeAddonInstructionBase + + m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MERGECONTEXT ]; + + Sequence< Any > aNodePropValues = GetProperties( aNodePropNames ); + + MergeMenuInstruction aMergeMenuInstruction; + aNodePropValues[0] >>= aMergeMenuInstruction.aMergePoint; + aNodePropValues[1] >>= aMergeMenuInstruction.aMergeCommand; + aNodePropValues[2] >>= aMergeMenuInstruction.aMergeCommandParameter; + aNodePropValues[3] >>= aMergeMenuInstruction.aMergeFallback; + aNodePropValues[4] >>= aMergeMenuInstruction.aMergeContext; + + ReadMergeMenuData( aMergeAddonInstructionBase, aMergeMenuInstruction.aMergeMenu ); + + aContainer.push_back( aMergeMenuInstruction ); + } + } +} + +void AddonsOptions_Impl::ReadMergeMenuData( std::u16string_view aMergeAddonInstructionBase, Sequence< Sequence< PropertyValue > >& rMergeMenu ) +{ + OUString aMergeMenuBaseNode( aMergeAddonInstructionBase+m_aPropMergeMenuNames[ OFFSET_MERGEMENU_MENUITEMS ] ); + + Sequence< OUString > aSubMenuNodeNames = GetNodeNames( aMergeMenuBaseNode ); + aMergeMenuBaseNode += m_aPathDelimiter; + + // extend the node names to have full path strings + for ( OUString& rName : asNonConstRange(aSubMenuNodeNames) ) + rName = aMergeMenuBaseNode + rName; + + ReadSubMenuEntries( aSubMenuNodeNames, rMergeMenu ); +} + +void AddonsOptions_Impl::ReadToolbarMergeInstructions( ToolbarMergingInstructions& rCachedToolbarMergingInstructions ) +{ + static const OUStringLiteral aToolbarMergeRootName( u"AddonUI/OfficeToolbarMerging/" ); + + Sequence< OUString > aAddonMergeNodesSeq = GetNodeNames( aToolbarMergeRootName ); + sal_uInt32 nCount = aAddonMergeNodesSeq.getLength(); + + // Init the property value sequence + Sequence< OUString > aNodePropNames( 6 ); + auto pNodePropNames = aNodePropNames.getArray(); + + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + OUString aMergeAddonInstructions( aToolbarMergeRootName + aAddonMergeNodesSeq[i] ); + + Sequence< OUString > aAddonInstMergeNodesSeq = GetNodeNames( aMergeAddonInstructions ); + sal_uInt32 nCountAddons = aAddonInstMergeNodesSeq.getLength(); + + for ( sal_uInt32 j = 0; j < nCountAddons; j++ ) + { + OUString aMergeAddonInstructionBase = aMergeAddonInstructions + + m_aPathDelimiter + + aAddonInstMergeNodesSeq[j] + + m_aPathDelimiter; + + // Create sequence for data access + pNodePropNames[0] = aMergeAddonInstructionBase + + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_TOOLBAR ]; + + pNodePropNames[1] = aMergeAddonInstructionBase + + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_MERGEPOINT ]; + + pNodePropNames[2] = aMergeAddonInstructionBase + + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_MERGECOMMAND ]; + + pNodePropNames[3] = aMergeAddonInstructionBase + + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_MERGECOMMANDPARAMETER ]; + + pNodePropNames[4] = aMergeAddonInstructionBase + + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_MERGEFALLBACK ]; + + pNodePropNames[5] = aMergeAddonInstructionBase + + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_MERGECONTEXT ]; + + Sequence< Any > aNodePropValues = GetProperties( aNodePropNames ); + + MergeToolbarInstruction aMergeToolbarInstruction; + aNodePropValues[0] >>= aMergeToolbarInstruction.aMergeToolbar; + aNodePropValues[1] >>= aMergeToolbarInstruction.aMergePoint; + aNodePropValues[2] >>= aMergeToolbarInstruction.aMergeCommand; + aNodePropValues[3] >>= aMergeToolbarInstruction.aMergeCommandParameter; + aNodePropValues[4] >>= aMergeToolbarInstruction.aMergeFallback; + aNodePropValues[5] >>= aMergeToolbarInstruction.aMergeContext; + + ReadMergeToolbarData( aMergeAddonInstructionBase, + aMergeToolbarInstruction.aMergeToolbarItems ); + + MergeToolbarInstructionContainer& rVector = rCachedToolbarMergingInstructions[ aMergeToolbarInstruction.aMergeToolbar ]; + rVector.push_back( aMergeToolbarInstruction ); + } + } +} + +void AddonsOptions_Impl::ReadMergeToolbarData( std::u16string_view aMergeAddonInstructionBase, Sequence< Sequence< PropertyValue > >& rMergeToolbarItems ) +{ + OUString aMergeToolbarBaseNode = aMergeAddonInstructionBase + + m_aPropMergeToolbarNames[ OFFSET_MERGETOOLBAR_TOOLBARITEMS ]; + + ReadToolBarItemSet( aMergeToolbarBaseNode, rMergeToolbarItems ); +} + +void AddonsOptions_Impl::ReadNotebookBarMergeInstructions( + NotebookBarMergingInstructions& rCachedNotebookBarMergingInstructions) +{ + static const OUStringLiteral aNotebookBarMergeRootName(u"AddonUI/OfficeNotebookBarMerging/"); + + Sequence<OUString> aAddonMergeNodesSeq = GetNodeNames(aNotebookBarMergeRootName); + sal_uInt32 nCount = aAddonMergeNodesSeq.getLength(); + + // Init the property value sequence + Sequence<OUString> aNodePropNames(6); + auto pNodePropNames = aNodePropNames.getArray(); + + for (sal_uInt32 i = 0; i < nCount; i++) + { + OUString aMergeAddonInstructions(aNotebookBarMergeRootName + aAddonMergeNodesSeq[i]); + + Sequence<OUString> aAddonInstMergeNodesSeq = GetNodeNames(aMergeAddonInstructions); + sal_uInt32 nCountAddons = aAddonInstMergeNodesSeq.getLength(); + + for (sal_uInt32 j = 0; j < nCountAddons; j++) + { + OUString aMergeAddonInstructionBase = aMergeAddonInstructions + + m_aPathDelimiter + + aAddonInstMergeNodesSeq[j] + + m_aPathDelimiter; + + // Create sequence for data access + pNodePropNames[0] = aMergeAddonInstructionBase + + m_aPropMergeNotebookBarNames[OFFSET_MERGENOTEBOOKBAR_NOTEBOOKBAR]; + + pNodePropNames[1] = aMergeAddonInstructionBase + + m_aPropMergeNotebookBarNames[OFFSET_MERGENOTEBOOKBAR_MERGEPOINT]; + + pNodePropNames[2] = aMergeAddonInstructionBase + + m_aPropMergeNotebookBarNames[OFFSET_MERGENOTEBOOKBAR_MERGECOMMAND]; + + pNodePropNames[3] = aMergeAddonInstructionBase + + m_aPropMergeNotebookBarNames[OFFSET_MERGENOTEBOOKBAR_MERGECOMMANDPARAMETER]; + + pNodePropNames[4] = aMergeAddonInstructionBase + + m_aPropMergeNotebookBarNames[OFFSET_MERGENOTEBOOKBAR_MERGEFALLBACK]; + + pNodePropNames[5] = aMergeAddonInstructionBase + + m_aPropMergeNotebookBarNames[OFFSET_MERGENOTEBOOKBAR_MERGECONTEXT]; + + Sequence<Any> aNodePropValues = GetProperties(aNodePropNames); + + MergeNotebookBarInstruction aMergeNotebookBarInstruction; + aNodePropValues[0] >>= aMergeNotebookBarInstruction.aMergeNotebookBar; + aNodePropValues[1] >>= aMergeNotebookBarInstruction.aMergePoint; + aNodePropValues[2] >>= aMergeNotebookBarInstruction.aMergeCommand; + aNodePropValues[3] >>= aMergeNotebookBarInstruction.aMergeCommandParameter; + aNodePropValues[4] >>= aMergeNotebookBarInstruction.aMergeFallback; + aNodePropValues[5] >>= aMergeNotebookBarInstruction.aMergeContext; + + ReadMergeNotebookBarData(aMergeAddonInstructionBase, + aMergeNotebookBarInstruction.aMergeNotebookBarItems); + + MergeNotebookBarInstructionContainer& rVector + = rCachedNotebookBarMergingInstructions[aMergeNotebookBarInstruction + .aMergeNotebookBar]; + rVector.push_back(aMergeNotebookBarInstruction); + } + } +} + +void AddonsOptions_Impl::ReadMergeNotebookBarData( + std::u16string_view aMergeAddonInstructionBase, + Sequence<Sequence<PropertyValue>>& rMergeNotebookBarItems) +{ + OUString aMergeNotebookBarBaseNode = aMergeAddonInstructionBase + + m_aPropMergeNotebookBarNames[OFFSET_MERGENOTEBOOKBAR_NOTEBOOKBARITEMS]; + + ReadNotebookBarItemSet(aMergeNotebookBarBaseNode, rMergeNotebookBarItems); +} + +void AddonsOptions_Impl::ReadStatusbarMergeInstructions( MergeStatusbarInstructionContainer& aContainer ) +{ + static const OUStringLiteral aStatusbarMergeRootName( u"AddonUI/OfficeStatusbarMerging/" ); + + Sequence< OUString > aAddonMergeNodesSeq = GetNodeNames( aStatusbarMergeRootName ); + sal_uInt32 nCount = aAddonMergeNodesSeq.getLength(); + + Sequence< OUString > aNodePropNames( 5 ); + auto pNodePropNames = aNodePropNames.getArray(); + + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + OUString aMergeAddonInstructions( aStatusbarMergeRootName + aAddonMergeNodesSeq[i] ); + + Sequence< OUString > aAddonInstMergeNodesSeq = GetNodeNames( aMergeAddonInstructions ); + sal_uInt32 nCountAddons = aAddonInstMergeNodesSeq.getLength(); + + for ( sal_uInt32 j = 0; j < nCountAddons; j++ ) + { + OUString aMergeAddonInstructionBase = aMergeAddonInstructions + + m_aPathDelimiter + + aAddonInstMergeNodesSeq[j] + + m_aPathDelimiter; + + // Create sequence for data access + pNodePropNames[0] = aMergeAddonInstructionBase + + m_aPropMergeMenuNames[ OFFSET_MERGESTATUSBAR_MERGEPOINT ]; + + pNodePropNames[1] = aMergeAddonInstructionBase + + m_aPropMergeMenuNames[ OFFSET_MERGESTATUSBAR_MERGECOMMAND ]; + + pNodePropNames[2] = aMergeAddonInstructionBase + + m_aPropMergeMenuNames[ OFFSET_MERGESTATUSBAR_MERGECOMMANDPARAMETER ]; + + pNodePropNames[3] = aMergeAddonInstructionBase + + m_aPropMergeMenuNames[ OFFSET_MERGESTATUSBAR_MERGEFALLBACK ]; + + pNodePropNames[4] = aMergeAddonInstructionBase + + m_aPropMergeMenuNames[ OFFSET_MERGESTATUSBAR_MERGECONTEXT ]; + + Sequence< Any > aNodePropValues = GetProperties( aNodePropNames ); + + MergeStatusbarInstruction aMergeStatusbarInstruction; + aNodePropValues[0] >>= aMergeStatusbarInstruction.aMergePoint; + aNodePropValues[1] >>= aMergeStatusbarInstruction.aMergeCommand; + aNodePropValues[2] >>= aMergeStatusbarInstruction.aMergeCommandParameter; + // aNodePropValues[3] >>= aMergeStatusbarInstruction.aMergeFallback; + aNodePropValues[4] >>= aMergeStatusbarInstruction.aMergeContext; + + ReadMergeStatusbarData( aMergeAddonInstructionBase, + aMergeStatusbarInstruction.aMergeStatusbarItems ); + + aContainer.push_back( aMergeStatusbarInstruction ); + } + } +} + +void AddonsOptions_Impl::ReadMergeStatusbarData( + std::u16string_view aMergeAddonInstructionBase, + Sequence< Sequence< PropertyValue > >& rMergeStatusbarItems ) +{ + OUString aMergeStatusbarBaseNode = aMergeAddonInstructionBase + + m_aPropMergeStatusbarNames[ OFFSET_MERGESTATUSBAR_STATUSBARITEMS ]; + + OUString aAddonStatusbarItemSetNode( aMergeStatusbarBaseNode + m_aPathDelimiter ); + Sequence< OUString > aAddonStatusbarItemSetNodeSeq = GetNodeNames( aMergeStatusbarBaseNode ); + + Sequence< PropertyValue > aStatusbarItem( PROPERTYCOUNT_STATUSBARITEM ); + auto pStatusbarItem = aStatusbarItem.getArray(); + pStatusbarItem[ OFFSET_STATUSBARITEM_URL ].Name = m_aPropNames[ INDEX_URL ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_TITLE ].Name = m_aPropNames[ INDEX_TITLE ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_CONTEXT ].Name = m_aPropNames[ INDEX_CONTEXT ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_ALIGN ].Name = m_aPropNames[ INDEX_ALIGN ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_AUTOSIZE ].Name = m_aPropNames[ INDEX_AUTOSIZE ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_OWNERDRAW ].Name = m_aPropNames[ INDEX_OWNERDRAW ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_MANDATORY ].Name = m_aPropNames[ INDEX_MANDATORY ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_WIDTH ].Name = m_aPropNames[ INDEX_WIDTH ]; + + sal_uInt32 nCount = aAddonStatusbarItemSetNodeSeq.getLength(); + for ( sal_uInt32 n = 0; n < nCount; n++ ) + { + OUString aStatusbarItemNode( aAddonStatusbarItemSetNode + aAddonStatusbarItemSetNodeSeq[n] ); + + if ( ReadStatusBarItem( aStatusbarItemNode, aStatusbarItem ) ) + { + sal_uInt32 nAddonCount = rMergeStatusbarItems.getLength(); + rMergeStatusbarItems.realloc( nAddonCount+1 ); + rMergeStatusbarItems.getArray()[nAddonCount] = aStatusbarItem; + } + } +} + +bool AddonsOptions_Impl::ReadStatusBarItem( + std::u16string_view aStatusarItemNodeName, + Sequence< PropertyValue >& aStatusbarItem ) +{ + bool bResult( false ); + OUString aURL; + OUString aAddonStatusbarItemTreeNode( aStatusarItemNodeName + m_aPathDelimiter ); + + Sequence< Any > aStatusbarItemNodePropValues = GetProperties( GetPropertyNamesStatusbarItem( aAddonStatusbarItemTreeNode ) ); + + // Command URL is required + if (( aStatusbarItemNodePropValues[ OFFSET_STATUSBARITEM_URL ] >>= aURL ) && aURL.getLength() > 0 ) + { + auto pStatusbarItem = aStatusbarItem.getArray(); + pStatusbarItem[ OFFSET_STATUSBARITEM_URL ].Value <<= aURL; + pStatusbarItem[ OFFSET_STATUSBARITEM_TITLE ].Value = aStatusbarItemNodePropValues[ OFFSET_STATUSBARITEM_TITLE ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_CONTEXT ].Value = aStatusbarItemNodePropValues[ OFFSET_STATUSBARITEM_CONTEXT ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_ALIGN ].Value = aStatusbarItemNodePropValues[ OFFSET_STATUSBARITEM_ALIGN ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_AUTOSIZE ].Value = aStatusbarItemNodePropValues[ OFFSET_STATUSBARITEM_AUTOSIZE ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_OWNERDRAW ].Value = aStatusbarItemNodePropValues[ OFFSET_STATUSBARITEM_OWNERDRAW ]; + pStatusbarItem[ OFFSET_STATUSBARITEM_MANDATORY ].Value = aStatusbarItemNodePropValues[ OFFSET_STATUSBARITEM_MANDATORY ]; + + // Configuration uses hyper for long. Therefore transform into sal_Int32 + sal_Int64 nValue( 0 ); + aStatusbarItemNodePropValues[ OFFSET_STATUSBARITEM_WIDTH ] >>= nValue; + pStatusbarItem[ OFFSET_STATUSBARITEM_WIDTH ].Value <<= sal_Int32( nValue ); + + bResult = true; + } + + return bResult; +} + +bool AddonsOptions_Impl::ReadMenuItem( std::u16string_view aMenuNodeName, Sequence< PropertyValue >& aMenuItem, bool bIgnoreSubMenu ) +{ + bool bResult = false; + OUString aStrValue; + OUString aAddonMenuItemTreeNode( aMenuNodeName + m_aPathDelimiter ); + + Sequence< Any > aMenuItemNodePropValues = GetProperties( GetPropertyNamesMenuItem( aAddonMenuItemTreeNode ) ); + if (( aMenuItemNodePropValues[ OFFSET_MENUITEM_TITLE ] >>= aStrValue ) && !aStrValue.isEmpty() ) + { + auto pMenuItem = aMenuItem.getArray(); + pMenuItem[ OFFSET_MENUITEM_TITLE ].Value <<= aStrValue; + + OUString aRootSubMenuName( aAddonMenuItemTreeNode + m_aPropNames[ INDEX_SUBMENU ] ); + Sequence< OUString > aRootSubMenuNodeNames = GetNodeNames( aRootSubMenuName ); + if ( aRootSubMenuNodeNames.hasElements() && !bIgnoreSubMenu ) + { + // Set a unique prefixed Add-On popup menu URL so it can be identified later + OUString aPopupMenuURL = GeneratePrefixURL(); + OUString aPopupMenuImageId; + + aMenuItemNodePropValues[ OFFSET_MENUITEM_IMAGEIDENTIFIER ] >>= aPopupMenuImageId; + ReadAndAssociateImages( aPopupMenuURL, aPopupMenuImageId ); + + // A popup menu must have a title and can have a URL and ImageIdentifier + // Set the other property values to empty + pMenuItem[ OFFSET_MENUITEM_URL ].Value <<= aPopupMenuURL; + pMenuItem[ OFFSET_MENUITEM_TARGET ].Value <<= OUString(); + pMenuItem[ OFFSET_MENUITEM_IMAGEIDENTIFIER ].Value <<= aPopupMenuImageId; + pMenuItem[ OFFSET_MENUITEM_CONTEXT ].Value = aMenuItemNodePropValues[ OFFSET_MENUITEM_CONTEXT ]; + + // Continue to read the sub menu nodes + Sequence< Sequence< PropertyValue > > aSubMenuSeq; + OUString aSubMenuRootNodeName( aRootSubMenuName + m_aPathDelimiter ); + for ( OUString& rName : asNonConstRange(aRootSubMenuNodeNames) ) + rName = aSubMenuRootNodeName + rName; + ReadSubMenuEntries( aRootSubMenuNodeNames, aSubMenuSeq ); + pMenuItem[ OFFSET_MENUITEM_SUBMENU ].Value <<= aSubMenuSeq; + bResult = true; + } + else if (( aMenuItemNodePropValues[ OFFSET_MENUITEM_URL ] >>= aStrValue ) && !aStrValue.isEmpty() ) + { + // A simple menu item => read the other properties; + OUString aMenuImageId; + + aMenuItemNodePropValues[ OFFSET_MENUITEM_IMAGEIDENTIFIER ] >>= aMenuImageId; + ReadAndAssociateImages( aStrValue, aMenuImageId ); + + pMenuItem[ OFFSET_MENUITEM_URL ].Value <<= aStrValue; + pMenuItem[ OFFSET_MENUITEM_TARGET ].Value = aMenuItemNodePropValues[ OFFSET_MENUITEM_TARGET ]; + pMenuItem[ OFFSET_MENUITEM_IMAGEIDENTIFIER ].Value <<= aMenuImageId; + pMenuItem[ OFFSET_MENUITEM_CONTEXT ].Value = aMenuItemNodePropValues[ OFFSET_MENUITEM_CONTEXT ]; + pMenuItem[ OFFSET_MENUITEM_SUBMENU ].Value <<= Sequence< Sequence< PropertyValue > >(); // Submenu set! + + bResult = true; + } + } + else if (( aMenuItemNodePropValues[ OFFSET_MENUITEM_URL ] >>= aStrValue ) && + aStrValue == SEPARATOR_URL ) + { + auto pMenuItem = aMenuItem.getArray(); + + // Separator + pMenuItem[ OFFSET_MENUITEM_URL ].Value <<= aStrValue; + pMenuItem[ OFFSET_MENUITEM_TARGET ].Value <<= OUString(); + pMenuItem[ OFFSET_MENUITEM_IMAGEIDENTIFIER ].Value <<= OUString(); + pMenuItem[ OFFSET_MENUITEM_CONTEXT ].Value <<= OUString(); + pMenuItem[ OFFSET_MENUITEM_SUBMENU ].Value <<= Sequence< Sequence< PropertyValue > >(); // Submenu set! + bResult = true; + } + + return bResult; +} + +bool AddonsOptions_Impl::ReadPopupMenu( std::u16string_view aPopupMenuNodeName, Sequence< PropertyValue >& aPopupMenu ) +{ + bool bResult = false; + OUString aStrValue; + OUString aAddonPopupMenuTreeNode( aPopupMenuNodeName + m_aPathDelimiter ); + + Sequence< Any > aPopupMenuNodePropValues = GetProperties( GetPropertyNamesPopupMenu( aAddonPopupMenuTreeNode ) ); + if (( aPopupMenuNodePropValues[ OFFSET_POPUPMENU_TITLE ] >>= aStrValue ) && + !aStrValue.isEmpty() ) + { + auto pPopupMenu = aPopupMenu.getArray(); + pPopupMenu[ OFFSET_POPUPMENU_TITLE ].Value <<= aStrValue; + + OUString aRootSubMenuName( aAddonPopupMenuTreeNode + m_aPropNames[ INDEX_SUBMENU ] ); + Sequence< OUString > aRootSubMenuNodeNames = GetNodeNames( aRootSubMenuName ); + if ( aRootSubMenuNodeNames.hasElements() ) + { + // A top-level popup menu needs a title + // Set a unique prefixed Add-On popup menu URL so it can be identified later + OUString aPopupMenuURL = GeneratePrefixURL(); + + pPopupMenu[ OFFSET_POPUPMENU_URL ].Value <<= aPopupMenuURL; + pPopupMenu[ OFFSET_POPUPMENU_CONTEXT ].Value = aPopupMenuNodePropValues[ OFFSET_POPUPMENU_CONTEXT ]; + + // Continue to read the sub menu nodes + Sequence< Sequence< PropertyValue > > aSubMenuSeq; + OUString aSubMenuRootNodeName( aRootSubMenuName + m_aPathDelimiter ); + for ( OUString& rName : asNonConstRange(aRootSubMenuNodeNames) ) + rName = aSubMenuRootNodeName + rName; + ReadSubMenuEntries( aRootSubMenuNodeNames, aSubMenuSeq ); + pPopupMenu[ OFFSET_POPUPMENU_SUBMENU ].Value <<= aSubMenuSeq; + bResult = true; + } + } + + return bResult; +} + +void AddonsOptions_Impl::AppendPopupMenu( Sequence< PropertyValue >& rTargetPopupMenu, const Sequence< PropertyValue >& rSourcePopupMenu ) +{ + Sequence< Sequence< PropertyValue > > aTargetSubMenuSeq; + Sequence< Sequence< PropertyValue > > aSourceSubMenuSeq; + + if (( rTargetPopupMenu[ OFFSET_POPUPMENU_SUBMENU ].Value >>= aTargetSubMenuSeq ) && + ( rSourcePopupMenu[ OFFSET_POPUPMENU_SUBMENU ].Value >>= aSourceSubMenuSeq )) + { + sal_uInt32 nIndex = aTargetSubMenuSeq.getLength(); + aTargetSubMenuSeq.realloc( nIndex + aSourceSubMenuSeq.getLength() ); + auto pTargetSubMenuSeq = aTargetSubMenuSeq.getArray(); + for ( Sequence<PropertyValue> const & rSeq : std::as_const(aSourceSubMenuSeq) ) + pTargetSubMenuSeq[nIndex++] = rSeq; + rTargetPopupMenu.getArray()[ OFFSET_POPUPMENU_SUBMENU ].Value <<= aTargetSubMenuSeq; + } +} + +bool AddonsOptions_Impl::ReadToolBarItem( std::u16string_view aToolBarItemNodeName, Sequence< PropertyValue >& aToolBarItem ) +{ + bool bResult = false; + OUString aURL; + OUString aAddonToolBarItemTreeNode( aToolBarItemNodeName + m_aPathDelimiter ); + + Sequence< Any > aToolBarItemNodePropValues = GetProperties( GetPropertyNamesToolBarItem( aAddonToolBarItemTreeNode ) ); + + // A toolbar item must have a command URL + if (( aToolBarItemNodePropValues[ OFFSET_TOOLBARITEM_URL ] >>= aURL ) && !aURL.isEmpty() ) + { + OUString aTitle; + if ( aURL == SEPARATOR_URL ) + { + auto pToolBarItem = aToolBarItem.getArray(); + + // A separator toolbar item only needs a URL + pToolBarItem[ OFFSET_TOOLBARITEM_URL ].Value <<= aURL; + pToolBarItem[ OFFSET_TOOLBARITEM_TITLE ].Value <<= OUString(); + pToolBarItem[ OFFSET_TOOLBARITEM_TARGET ].Value <<= OUString(); + pToolBarItem[ OFFSET_TOOLBARITEM_IMAGEIDENTIFIER ].Value <<= OUString(); + pToolBarItem[ OFFSET_TOOLBARITEM_CONTEXT ].Value <<= OUString(); + pToolBarItem[ OFFSET_TOOLBARITEM_CONTROLTYPE ].Value <<= OUString(); + pToolBarItem[ OFFSET_TOOLBARITEM_WIDTH ].Value <<= sal_Int32( 0 ); + + bResult = true; + } + else if (( aToolBarItemNodePropValues[ OFFSET_TOOLBARITEM_TITLE ] >>= aTitle ) && !aTitle.isEmpty() ) + { + auto pToolBarItem = aToolBarItem.getArray(); + + // A normal toolbar item must also have title => read the other properties; + OUString aImageId; + + // Try to map a user-defined image URL to our internal private image URL + aToolBarItemNodePropValues[ OFFSET_TOOLBARITEM_IMAGEIDENTIFIER ] >>= aImageId; + ReadAndAssociateImages( aURL, aImageId ); + + pToolBarItem[ OFFSET_TOOLBARITEM_URL ].Value <<= aURL; + pToolBarItem[ OFFSET_TOOLBARITEM_TITLE ].Value <<= aTitle; + pToolBarItem[ OFFSET_TOOLBARITEM_TARGET ].Value = aToolBarItemNodePropValues[ OFFSET_TOOLBARITEM_TARGET ]; + pToolBarItem[ OFFSET_TOOLBARITEM_IMAGEIDENTIFIER ].Value <<= aImageId; + pToolBarItem[ OFFSET_TOOLBARITEM_CONTEXT ].Value = aToolBarItemNodePropValues[ OFFSET_TOOLBARITEM_CONTEXT ]; + pToolBarItem[ OFFSET_TOOLBARITEM_CONTROLTYPE ].Value = aToolBarItemNodePropValues[ OFFSET_TOOLBARITEM_CONTROLTYPE ]; + + // Configuration uses hyper for long. Therefore transform into sal_Int32 + sal_Int64 nValue( 0 ); + aToolBarItemNodePropValues[ OFFSET_TOOLBARITEM_WIDTH ] >>= nValue; + pToolBarItem[ OFFSET_TOOLBARITEM_WIDTH ].Value <<= sal_Int32( nValue ); + + bResult = true; + } + } + + return bResult; +} + +bool AddonsOptions_Impl::ReadNotebookBarItem( std::u16string_view aNotebookBarItemNodeName, Sequence< PropertyValue >& aNotebookBarItem ) +{ + bool bResult = false; + OUString aURL; + OUString aAddonNotebookBarItemTreeNode( aNotebookBarItemNodeName + m_aPathDelimiter ); + + Sequence< Any > aNotebookBarItemNodePropValues = GetProperties( GetPropertyNamesNotebookBarItem( aAddonNotebookBarItemTreeNode ) ); + + // A toolbar item must have a command URL + if (( aNotebookBarItemNodePropValues[ OFFSET_NOTEBOOKBARITEM_URL ] >>= aURL ) && !aURL.isEmpty() ) + { + OUString aTitle; + if ( aURL == SEPARATOR_URL ) + { + auto pNotebookBarItem = aNotebookBarItem.getArray(); + + // A separator toolbar item only needs a URL + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_URL ].Value <<= aURL; + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_TITLE ].Value <<= OUString(); + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_TARGET ].Value <<= OUString(); + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_IMAGEIDENTIFIER ].Value <<= OUString(); + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_CONTEXT ].Value <<= OUString(); + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_CONTROLTYPE ].Value <<= OUString(); + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_WIDTH ].Value <<= sal_Int32( 0 ); + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_STYLE ].Value <<= OUString(); + + bResult = true; + } + else if (( aNotebookBarItemNodePropValues[ OFFSET_NOTEBOOKBARITEM_TITLE ] >>= aTitle ) && !aTitle.isEmpty() ) + { + auto pNotebookBarItem = aNotebookBarItem.getArray(); + + // A normal toolbar item must also have title => read the other properties; + OUString aImageId; + + // Try to map a user-defined image URL to our internal private image URL + aNotebookBarItemNodePropValues[ OFFSET_NOTEBOOKBARITEM_IMAGEIDENTIFIER ] >>= aImageId; + ReadAndAssociateImages( aURL, aImageId ); + + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_URL ].Value <<= aURL; + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_TITLE ].Value <<= aTitle; + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_TARGET ].Value = aNotebookBarItemNodePropValues[ OFFSET_NOTEBOOKBARITEM_TARGET ]; + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_IMAGEIDENTIFIER ].Value <<= aImageId; + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_CONTEXT ].Value = aNotebookBarItemNodePropValues[ OFFSET_NOTEBOOKBARITEM_CONTEXT ]; + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_CONTROLTYPE ].Value = aNotebookBarItemNodePropValues[ OFFSET_NOTEBOOKBARITEM_CONTROLTYPE ]; + + // Configuration uses hyper for long. Therefore transform into sal_Int32 + sal_Int64 nValue( 0 ); + aNotebookBarItemNodePropValues[ OFFSET_NOTEBOOKBARITEM_WIDTH ] >>= nValue; + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_WIDTH ].Value <<= sal_Int32( nValue ); + pNotebookBarItem[ OFFSET_NOTEBOOKBARITEM_STYLE ].Value = aNotebookBarItemNodePropValues[ OFFSET_NOTEBOOKBARITEM_STYLE ]; + + bResult = true; + } + } + + return bResult; +} + +void AddonsOptions_Impl::ReadSubMenuEntries( const Sequence< OUString >& aSubMenuNodeNames, Sequence< Sequence< PropertyValue > >& rSubMenuSeq ) +{ + Sequence< PropertyValue > aMenuItem( PROPERTYCOUNT_MENUITEM ); + auto pMenuItem = aMenuItem.getArray(); + + // Init the property value sequence + pMenuItem[ OFFSET_MENUITEM_URL ].Name = PROPERTYNAME_URL; + pMenuItem[ OFFSET_MENUITEM_TITLE ].Name = PROPERTYNAME_TITLE; + pMenuItem[ OFFSET_MENUITEM_TARGET ].Name = PROPERTYNAME_TARGET; + pMenuItem[ OFFSET_MENUITEM_IMAGEIDENTIFIER ].Name = PROPERTYNAME_IMAGEIDENTIFIER; + pMenuItem[ OFFSET_MENUITEM_CONTEXT ].Name = PROPERTYNAME_CONTEXT; + pMenuItem[ OFFSET_MENUITEM_SUBMENU ].Name = PROPERTYNAME_SUBMENU; // Submenu set! + + sal_uInt32 nIndex = 0; + sal_uInt32 nCount = aSubMenuNodeNames.getLength(); + for ( sal_uInt32 n = 0; n < nCount; n++ ) + { + if ( ReadMenuItem( aSubMenuNodeNames[n], aMenuItem )) + { + sal_uInt32 nSubMenuCount = rSubMenuSeq.getLength() + 1; + rSubMenuSeq.realloc( nSubMenuCount ); + rSubMenuSeq.getArray()[nIndex++] = aMenuItem; + } + } +} + +bool AddonsOptions_Impl::HasAssociatedImages( const OUString& aURL ) +{ + // FIXME: potentially this is not so useful in a world of delayed image loading + ImageManager::const_iterator pIter = m_aImageManager.find( aURL ); + return ( pIter != m_aImageManager.end() ); +} + +void AddonsOptions_Impl::SubstituteVariables( OUString& aURL ) +{ + aURL = comphelper::getExpandedUri( + comphelper::getProcessComponentContext(), aURL); +} + +BitmapEx AddonsOptions_Impl::ReadImageFromURL(const OUString& aImageURL) +{ + BitmapEx aImage; + + std::unique_ptr<SvStream> pStream = UcbStreamHelper::CreateStream( aImageURL, StreamMode::STD_READ ); + if ( pStream && ( pStream->GetErrorCode() == ERRCODE_NONE )) + { + // Use graphic class to also support more graphic formats (bmp,png,...) + Graphic aGraphic; + + GraphicFilter& rGF = GraphicFilter::GetGraphicFilter(); + rGF.ImportGraphic( aGraphic, u"", *pStream ); + + BitmapEx aBitmapEx = aGraphic.GetBitmapEx(); + + Size aBmpSize = aBitmapEx.GetSizePixel(); + if ( !aBmpSize.IsEmpty() ) + { + // Support non-transparent bitmaps to be downward compatible with OOo 1.1.x addons + if( !aBitmapEx.IsAlpha() ) + aBitmapEx = BitmapEx( aBitmapEx.GetBitmap(), COL_LIGHTMAGENTA ); + + aImage = aBitmapEx; + } + } + + return aImage; +} + +void AddonsOptions_Impl::ReadAndAssociateImages( const OUString& aURL, const OUString& aImageId ) +{ + if ( aImageId.isEmpty() ) + return; + + ImageEntry aImageEntry; + OUString aImageURL( aImageId ); + + SubstituteVariables( aImageURL ); + + // Loop to create the two possible image names and try to read the bitmap files + static const char* aExtArray[] = { "_16", "_26" }; + for ( size_t i = 0; i < SAL_N_ELEMENTS(aExtArray); i++ ) + { + OUStringBuffer aFileURL( aImageURL ); + aFileURL.appendAscii( aExtArray[i] ); + aFileURL.append( ".bmp" ); + + aImageEntry.addImage( !i ? IMGSIZE_SMALL : IMGSIZE_BIG, aFileURL.makeStringAndClear() ); + } + + m_aImageManager.emplace( aURL, aImageEntry ); +} + +std::unique_ptr<AddonsOptions_Impl::ImageEntry> AddonsOptions_Impl::ReadImageData( std::u16string_view aImagesNodeName ) +{ + Sequence< OUString > aImageDataNodeNames = GetPropertyNamesImages( aImagesNodeName ); + Sequence< Any > aPropertyData; + Sequence< sal_Int8 > aImageDataSeq; + OUString aImageURL; + + std::unique_ptr<ImageEntry> pEntry; + + // It is possible to use both forms (embedded image data and URLs to external bitmap files) at the + // same time. Embedded image data has a higher priority. + aPropertyData = GetProperties( aImageDataNodeNames ); + for ( int i = 0; i < PROPERTYCOUNT_IMAGES; i++ ) + { + if ( i < PROPERTYCOUNT_EMBEDDED_IMAGES ) + { + // Extract image data from the embedded hex binary sequence + BitmapEx aImage; + if (( aPropertyData[i] >>= aImageDataSeq ) && + aImageDataSeq.hasElements() && + ( CreateImageFromSequence( aImage, aImageDataSeq ) ) ) + { + if ( !pEntry ) + pEntry.reset(new ImageEntry); + pEntry->addImage(i == OFFSET_IMAGES_SMALL ? IMGSIZE_SMALL : IMGSIZE_BIG, aImage); + } + } + else if ( i == OFFSET_IMAGES_SMALL_URL || i == OFFSET_IMAGES_BIG_URL ) + { + if(!pEntry) + pEntry.reset(new ImageEntry()); + + // Retrieve image data from an external bitmap file. Make sure that embedded image data + // has a higher priority. + if (aPropertyData[i] >>= aImageURL) + { + SubstituteVariables(aImageURL); + pEntry->addImage(i == OFFSET_IMAGES_SMALL_URL ? IMGSIZE_SMALL : IMGSIZE_BIG, aImageURL); + } + } + } + + return pEntry; +} + +bool AddonsOptions_Impl::CreateImageFromSequence( BitmapEx& rImage, Sequence< sal_Int8 >& rBitmapDataSeq ) const +{ + bool bResult = false; + + if ( rBitmapDataSeq.hasElements() ) + { + SvMemoryStream aMemStream( rBitmapDataSeq.getArray(), rBitmapDataSeq.getLength(), StreamMode::STD_READ ); + + ReadDIBBitmapEx(rImage, aMemStream); + + if( !rImage.IsAlpha() ) + { + // Support non-transparent bitmaps to be downward compatible with OOo 1.1.x addons + rImage = BitmapEx( rImage.GetBitmap(), COL_LIGHTMAGENTA ); + } + + bResult = true; + } + + return bResult; +} + +Sequence< OUString > AddonsOptions_Impl::GetPropertyNamesMenuItem( std::u16string_view aPropertyRootNode ) const +{ + Sequence< OUString > lResult( PROPERTYCOUNT_MENUITEM ); + auto plResult = lResult.getArray(); + + // Create property names dependent from the root node name + plResult[OFFSET_MENUITEM_URL] = aPropertyRootNode + m_aPropNames[ INDEX_URL ]; + plResult[OFFSET_MENUITEM_TITLE] = aPropertyRootNode + m_aPropNames[ INDEX_TITLE ]; + plResult[OFFSET_MENUITEM_IMAGEIDENTIFIER] = aPropertyRootNode + m_aPropNames[ INDEX_IMAGEIDENTIFIER ]; + plResult[OFFSET_MENUITEM_TARGET] = aPropertyRootNode + m_aPropNames[ INDEX_TARGET ]; + plResult[OFFSET_MENUITEM_CONTEXT] = aPropertyRootNode + m_aPropNames[ INDEX_CONTEXT ]; + plResult[OFFSET_MENUITEM_SUBMENU] = aPropertyRootNode + m_aPropNames[ INDEX_SUBMENU ]; + + return lResult; +} + +Sequence< OUString > AddonsOptions_Impl::GetPropertyNamesPopupMenu( std::u16string_view aPropertyRootNode ) const +{ + // The URL is automatically set and not read from the configuration. + Sequence< OUString > lResult( PROPERTYCOUNT_POPUPMENU-1 ); + auto plResult = lResult.getArray(); + + // Create property names dependent from the root node name + plResult[OFFSET_POPUPMENU_TITLE] = aPropertyRootNode + m_aPropNames[ INDEX_TITLE ]; + plResult[OFFSET_POPUPMENU_CONTEXT] = aPropertyRootNode + m_aPropNames[ INDEX_CONTEXT ]; + plResult[OFFSET_POPUPMENU_SUBMENU] = aPropertyRootNode + m_aPropNames[ INDEX_SUBMENU ]; + + return lResult; +} + +Sequence< OUString > AddonsOptions_Impl::GetPropertyNamesToolBarItem( std::u16string_view aPropertyRootNode ) const +{ + Sequence< OUString > lResult( PROPERTYCOUNT_TOOLBARITEM ); + auto plResult = lResult.getArray(); + + // Create property names dependent from the root node name + plResult[0] = aPropertyRootNode + m_aPropNames[ INDEX_URL ]; + plResult[1] = aPropertyRootNode + m_aPropNames[ INDEX_TITLE ]; + plResult[2] = aPropertyRootNode + m_aPropNames[ INDEX_IMAGEIDENTIFIER]; + plResult[3] = aPropertyRootNode + m_aPropNames[ INDEX_TARGET ]; + plResult[4] = aPropertyRootNode + m_aPropNames[ INDEX_CONTEXT ]; + plResult[5] = aPropertyRootNode + m_aPropNames[ INDEX_CONTROLTYPE ]; + plResult[6] = aPropertyRootNode + m_aPropNames[ INDEX_WIDTH ]; + + return lResult; +} + +Sequence< OUString > AddonsOptions_Impl::GetPropertyNamesNotebookBarItem( std::u16string_view aPropertyRootNode ) const +{ + Sequence< OUString > lResult( PROPERTYCOUNT_NOTEBOOKBARITEM ); + auto plResult = lResult.getArray(); + + // Create property names dependent from the root node name + plResult[0] = aPropertyRootNode + m_aPropNames[ INDEX_URL ]; + plResult[1] = aPropertyRootNode + m_aPropNames[ INDEX_TITLE ]; + plResult[2] = aPropertyRootNode + m_aPropNames[ INDEX_IMAGEIDENTIFIER]; + plResult[3] = aPropertyRootNode + m_aPropNames[ INDEX_TARGET ]; + plResult[4] = aPropertyRootNode + m_aPropNames[ INDEX_CONTEXT ]; + plResult[5] = aPropertyRootNode + m_aPropNames[ INDEX_CONTROLTYPE ]; + plResult[6] = aPropertyRootNode + m_aPropNames[ INDEX_WIDTH ]; + plResult[7] = aPropertyRootNode + m_aPropNames[ INDEX_STYLE ]; + + return lResult; +} + +Sequence< OUString > AddonsOptions_Impl::GetPropertyNamesStatusbarItem( + std::u16string_view aPropertyRootNode ) const +{ + Sequence< OUString > lResult( PROPERTYCOUNT_STATUSBARITEM ); + auto plResult = lResult.getArray(); + + plResult[0] = OUString( aPropertyRootNode + m_aPropNames[ INDEX_URL ] ); + plResult[1] = OUString( aPropertyRootNode + m_aPropNames[ INDEX_TITLE ] ); + plResult[2] = OUString( aPropertyRootNode + m_aPropNames[ INDEX_CONTEXT ] ); + plResult[3] = OUString( aPropertyRootNode + m_aPropNames[ INDEX_ALIGN ] ); + plResult[4] = OUString( aPropertyRootNode + m_aPropNames[ INDEX_AUTOSIZE ] ); + plResult[5] = OUString( aPropertyRootNode + m_aPropNames[ INDEX_OWNERDRAW ] ); + plResult[6] = OUString( aPropertyRootNode + m_aPropNames[ INDEX_MANDATORY ] ); + plResult[7] = OUString( aPropertyRootNode + m_aPropNames[ INDEX_WIDTH ] ); + + return lResult; +} + +Sequence< OUString > AddonsOptions_Impl::GetPropertyNamesImages( std::u16string_view aPropertyRootNode ) const +{ + Sequence< OUString > lResult( PROPERTYCOUNT_IMAGES ); + auto plResult = lResult.getArray(); + + // Create property names dependent from the root node name + plResult[0] = aPropertyRootNode + m_aPropImagesNames[ OFFSET_IMAGES_SMALL ]; + plResult[1] = aPropertyRootNode + m_aPropImagesNames[ OFFSET_IMAGES_BIG ]; + plResult[2] = aPropertyRootNode + m_aPropImagesNames[ OFFSET_IMAGES_SMALLHC ]; + plResult[3] = aPropertyRootNode + m_aPropImagesNames[ OFFSET_IMAGES_BIGHC ]; + plResult[4] = aPropertyRootNode + m_aPropImagesNames[ OFFSET_IMAGES_SMALL_URL ]; + plResult[5] = aPropertyRootNode + m_aPropImagesNames[ OFFSET_IMAGES_BIG_URL ]; + plResult[6] = aPropertyRootNode + m_aPropImagesNames[ OFFSET_IMAGES_SMALLHC_URL]; + plResult[7] = aPropertyRootNode + m_aPropImagesNames[ OFFSET_IMAGES_BIGHC_URL ]; + + return lResult; +} + +namespace{ + //global + std::weak_ptr<AddonsOptions_Impl> g_pAddonsOptions; +} + +AddonsOptions::AddonsOptions() +{ + // Global access, must be guarded (multithreading!). + MutexGuard aGuard( GetOwnStaticMutex() ); + + m_pImpl = g_pAddonsOptions.lock(); + if( !m_pImpl ) + { + m_pImpl = std::make_shared<AddonsOptions_Impl>(); + g_pAddonsOptions = m_pImpl; + } +} + +AddonsOptions::~AddonsOptions() +{ + // Global access, must be guarded (multithreading!) + MutexGuard aGuard( GetOwnStaticMutex() ); + + m_pImpl.reset(); +} + +// public method + +bool AddonsOptions::HasAddonsMenu() const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->HasAddonsMenu(); +} + +// public method + +sal_Int32 AddonsOptions::GetAddonsToolBarCount() const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetAddonsToolBarCount(); +} + +// public method + +sal_Int32 AddonsOptions::GetAddonsNotebookBarCount() const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetAddonsNotebookBarCount(); +} + +// public method + +const Sequence< Sequence< PropertyValue > >& AddonsOptions::GetAddonsMenu() const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetAddonsMenu(); +} + +// public method + +const Sequence< Sequence< PropertyValue > >& AddonsOptions::GetAddonsMenuBarPart() const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetAddonsMenuBarPart(); +} + +// public method + +const Sequence< Sequence< PropertyValue > >& AddonsOptions::GetAddonsToolBarPart( sal_uInt32 nIndex ) const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetAddonsToolBarPart( nIndex ); +} + +// public method + +const Sequence< Sequence< PropertyValue > >& AddonsOptions::GetAddonsNotebookBarPart( sal_uInt32 nIndex ) const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetAddonsNotebookBarPart( nIndex ); +} + +// public method + +OUString AddonsOptions::GetAddonsToolbarResourceName( sal_uInt32 nIndex ) const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetAddonsToolbarResourceName( nIndex ); +} + +// public method + +OUString AddonsOptions::GetAddonsNotebookBarResourceName( sal_uInt32 nIndex ) const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetAddonsNotebookBarResourceName( nIndex ); +} + +// public method + +const Sequence< Sequence< PropertyValue > >& AddonsOptions::GetAddonsHelpMenu() const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetAddonsHelpMenu(); +} + +// public method + +const MergeMenuInstructionContainer& AddonsOptions::GetMergeMenuInstructions() const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetMergeMenuInstructions(); +} + +// public method + +bool AddonsOptions::GetMergeToolbarInstructions( + const OUString& rToolbarName, + MergeToolbarInstructionContainer& rToolbarInstructions ) const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetMergeToolbarInstructions( + rToolbarName, rToolbarInstructions ); +} + +// public method + +bool AddonsOptions::GetMergeNotebookBarInstructions( + const OUString& rNotebookBarName, + MergeNotebookBarInstructionContainer& rNotebookBarInstructions ) const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetMergeNotebookBarInstructions( + rNotebookBarName, rNotebookBarInstructions ); +} + +//public method + +const MergeStatusbarInstructionContainer& AddonsOptions::GetMergeStatusbarInstructions() const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetMergeStatusbarInstructions(); +} + +// public method + +BitmapEx AddonsOptions::GetImageFromURL( const OUString& aURL, bool bBig, bool bNoScale ) const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetImageFromURL( aURL, bBig, bNoScale ); +} + +// public method + +BitmapEx AddonsOptions::GetImageFromURL( const OUString& aURL, bool bBig ) const +{ + return GetImageFromURL( aURL, bBig, false ); +} + +Mutex& AddonsOptions::GetOwnStaticMutex() +{ + // Create static mutex variable. + static Mutex ourMutex; + + return ourMutex; +} + +IMPL_LINK_NOARG(AddonsOptions_Impl, NotifyEvent, void*, void) +{ + MutexGuard aGuard(AddonsOptions::GetOwnStaticMutex()); + ReadConfigurationData(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/classes/framelistanalyzer.cxx b/framework/source/fwe/classes/framelistanalyzer.cxx new file mode 100644 index 000000000..55edf3147 --- /dev/null +++ b/framework/source/fwe/classes/framelistanalyzer.cxx @@ -0,0 +1,260 @@ +/* -*- 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 <framework/framelistanalyzer.hxx> + +#include <targets.h> +#include <properties.h> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/UnknownModuleException.hpp> +#include <com/sun/star/frame/XFrame.hpp> + +#include <comphelper/processfactory.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> + +namespace framework{ + +FrameListAnalyzer::FrameListAnalyzer( const css::uno::Reference< css::frame::XFramesSupplier >& xSupplier , + const css::uno::Reference< css::frame::XFrame >& xReferenceFrame , + FrameAnalyzerFlags eDetectMode ) + : m_xSupplier (xSupplier ) + , m_xReferenceFrame(xReferenceFrame) + , m_eDetectMode (eDetectMode ) +{ + impl_analyze(); +} + +FrameListAnalyzer::~FrameListAnalyzer() +{ +} + +/** returns an analyzed list of all currently opened (top!) frames inside the desktop tree. + + We try to get a snapshot of all opened frames, which are part of the desktop frame container. + Of course we can't access frames, which stands outside of this tree. + But it's necessary to collect top frames here only. Otherwise we interpret closing of last + frame wrong. Further we analyze this list and split into different parts. + E.g. for "CloseDoc" we must know, which frames of the given list refer to the same model. + These frames must be closed then. But all other frames must be untouched. + In case the request was "CloseWin" these split lists can be used too, to decide if the last window + or document was closed. Then we have to initialize the backing window... + Last but not least we must know something about our special help frame. It must be handled + separately. And last but not least - the backing component frame must be detected too. +*/ + +void FrameListAnalyzer::impl_analyze() +{ + // reset all members to get a consistent state + m_bReferenceIsHidden = false; + m_bReferenceIsHelp = false; + m_bReferenceIsBacking = false; + m_xHelp.clear(); + m_xBackingComponent.clear(); + + // try to get the task container by using the given supplier + css::uno::Reference< css::container::XIndexAccess > xFrameContainer = m_xSupplier->getFrames(); + + // All return list get an initial size to include all possible frames. + // They will be packed at the end of this method ... using the actual step positions then. + sal_Int32 nVisibleStep = 0; + sal_Int32 nHiddenStep = 0; + sal_Int32 nModelStep = 0; + sal_Int32 nCount = xFrameContainer->getCount(); + + m_lOtherVisibleFrames.resize(nCount); + m_lOtherHiddenFrames.resize(nCount); + m_lModelFrames.resize(nCount); + + // ask for the model of the given reference frame. + // It must be compared with the model of every frame of the container + // to sort it into the list of frames with the same model. + // Suppress this step, if right detect mode isn't set. + css::uno::Reference< css::frame::XModel > xReferenceModel; + if (m_eDetectMode & FrameAnalyzerFlags::Model) + { + css::uno::Reference< css::frame::XController > xReferenceController; + if (m_xReferenceFrame.is()) + xReferenceController = m_xReferenceFrame->getController(); + if (xReferenceController.is()) + xReferenceModel = xReferenceController->getModel(); + } + + // check, if the reference frame is in hidden mode. + // But look, if this analyze step is really needed. + css::uno::Reference< css::beans::XPropertySet > xSet(m_xReferenceFrame, css::uno::UNO_QUERY); + if ( (m_eDetectMode & FrameAnalyzerFlags::Hidden) && xSet.is() ) + { + xSet->getPropertyValue(FRAME_PROPNAME_ASCII_ISHIDDEN) >>= m_bReferenceIsHidden; + } + + // check, if the reference frame includes the backing component. + // But look, if this analyze step is really needed. + if ((m_eDetectMode & FrameAnalyzerFlags::BackingComponent) && m_xReferenceFrame.is() ) + { + try + { + css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + css::uno::Reference< css::frame::XModuleManager2 > xModuleMgr = css::frame::ModuleManager::create(xContext); + OUString sModule = xModuleMgr->identify(m_xReferenceFrame); + m_bReferenceIsBacking = sModule == "com.sun.star.frame.StartModule"; + } + catch(const css::frame::UnknownModuleException&) + { + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + } + + // check, if the reference frame includes the help module. + // But look, if this analyze step is really needed. + if ( + (m_eDetectMode & FrameAnalyzerFlags::Help) && + (m_xReferenceFrame.is() ) && + (m_xReferenceFrame->getName() == SPECIALTARGET_HELPTASK) + ) + { + m_bReferenceIsHelp = true; + } + + try + { + // Step over all frames of the desktop frame container and analyze it. + for (sal_Int32 i=0; i<nCount; ++i) + { + // Ignore invalid items ... and of course the reference frame. + // It will be a member of the given frame list too - but it was already + // analyzed before! + css::uno::Reference< css::frame::XFrame > xFrame; + if ( + !(xFrameContainer->getByIndex(i) >>= xFrame) || + !(xFrame.is() ) || + (xFrame==m_xReferenceFrame ) + ) + continue; + + if ( + (m_eDetectMode & FrameAnalyzerFlags::Zombie) && + ( + (!xFrame->getContainerWindow().is()) || + (!xFrame->getComponentWindow().is()) + ) + ) + { + SAL_INFO("fwk", "FrameListAnalyzer::impl_analyze(): ZOMBIE!"); + } + + // a) Is it the special help task? + // Return it separated from any return list. + if ( + (m_eDetectMode & FrameAnalyzerFlags::Help) && + (xFrame->getName()==SPECIALTARGET_HELPTASK) + ) + { + m_xHelp = xFrame; + continue; + } + + // b) Or is includes this task the special backing component? + // Return it separated from any return list. + // But check if the reference task itself is the backing frame. + // Our user must know it to decide right. + if (m_eDetectMode & FrameAnalyzerFlags::BackingComponent) + { + try + { + css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + css::uno::Reference< css::frame::XModuleManager2 > xModuleMgr = css::frame::ModuleManager::create(xContext); + OUString sModule = xModuleMgr->identify(xFrame); + if (sModule == "com.sun.star.frame.StartModule") + { + m_xBackingComponent = xFrame; + continue; + } + } + catch (const css::uno::Exception&) + { + } + } + + // c) Or is it the a task, which uses the specified model? + // Add it to the list of "model frames". + if (m_eDetectMode & FrameAnalyzerFlags::Model) + { + css::uno::Reference< css::frame::XController > xController = xFrame->getController(); + css::uno::Reference< css::frame::XModel > xModel; + if (xController.is()) + xModel = xController->getModel(); + if (xModel==xReferenceModel) + { + m_lModelFrames[nModelStep] = xFrame; + ++nModelStep; + continue; + } + } + + // d) Or is it the a task, which use another or no model at all? + // Add it to the list of "other frames". But look for its + // visible state ... if it's allowed to do so. + + bool bHidden = false; + if (m_eDetectMode & FrameAnalyzerFlags::Hidden) + { + xSet.set(xFrame, css::uno::UNO_QUERY); + if (xSet.is()) + { + xSet->getPropertyValue(FRAME_PROPNAME_ASCII_ISHIDDEN) >>= bHidden; + } + } + + if (bHidden) + { + m_lOtherHiddenFrames[nHiddenStep] = xFrame; + ++nHiddenStep; + } + else + { + m_lOtherVisibleFrames[nVisibleStep] = xFrame; + ++nVisibleStep; + } + } + } + catch (const css::lang::IndexOutOfBoundsException&) + { + // stop copying if index seems to be wrong. + // This interface can't really guarantee its count for multithreaded + // environments. So it can occur! + } + + // Pack both lists by using the actual step positions. + // All empty or ignorable items should exist at the end of these lists + // behind the position pointers. So they will be removed by a reallocation. + m_lOtherVisibleFrames.resize(nVisibleStep); + m_lOtherHiddenFrames.resize(nHiddenStep); + m_lModelFrames.resize(nModelStep); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/classes/fwkresid.cxx b/framework/source/fwe/classes/fwkresid.cxx new file mode 100644 index 000000000..e9a1d639d --- /dev/null +++ b/framework/source/fwe/classes/fwkresid.cxx @@ -0,0 +1,24 @@ +/* -*- 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 <classes/fwkresid.hxx> + +OUString FwkResId(TranslateId aId) { return Translate::get(aId, Translate::Create("fwk")); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/classes/rootactiontriggercontainer.cxx b/framework/source/fwe/classes/rootactiontriggercontainer.cxx new file mode 100644 index 000000000..02fa9d2df --- /dev/null +++ b/framework/source/fwe/classes/rootactiontriggercontainer.cxx @@ -0,0 +1,251 @@ +/* -*- 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 <classes/rootactiontriggercontainer.hxx> +#include <classes/actiontriggercontainer.hxx> +#include <classes/actiontriggerpropertyset.hxx> +#include <classes/actiontriggerseparatorpropertyset.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <framework/actiontriggerhelper.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::beans; + +namespace framework +{ + +const css::uno::Sequence<sal_Int8>& RootActionTriggerContainer::getUnoTunnelId() noexcept +{ + static const sal_uInt8 pGUID[16] = { 0x17, 0x0F, 0xA2, 0xC9, 0xCA, 0x50, 0x4A, 0xD3, 0xA6, 0x3B, 0x39, 0x99, 0xC5, 0x96, 0x43, 0x27 }; + static css::uno::Sequence< sal_Int8 > seqID(reinterpret_cast<const sal_Int8*>(pGUID), 16); + return seqID; +} + +RootActionTriggerContainer::RootActionTriggerContainer(css::uno::Reference<css::awt::XPopupMenu> xMenu, + const OUString* pMenuIdentifier) + : m_bContainerCreated(false) + , m_xMenu(std::move(xMenu)) + , m_pMenuIdentifier(pMenuIdentifier) +{ +} + +RootActionTriggerContainer::~RootActionTriggerContainer() +{ +} + +// XInterface +Any SAL_CALL RootActionTriggerContainer::queryInterface( const Type& aType ) +{ + Any a = ::cppu::queryInterface( + aType , + static_cast< XMultiServiceFactory* >(this), + static_cast< XServiceInfo* >(this), + static_cast< XUnoTunnel* >(this), + static_cast< XTypeProvider* >(this), + static_cast< XNamed* >(this)); + + if( a.hasValue() ) + { + return a; + } + + return PropertySetContainer::queryInterface( aType ); +} + +void SAL_CALL RootActionTriggerContainer::acquire() noexcept +{ + PropertySetContainer::acquire(); +} + +void SAL_CALL RootActionTriggerContainer::release() noexcept +{ + PropertySetContainer::release(); +} + +// XMultiServiceFactory +Reference< XInterface > SAL_CALL RootActionTriggerContainer::createInstance( const OUString& aServiceSpecifier ) +{ + if ( aServiceSpecifier == SERVICENAME_ACTIONTRIGGER ) + return static_cast<OWeakObject *>( new ActionTriggerPropertySet()); + else if ( aServiceSpecifier == SERVICENAME_ACTIONTRIGGERCONTAINER ) + return static_cast<OWeakObject *>( new ActionTriggerContainer()); + else if ( aServiceSpecifier == SERVICENAME_ACTIONTRIGGERSEPARATOR ) + return static_cast<OWeakObject *>( new ActionTriggerSeparatorPropertySet()); + else + throw css::uno::RuntimeException("Unknown service specifier!", static_cast<OWeakObject *>(this) ); +} + +Reference< XInterface > SAL_CALL RootActionTriggerContainer::createInstanceWithArguments( const OUString& ServiceSpecifier, const Sequence< Any >& /*Arguments*/ ) +{ + return createInstance( ServiceSpecifier ); +} + +Sequence< OUString > SAL_CALL RootActionTriggerContainer::getAvailableServiceNames() +{ + Sequence< OUString > aSeq{ SERVICENAME_ACTIONTRIGGER, + SERVICENAME_ACTIONTRIGGERCONTAINER, + SERVICENAME_ACTIONTRIGGERSEPARATOR }; + return aSeq; +} + +// XIndexContainer +void SAL_CALL RootActionTriggerContainer::insertByIndex( sal_Int32 Index, const Any& Element ) +{ + SolarMutexGuard g; + + if ( !m_bContainerCreated ) + FillContainer(); + + PropertySetContainer::insertByIndex( Index, Element ); +} + +void SAL_CALL RootActionTriggerContainer::removeByIndex( sal_Int32 Index ) +{ + SolarMutexGuard g; + + if ( !m_bContainerCreated ) + FillContainer(); + + PropertySetContainer::removeByIndex( Index ); +} + +// XIndexReplace +void SAL_CALL RootActionTriggerContainer::replaceByIndex( sal_Int32 Index, const Any& Element ) +{ + SolarMutexGuard g; + + if ( !m_bContainerCreated ) + FillContainer(); + + PropertySetContainer::replaceByIndex( Index, Element ); +} + +// XIndexAccess +sal_Int32 SAL_CALL RootActionTriggerContainer::getCount() +{ + SolarMutexGuard g; + + if ( !m_bContainerCreated ) + { + if ( m_xMenu ) + return m_xMenu->getItemCount(); + else + return 0; + } + else + { + return PropertySetContainer::getCount(); + } +} + +Any SAL_CALL RootActionTriggerContainer::getByIndex( sal_Int32 Index ) +{ + SolarMutexGuard g; + + if ( !m_bContainerCreated ) + FillContainer(); + + return PropertySetContainer::getByIndex( Index ); +} + +// XElementAccess +Type SAL_CALL RootActionTriggerContainer::getElementType() +{ + return cppu::UnoType<XPropertySet>::get(); +} + +sal_Bool SAL_CALL RootActionTriggerContainer::hasElements() +{ + if (m_xMenu) + return m_xMenu->getItemCount() > 0; + return false; +} + +// XServiceInfo +OUString SAL_CALL RootActionTriggerContainer::getImplementationName() +{ + return IMPLEMENTATIONNAME_ROOTACTIONTRIGGERCONTAINER; +} + +sal_Bool SAL_CALL RootActionTriggerContainer::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL RootActionTriggerContainer::getSupportedServiceNames() +{ + return { SERVICENAME_ACTIONTRIGGERCONTAINER }; +} + +// XUnoTunnel +sal_Int64 SAL_CALL RootActionTriggerContainer::getSomething( const Sequence< sal_Int8 >& aIdentifier ) +{ + return comphelper::getSomethingImpl(aIdentifier, this); +} + +// XTypeProvider +Sequence< Type > SAL_CALL RootActionTriggerContainer::getTypes() +{ + // Create a static typecollection ... + static ::cppu::OTypeCollection ourTypeCollection( + cppu::UnoType<XMultiServiceFactory>::get(), + cppu::UnoType<XIndexContainer>::get(), + cppu::UnoType<XServiceInfo>::get(), + cppu::UnoType<XTypeProvider>::get(), + cppu::UnoType<XUnoTunnel>::get(), + cppu::UnoType<XNamed>::get()); + + return ourTypeCollection.getTypes(); +} + +Sequence< sal_Int8 > SAL_CALL RootActionTriggerContainer::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// private implementation helper +void RootActionTriggerContainer::FillContainer() +{ + m_bContainerCreated = true; + ActionTriggerHelper::FillActionTriggerContainerFromMenu( + this, m_xMenu); +} +OUString RootActionTriggerContainer::getName() +{ + OUString sRet; + if( m_pMenuIdentifier ) + sRet = *m_pMenuIdentifier; + return sRet; +} + +void RootActionTriggerContainer::setName( const OUString& ) +{ + throw RuntimeException(); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/classes/sfxhelperfunctions.cxx b/framework/source/fwe/classes/sfxhelperfunctions.cxx new file mode 100644 index 000000000..170f3e715 --- /dev/null +++ b/framework/source/fwe/classes/sfxhelperfunctions.cxx @@ -0,0 +1,181 @@ +/* -*- 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 <framework/sfxhelperfunctions.hxx> +#include <framework/ContextChangeEventMultiplexerTunnel.hxx> +#include <helper/mischelper.hxx> +#include <svtools/toolboxcontroller.hxx> +#include <svtools/statusbarcontroller.hxx> + +static pfunc_setToolBoxControllerCreator pToolBoxControllerCreator = nullptr; +static pfunc_setWeldToolBoxControllerCreator pWeldToolBoxControllerCreator = nullptr; +static pfunc_setStatusBarControllerCreator pStatusBarControllerCreator = nullptr; +static pfunc_getRefreshToolbars pRefreshToolbars = nullptr; +static pfunc_createDockingWindow pCreateDockingWindow = nullptr; +static pfunc_isDockingWindowVisible pIsDockingWindowVisible = nullptr; + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::frame; + +namespace framework +{ + +pfunc_setToolBoxControllerCreator SetToolBoxControllerCreator( pfunc_setToolBoxControllerCreator pSetToolBoxControllerCreator ) +{ + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pfunc_setToolBoxControllerCreator pOldSetToolBoxControllerCreator = pToolBoxControllerCreator; + pToolBoxControllerCreator = pSetToolBoxControllerCreator; + return pOldSetToolBoxControllerCreator; +} + +rtl::Reference<svt::ToolboxController> CreateToolBoxController( const Reference< XFrame >& rFrame, ToolBox* pToolbox, ToolBoxItemId nID, const OUString& aCommandURL ) +{ + pfunc_setToolBoxControllerCreator pFactory = nullptr; + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pFactory = pToolBoxControllerCreator; + } + + if ( pFactory ) + return (*pFactory)( rFrame, pToolbox, nID, aCommandURL ); + else + return nullptr; +} + +pfunc_setWeldToolBoxControllerCreator SetWeldToolBoxControllerCreator( pfunc_setWeldToolBoxControllerCreator pSetWeldToolBoxControllerCreator ) +{ + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pfunc_setWeldToolBoxControllerCreator pOldSetToolBoxControllerCreator = pWeldToolBoxControllerCreator; + pWeldToolBoxControllerCreator = pSetWeldToolBoxControllerCreator; + return pOldSetToolBoxControllerCreator; +} + +css::uno::Reference<css::frame::XToolbarController> CreateWeldToolBoxController( const Reference< XFrame >& rFrame, weld::Toolbar* pToolbar, weld::Builder* pBuilder, const OUString& aCommandURL ) +{ + pfunc_setWeldToolBoxControllerCreator pFactory = nullptr; + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pFactory = pWeldToolBoxControllerCreator; + } + + if ( pFactory ) + return (*pFactory)( rFrame, pToolbar, pBuilder, aCommandURL ); + else + return nullptr; +} + +pfunc_setStatusBarControllerCreator SetStatusBarControllerCreator( pfunc_setStatusBarControllerCreator pSetStatusBarControllerCreator ) +{ + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pfunc_setStatusBarControllerCreator pOldSetStatusBarControllerCreator = pSetStatusBarControllerCreator; + pStatusBarControllerCreator = pSetStatusBarControllerCreator; + return pOldSetStatusBarControllerCreator; +} + +rtl::Reference<svt::StatusbarController> CreateStatusBarController( const Reference< XFrame >& rFrame, StatusBar* pStatusBar, unsigned short nID, const OUString& aCommandURL ) +{ + pfunc_setStatusBarControllerCreator pFactory = nullptr; + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pFactory = pStatusBarControllerCreator; + } + + if ( pFactory ) + return (*pFactory)( rFrame, pStatusBar, nID, aCommandURL ); + else + return nullptr; +} + +pfunc_getRefreshToolbars SetRefreshToolbars( pfunc_getRefreshToolbars pNewRefreshToolbarsFunc ) +{ + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pfunc_getRefreshToolbars pOldFunc = pRefreshToolbars; + pRefreshToolbars = pNewRefreshToolbarsFunc; + + return pOldFunc; +} + +void RefreshToolbars( css::uno::Reference< css::frame::XFrame > const & rFrame ) +{ + pfunc_getRefreshToolbars pCallback = nullptr; + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pCallback = pRefreshToolbars; + } + + if ( pCallback ) + (*pCallback)( rFrame ); +} + +pfunc_createDockingWindow SetDockingWindowCreator( pfunc_createDockingWindow pNewCreateDockingWindow ) +{ + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pfunc_createDockingWindow pOldFunc = pCreateDockingWindow; + pCreateDockingWindow = pNewCreateDockingWindow; + + return pOldFunc; +} + +void CreateDockingWindow( const css::uno::Reference< css::frame::XFrame >& rFrame, std::u16string_view rResourceURL ) +{ + pfunc_createDockingWindow pFactory = nullptr; + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pFactory = pCreateDockingWindow; + } + + if ( pFactory ) + (*pFactory)( rFrame, rResourceURL ); +} + +pfunc_isDockingWindowVisible SetIsDockingWindowVisible( pfunc_isDockingWindowVisible pNewIsDockingWindowVisible) +{ + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pfunc_isDockingWindowVisible pOldFunc = pIsDockingWindowVisible; + pIsDockingWindowVisible = pNewIsDockingWindowVisible; + + return pOldFunc; +} + +bool IsDockingWindowVisible( const css::uno::Reference< css::frame::XFrame >& rFrame, std::u16string_view rResourceURL ) +{ + pfunc_isDockingWindowVisible pCall = nullptr; + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + pCall = pIsDockingWindowVisible; + } + + if ( pCall ) + return (*pCall)( rFrame, rResourceURL ); + else + return false; +} + +using namespace ::com::sun::star; +uno::Reference<ui::XContextChangeEventListener> GetFirstListenerWith( + css::uno::Reference<css::uno::XComponentContext> const & xComponentContext, + uno::Reference<uno::XInterface> const& xEventFocus, + std::function<bool (uno::Reference<ui::XContextChangeEventListener> const&)> const& rPredicate) +{ + return GetFirstListenerWith_Impl(xComponentContext, xEventFocus, rPredicate); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/dispatch/interaction.cxx b/framework/source/fwe/dispatch/interaction.cxx new file mode 100644 index 000000000..d0cf88e6f --- /dev/null +++ b/framework/source/fwe/dispatch/interaction.cxx @@ -0,0 +1,225 @@ +/* -*- 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 <comphelper/interaction.hxx> +#include <framework/interaction.hxx> +#include <com/sun/star/document/XInteractionFilterSelect.hpp> +#include <com/sun/star/document/NoSuchFilterRequest.hpp> +#include <utility> + +using namespace ::com::sun::star; + +namespace framework{ + +namespace { + +/*-************************************************************************************************************ + @short declaration of special continuation for filter selection + @descr Sometimes filter detection during loading document failed. Then we need a possibility + to ask user for his decision. These continuation transport selected filter by user to + code user of interaction. + + @attention This implementation could be used one times only. We don't support a resettable continuation yet! + Why? Normally interaction should show a filter selection dialog and ask user for his decision. + He can select any filter - then instances of these class will be called by handler... or user + close dialog without any selection. Then another continuation should be selected by handler to + abort continuations... Retrying isn't very useful here... I think. + + @implements XInteractionFilterSelect + + @base ImplInheritanceHelper + ContinuationBase + + @devstatus ready to use + @threadsafe no (used on once position only!) +*//*-*************************************************************************************************************/ +class ContinuationFilterSelect : public comphelper::OInteraction< css::document::XInteractionFilterSelect > +{ + // c++ interface + public: + ContinuationFilterSelect(); + + // uno interface + public: + virtual void SAL_CALL setFilter( const OUString& sFilter ) override; + virtual OUString SAL_CALL getFilter( ) override; + + // member + private: + OUString m_sFilter; + +}; // class ContinuationFilterSelect + +} + +// initialize continuation with right start values + +ContinuationFilterSelect::ContinuationFilterSelect() +{ +} + +// handler should use it after selection to set user specified filter for transport + +void SAL_CALL ContinuationFilterSelect::setFilter( const OUString& sFilter ) +{ + m_sFilter = sFilter; +} + +// read access to transported filter + +OUString SAL_CALL ContinuationFilterSelect::getFilter() +{ + return m_sFilter; +} + +class RequestFilterSelect_Impl : public ::cppu::WeakImplHelper< css::task::XInteractionRequest > +{ +public: + explicit RequestFilterSelect_Impl(const OUString& rURL); + bool isAbort () const; + OUString getFilter() const; + +public: + virtual css::uno::Any SAL_CALL getRequest() override; + virtual css::uno::Sequence< css::uno::Reference< css::task::XInteractionContinuation > > SAL_CALL getContinuations() override; + +private: + css::uno::Any m_aRequest; + rtl::Reference<comphelper::OInteractionAbort> m_xAbort; + rtl::Reference<ContinuationFilterSelect> m_xFilter; +}; + +// initialize instance with all necessary information +// We use it without any further checks on our member then ...! + +RequestFilterSelect_Impl::RequestFilterSelect_Impl( const OUString& sURL ) +{ + css::uno::Reference< css::uno::XInterface > temp2; + css::document::NoSuchFilterRequest aFilterRequest( OUString(), + temp2 , + sURL ); + m_aRequest <<= aFilterRequest; + + m_xAbort = new comphelper::OInteractionAbort; + m_xFilter = new ContinuationFilterSelect; +} + +// return abort state of interaction +// If it is true, return value of method "getFilter()" will be unspecified then! + +bool RequestFilterSelect_Impl::isAbort() const +{ + return m_xAbort->wasSelected(); +} + +// return user selected filter +// Return value valid for non aborted interaction only. Please check "isAbort()" before you call these only! + +OUString RequestFilterSelect_Impl::getFilter() const +{ + return m_xFilter->getFilter(); +} + +// handler call it to get type of request +// Is hard coded to "please select filter" here. see ctor for further information. + +css::uno::Any SAL_CALL RequestFilterSelect_Impl::getRequest() +{ + return m_aRequest; +} + +// handler call it to get possible continuations +// We support "abort/select_filter" only here. +// After interaction we support read access on these continuations on our c++ interface to +// return user decision. + +css::uno::Sequence< css::uno::Reference< css::task::XInteractionContinuation > > SAL_CALL RequestFilterSelect_Impl::getContinuations() +{ + return { m_xAbort, m_xFilter }; +} + +RequestFilterSelect::RequestFilterSelect( const OUString& sURL ) + : mxImpl(new RequestFilterSelect_Impl( sURL )) +{ +} + +RequestFilterSelect::~RequestFilterSelect() +{ +} + +// return abort state of interaction +// If it is true, return value of method "getFilter()" will be unspecified then! + +bool RequestFilterSelect::isAbort() const +{ + return mxImpl->isAbort(); +} + +// return user selected filter +// Return value valid for non aborted interaction only. Please check "isAbort()" before you call these only! + +OUString RequestFilterSelect::getFilter() const +{ + return mxImpl->getFilter(); +} + +uno::Reference < task::XInteractionRequest > RequestFilterSelect::GetRequest() const +{ + return mxImpl; +} + +namespace { + +class InteractionRequest_Impl : public ::cppu::WeakImplHelper< css::task::XInteractionRequest > +{ + uno::Any m_aRequest; + uno::Sequence< css::uno::Reference< css::task::XInteractionContinuation > > m_lContinuations; + +public: + InteractionRequest_Impl( css::uno::Any aRequest, + const css::uno::Sequence< css::uno::Reference< css::task::XInteractionContinuation > >& lContinuations ) + : m_aRequest(std::move(aRequest)), m_lContinuations(lContinuations) + { + } + + virtual uno::Any SAL_CALL getRequest() override; + virtual uno::Sequence< uno::Reference< task::XInteractionContinuation > > SAL_CALL getContinuations() override; +}; + +} + +uno::Any SAL_CALL InteractionRequest_Impl::getRequest() +{ + return m_aRequest; +} + +uno::Sequence< uno::Reference< task::XInteractionContinuation > > SAL_CALL InteractionRequest_Impl::getContinuations() +{ + return m_lContinuations; +} + +uno::Reference < task::XInteractionRequest > InteractionRequest::CreateRequest( + const uno::Any& aRequest, const uno::Sequence< uno::Reference< task::XInteractionContinuation > >& lContinuations ) +{ + return new InteractionRequest_Impl( aRequest, lContinuations ); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/helper/actiontriggerhelper.cxx b/framework/source/fwe/helper/actiontriggerhelper.cxx new file mode 100644 index 000000000..88edd70be --- /dev/null +++ b/framework/source/fwe/helper/actiontriggerhelper.cxx @@ -0,0 +1,376 @@ +/* -*- 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 <framework/actiontriggerhelper.hxx> +#include <classes/actiontriggerseparatorpropertyset.hxx> +#include <classes/rootactiontriggercontainer.hxx> +#include <framework/addonsoptions.hxx> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/awt/XPopupMenu.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <toolkit/awt/vclxmenu.hxx> +#include <tools/stream.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <o3tl/string_view.hxx> + +const sal_uInt16 START_ITEMID = 1000; + +using namespace com::sun::star::awt; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::container; + +namespace framework +{ + +// implementation helper ( menu => ActionTrigger ) + +static bool IsSeparator( const Reference< XPropertySet >& xPropertySet ) +{ + Reference< XServiceInfo > xServiceInfo( xPropertySet, UNO_QUERY ); + try + { + return xServiceInfo->supportsService( SERVICENAME_ACTIONTRIGGERSEPARATOR ); + } + catch (const Exception&) + { + } + + return false; +} + +static void GetMenuItemAttributes( const Reference< XPropertySet >& xActionTriggerPropertySet, + OUString& aMenuLabel, + OUString& aCommandURL, + OUString& aHelpURL, + Reference< XBitmap >& xBitmap, + Reference< XIndexContainer >& xSubContainer ) +{ + Any a; + + try + { + // mandatory properties + a = xActionTriggerPropertySet->getPropertyValue("Text"); + a >>= aMenuLabel; + a = xActionTriggerPropertySet->getPropertyValue("CommandURL"); + a >>= aCommandURL; + a = xActionTriggerPropertySet->getPropertyValue("Image"); + a >>= xBitmap; + a = xActionTriggerPropertySet->getPropertyValue("SubContainer"); + a >>= xSubContainer; + } + catch (const Exception&) + { + } + + // optional properties + try + { + a = xActionTriggerPropertySet->getPropertyValue("HelpURL"); + a >>= aHelpURL; + } + catch (const Exception&) + { + } +} + +static void InsertSubMenuItems(const Reference<XPopupMenu>& rSubMenu, sal_uInt16& nItemId, + const Reference<XIndexContainer>& xActionTriggerContainer) +{ + if ( !xActionTriggerContainer.is() ) + return; + + AddonsOptions aAddonOptions; + OUString aSlotURL( "slot:" ); + + for ( sal_Int32 i = 0; i < xActionTriggerContainer->getCount(); i++ ) + { + try + { + Reference< XPropertySet > xPropSet; + if (( xActionTriggerContainer->getByIndex( i ) >>= xPropSet ) && ( xPropSet.is() )) + { + if ( IsSeparator( xPropSet )) + { + // Separator + SolarMutexGuard aGuard; + rSubMenu->insertSeparator(i); + } + else + { + // Menu item + OUString aLabel; + OUString aCommandURL; + OUString aHelpURL; + Reference< XBitmap > xBitmap; + Reference< XIndexContainer > xSubContainer; + + sal_uInt16 nNewItemId = nItemId++; + GetMenuItemAttributes( xPropSet, aLabel, aCommandURL, aHelpURL, xBitmap, xSubContainer ); + + SolarMutexGuard aGuard; + { + // insert new menu item + sal_Int32 nIndex = aCommandURL.indexOf( aSlotURL ); + if ( nIndex >= 0 ) + { + // Special code for our menu implementation: some menu items don't have a + // command url but uses the item id as a unique identifier. These entries + // got a special url during conversion from menu=>actiontriggercontainer. + // Now we have to extract this special url and set the correct item id!!! + nNewItemId = static_cast<sal_uInt16>(o3tl::toInt32(aCommandURL.subView( nIndex+aSlotURL.getLength() ))); + rSubMenu->insertItem(nNewItemId, aLabel, 0, i); + } + else + { + rSubMenu->insertItem(nNewItemId, aLabel, 0, i); + rSubMenu->setCommand(nNewItemId, aCommandURL); + } + + // handle bitmap + if ( xBitmap.is() ) + { + bool bImageSet = false; + + Reference<css::graphic::XGraphic> xGraphic(xBitmap, UNO_QUERY); + if (xGraphic.is()) + { + // we can take the optimized route if XGraphic is supported + rSubMenu->setItemImage(nNewItemId, xGraphic, false); + bImageSet = true; + } + + if ( !bImageSet ) + { + // This is an unknown implementation of a XBitmap interface. We have to + // use a more time consuming way to build an Image! + BitmapEx aBitmap; + + Sequence< sal_Int8 > aDIBSeq; + { + aDIBSeq = xBitmap->getDIB(); + SvMemoryStream aMem( const_cast<sal_Int8 *>(aDIBSeq.getConstArray()), aDIBSeq.getLength(), StreamMode::READ ); + ReadDIBBitmapEx(aBitmap, aMem); + } + + aDIBSeq = xBitmap->getMaskDIB(); + if ( aDIBSeq.hasElements() ) + { + Bitmap aMaskBitmap; + SvMemoryStream aMem( const_cast<sal_Int8 *>(aDIBSeq.getConstArray()), aDIBSeq.getLength(), StreamMode::READ ); + ReadDIB(aMaskBitmap, aMem, true); + aBitmap = BitmapEx(aBitmap.GetBitmap(), aMaskBitmap); + } + + if (!aBitmap.IsEmpty()) + rSubMenu->setItemImage(nNewItemId, Graphic(aBitmap).GetXGraphic(), false); + } + } + else + { + // Support add-on images for context menu interceptors + BitmapEx aBitmap(aAddonOptions.GetImageFromURL(aCommandURL, false, true)); + if (!aBitmap.IsEmpty()) + rSubMenu->setItemImage(nNewItemId, Graphic(aBitmap).GetXGraphic(), false); + } + + if ( xSubContainer.is() ) + { + rtl::Reference xNewSubMenu(new VCLXPopupMenu); + + // Sub menu (recursive call CreateSubMenu ) + InsertSubMenuItems(xNewSubMenu, nItemId, xSubContainer); + rSubMenu->setPopupMenu(nNewItemId, xNewSubMenu); + } + } + } + } + } + catch (const IndexOutOfBoundsException&) + { + return; + } + catch (const WrappedTargetException&) + { + return; + } + catch (const RuntimeException&) + { + return; + } + } +} + +// implementation helper ( ActionTrigger => menu ) + +/// @throws RuntimeException +static Reference< XPropertySet > CreateActionTrigger(sal_uInt16 nItemId, + const Reference<XPopupMenu>& rMenu, + const Reference<XIndexContainer>& rActionTriggerContainer) +{ + Reference< XPropertySet > xPropSet; + + Reference< XMultiServiceFactory > xMultiServiceFactory( rActionTriggerContainer, UNO_QUERY ); + if ( xMultiServiceFactory.is() ) + { + xPropSet.set( xMultiServiceFactory->createInstance( "com.sun.star.ui.ActionTrigger" ), + UNO_QUERY ); + + Any a; + + try + { + // Retrieve the menu attributes and set them in our PropertySet + OUString aLabel = rMenu->getItemText(nItemId); + a <<= aLabel; + xPropSet->setPropertyValue("Text", a ); + + OUString aCommandURL = rMenu->getCommand(nItemId); + + if ( aCommandURL.isEmpty() ) + { + aCommandURL = "slot:" + OUString::number( nItemId ); + } + + a <<= aCommandURL; + xPropSet->setPropertyValue("CommandURL", a ); + + Reference<XBitmap> xBitmap(rMenu->getItemImage(nItemId), UNO_QUERY); + if (xBitmap.is()) + { + a <<= xBitmap; + xPropSet->setPropertyValue("Image", a ); + } + } + catch (const Exception&) + { + } + } + + return xPropSet; +} + +/// @throws RuntimeException +static Reference< XPropertySet > CreateActionTriggerSeparator( const Reference< XIndexContainer >& rActionTriggerContainer ) +{ + Reference< XMultiServiceFactory > xMultiServiceFactory( rActionTriggerContainer, UNO_QUERY ); + if ( xMultiServiceFactory.is() ) + { + return Reference< XPropertySet >( xMultiServiceFactory->createInstance( + "com.sun.star.ui.ActionTriggerSeparator" ), + UNO_QUERY ); + } + + return Reference< XPropertySet >(); +} + +/// @throws RuntimeException +static Reference< XIndexContainer > CreateActionTriggerContainer( const Reference< XIndexContainer >& rActionTriggerContainer ) +{ + Reference< XMultiServiceFactory > xMultiServiceFactory( rActionTriggerContainer, UNO_QUERY ); + if ( xMultiServiceFactory.is() ) + { + return Reference< XIndexContainer >( xMultiServiceFactory->createInstance( + "com.sun.star.ui.ActionTriggerContainer" ), + UNO_QUERY ); + } + + return Reference< XIndexContainer >(); +} + +static void FillActionTriggerContainerWithMenu(const Reference<XPopupMenu>& rMenu, + const Reference<XIndexContainer>& rActionTriggerContainer) +{ + SolarMutexGuard aGuard; + + for (sal_uInt16 nPos = 0, nCount = rMenu->getItemCount(); nPos < nCount; ++nPos) + { + sal_uInt16 nItemId = rMenu->getItemId(nPos); + css::awt::MenuItemType nType = rMenu->getItemType(nPos); + + try + { + Any a; + Reference< XPropertySet > xPropSet; + + if (nType == css::awt::MenuItemType_SEPARATOR) + { + xPropSet = CreateActionTriggerSeparator( rActionTriggerContainer ); + + a <<= xPropSet; + rActionTriggerContainer->insertByIndex( nPos, a ); + } + else + { + xPropSet = CreateActionTrigger(nItemId, rMenu, rActionTriggerContainer); + + a <<= xPropSet; + rActionTriggerContainer->insertByIndex( nPos, a ); + + css::uno::Reference<XPopupMenu> xPopupMenu = rMenu->getPopupMenu(nItemId); + if (xPopupMenu.is()) + { + // recursive call to build next sub menu + Reference< XIndexContainer > xSubContainer = CreateActionTriggerContainer( rActionTriggerContainer ); + + a <<= xSubContainer; + xPropSet->setPropertyValue("SubContainer", a ); + FillActionTriggerContainerWithMenu(xPopupMenu, xSubContainer); + } + } + } + catch (const Exception&) + { + } + } +} + +void ActionTriggerHelper::CreateMenuFromActionTriggerContainer( + const Reference<XPopupMenu>& rNewMenu, + const Reference<XIndexContainer>& rActionTriggerContainer) +{ + sal_uInt16 nItemId = START_ITEMID; + + if ( rActionTriggerContainer.is() ) + InsertSubMenuItems(rNewMenu, nItemId, rActionTriggerContainer); +} + +void ActionTriggerHelper::FillActionTriggerContainerFromMenu( + Reference< XIndexContainer > const & xActionTriggerContainer, + const css::uno::Reference<XPopupMenu>& rMenu) +{ + FillActionTriggerContainerWithMenu(rMenu, xActionTriggerContainer); +} + +Reference< XIndexContainer > ActionTriggerHelper::CreateActionTriggerContainerFromMenu( + const css::uno::Reference<XPopupMenu>& rMenu, + const OUString* pMenuIdentifier ) +{ + return new RootActionTriggerContainer(rMenu, pMenuIdentifier); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/helper/configimporter.cxx b/framework/source/fwe/helper/configimporter.cxx new file mode 100644 index 000000000..bc92d180b --- /dev/null +++ b/framework/source/fwe/helper/configimporter.cxx @@ -0,0 +1,74 @@ +/* -*- 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 <framework/configimporter.hxx> +#include <toolboxconfiguration.hxx> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/ui/XUIConfigurationManager2.hpp> + +using namespace ::com::sun::star; + +namespace framework +{ + +bool UIConfigurationImporterOOo1x::ImportCustomToolbars( + const uno::Reference< ui::XUIConfigurationManager2 >& rContainerFactory, + std::vector< uno::Reference< container::XIndexContainer > >& rSeqContainer, + const uno::Reference< uno::XComponentContext >& rxContext, + const uno::Reference< embed::XStorage >& rToolbarStorage ) +{ + bool bResult ( false ); + if ( rToolbarStorage.is() && rContainerFactory.is() ) + { + try + { + for ( sal_uInt16 i = 1; i <= 4; i++ ) + { + OUString aTbxStreamName = "userdeftoolbox" + OUString::number(i) + ".xml"; + uno::Reference< io::XStream > xStream = rToolbarStorage->openStreamElement( aTbxStreamName, embed::ElementModes::READ ); + if ( xStream.is() ) + { + uno::Reference< io::XInputStream > xInputStream = xStream->getInputStream(); + if ( xInputStream.is() ) + { + uno::Reference< container::XIndexContainer > xContainer = rContainerFactory->createSettings(); + if ( ToolBoxConfiguration::LoadToolBox( rxContext, xInputStream, xContainer )) + { + rSeqContainer.push_back( xContainer ); + bResult = true; + } + } + } + } + } + catch ( const uno::RuntimeException& ) + { + throw; + } + catch ( const uno::Exception& ) + { + } + } + + return bResult; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/helper/documentundoguard.cxx b/framework/source/fwe/helper/documentundoguard.cxx new file mode 100644 index 000000000..5f7d16041 --- /dev/null +++ b/framework/source/fwe/helper/documentundoguard.cxx @@ -0,0 +1,223 @@ +/* -*- 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 <framework/documentundoguard.hxx> + +#include <com/sun/star/document/XUndoManagerSupplier.hpp> + +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <tools/diagnose_ex.h> + +namespace framework +{ + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::UNO_QUERY_THROW; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::document::XUndoManagerSupplier; + using ::com::sun::star::document::XUndoManager; + using ::com::sun::star::document::XUndoManagerListener; + using ::com::sun::star::document::UndoManagerEvent; + using ::com::sun::star::lang::EventObject; + + //= UndoManagerContextListener + + typedef ::cppu::WeakImplHelper < XUndoManagerListener + > UndoManagerContextListener_Base; + + namespace { + + class UndoManagerContextListener : public UndoManagerContextListener_Base + { + public: + explicit UndoManagerContextListener( const Reference< XUndoManager >& i_undoManager ) + :m_xUndoManager( i_undoManager ) + ,m_nRelativeContextDepth( 0 ) + ,m_documentDisposed( false ) + { + osl_atomic_increment( &m_refCount ); + { + m_xUndoManager->addUndoManagerListener( this ); + } + osl_atomic_decrement( &m_refCount ); + } + + void finish() + { + OSL_ENSURE( m_nRelativeContextDepth >= 0, "UndoManagerContextListener: more contexts left than entered?" ); + + if ( m_documentDisposed ) + return; + + // work with a copy of m_nRelativeContextDepth, to be independent from possible bugs in the + // listener notifications (where it would be decremented with every leaveUndoContext) + sal_Int32 nDepth = m_nRelativeContextDepth; + while ( nDepth-- > 0 ) + { + m_xUndoManager->leaveUndoContext(); + } + m_xUndoManager->removeUndoManagerListener( this ); + } + + // XUndoManagerListener + virtual void SAL_CALL undoActionAdded( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL actionUndone( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL actionRedone( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL allActionsCleared( const EventObject& i_event ) override; + virtual void SAL_CALL redoActionsCleared( const EventObject& i_event ) override; + virtual void SAL_CALL resetAll( const EventObject& i_event ) override; + virtual void SAL_CALL enteredContext( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL enteredHiddenContext( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL leftContext( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL leftHiddenContext( const UndoManagerEvent& i_event ) override; + virtual void SAL_CALL cancelledContext( const UndoManagerEvent& i_event ) override; + + // XEventListener + virtual void SAL_CALL disposing( const EventObject& i_event ) override; + + private: + Reference< XUndoManager > const m_xUndoManager; + oslInterlockedCount m_nRelativeContextDepth; + bool m_documentDisposed; + }; + + } + + void SAL_CALL UndoManagerContextListener::undoActionAdded( const UndoManagerEvent& ) + { + // not interested in + } + + void SAL_CALL UndoManagerContextListener::actionUndone( const UndoManagerEvent& ) + { + // not interested in + } + + void SAL_CALL UndoManagerContextListener::actionRedone( const UndoManagerEvent& ) + { + // not interested in + } + + void SAL_CALL UndoManagerContextListener::allActionsCleared( const EventObject& ) + { + // not interested in + } + + void SAL_CALL UndoManagerContextListener::redoActionsCleared( const EventObject& ) + { + // not interested in + } + + void SAL_CALL UndoManagerContextListener::resetAll( const EventObject& ) + { + m_nRelativeContextDepth = 0; + } + + void SAL_CALL UndoManagerContextListener::enteredContext( const UndoManagerEvent& ) + { + osl_atomic_increment( &m_nRelativeContextDepth ); + } + + void SAL_CALL UndoManagerContextListener::enteredHiddenContext( const UndoManagerEvent& ) + { + osl_atomic_increment( &m_nRelativeContextDepth ); + } + + void SAL_CALL UndoManagerContextListener::leftContext( const UndoManagerEvent& ) + { + osl_atomic_decrement( &m_nRelativeContextDepth ); + } + + void SAL_CALL UndoManagerContextListener::leftHiddenContext( const UndoManagerEvent& ) + { + osl_atomic_decrement( &m_nRelativeContextDepth ); + } + + void SAL_CALL UndoManagerContextListener::cancelledContext( const UndoManagerEvent& ) + { + osl_atomic_decrement( &m_nRelativeContextDepth ); + } + + void SAL_CALL UndoManagerContextListener::disposing( const EventObject& ) + { + m_documentDisposed = true; + } + + //= DocumentUndoGuard_Data + + struct DocumentUndoGuard_Data + { + Reference< XUndoManager > xUndoManager; + ::rtl::Reference< UndoManagerContextListener > pContextListener; + }; + + namespace + { + + void lcl_init( DocumentUndoGuard_Data& i_data, const Reference< XInterface >& i_undoSupplierComponent ) + { + try + { + Reference< XUndoManagerSupplier > xUndoSupplier( i_undoSupplierComponent, UNO_QUERY ); + if ( xUndoSupplier.is() ) + i_data.xUndoManager.set( xUndoSupplier->getUndoManager(), css::uno::UNO_SET_THROW ); + + if ( i_data.xUndoManager.is() ) + i_data.pContextListener.set( new UndoManagerContextListener( i_data.xUndoManager ) ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + } + + void lcl_restore( DocumentUndoGuard_Data& i_data ) + { + try + { + if ( i_data.pContextListener.is() ) + i_data.pContextListener->finish(); + i_data.pContextListener.clear(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + } + } + + //= DocumentUndoGuard + + DocumentUndoGuard::DocumentUndoGuard( const Reference< XInterface >& i_undoSupplierComponent ) + :m_xData( new DocumentUndoGuard_Data ) + { + lcl_init( *m_xData, i_undoSupplierComponent ); + } + + DocumentUndoGuard::~DocumentUndoGuard() + { + lcl_restore( *m_xData ); + } + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/helper/propertysetcontainer.cxx b/framework/source/fwe/helper/propertysetcontainer.cxx new file mode 100644 index 000000000..ad7d63953 --- /dev/null +++ b/framework/source/fwe/helper/propertysetcontainer.cxx @@ -0,0 +1,158 @@ +/* -*- 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 <helper/propertysetcontainer.hxx> + +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <cppuhelper/queryinterface.hxx> +#include <vcl/svapp.hxx> + +constexpr OUStringLiteral WRONG_TYPE_EXCEPTION = u"Only XPropertSet allowed!"; + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::container; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; + +namespace framework +{ + +PropertySetContainer::PropertySetContainer() +{ +} + +PropertySetContainer::~PropertySetContainer() +{ +} + +// XInterface +void SAL_CALL PropertySetContainer::acquire() noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL PropertySetContainer::release() noexcept +{ + OWeakObject::release(); +} + +Any SAL_CALL PropertySetContainer::queryInterface( const Type& rType ) +{ + Any a = ::cppu::queryInterface( + rType , + static_cast< XIndexContainer* >(this), + static_cast< XIndexReplace* >(this), + static_cast< XIndexAccess* >(this), + static_cast< XElementAccess* >(this) ); + + if( a.hasValue() ) + { + return a; + } + + return OWeakObject::queryInterface( rType ); +} + +// XIndexContainer +void SAL_CALL PropertySetContainer::insertByIndex( sal_Int32 Index, const css::uno::Any& Element ) +{ + SolarMutexGuard g; + + sal_Int32 nSize = m_aPropertySetVector.size(); + + if ( nSize < Index ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + Reference< XPropertySet > aPropertySetElement; + + if ( !(Element >>= aPropertySetElement) ) + { + throw IllegalArgumentException( + WRONG_TYPE_EXCEPTION, + static_cast<OWeakObject *>(this), 2 ); + } + + if ( nSize == Index ) + m_aPropertySetVector.push_back( aPropertySetElement ); + else + { + PropertySetVector::iterator aIter = m_aPropertySetVector.begin(); + aIter += Index; + m_aPropertySetVector.insert( aIter, aPropertySetElement ); + } +} + +void SAL_CALL PropertySetContainer::removeByIndex( sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + if ( static_cast<sal_Int32>(m_aPropertySetVector.size()) <= nIndex ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + m_aPropertySetVector.erase(m_aPropertySetVector.begin() + nIndex); +} + +// XIndexReplace +void SAL_CALL PropertySetContainer::replaceByIndex( sal_Int32 Index, const css::uno::Any& Element ) +{ + if ( static_cast<sal_Int32>(m_aPropertySetVector.size()) <= Index ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + Reference< XPropertySet > aPropertySetElement; + + if ( !(Element >>= aPropertySetElement) ) + { + throw IllegalArgumentException( + WRONG_TYPE_EXCEPTION, + static_cast<OWeakObject *>(this), 2 ); + } + + m_aPropertySetVector[ Index ] = aPropertySetElement; +} + +// XIndexAccess +sal_Int32 SAL_CALL PropertySetContainer::getCount() +{ + SolarMutexGuard g; + + return m_aPropertySetVector.size(); +} + +Any SAL_CALL PropertySetContainer::getByIndex( sal_Int32 Index ) +{ + SolarMutexGuard g; + + if ( static_cast<sal_Int32>(m_aPropertySetVector.size()) <= Index ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + return Any(m_aPropertySetVector[ Index ]); +} + +// XElementAccess +sal_Bool SAL_CALL PropertySetContainer::hasElements() +{ + SolarMutexGuard g; + + return !( m_aPropertySetVector.empty() ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/helper/titlehelper.cxx b/framework/source/fwe/helper/titlehelper.cxx new file mode 100644 index 000000000..aa4e8f0b2 --- /dev/null +++ b/framework/source/fwe/helper/titlehelper.cxx @@ -0,0 +1,685 @@ +/* -*- 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 <framework/titlehelper.hxx> +#include <classes/fwkresid.hxx> +#include <strings.hrc> +#include <properties.h> + +#include <com/sun/star/frame/UntitledNumbersConst.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XUntitledNumbers.hpp> +#include <com/sun/star/frame/XModel3.hpp> +#include <com/sun/star/document/XDocumentEventBroadcaster.hpp> + +#include <unotools/configmgr.hxx> +#include <unotools/bootstrap.hxx> +#include <unotools/mediadescriptor.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <rtl/ustrbuf.hxx> +#include <osl/mutex.hxx> +#include <tools/urlobj.hxx> +#include <utility> +#include <vcl/svapp.hxx> + + +using namespace css; +using namespace css::uno; +using namespace css::frame; + +namespace framework{ + +TitleHelper::TitleHelper(css::uno::Reference< css::uno::XComponentContext > xContext, + const css::uno::Reference< css::uno::XInterface >& xOwner, + const css::uno::Reference< css::frame::XUntitledNumbers >& xNumbers) + : ::cppu::BaseMutex () + , m_xContext (std::move(xContext)) + , m_bExternalTitle (false) + , m_nLeasedNumber (css::frame::UntitledNumbersConst::INVALID_NUMBER) + , m_aListener (m_aMutex) +{ + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + m_xOwner = xOwner; + m_xUntitledNumbers = xNumbers; + } + // <- SYNCHRONIZED + + css::uno::Reference< css::frame::XModel > xModel(xOwner, css::uno::UNO_QUERY); + if (xModel.is ()) + { + impl_startListeningForModel (xModel); + return; + } + + css::uno::Reference< css::frame::XController > xController(xOwner, css::uno::UNO_QUERY); + if (xController.is ()) + { + impl_startListeningForController (xController); + return; + } + + css::uno::Reference< css::frame::XFrame > xFrame(xOwner, css::uno::UNO_QUERY); + if (xFrame.is ()) + { + impl_startListeningForFrame (xFrame); + return; + } +} + +TitleHelper::~TitleHelper() +{ +} + +OUString SAL_CALL TitleHelper::getTitle() +{ + // SYNCHRONIZED -> + osl::MutexGuard aLock(m_aMutex); + + // An external title will win always and disable all internal logic about + // creating/using a title value. + // Even an empty string will be accepted as valid title ! + if (m_bExternalTitle) + return m_sTitle; + + // Title seems to be up-to-date. Return it directly. + if (!m_sTitle.isEmpty()) + return m_sTitle; + + // Title seems to be unused till now ... do bootstraping + impl_updateTitle (true); + + return m_sTitle; + // <- SYNCHRONIZED +} + +void SAL_CALL TitleHelper::setTitle(const OUString& sTitle) +{ + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + m_bExternalTitle = true; + m_sTitle = sTitle; + } + // <- SYNCHRONIZED + + impl_sendTitleChangedEvent (); +} + +void SAL_CALL TitleHelper::addTitleChangeListener(const css::uno::Reference< css::frame::XTitleChangeListener >& xListener) +{ + // container is threadsafe by himself + m_aListener.addInterface( cppu::UnoType<css::frame::XTitleChangeListener>::get(), xListener ); +} + +void SAL_CALL TitleHelper::removeTitleChangeListener(const css::uno::Reference< css::frame::XTitleChangeListener >& xListener) +{ + // container is threadsafe by himself + m_aListener.removeInterface( cppu::UnoType<css::frame::XTitleChangeListener>::get(), xListener ); +} + +void SAL_CALL TitleHelper::titleChanged(const css::frame::TitleChangedEvent& aEvent) +{ + css::uno::Reference< css::frame::XTitle > xSubTitle; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xSubTitle.set(m_xSubTitle.get (), css::uno::UNO_QUERY); + } + // <- SYNCHRONIZED + + if (aEvent.Source != xSubTitle) + return; + + impl_updateTitle (); +} + +void SAL_CALL TitleHelper::documentEventOccured(const css::document::DocumentEvent& aEvent) +{ + if ( ! aEvent.EventName.equalsIgnoreAsciiCase("OnSaveAsDone") + && ! aEvent.EventName.equalsIgnoreAsciiCase("OnModeChanged") + && ! aEvent.EventName.equalsIgnoreAsciiCase("OnTitleChanged")) + return; + + css::uno::Reference< css::frame::XModel > xOwner; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xOwner.set(m_xOwner.get (), css::uno::UNO_QUERY); + } + // <- SYNCHRONIZED + + if (aEvent.Source != xOwner + || ((aEvent.EventName.equalsIgnoreAsciiCase("OnModeChanged") + || aEvent.EventName.equalsIgnoreAsciiCase("OnTitleChanged")) + && !xOwner.is())) + { + return; + } + + impl_updateTitle (); +} + +void SAL_CALL TitleHelper::frameAction(const css::frame::FrameActionEvent& aEvent) +{ + css::uno::Reference< css::frame::XFrame > xOwner; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xOwner.set(m_xOwner.get (), css::uno::UNO_QUERY); + } + // <- SYNCHRONIZED + + if (aEvent.Source != xOwner) + return; + + // we are interested on events only, which must trigger a title bar update + // because component was changed. + if ( + (aEvent.Action == css::frame::FrameAction_COMPONENT_ATTACHED ) || + (aEvent.Action == css::frame::FrameAction_COMPONENT_REATTACHED) || + (aEvent.Action == css::frame::FrameAction_COMPONENT_DETACHING ) + ) + { + impl_updateListeningForFrame (xOwner); + impl_updateTitle (); + } +} + +void SAL_CALL TitleHelper::disposing(const css::lang::EventObject& aEvent) +{ + css::uno::Reference< css::uno::XInterface > xOwner; + css::uno::Reference< css::frame::XUntitledNumbers > xNumbers; + ::sal_Int32 nLeasedNumber; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xOwner = m_xOwner; + xNumbers.set(m_xUntitledNumbers.get(), css::uno::UNO_QUERY); + nLeasedNumber = m_nLeasedNumber; + } + // <- SYNCHRONIZED + + if ( ! xOwner.is ()) + return; + + css::uno::Reference< css::frame::XFrame > xFrame(xOwner, css::uno::UNO_QUERY); + if (xFrame.is()) + xFrame->removeFrameActionListener(this); + + if (xOwner != aEvent.Source) + return; + + if ( + (xNumbers.is () ) && + (nLeasedNumber != css::frame::UntitledNumbersConst::INVALID_NUMBER) + ) + xNumbers->releaseNumber (nLeasedNumber); + + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + m_xOwner.clear(); + m_sTitle = OUString (); + m_nLeasedNumber = css::frame::UntitledNumbersConst::INVALID_NUMBER; + } + // <- SYNCHRONIZED +} + +void TitleHelper::impl_sendTitleChangedEvent () +{ + css::uno::Reference<css::uno::XInterface> xOwner; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xOwner = m_xOwner; + } + // <- SYNCHRONIZED + + css::frame::TitleChangedEvent aEvent(xOwner, m_sTitle); + + if( ! aEvent.Source.is() ) + return; + + comphelper::OInterfaceContainerHelper2* pContainer = m_aListener.getContainer( cppu::UnoType<css::frame::XTitleChangeListener>::get()); + if ( ! pContainer) + return; + + comphelper::OInterfaceIteratorHelper2 pIt( *pContainer ); + while ( pIt.hasMoreElements() ) + { + try + { + static_cast<css::frame::XTitleChangeListener*>(pIt.next())->titleChanged( aEvent ); + } + catch(const css::uno::Exception&) + { + pIt.remove(); + } + } +} + +void TitleHelper::impl_updateTitle (bool init) +{ + css::uno::Reference< css::frame::XModel3 > xModel; + css::uno::Reference< css::frame::XController > xController; + css::uno::Reference< css::frame::XFrame > xFrame; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xModel.set (m_xOwner.get(), css::uno::UNO_QUERY); + xController.set(m_xOwner.get(), css::uno::UNO_QUERY); + xFrame.set (m_xOwner.get(), css::uno::UNO_QUERY); + } + // <- SYNCHRONIZED + + if (xModel.is ()) + { + impl_updateTitleForModel (xModel, init); + } + else if (xController.is ()) + { + impl_updateTitleForController (xController, init); + } + else if (xFrame.is ()) + { + impl_updateTitleForFrame (xFrame, init); + } +} + +void TitleHelper::impl_updateTitleForModel (const css::uno::Reference< css::frame::XModel3 >& xModel, bool init) +{ + css::uno::Reference< css::uno::XInterface > xOwner; + css::uno::Reference< css::frame::XUntitledNumbers > xNumbers; + ::sal_Int32 nLeasedNumber; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + // external title won't be updated internally! + // It has to be set from outside new. + if (m_bExternalTitle) + return; + + xOwner = m_xOwner; + xNumbers.set (m_xUntitledNumbers.get(), css::uno::UNO_QUERY); + nLeasedNumber = m_nLeasedNumber; + } + // <- SYNCHRONIZED + + if ( + ( ! xOwner.is ()) || + ( ! xNumbers.is ()) || + ( ! xModel.is ()) + ) + return; + + OUString sTitle; + OUString sURL; + + css::uno::Reference< css::frame::XStorable > xURLProvider(xModel , css::uno::UNO_QUERY); + if (xURLProvider.is()) + sURL = xURLProvider->getLocation (); + + utl::MediaDescriptor aDescriptor(xModel->getArgs2( { utl::MediaDescriptor::PROP_SUGGESTEDSAVEASNAME } )); + const OUString sSuggestedSaveAsName = aDescriptor.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_SUGGESTEDSAVEASNAME, OUString()); + + if (!sURL.isEmpty()) + { + sTitle = impl_convertURL2Title(sURL); + if (nLeasedNumber != css::frame::UntitledNumbersConst::INVALID_NUMBER) + xNumbers->releaseNumber (nLeasedNumber); + nLeasedNumber = css::frame::UntitledNumbersConst::INVALID_NUMBER; + } + else if (!sSuggestedSaveAsName.isEmpty()) + { + // tdf#121537 Use suggested save as name for title if file has not yet been saved + sTitle = sSuggestedSaveAsName; + } + else + { + if (nLeasedNumber == css::frame::UntitledNumbersConst::INVALID_NUMBER) + nLeasedNumber = xNumbers->leaseNumber (xOwner); + + if (nLeasedNumber != css::frame::UntitledNumbersConst::INVALID_NUMBER) + sTitle = xNumbers->getUntitledPrefix() + OUString::number(nLeasedNumber); + else + sTitle = xNumbers->getUntitledPrefix() + "?"; + } + + bool bChanged; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + // WORKAROUND: the notification is currently sent always, + // can be changed after shared mode is supported per UNO API + bChanged = !init; // && m_sTitle != sTitle + + m_sTitle = sTitle; + m_nLeasedNumber = nLeasedNumber; + } + // <- SYNCHRONIZED + + if (bChanged) + impl_sendTitleChangedEvent (); +} + +void TitleHelper::impl_updateTitleForController (const css::uno::Reference< css::frame::XController >& xController, bool init) +{ + css::uno::Reference< css::uno::XInterface > xOwner; + css::uno::Reference< css::frame::XUntitledNumbers > xNumbers; + ::sal_Int32 nLeasedNumber; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + // external title won't be updated internally! + // It has to be set from outside new. + if (m_bExternalTitle) + return; + + xOwner = m_xOwner; + xNumbers.set (m_xUntitledNumbers.get(), css::uno::UNO_QUERY); + nLeasedNumber = m_nLeasedNumber; + } + // <- SYNCHRONIZED + + if ( + ( ! xOwner.is ()) || + ( ! xNumbers.is ()) || + ( ! xController.is ()) + ) + return; + + OUStringBuffer sTitle(256); + + if (nLeasedNumber == css::frame::UntitledNumbersConst::INVALID_NUMBER) + nLeasedNumber = xNumbers->leaseNumber (xOwner); + + css::uno::Reference< css::frame::XTitle > xModelTitle(xController->getModel (), css::uno::UNO_QUERY); + css::uno::Reference< css::frame::XModel > xModel = xController->getModel (); + if (!xModelTitle.is ()) + xModelTitle.set(xController, css::uno::UNO_QUERY); + if (xModelTitle.is ()) + { + sTitle.append (xModelTitle->getTitle ()); + if ( nLeasedNumber > 1 ) + { + sTitle.append(" : "); + sTitle.append(nLeasedNumber); + } + if (xModel.is ()) + { + INetURLObject aURL (xModel->getURL ()); + if (aURL.GetProtocol () != INetProtocol::File + && aURL.GetProtocol () != INetProtocol::NotValid) + { + OUString sRemoteText (FwkResId (STR_REMOTE_TITLE)); + sTitle.append (sRemoteText); + } + } + } + else + { + sTitle.append (xNumbers->getUntitledPrefix ()); + if ( nLeasedNumber > 1 ) + { + sTitle.append(nLeasedNumber ); + } + } + + bool bChanged; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + OUString sNewTitle = sTitle.makeStringAndClear (); + bChanged = !init && m_sTitle != sNewTitle; + m_sTitle = sNewTitle; + m_nLeasedNumber = nLeasedNumber; + } + // <- SYNCHRONIZED + + if (bChanged) + impl_sendTitleChangedEvent (); +} + +void TitleHelper::impl_updateTitleForFrame (const css::uno::Reference< css::frame::XFrame >& xFrame, bool init) +{ + if ( ! xFrame.is ()) + return; + + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + // external title won't be updated internally! + // It has to be set from outside new. + if (m_bExternalTitle) + return; + } + // <- SYNCHRONIZED + + css::uno::Reference< css::uno::XInterface > xComponent = xFrame->getController (); + if ( ! xComponent.is ()) + xComponent = xFrame->getComponentWindow (); + + OUStringBuffer sTitle (256); + + impl_appendComponentTitle (sTitle, xComponent); +#ifndef MACOSX + // fdo#70376: We want the window title to contain just the + // document name (from the above "component title"). + impl_appendProductName (sTitle); + impl_appendModuleName (sTitle); + impl_appendDebugVersion (sTitle); +#endif + impl_appendSafeMode (sTitle); + + bool bChanged; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + OUString sNewTitle = sTitle.makeStringAndClear (); + bChanged = !init && m_sTitle != sNewTitle; + m_sTitle = sNewTitle; + } + // <- SYNCHRONIZED + + if (bChanged) + impl_sendTitleChangedEvent (); +} + +void TitleHelper::impl_appendComponentTitle ( OUStringBuffer& sTitle , + const css::uno::Reference< css::uno::XInterface >& xComponent) +{ + css::uno::Reference< css::frame::XTitle > xTitle(xComponent, css::uno::UNO_QUERY); + + // Note: Title has to be used (even if it's empty) if the right interface is supported. + if (xTitle.is ()) + sTitle.append (xTitle->getTitle ()); +} + +void TitleHelper::impl_appendProductName (OUStringBuffer& sTitle) +{ + OUString name(utl::ConfigManager::getProductName()); + if (!name.isEmpty()) + { + if (!sTitle.isEmpty()) + sTitle.append(" - "); + sTitle.append(name); + } +} + +void TitleHelper::impl_appendModuleName (OUStringBuffer& sTitle) +{ + css::uno::Reference< css::uno::XInterface > xOwner; + css::uno::Reference< css::uno::XComponentContext > xContext; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + xOwner = m_xOwner.get(); + xContext = m_xContext; + } + // <- SYNCHRONIZED + + try + { + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager = + css::frame::ModuleManager::create(xContext); + + const OUString sID = xModuleManager->identify(xOwner); + ::comphelper::SequenceAsHashMap lProps = xModuleManager->getByName (sID); + const OUString sUIName = lProps.getUnpackedValueOrDefault (OFFICEFACTORY_PROPNAME_ASCII_UINAME, OUString()); + + // An UIname property is an optional value ! + // So please add it to the title in case it does really exists only. + if (!sUIName.isEmpty()) + { + sTitle.append (" " ); + sTitle.append (sUIName); + } + } + catch(const css::uno::Exception&) + {} +} + +#ifdef DBG_UTIL +void TitleHelper::impl_appendDebugVersion (OUStringBuffer& sTitle) +{ + OUString version(utl::ConfigManager::getProductVersion()); + sTitle.append(' '); + sTitle.append(version); + OUString sVersion = ::utl::Bootstrap::getBuildIdData("development"); + sTitle.append(" ["); + sTitle.append(sVersion); + sTitle.append("]"); +} +#else +void TitleHelper::impl_appendDebugVersion (OUStringBuffer&) +{ +} +#endif + +void TitleHelper::impl_appendSafeMode (OUStringBuffer& sTitle) +{ + if (Application::IsSafeModeEnabled()) + sTitle.append(FwkResId (STR_SAFEMODE_TITLE)); +} + +void TitleHelper::impl_startListeningForModel (const css::uno::Reference< css::frame::XModel >& xModel) +{ + css::uno::Reference< css::document::XDocumentEventBroadcaster > xBroadcaster(xModel, css::uno::UNO_QUERY); + if ( ! xBroadcaster.is ()) + return; + + xBroadcaster->addDocumentEventListener (static_cast< css::document::XDocumentEventListener* >(this)); +} + +void TitleHelper::impl_startListeningForController (const css::uno::Reference< css::frame::XController >& xController) +{ + xController->addEventListener (static_cast< css::lang::XEventListener* > (static_cast< css::frame::XFrameActionListener* > (this) ) ); + css::uno::Reference< css::frame::XTitle > xSubTitle(xController->getModel (), css::uno::UNO_QUERY); + impl_setSubTitle (xSubTitle); +} + +void TitleHelper::impl_startListeningForFrame (const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + xFrame->addFrameActionListener(this ); + impl_updateListeningForFrame (xFrame); +} + +void TitleHelper::impl_updateListeningForFrame (const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + css::uno::Reference< css::frame::XTitle > xSubTitle(xFrame->getController (), css::uno::UNO_QUERY); + impl_setSubTitle (xSubTitle); +} + +void TitleHelper::impl_setSubTitle (const css::uno::Reference< css::frame::XTitle >& xSubTitle) +{ + css::uno::Reference< css::frame::XTitle > xOldSubTitle; + // SYNCHRONIZED -> + { + osl::MutexGuard aLock(m_aMutex); + + // ignore duplicate calls. Makes outside using of this helper more easy :-) + xOldSubTitle.set(m_xSubTitle.get(), css::uno::UNO_QUERY); + if (xOldSubTitle == xSubTitle) + return; + + m_xSubTitle = xSubTitle; + } + // <- SYNCHRONIZED + + css::uno::Reference< css::frame::XTitleChangeBroadcaster > xOldBroadcaster(xOldSubTitle , css::uno::UNO_QUERY ); + css::uno::Reference< css::frame::XTitleChangeBroadcaster > xNewBroadcaster(xSubTitle , css::uno::UNO_QUERY ); + css::uno::Reference< css::frame::XTitleChangeListener > xThis(this); + + if (xOldBroadcaster.is()) + xOldBroadcaster->removeTitleChangeListener (xThis); + + if (xNewBroadcaster.is()) + xNewBroadcaster->addTitleChangeListener (xThis); +} + +OUString TitleHelper::impl_convertURL2Title(std::u16string_view sURL) +{ + INetURLObject aURL (sURL); + OUString sTitle; + + if (aURL.GetProtocol() == INetProtocol::File) + { + if (aURL.HasMark()) + aURL = INetURLObject(aURL.GetURLNoMark()); + + sTitle = aURL.getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset); + } + else + { + if (aURL.hasExtension()) + sTitle = aURL.getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset); + + if ( sTitle.isEmpty() ) + sTitle = aURL.GetHostPort(INetURLObject::DecodeMechanism::WithCharset); + + if ( sTitle.isEmpty() ) + sTitle = aURL.GetURLNoPass(INetURLObject::DecodeMechanism::WithCharset); + } + + return sTitle; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/helper/undomanagerhelper.cxx b/framework/source/fwe/helper/undomanagerhelper.cxx new file mode 100644 index 000000000..779fe02e6 --- /dev/null +++ b/framework/source/fwe/helper/undomanagerhelper.cxx @@ -0,0 +1,1056 @@ +/* -*- 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 <framework/undomanagerhelper.hxx> +#include <framework/imutex.hxx> + +#include <com/sun/star/document/EmptyUndoStackException.hpp> +#include <com/sun/star/document/UndoContextNotClosedException.hpp> +#include <com/sun/star/document/UndoFailedException.hpp> +#include <com/sun/star/document/XUndoManager.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/util/InvalidStateException.hpp> +#include <com/sun/star/util/NotLockedException.hpp> +#include <com/sun/star/util/XModifyListener.hpp> + +#include <comphelper/interfacecontainer3.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <comphelper/flagguard.hxx> +#include <comphelper/asyncnotification.hxx> +#include <svl/undo.hxx> +#include <tools/diagnose_ex.h> +#include <osl/conditn.hxx> + +#include <functional> +#include <mutex> +#include <stack> +#include <queue> +#include <utility> + +namespace framework +{ + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::RuntimeException; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::document::XUndoManagerListener; + using ::com::sun::star::document::UndoManagerEvent; + using ::com::sun::star::document::EmptyUndoStackException; + using ::com::sun::star::document::UndoContextNotClosedException; + using ::com::sun::star::document::UndoFailedException; + using ::com::sun::star::util::NotLockedException; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::document::XUndoAction; + using ::com::sun::star::lang::XComponent; + using ::com::sun::star::document::XUndoManager; + using ::com::sun::star::util::InvalidStateException; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::util::XModifyListener; + + //= UndoActionWrapper + + namespace { + + class UndoActionWrapper : public SfxUndoAction + { + public: + explicit UndoActionWrapper( + Reference< XUndoAction > const& i_undoAction + ); + virtual ~UndoActionWrapper() override; + + virtual OUString GetComment() const override; + virtual void Undo() override; + virtual void Redo() override; + virtual bool CanRepeat(SfxRepeatTarget&) const override; + + private: + const Reference< XUndoAction > m_xUndoAction; + }; + + } + + UndoActionWrapper::UndoActionWrapper( Reference< XUndoAction > const& i_undoAction ) + : m_xUndoAction( i_undoAction ) + { + ENSURE_OR_THROW( m_xUndoAction.is(), "illegal undo action" ); + } + + UndoActionWrapper::~UndoActionWrapper() + { + try + { + Reference< XComponent > xComponent( m_xUndoAction, UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + } + + OUString UndoActionWrapper::GetComment() const + { + OUString sComment; + try + { + sComment = m_xUndoAction->getTitle(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + return sComment; + } + + void UndoActionWrapper::Undo() + { + m_xUndoAction->undo(); + } + + void UndoActionWrapper::Redo() + { + m_xUndoAction->redo(); + } + + bool UndoActionWrapper::CanRepeat(SfxRepeatTarget&) const + { + return false; + } + + //= UndoManagerRequest + + namespace { + + class UndoManagerRequest : public ::comphelper::AnyEvent + { + public: + explicit UndoManagerRequest( ::std::function<void ()> i_request ) + :m_request(std::move( i_request )) + { + m_finishCondition.reset(); + } + + void execute() + { + try + { + m_request(); + } + catch( const Exception& ) + { + m_caughtException = ::cppu::getCaughtException(); + } + m_finishCondition.set(); + } + + void wait() + { + m_finishCondition.wait(); + if ( m_caughtException.hasValue() ) + ::cppu::throwException( m_caughtException ); + } + + void cancel( const Reference< XInterface >& i_context ) + { + m_caughtException <<= RuntimeException( + "Concurrency error: an earlier operation on the stack failed.", + i_context + ); + m_finishCondition.set(); + } + + protected: + virtual ~UndoManagerRequest() override + { + } + + private: + ::std::function<void ()> m_request; + Any m_caughtException; + ::osl::Condition m_finishCondition; + }; + + } + + //= UndoManagerHelper_Impl + + class UndoManagerHelper_Impl : public SfxUndoListener + { + private: + ::osl::Mutex m_aMutex; + std::mutex m_aQueueMutex; + bool m_bAPIActionRunning; + bool m_bProcessingEvents; + sal_Int32 m_nLockCount; + ::comphelper::OInterfaceContainerHelper3<XUndoManagerListener> m_aUndoListeners; + ::comphelper::OInterfaceContainerHelper3<XModifyListener> m_aModifyListeners; + IUndoManagerImplementation& m_rUndoManagerImplementation; + ::std::stack< bool > m_aContextVisibilities; +#if OSL_DEBUG_LEVEL > 0 + bool m_bContextAPIFlagsEverPushed = {false}; + ::std::stack< bool > m_aContextAPIFlags; +#endif + ::std::queue< ::rtl::Reference< UndoManagerRequest > > + m_aEventQueue; + + public: + ::osl::Mutex& getMutex() { return m_aMutex; } + + public: + explicit UndoManagerHelper_Impl( IUndoManagerImplementation& i_undoManagerImpl ) + :m_bAPIActionRunning( false ) + ,m_bProcessingEvents( false ) + ,m_nLockCount( 0 ) + ,m_aUndoListeners( m_aMutex ) + ,m_aModifyListeners( m_aMutex ) + ,m_rUndoManagerImplementation( i_undoManagerImpl ) + { + getUndoManager().AddUndoListener( *this ); + } + + virtual ~UndoManagerHelper_Impl() + { + } + + SfxUndoManager& getUndoManager() const + { + return m_rUndoManagerImplementation.getImplUndoManager(); + } + + Reference< XUndoManager > getXUndoManager() const + { + return m_rUndoManagerImplementation.getThis(); + } + + // SfxUndoListener + virtual void actionUndone( const OUString& i_actionComment ) override; + virtual void actionRedone( const OUString& i_actionComment ) override; + virtual void undoActionAdded( const OUString& i_actionComment ) override; + virtual void cleared() override; + virtual void clearedRedo() override; + virtual void resetAll() override; + virtual void listActionEntered( const OUString& i_comment ) override; + virtual void listActionLeft( const OUString& i_comment ) override; + virtual void listActionCancelled() override; + + // public operations + void disposing(); + + void enterUndoContext( const OUString& i_title, const bool i_hidden, IMutexGuard& i_instanceLock ); + void leaveUndoContext( IMutexGuard& i_instanceLock ); + void addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock ); + void undo( IMutexGuard& i_instanceLock ); + void redo( IMutexGuard& i_instanceLock ); + void clear( IMutexGuard& i_instanceLock ); + void clearRedo( IMutexGuard& i_instanceLock ); + void reset( IMutexGuard& i_instanceLock ); + + void lock(); + void unlock(); + + void addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) + { + m_aUndoListeners.addInterface( i_listener ); + } + + void removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) + { + m_aUndoListeners.removeInterface( i_listener ); + } + + void addModifyListener( const Reference< XModifyListener >& i_listener ) + { + m_aModifyListeners.addInterface( i_listener ); + } + + void removeModifyListener( const Reference< XModifyListener >& i_listener ) + { + m_aModifyListeners.removeInterface( i_listener ); + } + + UndoManagerEvent + buildEvent( OUString const& i_title ) const; + + void impl_notifyModified(); + void notify( OUString const& i_title, + void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) + ); + void notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) ); + + private: + /// adds a function to be called to the request processor's queue + void impl_processRequest(::std::function<void ()> const& i_request, IMutexGuard& i_instanceLock); + + /// impl-versions of the XUndoManager API. + void impl_enterUndoContext( const OUString& i_title, const bool i_hidden ); + void impl_leaveUndoContext(); + void impl_addUndoAction( const Reference< XUndoAction >& i_action ); + void impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo ); + void impl_clear(); + void impl_clearRedo(); + void impl_reset(); + }; + + void UndoManagerHelper_Impl::disposing() + { + EventObject aEvent; + aEvent.Source = getXUndoManager(); + m_aUndoListeners.disposeAndClear( aEvent ); + m_aModifyListeners.disposeAndClear( aEvent ); + + ::osl::MutexGuard aGuard( m_aMutex ); + + getUndoManager().RemoveUndoListener( *this ); + } + + UndoManagerEvent UndoManagerHelper_Impl::buildEvent( OUString const& i_title ) const + { + UndoManagerEvent aEvent; + aEvent.Source = getXUndoManager(); + aEvent.UndoActionTitle = i_title; + aEvent.UndoContextDepth = getUndoManager().GetListActionDepth(); + return aEvent; + } + + void UndoManagerHelper_Impl::impl_notifyModified() + { + const EventObject aEvent( getXUndoManager() ); + m_aModifyListeners.notifyEach( &XModifyListener::modified, aEvent ); + } + + void UndoManagerHelper_Impl::notify( OUString const& i_title, + void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) ) + { + const UndoManagerEvent aEvent( buildEvent( i_title ) ); + + // TODO: this notification method here is used by UndoManagerHelper_Impl, to multiplex the notifications we + // receive from the SfxUndoManager. Those notifications are sent with a locked SolarMutex, which means + // we're doing the multiplexing here with a locked SM, too. Which is Bad (TM). + // Fixing this properly would require outsourcing all the notifications into an own thread - which might lead + // to problems of its own, since clients might expect synchronous notifications. + + m_aUndoListeners.notifyEach( i_notificationMethod, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) ) + { + const EventObject aEvent( getXUndoManager() ); + + // TODO: the same comment as in the other notify, regarding SM locking applies here ... + + m_aUndoListeners.notifyEach( i_notificationMethod, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::enterUndoContext( const OUString& i_title, const bool i_hidden, IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this, &i_title, i_hidden] () { return this->impl_enterUndoContext(i_title, i_hidden); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::leaveUndoContext( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this] () { return this->impl_leaveUndoContext(); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock ) + { + if ( !i_action.is() ) + throw IllegalArgumentException( + "illegal undo action object", + getXUndoManager(), + 1 + ); + + impl_processRequest( + [this, &i_action] () { return this->impl_addUndoAction(i_action); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::clear( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this] () { return this->impl_clear(); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::clearRedo( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this] () { return this->impl_clearRedo(); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::reset( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this] () { return this->impl_reset(); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::lock() + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( getMutex() ); + + if ( ++m_nLockCount == 1 ) + { + SfxUndoManager& rUndoManager = getUndoManager(); + rUndoManager.EnableUndo( false ); + } + // <--- SYNCHRONIZED + } + + void UndoManagerHelper_Impl::unlock() + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( getMutex() ); + + if ( m_nLockCount == 0 ) + throw NotLockedException( "Undo manager is not locked", getXUndoManager() ); + + if ( --m_nLockCount == 0 ) + { + SfxUndoManager& rUndoManager = getUndoManager(); + rUndoManager.EnableUndo( true ); + } + // <--- SYNCHRONIZED + } + + void UndoManagerHelper_Impl::impl_processRequest(::std::function<void ()> const& i_request, IMutexGuard& i_instanceLock) + { + // create the request, and add it to our queue + ::rtl::Reference< UndoManagerRequest > pRequest( new UndoManagerRequest( i_request ) ); + { + std::unique_lock aQueueGuard( m_aQueueMutex ); + m_aEventQueue.push( pRequest ); + } + + i_instanceLock.clear(); + + if ( m_bProcessingEvents ) + { + // another thread is processing the event queue currently => it will also process the event which we just added + pRequest->wait(); + return; + } + + m_bProcessingEvents = true; + do + { + pRequest.clear(); + { + std::unique_lock aQueueGuard( m_aQueueMutex ); + if ( m_aEventQueue.empty() ) + { + // reset the flag before releasing the queue mutex, otherwise it's possible that another thread + // could add an event after we release the mutex, but before we reset the flag. If then this other + // thread checks the flag before be reset it, this thread's event would starve. + m_bProcessingEvents = false; + return; + } + pRequest = m_aEventQueue.front(); + m_aEventQueue.pop(); + } + try + { + pRequest->execute(); + pRequest->wait(); + } + catch( ... ) + { + { + // no chance to process further requests, if the current one failed + // => discard them + std::unique_lock aQueueGuard( m_aQueueMutex ); + while ( !m_aEventQueue.empty() ) + { + pRequest = m_aEventQueue.front(); + m_aEventQueue.pop(); + pRequest->cancel( getXUndoManager() ); + } + m_bProcessingEvents = false; + } + // re-throw the error + throw; + } + } + while ( true ); + } + + void UndoManagerHelper_Impl::impl_enterUndoContext( const OUString& i_title, const bool i_hidden ) + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( !rUndoManager.IsUndoEnabled() ) + // ignore this request if the manager is locked + return; + + if ( i_hidden && ( rUndoManager.GetUndoActionCount() == 0 ) ) + throw EmptyUndoStackException( + "can't enter a hidden context without a previous Undo action", + m_rUndoManagerImplementation.getThis() + ); + + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + rUndoManager.EnterListAction( i_title, OUString(), 0, ViewShellId(-1) ); + } + + m_aContextVisibilities.push( i_hidden ); + + const UndoManagerEvent aEvent( buildEvent( i_title ) ); + aGuard.clear(); + // <--- SYNCHRONIZED + + m_aUndoListeners.notifyEach( i_hidden ? &XUndoManagerListener::enteredHiddenContext : &XUndoManagerListener::enteredContext, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::impl_leaveUndoContext() + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( !rUndoManager.IsUndoEnabled() ) + // ignore this request if the manager is locked + return; + + if ( !rUndoManager.IsInListAction() ) + throw InvalidStateException( + "no active undo context", + getXUndoManager() + ); + + size_t nContextElements = 0; + + const bool isHiddenContext = m_aContextVisibilities.top(); + m_aContextVisibilities.pop(); + + const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ) > 0 ); + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + if ( isHiddenContext ) + nContextElements = rUndoManager.LeaveAndMergeListAction(); + else + nContextElements = rUndoManager.LeaveListAction(); + } + const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ) > 0 ); + + // prepare notification + void ( SAL_CALL XUndoManagerListener::*notificationMethod )( const UndoManagerEvent& ) = nullptr; + + UndoManagerEvent aContextEvent( buildEvent( OUString() ) ); + const EventObject aClearedEvent( getXUndoManager() ); + if ( nContextElements == 0 ) + { + notificationMethod = &XUndoManagerListener::cancelledContext; + } + else if ( isHiddenContext ) + { + notificationMethod = &XUndoManagerListener::leftHiddenContext; + } + else + { + aContextEvent.UndoActionTitle = rUndoManager.GetUndoActionComment(); + notificationMethod = &XUndoManagerListener::leftContext; + } + + aGuard.clear(); + // <--- SYNCHRONIZED + + if ( bHadRedoActions && !bHasRedoActions ) + m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aClearedEvent ); + m_aUndoListeners.notifyEach( notificationMethod, aContextEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo ) + { + ::osl::Guard< ::framework::IMutex > aExternalGuard( i_externalLock.getGuardedMutex() ); + // note that this assumes that the mutex has been released in the thread which added the + // Undo/Redo request, so we can successfully acquire it + + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( rUndoManager.IsInListAction() ) + throw UndoContextNotClosedException( OUString(), getXUndoManager() ); + + const size_t nElements = i_undo + ? rUndoManager.GetUndoActionCount( SfxUndoManager::TopLevel ) + : rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ); + if ( nElements == 0 ) + throw EmptyUndoStackException("stack is empty", getXUndoManager() ); + + aGuard.clear(); + // <--- SYNCHRONIZED + + try + { + if ( i_undo ) + rUndoManager.Undo(); + else + rUndoManager.Redo(); + } + catch( const RuntimeException& ) { /* allowed to leave here */ throw; } + catch( const UndoFailedException& ) { /* allowed to leave here */ throw; } + catch( const Exception& ) + { + // not allowed to leave + const Any aError( ::cppu::getCaughtException() ); + throw UndoFailedException( OUString(), getXUndoManager(), aError ); + } + + // note that in opposite to all of the other methods, we do *not* have our mutex locked when calling + // into the SfxUndoManager implementation. This ensures that an actual XUndoAction::undo/redo is also + // called without our mutex being locked. + // As a consequence, we do not set m_bAPIActionRunning here. Instead, our actionUndone/actionRedone methods + // *always* multiplex the event to our XUndoManagerListeners, not only when m_bAPIActionRunning is FALSE (This + // again is different from all other SfxUndoListener methods). + // So, we do not need to do this notification here ourself. + } + + void UndoManagerHelper_Impl::impl_addUndoAction( const Reference< XUndoAction >& i_action ) + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( !rUndoManager.IsUndoEnabled() ) + // ignore the request if the manager is locked + return; + + const UndoManagerEvent aEventAdd( buildEvent( i_action->getTitle() ) ); + const EventObject aEventClear( getXUndoManager() ); + + const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount() > 0 ); + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + rUndoManager.AddUndoAction( std::make_unique<UndoActionWrapper>( i_action ) ); + } + const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount() > 0 ); + + aGuard.clear(); + // <--- SYNCHRONIZED + + m_aUndoListeners.notifyEach( &XUndoManagerListener::undoActionAdded, aEventAdd ); + if ( bHadRedoActions && !bHasRedoActions ) + m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEventClear ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::impl_clear() + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( rUndoManager.IsInListAction() ) + throw UndoContextNotClosedException( OUString(), getXUndoManager() ); + + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + rUndoManager.Clear(); + } + + const EventObject aEvent( getXUndoManager() ); + aGuard.clear(); + // <--- SYNCHRONIZED + + m_aUndoListeners.notifyEach( &XUndoManagerListener::allActionsCleared, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::impl_clearRedo() + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + if ( rUndoManager.IsInListAction() ) + throw UndoContextNotClosedException( OUString(), getXUndoManager() ); + + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + rUndoManager.ClearRedo(); + } + + const EventObject aEvent( getXUndoManager() ); + aGuard.clear(); + // <--- SYNCHRONIZED + + m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::impl_reset() + { + // SYNCHRONIZED ---> + ::osl::ClearableMutexGuard aGuard( m_aMutex ); + + SfxUndoManager& rUndoManager = getUndoManager(); + { + ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); + rUndoManager.Reset(); + } + + const EventObject aEvent( getXUndoManager() ); + aGuard.clear(); + // <--- SYNCHRONIZED + + m_aUndoListeners.notifyEach( &XUndoManagerListener::resetAll, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::actionUndone( const OUString& i_actionComment ) + { + UndoManagerEvent aEvent; + aEvent.Source = getXUndoManager(); + aEvent.UndoActionTitle = i_actionComment; + aEvent.UndoContextDepth = 0; // Undo can happen on level 0 only + m_aUndoListeners.notifyEach( &XUndoManagerListener::actionUndone, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::actionRedone( const OUString& i_actionComment ) + { + UndoManagerEvent aEvent; + aEvent.Source = getXUndoManager(); + aEvent.UndoActionTitle = i_actionComment; + aEvent.UndoContextDepth = 0; // Redo can happen on level 0 only + m_aUndoListeners.notifyEach( &XUndoManagerListener::actionRedone, aEvent ); + impl_notifyModified(); + } + + void UndoManagerHelper_Impl::undoActionAdded( const OUString& i_actionComment ) + { + if ( m_bAPIActionRunning ) + return; + + notify( i_actionComment, &XUndoManagerListener::undoActionAdded ); + } + + void UndoManagerHelper_Impl::cleared() + { + if ( m_bAPIActionRunning ) + return; + + notify( &XUndoManagerListener::allActionsCleared ); + } + + void UndoManagerHelper_Impl::clearedRedo() + { + if ( m_bAPIActionRunning ) + return; + + notify( &XUndoManagerListener::redoActionsCleared ); + } + + void UndoManagerHelper_Impl::resetAll() + { + if ( m_bAPIActionRunning ) + return; + + notify( &XUndoManagerListener::resetAll ); + } + + void UndoManagerHelper_Impl::listActionEntered( const OUString& i_comment ) + { +#if OSL_DEBUG_LEVEL > 0 + m_aContextAPIFlags.push( m_bAPIActionRunning ); + m_bContextAPIFlagsEverPushed = true; +#endif + + if ( m_bAPIActionRunning ) + return; + + notify( i_comment, &XUndoManagerListener::enteredContext ); + } + + void UndoManagerHelper_Impl::listActionLeft( const OUString& i_comment ) + { +#if OSL_DEBUG_LEVEL > 0 + // It may happen that the very first event listener is added during a + // list action after listActionEntered() was already called, e.g. Calc + // formula calculation event listener during the input of the very + // first formula. Instead of checking m_aContextAPIFlags for empty, + // still assert (on calling top()) other stack mismatches but ignore + // this one case. See tdf#142980 + if (m_bContextAPIFlagsEverPushed) + { + const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top(); + m_aContextAPIFlags.pop(); + OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionLeft: API and non-API contexts interwoven!" ); + } +#endif + + if ( m_bAPIActionRunning ) + return; + + notify( i_comment, &XUndoManagerListener::leftContext ); + } + + void UndoManagerHelper_Impl::listActionCancelled() + { +#if OSL_DEBUG_LEVEL > 0 + const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top(); + m_aContextAPIFlags.pop(); + OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionCancelled: API and non-API contexts interwoven!" ); +#endif + + if ( m_bAPIActionRunning ) + return; + + notify( OUString(), &XUndoManagerListener::cancelledContext ); + } + + //= UndoManagerHelper + + UndoManagerHelper::UndoManagerHelper( IUndoManagerImplementation& i_undoManagerImpl ) + :m_xImpl( new UndoManagerHelper_Impl( i_undoManagerImpl ) ) + { + } + + UndoManagerHelper::~UndoManagerHelper() + { + } + + void UndoManagerHelper::disposing() + { + m_xImpl->disposing(); + } + + void UndoManagerHelper::enterUndoContext( const OUString& i_title, IMutexGuard& i_instanceLock ) + { + m_xImpl->enterUndoContext( i_title, false, i_instanceLock ); + } + + void UndoManagerHelper::enterHiddenUndoContext( IMutexGuard& i_instanceLock ) + { + m_xImpl->enterUndoContext( OUString(), true, i_instanceLock ); + } + + void UndoManagerHelper::leaveUndoContext( IMutexGuard& i_instanceLock ) + { + m_xImpl->leaveUndoContext( i_instanceLock ); + } + + void UndoManagerHelper_Impl::undo( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this, &i_instanceLock] () { return this->impl_doUndoRedo(i_instanceLock, true); }, + i_instanceLock + ); + } + + void UndoManagerHelper_Impl::redo( IMutexGuard& i_instanceLock ) + { + impl_processRequest( + [this, &i_instanceLock] () { return this->impl_doUndoRedo(i_instanceLock, false); }, + i_instanceLock + ); + } + + void UndoManagerHelper::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock ) + { + m_xImpl->addUndoAction( i_action, i_instanceLock ); + } + + void UndoManagerHelper::undo( IMutexGuard& i_instanceLock ) + { + m_xImpl->undo( i_instanceLock ); + } + + void UndoManagerHelper::redo( IMutexGuard& i_instanceLock ) + { + m_xImpl->redo( i_instanceLock ); + } + + bool UndoManagerHelper::isUndoPossible() const + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( m_xImpl->getMutex() ); + SfxUndoManager& rUndoManager = m_xImpl->getUndoManager(); + if ( rUndoManager.IsInListAction() ) + return false; + return rUndoManager.GetUndoActionCount( SfxUndoManager::TopLevel ) > 0; + // <--- SYNCHRONIZED + } + + bool UndoManagerHelper::isRedoPossible() const + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( m_xImpl->getMutex() ); + const SfxUndoManager& rUndoManager = m_xImpl->getUndoManager(); + if ( rUndoManager.IsInListAction() ) + return false; + return rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ) > 0; + // <--- SYNCHRONIZED + } + + namespace + { + + OUString lcl_getCurrentActionTitle( UndoManagerHelper_Impl& i_impl, const bool i_undo ) + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( i_impl.getMutex() ); + + const SfxUndoManager& rUndoManager = i_impl.getUndoManager(); + const size_t nActionCount = i_undo + ? rUndoManager.GetUndoActionCount( SfxUndoManager::TopLevel ) + : rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ); + if ( nActionCount == 0 ) + throw EmptyUndoStackException( + i_undo ? OUString( "no action on the undo stack" ) + : OUString( "no action on the redo stack" ), + i_impl.getXUndoManager() + ); + return i_undo + ? rUndoManager.GetUndoActionComment( 0, SfxUndoManager::TopLevel ) + : rUndoManager.GetRedoActionComment( 0, SfxUndoManager::TopLevel ); + // <--- SYNCHRONIZED + } + + Sequence< OUString > lcl_getAllActionTitles( UndoManagerHelper_Impl& i_impl, const bool i_undo ) + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( i_impl.getMutex() ); + + const SfxUndoManager& rUndoManager = i_impl.getUndoManager(); + const size_t nCount = i_undo + ? rUndoManager.GetUndoActionCount( SfxUndoManager::TopLevel ) + : rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ); + + Sequence< OUString > aTitles( nCount ); + auto aTitlesRange = asNonConstRange(aTitles); + for ( size_t i=0; i<nCount; ++i ) + { + aTitlesRange[i] = i_undo + ? rUndoManager.GetUndoActionComment( i, SfxUndoManager::TopLevel ) + : rUndoManager.GetRedoActionComment( i, SfxUndoManager::TopLevel ); + } + return aTitles; + // <--- SYNCHRONIZED + } + } + + OUString UndoManagerHelper::getCurrentUndoActionTitle() const + { + return lcl_getCurrentActionTitle( *m_xImpl, true ); + } + + OUString UndoManagerHelper::getCurrentRedoActionTitle() const + { + return lcl_getCurrentActionTitle( *m_xImpl, false ); + } + + Sequence< OUString > UndoManagerHelper::getAllUndoActionTitles() const + { + return lcl_getAllActionTitles( *m_xImpl, true ); + } + + Sequence< OUString > UndoManagerHelper::getAllRedoActionTitles() const + { + return lcl_getAllActionTitles( *m_xImpl, false ); + } + + void UndoManagerHelper::clear( IMutexGuard& i_instanceLock ) + { + m_xImpl->clear( i_instanceLock ); + } + + void UndoManagerHelper::clearRedo( IMutexGuard& i_instanceLock ) + { + m_xImpl->clearRedo( i_instanceLock ); + } + + void UndoManagerHelper::reset( IMutexGuard& i_instanceLock ) + { + m_xImpl->reset( i_instanceLock ); + } + + void UndoManagerHelper::lock() + { + m_xImpl->lock(); + } + + void UndoManagerHelper::unlock() + { + m_xImpl->unlock(); + } + + bool UndoManagerHelper::isLocked() + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( m_xImpl->getMutex() ); + + SfxUndoManager& rUndoManager = m_xImpl->getUndoManager(); + return !rUndoManager.IsUndoEnabled(); + // <--- SYNCHRONIZED + } + + void UndoManagerHelper::addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) + { + if ( i_listener.is() ) + m_xImpl->addUndoManagerListener( i_listener ); + } + + void UndoManagerHelper::removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) + { + if ( i_listener.is() ) + m_xImpl->removeUndoManagerListener( i_listener ); + } + + void UndoManagerHelper::addModifyListener( const Reference< XModifyListener >& i_listener ) + { + if ( i_listener.is() ) + m_xImpl->addModifyListener( i_listener ); + } + + void UndoManagerHelper::removeModifyListener( const Reference< XModifyListener >& i_listener ) + { + if ( i_listener.is() ) + m_xImpl->removeModifyListener( i_listener ); + } + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/xml/menuconfiguration.cxx b/framework/source/fwe/xml/menuconfiguration.cxx new file mode 100644 index 000000000..d2131c8a8 --- /dev/null +++ b/framework/source/fwe/xml/menuconfiguration.cxx @@ -0,0 +1,156 @@ +/* -*- 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 <menuconfiguration.hxx> + +#include <addonmenu.hxx> +#include <utility> +#include <xml/menudocumenthandler.hxx> +#include <xml/saxnamespacefilter.hxx> + +#include <uielement/rootitemcontainer.hxx> + +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <cppuhelper/exc_hlp.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::io; + +namespace framework +{ + +MenuConfiguration::MenuConfiguration( css::uno::Reference< css::uno::XComponentContext > xContext ) +: m_xContext(std::move( xContext )) +{ +} + +MenuConfiguration::~MenuConfiguration() +{ +} + +Reference< XIndexAccess > MenuConfiguration::CreateMenuBarConfigurationFromXML( + Reference< XInputStream > const & rInputStream ) +{ + Reference< XParser > xParser = Parser::create( m_xContext ); + + // connect stream to input stream to the parser + InputSource aInputSource; + + aInputSource.aInputStream = rInputStream; + + // create menu bar + Reference< XIndexContainer > xItemContainer( new RootItemContainer() ); + + // create namespace filter and set menudocument handler inside to support xml namespaces + + Reference< XDocumentHandler > xDocHandler( new OReadMenuDocumentHandler( xItemContainer )); + + Reference< XDocumentHandler > xFilter( new SaxNamespaceFilter( xDocHandler )); + + // connect parser and filter + xParser->setDocumentHandler( xFilter ); + + try + { + xParser->parseStream( aInputSource ); + return xItemContainer; + } + catch ( const RuntimeException& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetException( e.Message, Reference< XInterface >(), anyEx ); + } + catch( const SAXException& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + SAXException aWrappedSAXException; + + if ( !( e.WrappedException >>= aWrappedSAXException )) + throw WrappedTargetException( e.Message, Reference< XInterface >(), anyEx ); + else + throw WrappedTargetException( aWrappedSAXException.Message, Reference< XInterface >(), e.WrappedException ); + } + catch( const css::io::IOException& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetException( e.Message, Reference< XInterface >(), anyEx ); + } +} + +void MenuConfiguration::StoreMenuBarConfigurationToXML( + Reference< XIndexAccess > const & rMenuBarConfiguration, + Reference< XOutputStream > const & rOutputStream, bool bIsMenuBar ) +{ + Reference< XWriter > xWriter = Writer::create(m_xContext); + xWriter->setOutputStream( rOutputStream ); + + try + { + OWriteMenuDocumentHandler aWriteMenuDocumentHandler( rMenuBarConfiguration, xWriter, bIsMenuBar ); + aWriteMenuDocumentHandler.WriteMenuDocument(); + } + catch ( const RuntimeException& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetException( e.Message, Reference< XInterface >(), anyEx ); + } + catch ( const SAXException& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetException( e.Message, Reference< XInterface >(), anyEx ); + } + catch ( const css::io::IOException& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw WrappedTargetException( e.Message, Reference< XInterface >(), anyEx ); + } +} + +void* MenuAttributes::CreateAttribute(const OUString& rFrame, const OUString& rImageIdStr) +{ + MenuAttributes* pAttributes = new MenuAttributes(rFrame, rImageIdStr); + pAttributes->acquire(); + return pAttributes; +} + +void* MenuAttributes::CreateAttribute(const css::uno::WeakReference<css::frame::XDispatchProvider>& rDispatchProvider) +{ + MenuAttributes* pAttributes = new MenuAttributes(rDispatchProvider); + pAttributes->acquire(); + return pAttributes; +} + +void MenuAttributes::ReleaseAttribute(void* nAttributePtr) +{ + if (!nAttributePtr) + return; + MenuAttributes* pAttributes = static_cast<MenuAttributes*>(nAttributePtr); + pAttributes->release(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/xml/menudocumenthandler.cxx b/framework/source/fwe/xml/menudocumenthandler.cxx new file mode 100644 index 000000000..cfb4e9f97 --- /dev/null +++ b/framework/source/fwe/xml/menudocumenthandler.cxx @@ -0,0 +1,897 @@ +/* -*- 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/macros.h> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> + +#include <xml/menudocumenthandler.hxx> + +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <com/sun/star/xml/sax/XExtendedDocumentHandler.hpp> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <com/sun/star/ui/ItemType.hpp> +#include <com/sun/star/ui/ItemStyle.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/attributelist.hxx> + +#ifdef ATTRIBUTE_HELPID +#undef ATTRIBUTE_HELPID +#endif + +constexpr OUStringLiteral XMLNS_MENU = u"http://openoffice.org/2001/menu"; + +constexpr OUStringLiteral ELEMENT_MENUBAR = u"http://openoffice.org/2001/menu^menubar"; +constexpr OUStringLiteral ELEMENT_MENU = u"http://openoffice.org/2001/menu^menu"; +constexpr OUStringLiteral ELEMENT_MENUPOPUP = u"http://openoffice.org/2001/menu^menupopup"; +constexpr OUStringLiteral ELEMENT_MENUITEM = u"http://openoffice.org/2001/menu^menuitem"; +constexpr OUStringLiteral ELEMENT_MENUSEPARATOR = u"http://openoffice.org/2001/menu^menuseparator"; + +constexpr OUStringLiteral ELEMENT_NS_MENUBAR = u"menu:menubar"; +constexpr OUStringLiteral ELEMENT_NS_MENU = u"menu:menu"; +constexpr OUStringLiteral ELEMENT_NS_MENUPOPUP = u"menu:menupopup"; +constexpr OUStringLiteral ELEMENT_NS_MENUITEM = u"menu:menuitem"; +constexpr OUStringLiteral ELEMENT_NS_MENUSEPARATOR = u"menu:menuseparator"; + +constexpr OUStringLiteral ATTRIBUTE_ID = u"http://openoffice.org/2001/menu^id"; +constexpr OUStringLiteral ATTRIBUTE_LABEL = u"http://openoffice.org/2001/menu^label"; +constexpr OUStringLiteral ATTRIBUTE_HELPID = u"http://openoffice.org/2001/menu^helpid"; +constexpr OUStringLiteral ATTRIBUTE_STYLE = u"http://openoffice.org/2001/menu^style"; + +constexpr OUStringLiteral ATTRIBUTE_NS_ID = u"menu:id"; +constexpr OUStringLiteral ATTRIBUTE_NS_LABEL = u"menu:label"; +constexpr OUStringLiteral ATTRIBUTE_NS_HELPID = u"menu:helpid"; +constexpr OUStringLiteral ATTRIBUTE_NS_STYLE = u"menu:style"; + +constexpr OUStringLiteral ATTRIBUTE_XMLNS_MENU = u"xmlns:menu"; + +constexpr OUStringLiteral ATTRIBUTE_TYPE_CDATA = u"CDATA"; + +constexpr OUStringLiteral MENUBAR_DOCTYPE = u"<!DOCTYPE menu:menubar PUBLIC \"-//OpenOffice.org//DTD OfficeDocument 1.0//EN\" \"menubar.dtd\">"; + +#define ATTRIBUTE_ITEMSTYLE_TEXT "text" +#define ATTRIBUTE_ITEMSTYLE_IMAGE "image" +#define ATTRIBUTE_ITEMSTYLE_RADIO "radio" + +// Property names of a menu/menu item ItemDescriptor +constexpr OUStringLiteral ITEM_DESCRIPTOR_COMMANDURL = u"CommandURL"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_HELPURL = u"HelpURL"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_CONTAINER = u"ItemDescriptorContainer"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_LABEL = u"Label"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_TYPE = u"Type"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_STYLE = u"Style"; + +// using namespaces + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::ui; + +namespace framework +{ + +namespace { + +struct MenuStyleItem +{ + sal_Int16 nBit; + const char* attrName; +}; + +} + +const MenuStyleItem MenuItemStyles[ ] = { + { css::ui::ItemStyle::ICON, ATTRIBUTE_ITEMSTYLE_IMAGE }, + { css::ui::ItemStyle::TEXT, ATTRIBUTE_ITEMSTYLE_TEXT }, + { css::ui::ItemStyle::RADIO_CHECK, ATTRIBUTE_ITEMSTYLE_RADIO } +}; + +sal_Int32 const nMenuStyleItemEntries = SAL_N_ELEMENTS(MenuItemStyles); + +static void ExtractMenuParameters( const Sequence< PropertyValue >& rProp, + OUString& rCommandURL, + OUString& rLabel, + OUString& rHelpURL, + Reference< XIndexAccess >& rSubMenu, + sal_Int16& rType, + sal_Int16& rStyle ) +{ + for ( const PropertyValue& p : rProp ) + { + if ( p.Name == ITEM_DESCRIPTOR_COMMANDURL ) + { + p.Value >>= rCommandURL; + rCommandURL = rCommandURL.intern(); + } + else if ( p.Name == ITEM_DESCRIPTOR_HELPURL ) + { + p.Value >>= rHelpURL; + } + else if ( p.Name == ITEM_DESCRIPTOR_CONTAINER ) + { + p.Value >>= rSubMenu; + } + else if ( p.Name == ITEM_DESCRIPTOR_LABEL ) + { + p.Value >>= rLabel; + } + else if ( p.Name == ITEM_DESCRIPTOR_TYPE ) + { + p.Value >>= rType; + } + else if ( p.Name == ITEM_DESCRIPTOR_STYLE ) + { + p.Value >>= rStyle; + } + } +} + +// Base class implementation + +ReadMenuDocumentHandlerBase::ReadMenuDocumentHandlerBase() : + m_aType( ITEM_DESCRIPTOR_TYPE ), + m_aLabel( ITEM_DESCRIPTOR_LABEL ), + m_aContainer( ITEM_DESCRIPTOR_CONTAINER ), + m_aHelpURL( ITEM_DESCRIPTOR_HELPURL ), + m_aCommandURL( ITEM_DESCRIPTOR_COMMANDURL ), + m_aStyle( ITEM_DESCRIPTOR_STYLE ) +{ +} + +ReadMenuDocumentHandlerBase::~ReadMenuDocumentHandlerBase() +{ +} + +void SAL_CALL ReadMenuDocumentHandlerBase::ignorableWhitespace( + const OUString& ) +{ +} + +void SAL_CALL ReadMenuDocumentHandlerBase::processingInstruction( + const OUString& /*aTarget*/, const OUString& /*aData*/ ) +{ +} + +void SAL_CALL ReadMenuDocumentHandlerBase::setDocumentLocator( + const Reference< XLocator > &xLocator) +{ + m_xLocator = xLocator; +} + +OUString ReadMenuDocumentHandlerBase::getErrorLineString() +{ + if ( m_xLocator.is() ) + return "Line: " + OUString::number( m_xLocator->getLineNumber() ) + " - "; + else + return OUString(); +} + +void ReadMenuDocumentHandlerBase::initPropertyCommon( + Sequence< PropertyValue > &rProps, const OUString &rCommandURL, + const OUString &rHelpId, const OUString &rLabel, sal_Int16 nItemStyleBits ) +{ + auto pProps = rProps.getArray(); + + pProps[0].Name = m_aCommandURL; + pProps[1].Name = m_aHelpURL; + pProps[2].Name = m_aContainer; + pProps[3].Name = m_aLabel; + pProps[4].Name = m_aStyle; + pProps[5].Name = m_aType; + + // Common values + pProps[0].Value <<= rCommandURL.intern(); + pProps[1].Value <<= rHelpId; + pProps[2].Value <<= Reference< XIndexContainer >(); + pProps[3].Value <<= rLabel; + pProps[4].Value <<= nItemStyleBits; + pProps[5].Value <<= css::ui::ItemType::DEFAULT; +} + +OReadMenuDocumentHandler::OReadMenuDocumentHandler( + const Reference< XIndexContainer >& rMenuBarContainer ) +: m_nElementDepth( 0 ), + m_eReaderMode( ReaderMode::None ), + m_xMenuBarContainer( rMenuBarContainer ), + m_xContainerFactory( rMenuBarContainer, UNO_QUERY ) +{ +} + +OReadMenuDocumentHandler::~OReadMenuDocumentHandler() +{ +} + +void SAL_CALL OReadMenuDocumentHandler::startDocument() +{ +} + +void SAL_CALL OReadMenuDocumentHandler::endDocument() +{ + if ( m_nElementDepth > 0 ) + { + OUString aErrorMessage = getErrorLineString() + + "A closing element is missing!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } +} + +void SAL_CALL OReadMenuDocumentHandler::startElement( + const OUString& aName, const Reference< XAttributeList > &xAttrList ) +{ + if ( m_eReaderMode != ReaderMode::None ) + { + ++m_nElementDepth; + m_xReader->startElement( aName, xAttrList ); + } + else + { + if ( aName == ELEMENT_MENUBAR ) + { + m_eReaderMode = ReaderMode::MenuBar; + m_xReader.set( new OReadMenuBarHandler( m_xMenuBarContainer, m_xContainerFactory )); + } + else if ( aName == ELEMENT_MENUPOPUP ) + { + m_eReaderMode = ReaderMode::MenuPopup; + m_xReader.set( new OReadMenuPopupHandler( m_xMenuBarContainer, m_xContainerFactory )); + } + ++m_nElementDepth; + m_xReader->startDocument(); + } +} + +void SAL_CALL OReadMenuDocumentHandler::characters(const OUString&) +{ +} + +void SAL_CALL OReadMenuDocumentHandler::endElement( const OUString& aName ) +{ + if ( m_eReaderMode == ReaderMode::None ) + return; + + --m_nElementDepth; + m_xReader->endElement( aName ); + if ( 0 != m_nElementDepth ) + return; + + m_xReader->endDocument(); + m_xReader.clear(); + if ( m_eReaderMode == ReaderMode::MenuBar && aName != ELEMENT_MENUBAR ) + { + OUString aErrorMessage = getErrorLineString() + + "closing element menubar expected!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + else if ( m_eReaderMode == ReaderMode::MenuPopup && aName != ELEMENT_MENUPOPUP ) + { + OUString aErrorMessage = getErrorLineString() + + "closing element menupopup expected!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + m_eReaderMode = ReaderMode::None; +} + +OReadMenuBarHandler::OReadMenuBarHandler( + const Reference< XIndexContainer >& rMenuBarContainer, + const Reference< XSingleComponentFactory >& rFactory ) +: m_nElementDepth( 0 ), + m_bMenuMode( false ), + m_xMenuBarContainer( rMenuBarContainer ), + m_xContainerFactory( rFactory ) +{ +} + +OReadMenuBarHandler::~OReadMenuBarHandler() +{ +} + +void SAL_CALL OReadMenuBarHandler::startDocument() +{ +} + +void SAL_CALL OReadMenuBarHandler::endDocument() +{ +} + +void SAL_CALL OReadMenuBarHandler::startElement( + const OUString& rName, const Reference< XAttributeList > &xAttrList ) +{ + if ( m_bMenuMode ) + { + ++m_nElementDepth; + m_xReader->startElement( rName, xAttrList ); + } + else if ( rName == ELEMENT_MENU ) + { + ++m_nElementDepth; + + OUString aHelpId; + OUString aCommandId; + OUString aLabel; + sal_Int16 nItemBits(0); + + m_bMenuMode = true; + + // Container must be factory to create sub container + Reference< XComponentContext > xComponentContext( + comphelper::getProcessComponentContext() ); + + Reference< XIndexContainer > xSubItemContainer; + if ( m_xContainerFactory.is() ) + xSubItemContainer.set( m_xContainerFactory->createInstanceWithContext( xComponentContext ), UNO_QUERY ); + + if ( xSubItemContainer.is() ) + { + // read attributes for menu + for ( sal_Int16 i=0; i< xAttrList->getLength(); i++ ) + { + OUString aName = xAttrList->getNameByIndex( i ); + const OUString aValue = xAttrList->getValueByIndex( i ); + if ( aName == ATTRIBUTE_ID ) + aCommandId = aValue; + else if ( aName == ATTRIBUTE_LABEL ) + aLabel = aValue; + else if ( aName == ATTRIBUTE_HELPID ) + aHelpId = aValue; + else if ( aName == ATTRIBUTE_STYLE ) + { + sal_Int32 nIndex = 0; + do + { + OUString aToken = aValue.getToken( 0, '+', nIndex ); + if ( !aToken.isEmpty() ) + { + if ( aToken == ATTRIBUTE_ITEMSTYLE_TEXT ) + nItemBits |= css::ui::ItemStyle::TEXT; + else if ( aToken == ATTRIBUTE_ITEMSTYLE_IMAGE ) + nItemBits |= css::ui::ItemStyle::ICON; + else if ( aToken == ATTRIBUTE_ITEMSTYLE_RADIO ) + nItemBits |= css::ui::ItemStyle::RADIO_CHECK; + } + } + while ( nIndex >= 0 ); + } + } + + if ( !aCommandId.isEmpty() ) + { + Sequence< PropertyValue > aSubMenuProp( 6 ); + initPropertyCommon( aSubMenuProp, aCommandId, aHelpId, aLabel, nItemBits ); + aSubMenuProp.getArray()[2].Value <<= xSubItemContainer; + + m_xMenuBarContainer->insertByIndex( m_xMenuBarContainer->getCount(), Any( aSubMenuProp ) ); + } + else + { + OUString aErrorMessage = getErrorLineString() + + "attribute id for element menu required!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_xReader.set( new OReadMenuHandler( xSubItemContainer, m_xContainerFactory )); + m_xReader->startDocument(); + } + } + else + { + OUString aErrorMessage = getErrorLineString() + + "element menu expected!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } +} + +void SAL_CALL OReadMenuBarHandler::characters(const OUString&) +{ +} + +void OReadMenuBarHandler::endElement( const OUString& aName ) +{ + if ( !m_bMenuMode ) + return; + + --m_nElementDepth; + if ( 0 == m_nElementDepth ) + { + m_xReader->endDocument(); + m_xReader.clear(); + m_bMenuMode = false; + if ( aName != ELEMENT_MENU ) + { + OUString aErrorMessage = getErrorLineString() + + "closing element menu expected!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + } + else + m_xReader->endElement( aName ); +} + +OReadMenuHandler::OReadMenuHandler( + const Reference< XIndexContainer >& rMenuContainer, + const Reference< XSingleComponentFactory >& rFactory ) : + m_nElementDepth( 0 ), + m_bMenuPopupMode( false ), + m_xMenuContainer( rMenuContainer ), + m_xContainerFactory( rFactory ) +{ +} + +OReadMenuHandler::~OReadMenuHandler() +{ +} + +void SAL_CALL OReadMenuHandler::startDocument() +{ +} + +void SAL_CALL OReadMenuHandler::endDocument() +{ +} + +void SAL_CALL OReadMenuHandler::startElement( + const OUString& aName, const Reference< XAttributeList > &xAttrList ) +{ + if ( m_bMenuPopupMode ) + { + ++m_nElementDepth; + m_xReader->startElement( aName, xAttrList ); + } + else if ( aName == ELEMENT_MENUPOPUP ) + { + ++m_nElementDepth; + m_bMenuPopupMode = true; + m_xReader.set( new OReadMenuPopupHandler( m_xMenuContainer, m_xContainerFactory )); + m_xReader->startDocument(); + } + else + { + OUString aErrorMessage = getErrorLineString() + + "unknown element found!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } +} + +void SAL_CALL OReadMenuHandler::characters(const OUString&) +{ +} + +void SAL_CALL OReadMenuHandler::endElement( const OUString& aName ) +{ + if ( !m_bMenuPopupMode ) + return; + + --m_nElementDepth; + if ( 0 == m_nElementDepth ) + { + m_xReader->endDocument(); + m_xReader.clear(); + m_bMenuPopupMode = false; + if ( aName != ELEMENT_MENUPOPUP ) + { + OUString aErrorMessage = getErrorLineString() + + "closing element menupopup expected!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + } + else + m_xReader->endElement( aName ); +} + +OReadMenuPopupHandler::OReadMenuPopupHandler( + const Reference< XIndexContainer >& rMenuContainer, + const Reference< XSingleComponentFactory >& rFactory ) : + m_nElementDepth( 0 ), + m_bMenuMode( false ), + m_xMenuContainer( rMenuContainer ), + m_xContainerFactory( rFactory ), + m_xComponentContext( comphelper::getProcessComponentContext() ), + m_nNextElementExpected( ELEM_CLOSE_NONE ) +{ +} + +OReadMenuPopupHandler::~OReadMenuPopupHandler() +{ +} + +void SAL_CALL OReadMenuPopupHandler::startDocument() +{ +} + +void SAL_CALL OReadMenuPopupHandler::endDocument() +{ +} + +void SAL_CALL OReadMenuPopupHandler::startElement( + const OUString& rName, const Reference< XAttributeList > &xAttrList ) +{ + ++m_nElementDepth; + + if ( m_bMenuMode ) + m_xReader->startElement( rName, xAttrList ); + else if ( rName == ELEMENT_MENU ) + { + OUString aHelpId; + OUString aCommandId; + OUString aLabel; + sal_Int16 nItemBits(0); + + m_bMenuMode = true; + + // Container must be factory to create sub container + Reference< XIndexContainer > xSubItemContainer; + if ( m_xContainerFactory.is() ) + xSubItemContainer.set( m_xContainerFactory->createInstanceWithContext( m_xComponentContext ), UNO_QUERY ); + + // read attributes for menu + for ( sal_Int16 i=0; i< xAttrList->getLength(); i++ ) + { + OUString aName = xAttrList->getNameByIndex( i ); + const OUString aValue = xAttrList->getValueByIndex( i ); + if ( aName == ATTRIBUTE_ID ) + aCommandId = aValue; + else if ( aName == ATTRIBUTE_LABEL ) + aLabel = aValue; + else if ( aName == ATTRIBUTE_HELPID ) + aHelpId = aValue; + else if ( aName == ATTRIBUTE_STYLE ) + { + sal_Int32 nIndex = 0; + do + { + OUString aToken = aValue.getToken( 0, '+', nIndex ); + if ( !aToken.isEmpty() ) + { + if ( aToken == ATTRIBUTE_ITEMSTYLE_TEXT ) + nItemBits |= css::ui::ItemStyle::TEXT; + else if ( aToken == ATTRIBUTE_ITEMSTYLE_IMAGE ) + nItemBits |= css::ui::ItemStyle::ICON; + else if ( aToken == ATTRIBUTE_ITEMSTYLE_RADIO ) + nItemBits |= css::ui::ItemStyle::RADIO_CHECK; + } + } + while ( nIndex >= 0 ); + } + + } + + if ( !aCommandId.isEmpty() ) + { + Sequence< PropertyValue > aSubMenuProp( 6 ); + initPropertyCommon( aSubMenuProp, aCommandId, aHelpId, aLabel, nItemBits ); + aSubMenuProp.getArray()[2].Value <<= xSubItemContainer; + + m_xMenuContainer->insertByIndex( m_xMenuContainer->getCount(), Any( aSubMenuProp ) ); + } + else + { + OUString aErrorMessage = getErrorLineString() + + "attribute id for element menu required!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_xReader.set( new OReadMenuHandler( xSubItemContainer, m_xContainerFactory )); + m_xReader->startDocument(); + } + else if ( rName == ELEMENT_MENUITEM ) + { + OUString aHelpId; + OUString aCommandId; + OUString aLabel; + sal_Int16 nItemBits(0); + // read attributes for menu item + for ( sal_Int16 i=0; i< xAttrList->getLength(); i++ ) + { + OUString aName = xAttrList->getNameByIndex( i ); + const OUString aValue = xAttrList->getValueByIndex( i ); + if ( aName == ATTRIBUTE_ID ) + aCommandId = aValue; + else if ( aName == ATTRIBUTE_LABEL ) + aLabel = aValue; + else if ( aName == ATTRIBUTE_HELPID ) + aHelpId = aValue; + else if ( aName == ATTRIBUTE_STYLE ) + { + sal_Int32 nIndex = 0; + do + { + OUString aToken = aValue.getToken( 0, '+', nIndex ); + if ( !aToken.isEmpty() ) + { + if ( aToken == ATTRIBUTE_ITEMSTYLE_TEXT ) + nItemBits |= css::ui::ItemStyle::TEXT; + else if ( aToken == ATTRIBUTE_ITEMSTYLE_IMAGE ) + nItemBits |= css::ui::ItemStyle::ICON; + else if ( aToken == ATTRIBUTE_ITEMSTYLE_RADIO ) + nItemBits |= css::ui::ItemStyle::RADIO_CHECK; + } + } + while ( nIndex >= 0 ); + } + + } + + if ( !aCommandId.isEmpty() ) + { + Sequence< PropertyValue > aMenuItem( 6 ); + initPropertyCommon( aMenuItem, aCommandId, aHelpId, aLabel, nItemBits ); + aMenuItem.getArray()[2].Value <<= Reference< XIndexContainer >(); + + m_xMenuContainer->insertByIndex( m_xMenuContainer->getCount(), Any( aMenuItem ) ); + } + + m_nNextElementExpected = ELEM_CLOSE_MENUITEM; + } + else if ( rName == ELEMENT_MENUSEPARATOR ) + { + Sequence< PropertyValue > aMenuSeparator{ comphelper::makePropertyValue( + ITEM_DESCRIPTOR_TYPE, css::ui::ItemType::SEPARATOR_LINE) }; + + m_xMenuContainer->insertByIndex( m_xMenuContainer->getCount(), Any( aMenuSeparator ) ); + + m_nNextElementExpected = ELEM_CLOSE_MENUSEPARATOR; + } + else + { + OUString aErrorMessage = getErrorLineString() + + "unknown element found!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } +} + +void SAL_CALL OReadMenuPopupHandler::characters(const OUString&) +{ +} + +void SAL_CALL OReadMenuPopupHandler::endElement( const OUString& aName ) +{ + --m_nElementDepth; + if ( m_bMenuMode ) + { + if ( 0 == m_nElementDepth ) + { + m_xReader->endDocument(); + m_xReader.clear(); + m_bMenuMode = false; + if ( aName != ELEMENT_MENU ) + { + OUString aErrorMessage = getErrorLineString() + + "closing element menu expected!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + } + else + m_xReader->endElement( aName ); + } + else + { + if ( m_nNextElementExpected == ELEM_CLOSE_MENUITEM ) + { + if ( aName != ELEMENT_MENUITEM ) + { + OUString aErrorMessage = getErrorLineString() + + "closing element menuitem expected!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + } + else if ( m_nNextElementExpected == ELEM_CLOSE_MENUSEPARATOR ) + { + if ( aName != ELEMENT_MENUSEPARATOR ) + { + OUString aErrorMessage = getErrorLineString() + + "closing element menuseparator expected!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + } + + m_nNextElementExpected = ELEM_CLOSE_NONE; + } +} + +// --------------------------------- Write XML --------------------------------- + +OWriteMenuDocumentHandler::OWriteMenuDocumentHandler( + const Reference< XIndexAccess >& rMenuBarContainer, + const Reference< XDocumentHandler >& rDocumentHandler, + bool bIsMenuBar ) : + m_xMenuBarContainer( rMenuBarContainer ), + m_xWriteDocumentHandler( rDocumentHandler ), + m_bIsMenuBar( bIsMenuBar ) +{ + m_xEmptyList = new ::comphelper::AttributeList; + m_aAttributeType = ATTRIBUTE_TYPE_CDATA; +} + +OWriteMenuDocumentHandler::~OWriteMenuDocumentHandler() +{ +} + +void OWriteMenuDocumentHandler::WriteMenuDocument() +{ + rtl::Reference<::comphelper::AttributeList> pList = new ::comphelper::AttributeList; + + m_xWriteDocumentHandler->startDocument(); + + // write DOCTYPE line! + Reference< XExtendedDocumentHandler > xExtendedDocHandler( m_xWriteDocumentHandler, UNO_QUERY ); + if ( m_bIsMenuBar /*FIXME*/ && xExtendedDocHandler.is() ) + { + xExtendedDocHandler->unknown( MENUBAR_DOCTYPE ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + } + + pList->AddAttribute( ATTRIBUTE_XMLNS_MENU, + m_aAttributeType, + XMLNS_MENU ); + + if ( m_bIsMenuBar ) //FIXME + pList->AddAttribute( ATTRIBUTE_NS_ID, + m_aAttributeType, + "menubar" ); + + OUString aRootElement; + if ( m_bIsMenuBar ) + aRootElement = ELEMENT_NS_MENUBAR; + else + aRootElement = ELEMENT_NS_MENUPOPUP; + m_xWriteDocumentHandler->startElement( aRootElement, pList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + + WriteMenu( m_xMenuBarContainer ); + + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( aRootElement ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endDocument(); +} + +void OWriteMenuDocumentHandler::WriteMenu( const Reference< XIndexAccess >& rMenuContainer ) +{ + sal_Int32 nItemCount = rMenuContainer->getCount(); + bool bSeparator = false; + Any aAny; + + for ( sal_Int32 nItemPos = 0; nItemPos < nItemCount; nItemPos++ ) + { + Sequence< PropertyValue > aProps; + aAny = rMenuContainer->getByIndex( nItemPos ); + if ( aAny >>= aProps ) + { + OUString aCommandURL; + OUString aLabel; + OUString aHelpURL; + sal_Int16 nType( css::ui::ItemType::DEFAULT ); + sal_Int16 nItemBits( 0 ); + Reference< XIndexAccess > xSubMenu; + + ExtractMenuParameters( aProps, aCommandURL, aLabel, aHelpURL, xSubMenu, nType, nItemBits ); + if ( xSubMenu.is() ) + { + if ( !aCommandURL.isEmpty() ) + { + rtl::Reference<::comphelper::AttributeList> pListMenu = new ::comphelper::AttributeList; + + pListMenu->AddAttribute( ATTRIBUTE_NS_ID, + m_aAttributeType, + aCommandURL ); + + if ( !aLabel.isEmpty() ) + pListMenu->AddAttribute( ATTRIBUTE_NS_LABEL, + m_aAttributeType, + aLabel ); + + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->startElement( ELEMENT_NS_MENU, pListMenu ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->startElement( ELEMENT_NS_MENUPOPUP, m_xEmptyList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + + WriteMenu( xSubMenu ); + + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_MENUPOPUP ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_MENU ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + bSeparator = false; + } + } + else + { + if ( nType == css::ui::ItemType::DEFAULT ) + { + if ( !aCommandURL.isEmpty() ) + { + bSeparator = false; + WriteMenuItem( aCommandURL, aLabel, aHelpURL, nItemBits ); + } + } + else if ( !bSeparator ) + { + // Don't write two separators together + WriteMenuSeparator(); + bSeparator = true; + } + } + } + } +} + +void OWriteMenuDocumentHandler::WriteMenuItem( const OUString& aCommandURL, const OUString& aLabel, const OUString& aHelpURL, sal_Int16 nStyle ) +{ + rtl::Reference<::comphelper::AttributeList> pList = new ::comphelper::AttributeList; + + pList->AddAttribute( ATTRIBUTE_NS_ID, + m_aAttributeType, + aCommandURL ); + + if ( !aHelpURL.isEmpty() ) + { + pList->AddAttribute( ATTRIBUTE_NS_HELPID, + m_aAttributeType, + aHelpURL ); + } + + if ( !aLabel.isEmpty() ) + { + pList->AddAttribute( ATTRIBUTE_NS_LABEL, + m_aAttributeType, + aLabel ); + } + if ( nStyle > 0 ) + { + OUStringBuffer aValue; + const MenuStyleItem* pStyle = MenuItemStyles; + + for ( sal_Int32 nIndex = 0; nIndex < nMenuStyleItemEntries; ++nIndex, ++pStyle ) + { + if ( nStyle & pStyle->nBit ) + { + if ( !aValue.isEmpty() ) + aValue.append("+"); + aValue.appendAscii( pStyle->attrName ); + } + } + pList->AddAttribute( ATTRIBUTE_NS_STYLE, + m_aAttributeType, + aValue.makeStringAndClear() ); + } + + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->startElement( ELEMENT_NS_MENUITEM, pList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_MENUITEM ); +} + +void OWriteMenuDocumentHandler::WriteMenuSeparator() +{ + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->startElement( ELEMENT_NS_MENUSEPARATOR, m_xEmptyList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_MENUSEPARATOR ); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/xml/saxnamespacefilter.cxx b/framework/source/fwe/xml/saxnamespacefilter.cxx new file mode 100644 index 000000000..ac1ff571c --- /dev/null +++ b/framework/source/fwe/xml/saxnamespacefilter.cxx @@ -0,0 +1,163 @@ +/* -*- 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 <vector> + +#include <com/sun/star/xml/sax/SAXException.hpp> + +#include <xml/saxnamespacefilter.hxx> + +#include <comphelper/attributelist.hxx> +#include <rtl/ref.hxx> + +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star::uno; + +namespace framework{ + +SaxNamespaceFilter::SaxNamespaceFilter( Reference< XDocumentHandler > const & rSax1DocumentHandler ) : + xDocumentHandler( rSax1DocumentHandler ) +{ +} + +SaxNamespaceFilter::~SaxNamespaceFilter() +{ +} + +// XDocumentHandler +void SAL_CALL SaxNamespaceFilter::startDocument() +{ +} + +void SAL_CALL SaxNamespaceFilter::endDocument() +{ +} + +void SAL_CALL SaxNamespaceFilter::startElement( + const OUString& rName, const Reference< XAttributeList > &xAttribs ) +{ + XMLNamespaces aXMLNamespaces; + if ( !m_aNamespaceStack.empty() ) + aXMLNamespaces = m_aNamespaceStack.top(); + + rtl::Reference<::comphelper::AttributeList> pNewList = new ::comphelper::AttributeList(); + + // examine all namespaces for this level + ::std::vector< sal_Int16 > aAttributeIndexes; + { + for ( sal_Int16 i=0; i< xAttribs->getLength(); i++ ) + { + OUString aName = xAttribs->getNameByIndex( i ); + if ( aName.startsWith( "xmlns" ) ) + aXMLNamespaces.addNamespace( aName, xAttribs->getValueByIndex( i )); + else + aAttributeIndexes.push_back( i ); + } + } + + // current namespaces for this level + m_aNamespaceStack.push( aXMLNamespaces ); + + try + { + // apply namespaces to all remaining attributes + for (auto const& attributeIndex : aAttributeIndexes) + { + OUString aAttributeName = xAttribs->getNameByIndex(attributeIndex); + OUString aValue = xAttribs->getValueByIndex(attributeIndex); + OUString aNamespaceAttributeName = aXMLNamespaces.applyNSToAttributeName( aAttributeName ); + pNewList->AddAttribute( aNamespaceAttributeName, "CDATA", aValue ); + } + } + catch ( SAXException& e ) + { + e.Message = getErrorLineString() + e.Message; + throw; + } + + OUString aNamespaceElementName; + + try + { + aNamespaceElementName = aXMLNamespaces.applyNSToElementName( rName ); + } + catch ( SAXException& e ) + { + e.Message = getErrorLineString() + e.Message; + throw; + } + + xDocumentHandler->startElement( aNamespaceElementName, pNewList ); +} + +void SAL_CALL SaxNamespaceFilter::endElement(const OUString& aName) +{ + XMLNamespaces& aXMLNamespaces = m_aNamespaceStack.top(); + OUString aNamespaceElementName; + + try + { + aNamespaceElementName = aXMLNamespaces.applyNSToElementName( aName ); + } + catch ( SAXException& e ) + { + e.Message = getErrorLineString() + e.Message; + throw; + } + + xDocumentHandler->endElement( aNamespaceElementName ); + m_aNamespaceStack.pop(); +} + +void SAL_CALL SaxNamespaceFilter::characters(const OUString& aChars) +{ + xDocumentHandler->characters( aChars ); +} + +void SAL_CALL SaxNamespaceFilter::ignorableWhitespace(const OUString& aWhitespaces) +{ + xDocumentHandler->ignorableWhitespace( aWhitespaces ); +} + +void SAL_CALL SaxNamespaceFilter::processingInstruction( + const OUString& aTarget, const OUString& aData) +{ + xDocumentHandler->processingInstruction( aTarget, aData ); +} + +void SAL_CALL SaxNamespaceFilter::setDocumentLocator( + const Reference< XLocator > &xLocator) +{ + m_xLocator = xLocator; + xDocumentHandler->setDocumentLocator( xLocator ); +} + +OUString SaxNamespaceFilter::getErrorLineString() +{ + if ( m_xLocator.is() ) + return "Line: " + OUString::number( m_xLocator->getLineNumber() ) + " - "; + else + return OUString(); +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/xml/statusbarconfiguration.cxx b/framework/source/fwe/xml/statusbarconfiguration.cxx new file mode 100644 index 000000000..ce974237b --- /dev/null +++ b/framework/source/fwe/xml/statusbarconfiguration.cxx @@ -0,0 +1,105 @@ +/* -*- 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 <statusbarconfiguration.hxx> +#include <xml/statusbardocumenthandler.hxx> +#include <xml/saxnamespacefilter.hxx> + +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XInputStream.hpp> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::container; + +namespace framework +{ + +bool StatusBarConfiguration::LoadStatusBar( + const Reference< XComponentContext >& rxContext, + const Reference< XInputStream >& xInputStream, + const Reference< XIndexContainer >& rStatusbarConfiguration ) +{ + Reference< XParser > xParser = Parser::create(rxContext); + + // connect stream to input stream to the parser + InputSource aInputSource; + aInputSource.aInputStream = xInputStream; + + // create namespace filter and set menudocument handler inside to support xml namespaces + Reference< XDocumentHandler > xDocHandler( new OReadStatusBarDocumentHandler( rStatusbarConfiguration )); + Reference< XDocumentHandler > xFilter( new SaxNamespaceFilter( xDocHandler )); + + // connect parser and filter + xParser->setDocumentHandler( xFilter ); + + try + { + xParser->parseStream( aInputSource ); + return true; + } + catch ( const RuntimeException& ) + { + return false; + } + catch( const SAXException& ) + { + return false; + } + catch( const css::io::IOException& ) + { + return false; + } +} + +bool StatusBarConfiguration::StoreStatusBar( + const Reference< XComponentContext >& rxContext, + const Reference< XOutputStream >& xOutputStream, + const Reference< XIndexAccess >& rStatusbarConfiguration ) +{ + Reference< XWriter > xWriter = Writer::create( rxContext ); + xWriter->setOutputStream( xOutputStream ); + + try + { + OWriteStatusBarDocumentHandler aWriteStatusBarDocumentHandler( rStatusbarConfiguration, xWriter ); + aWriteStatusBarDocumentHandler.WriteStatusBarDocument(); + return true; + } + catch ( const RuntimeException& ) + { + return false; + } + catch ( const SAXException& ) + { + return false; + } + catch ( const css::io::IOException& ) + { + return false; + } +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/xml/statusbardocumenthandler.cxx b/framework/source/fwe/xml/statusbardocumenthandler.cxx new file mode 100644 index 000000000..3786e2fbd --- /dev/null +++ b/framework/source/fwe/xml/statusbardocumenthandler.cxx @@ -0,0 +1,632 @@ +/* -*- 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 <xml/statusbardocumenthandler.hxx> + +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <com/sun/star/xml/sax/XExtendedDocumentHandler.hpp> +#include <com/sun/star/ui/ItemStyle.hpp> +#include <com/sun/star/ui/ItemType.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> + +#include <vcl/status.hxx> + +#include <comphelper/attributelist.hxx> +#include <comphelper/propertyvalue.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star::ui; +using namespace ::com::sun::star::container; + +constexpr OUStringLiteral XMLNS_STATUSBAR = u"http://openoffice.org/2001/statusbar"; +constexpr OUStringLiteral XMLNS_XLINK = u"http://www.w3.org/1999/xlink"; +constexpr OUStringLiteral XMLNS_STATUSBAR_PREFIX = u"statusbar:"; +constexpr OUStringLiteral XMLNS_XLINK_PREFIX = u"xlink:"; + +constexpr OUStringLiteral XMLNS_FILTER_SEPARATOR = u"^"; + +#define ELEMENT_STATUSBAR "statusbar" +#define ELEMENT_STATUSBARITEM "statusbaritem" + +#define ATTRIBUTE_ALIGN "align" +#define ATTRIBUTE_STYLE "style" +#define ATTRIBUTE_URL "href" +#define ATTRIBUTE_WIDTH "width" +#define ATTRIBUTE_OFFSET "offset" +#define ATTRIBUTE_AUTOSIZE "autosize" +#define ATTRIBUTE_OWNERDRAW "ownerdraw" +#define ATTRIBUTE_HELPURL "helpid" +#define ATTRIBUTE_MANDATORY "mandatory" + +constexpr OUStringLiteral ELEMENT_NS_STATUSBAR = u"statusbar:statusbar"; +constexpr OUStringLiteral ELEMENT_NS_STATUSBARITEM = u"statusbar:statusbaritem"; + +constexpr OUStringLiteral ATTRIBUTE_XMLNS_STATUSBAR = u"xmlns:statusbar"; +constexpr OUStringLiteral ATTRIBUTE_XMLNS_XLINK = u"xmlns:xlink"; + +constexpr OUStringLiteral ATTRIBUTE_TYPE_CDATA = u"CDATA"; + +constexpr OUStringLiteral ATTRIBUTE_BOOLEAN_TRUE = u"true"; +constexpr OUStringLiteral ATTRIBUTE_BOOLEAN_FALSE = u"false"; + +constexpr OUStringLiteral ATTRIBUTE_ALIGN_LEFT = u"left"; +constexpr OUStringLiteral ATTRIBUTE_ALIGN_RIGHT = u"right"; +constexpr OUStringLiteral ATTRIBUTE_ALIGN_CENTER = u"center"; + +constexpr OUStringLiteral ATTRIBUTE_STYLE_IN = u"in"; +constexpr OUStringLiteral ATTRIBUTE_STYLE_OUT = u"out"; +constexpr OUStringLiteral ATTRIBUTE_STYLE_FLAT = u"flat"; + +constexpr OUStringLiteral STATUSBAR_DOCTYPE = u"<!DOCTYPE statusbar:statusbar PUBLIC \"-//OpenOffice.org//DTD OfficeDocument 1.0//EN\" \"statusbar.dtd\">"; + +namespace framework +{ + +// Property names of a menu/menu item ItemDescriptor +constexpr OUStringLiteral ITEM_DESCRIPTOR_COMMANDURL = u"CommandURL"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_HELPURL = u"HelpURL"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_OFFSET = u"Offset"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_STYLE = u"Style"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_WIDTH = u"Width"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_TYPE = u"Type"; + +static void ExtractStatusbarItemParameters( + const Sequence< PropertyValue >& rProp, + OUString& rCommandURL, + OUString& rHelpURL, + sal_Int16& rOffset, + sal_Int16& rStyle, + sal_Int16& rWidth ) +{ + for ( const PropertyValue& rEntry : rProp ) + { + if ( rEntry.Name == ITEM_DESCRIPTOR_COMMANDURL ) + { + rEntry.Value >>= rCommandURL; + rCommandURL = rCommandURL.intern(); + } + else if ( rEntry.Name == ITEM_DESCRIPTOR_HELPURL ) + { + rEntry.Value >>= rHelpURL; + } + else if ( rEntry.Name == ITEM_DESCRIPTOR_OFFSET ) + { + rEntry.Value >>= rOffset; + } + else if ( rEntry.Name == ITEM_DESCRIPTOR_STYLE ) + { + rEntry.Value >>= rStyle; + } + else if ( rEntry.Name == ITEM_DESCRIPTOR_WIDTH ) + { + rEntry.Value >>= rWidth; + } + } +} + +namespace { + +struct StatusBarEntryProperty +{ + OReadStatusBarDocumentHandler::StatusBar_XML_Namespace nNamespace; + char aEntryName[20]; +}; + +} + +StatusBarEntryProperty const StatusBarEntries[OReadStatusBarDocumentHandler::SB_XML_ENTRY_COUNT] = +{ + { OReadStatusBarDocumentHandler::SB_NS_STATUSBAR, ELEMENT_STATUSBAR }, + { OReadStatusBarDocumentHandler::SB_NS_STATUSBAR, ELEMENT_STATUSBARITEM }, + { OReadStatusBarDocumentHandler::SB_NS_XLINK, ATTRIBUTE_URL }, + { OReadStatusBarDocumentHandler::SB_NS_STATUSBAR, ATTRIBUTE_ALIGN }, + { OReadStatusBarDocumentHandler::SB_NS_STATUSBAR, ATTRIBUTE_STYLE }, + { OReadStatusBarDocumentHandler::SB_NS_STATUSBAR, ATTRIBUTE_AUTOSIZE }, + { OReadStatusBarDocumentHandler::SB_NS_STATUSBAR, ATTRIBUTE_OWNERDRAW }, + { OReadStatusBarDocumentHandler::SB_NS_STATUSBAR, ATTRIBUTE_WIDTH }, + { OReadStatusBarDocumentHandler::SB_NS_STATUSBAR, ATTRIBUTE_OFFSET }, + { OReadStatusBarDocumentHandler::SB_NS_STATUSBAR, ATTRIBUTE_HELPURL }, + { OReadStatusBarDocumentHandler::SB_NS_STATUSBAR, ATTRIBUTE_MANDATORY } +}; + +OReadStatusBarDocumentHandler::OReadStatusBarDocumentHandler( + const Reference< XIndexContainer >& rStatusBarItems ) : + m_aStatusBarItems( rStatusBarItems ) +{ + // create hash map + for ( int i = 0; i < SB_XML_ENTRY_COUNT; i++ ) + { + if ( StatusBarEntries[i].nNamespace == SB_NS_STATUSBAR ) + { + OUString temp = XMLNS_STATUSBAR + XMLNS_FILTER_SEPARATOR + + OUString::createFromAscii( StatusBarEntries[i].aEntryName ); + m_aStatusBarMap.emplace( temp, static_cast<StatusBar_XML_Entry>(i) ); + } + else + { + OUString temp = XMLNS_XLINK + XMLNS_FILTER_SEPARATOR + + OUString::createFromAscii( StatusBarEntries[i].aEntryName ); + m_aStatusBarMap.emplace( temp, static_cast<StatusBar_XML_Entry>(i) ); + } + } + + m_bStatusBarStartFound = false; + m_bStatusBarItemStartFound = false; +} + +OReadStatusBarDocumentHandler::~OReadStatusBarDocumentHandler() +{ +} + +// XDocumentHandler +void SAL_CALL OReadStatusBarDocumentHandler::startDocument() +{ +} + +void SAL_CALL OReadStatusBarDocumentHandler::endDocument() +{ + if ( m_bStatusBarStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "No matching start or end element 'statusbar' found!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } +} + +void SAL_CALL OReadStatusBarDocumentHandler::startElement( + const OUString& aName, const Reference< XAttributeList > &xAttribs ) +{ + StatusBarHashMap::const_iterator pStatusBarEntry = m_aStatusBarMap.find( aName ); + if ( pStatusBarEntry == m_aStatusBarMap.end() ) + return; + + switch ( pStatusBarEntry->second ) + { + case SB_ELEMENT_STATUSBAR: + { + if ( m_bStatusBarStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element 'statusbar:statusbar' cannot be embedded into 'statusbar:statusbar'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bStatusBarStartFound = true; + } + break; + + case SB_ELEMENT_STATUSBARITEM: + { + if ( !m_bStatusBarStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element 'statusbar:statusbaritem' must be embedded into element 'statusbar:statusbar'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + if ( m_bStatusBarItemStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element statusbar:statusbaritem is not a container!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + OUString aCommandURL; + OUString aHelpURL; + sal_Int16 nItemBits( ItemStyle::ALIGN_CENTER|ItemStyle::DRAW_IN3D|ItemStyle::MANDATORY ); + sal_Int16 nWidth( 0 ); + sal_Int16 nOffset( STATUSBAR_OFFSET ); + bool bCommandURL( false ); + + m_bStatusBarItemStartFound = true; + for ( sal_Int16 n = 0; n < xAttribs->getLength(); n++ ) + { + pStatusBarEntry = m_aStatusBarMap.find( xAttribs->getNameByIndex( n ) ); + if ( pStatusBarEntry != m_aStatusBarMap.end() ) + { + switch ( pStatusBarEntry->second ) + { + case SB_ATTRIBUTE_URL: + { + bCommandURL = true; + aCommandURL = xAttribs->getValueByIndex( n ); + } + break; + + case SB_ATTRIBUTE_ALIGN: + { + if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_ALIGN_LEFT ) + { + nItemBits |= ItemStyle::ALIGN_LEFT; + nItemBits &= ~ItemStyle::ALIGN_CENTER; + } + else if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_ALIGN_CENTER ) + { + nItemBits |= ItemStyle::ALIGN_CENTER; + nItemBits &= ~ItemStyle::ALIGN_LEFT; + } + else if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_ALIGN_RIGHT ) + { + nItemBits |= ItemStyle::ALIGN_RIGHT; + } + else + { + OUString aErrorMessage = getErrorLineString() + "Attribute statusbar:align must have one value of 'left','right' or 'center'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + } + break; + + case SB_ATTRIBUTE_STYLE: + { + if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_STYLE_IN ) + { + nItemBits |= ItemStyle::DRAW_IN3D; + nItemBits &= ~ItemStyle::DRAW_OUT3D; + } + else if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_STYLE_OUT ) + { + nItemBits |= ItemStyle::DRAW_OUT3D; + nItemBits &= ~ItemStyle::DRAW_IN3D; + } + else if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_STYLE_FLAT ) + { + nItemBits |= ItemStyle::DRAW_FLAT; + } + else + { + OUString aErrorMessage = getErrorLineString() + "Attribute statusbar:autosize must have value 'true' or 'false'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + } + break; + + case SB_ATTRIBUTE_AUTOSIZE: + { + if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_BOOLEAN_TRUE ) + nItemBits |= ItemStyle::AUTO_SIZE; + else if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_BOOLEAN_FALSE ) + nItemBits &= ~ItemStyle::AUTO_SIZE; + else + { + OUString aErrorMessage = getErrorLineString() + "Attribute statusbar:autosize must have value 'true' or 'false'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + } + break; + + case SB_ATTRIBUTE_OWNERDRAW: + { + if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_BOOLEAN_TRUE ) + nItemBits |= ItemStyle::OWNER_DRAW; + else if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_BOOLEAN_FALSE ) + nItemBits &= ~ItemStyle::OWNER_DRAW; + else + { + OUString aErrorMessage = getErrorLineString() + "Attribute statusbar:ownerdraw must have value 'true' or 'false'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + } + break; + + case SB_ATTRIBUTE_WIDTH: + { + nWidth = static_cast<sal_Int16>(xAttribs->getValueByIndex( n ).toInt32()); + } + break; + + case SB_ATTRIBUTE_OFFSET: + { + nOffset = static_cast<sal_Int16>(xAttribs->getValueByIndex( n ).toInt32()); + } + break; + + case SB_ATTRIBUTE_HELPURL: + { + aHelpURL = xAttribs->getValueByIndex( n ); + } + break; + + case SB_ATTRIBUTE_MANDATORY: + { + if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_BOOLEAN_TRUE ) + nItemBits |= ItemStyle::MANDATORY; + else if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_BOOLEAN_FALSE ) + nItemBits &= ~ItemStyle::MANDATORY; + else + { + OUString aErrorMessage = getErrorLineString() + "Attribute statusbar:mandatory must have value 'true' or 'false'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + } + break; + + default: + break; + } + } + } // for + + if ( !bCommandURL ) + { + OUString aErrorMessage = getErrorLineString() + "Required attribute statusbar:url must have a value!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + else + { + Sequence< PropertyValue > aStatusbarItemProp{ + comphelper::makePropertyValue(ITEM_DESCRIPTOR_COMMANDURL, aCommandURL), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_HELPURL, aHelpURL), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_OFFSET, nOffset), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_STYLE, nItemBits), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_WIDTH, nWidth), + comphelper::makePropertyValue(ITEM_DESCRIPTOR_TYPE, css::ui::ItemType::DEFAULT) + }; + + m_aStatusBarItems->insertByIndex( m_aStatusBarItems->getCount(), Any( aStatusbarItemProp ) ); + } + } + break; + + default: + break; + } +} + +void SAL_CALL OReadStatusBarDocumentHandler::endElement(const OUString& aName) +{ + StatusBarHashMap::const_iterator pStatusBarEntry = m_aStatusBarMap.find( aName ); + if ( pStatusBarEntry == m_aStatusBarMap.end() ) + return; + + switch ( pStatusBarEntry->second ) + { + case SB_ELEMENT_STATUSBAR: + { + if ( !m_bStatusBarStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "End element 'statusbar' found, but no start element 'statusbar'"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bStatusBarStartFound = false; + } + break; + + case SB_ELEMENT_STATUSBARITEM: + { + if ( !m_bStatusBarItemStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "End element 'statusbar:statusbaritem' found, but no start element 'statusbar:statusbaritem'"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bStatusBarItemStartFound = false; + } + break; + + default: break; + } +} + +void SAL_CALL OReadStatusBarDocumentHandler::characters(const OUString&) +{ +} + +void SAL_CALL OReadStatusBarDocumentHandler::ignorableWhitespace(const OUString&) +{ +} + +void SAL_CALL OReadStatusBarDocumentHandler::processingInstruction( + const OUString& /*aTarget*/, const OUString& /*aData*/ ) +{ +} + +void SAL_CALL OReadStatusBarDocumentHandler::setDocumentLocator( + const Reference< XLocator > &xLocator) +{ + m_xLocator = xLocator; +} + +OUString OReadStatusBarDocumentHandler::getErrorLineString() +{ + if ( m_xLocator.is() ) + return "Line: " + OUString::number( m_xLocator->getLineNumber() ) + " - "; + else + return OUString(); +} + +// OWriteStatusBarDocumentHandler + +OWriteStatusBarDocumentHandler::OWriteStatusBarDocumentHandler( + const Reference< XIndexAccess >& aStatusBarItems, + const Reference< XDocumentHandler >& rWriteDocumentHandler ) : + m_aStatusBarItems( aStatusBarItems ), + m_xWriteDocumentHandler( rWriteDocumentHandler ) +{ + m_xEmptyList = new ::comphelper::AttributeList; + m_aAttributeType = ATTRIBUTE_TYPE_CDATA; + m_aXMLXlinkNS = XMLNS_XLINK_PREFIX; + m_aXMLStatusBarNS = XMLNS_STATUSBAR_PREFIX; +} + +OWriteStatusBarDocumentHandler::~OWriteStatusBarDocumentHandler() +{ +} + +void OWriteStatusBarDocumentHandler::WriteStatusBarDocument() +{ + m_xWriteDocumentHandler->startDocument(); + + // write DOCTYPE line! + Reference< XExtendedDocumentHandler > xExtendedDocHandler( m_xWriteDocumentHandler, UNO_QUERY ); + if ( xExtendedDocHandler.is() ) + { + xExtendedDocHandler->unknown( STATUSBAR_DOCTYPE ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + } + + rtl::Reference<::comphelper::AttributeList> pList = new ::comphelper::AttributeList; + + pList->AddAttribute( ATTRIBUTE_XMLNS_STATUSBAR, + m_aAttributeType, + XMLNS_STATUSBAR ); + + pList->AddAttribute( ATTRIBUTE_XMLNS_XLINK, + m_aAttributeType, + XMLNS_XLINK ); + + m_xWriteDocumentHandler->startElement( ELEMENT_NS_STATUSBAR, pList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + + sal_Int32 nItemCount = m_aStatusBarItems->getCount(); + Any aAny; + + for ( sal_Int32 nItemPos = 0; nItemPos < nItemCount; nItemPos++ ) + { + Sequence< PropertyValue > aProps; + aAny = m_aStatusBarItems->getByIndex( nItemPos ); + if ( aAny >>= aProps ) + { + OUString aCommandURL; + OUString aHelpURL; + sal_Int16 nStyle( ItemStyle::ALIGN_CENTER|ItemStyle::DRAW_IN3D ); + sal_Int16 nWidth( 0 ); + sal_Int16 nOffset( STATUSBAR_OFFSET ); + + ExtractStatusbarItemParameters( + aProps, + aCommandURL, + aHelpURL, + nOffset, + nStyle, + nWidth ); + + if ( !aCommandURL.isEmpty() ) + WriteStatusBarItem( aCommandURL, nOffset, nStyle, nWidth ); + } + } + + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_STATUSBAR ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endDocument(); +} + +// protected member functions + +void OWriteStatusBarDocumentHandler::WriteStatusBarItem( + const OUString& rCommandURL, + sal_Int16 nOffset, + sal_Int16 nStyle, + sal_Int16 nWidth ) +{ + rtl::Reference<::comphelper::AttributeList> pList = new ::comphelper::AttributeList; + + if (m_aAttributeURL.isEmpty() ) + { + m_aAttributeURL = m_aXMLXlinkNS + ATTRIBUTE_URL; + } + + // save required attribute (URL) + pList->AddAttribute( m_aAttributeURL, m_aAttributeType, rCommandURL ); + + // alignment + if ( nStyle & ItemStyle::ALIGN_RIGHT ) + { + pList->AddAttribute( m_aXMLStatusBarNS + ATTRIBUTE_ALIGN, + m_aAttributeType, + ATTRIBUTE_ALIGN_RIGHT ); + } + else if ( nStyle & ItemStyle::ALIGN_CENTER ) + { + pList->AddAttribute( m_aXMLStatusBarNS + ATTRIBUTE_ALIGN, + m_aAttributeType, + ATTRIBUTE_ALIGN_CENTER ); + } + else + { + pList->AddAttribute( m_aXMLStatusBarNS + ATTRIBUTE_ALIGN, + m_aAttributeType, + ATTRIBUTE_ALIGN_LEFT ); + } + + // style ( StatusBarItemBits::In is default ) + if ( nStyle & ItemStyle::DRAW_FLAT ) + { + pList->AddAttribute( m_aXMLStatusBarNS + ATTRIBUTE_STYLE, + m_aAttributeType, + ATTRIBUTE_STYLE_FLAT ); + } + else if ( nStyle & ItemStyle::DRAW_OUT3D ) + { + pList->AddAttribute( m_aXMLStatusBarNS + ATTRIBUTE_STYLE, + m_aAttributeType, + ATTRIBUTE_STYLE_OUT ); + } + + // autosize (default sal_False) + if ( nStyle & ItemStyle::AUTO_SIZE ) + { + pList->AddAttribute( m_aXMLStatusBarNS + ATTRIBUTE_AUTOSIZE, + m_aAttributeType, + ATTRIBUTE_BOOLEAN_TRUE ); + } + + // ownerdraw (default sal_False) + if ( nStyle & ItemStyle::OWNER_DRAW ) + { + pList->AddAttribute( m_aXMLStatusBarNS + ATTRIBUTE_OWNERDRAW, + m_aAttributeType, + ATTRIBUTE_BOOLEAN_TRUE ); + } + + // width (default 0) + if ( nWidth > 0 ) + { + pList->AddAttribute( m_aXMLStatusBarNS + ATTRIBUTE_WIDTH, + m_aAttributeType, + OUString::number( nWidth ) ); + } + + // offset (default STATUSBAR_OFFSET) + if ( nOffset != STATUSBAR_OFFSET ) + { + pList->AddAttribute( m_aXMLStatusBarNS + ATTRIBUTE_OFFSET, + m_aAttributeType, + OUString::number( nOffset ) ); + } + + // mandatory (default sal_True) + if ( !( nStyle & ItemStyle::MANDATORY ) ) + { + pList->AddAttribute( m_aXMLStatusBarNS + ATTRIBUTE_MANDATORY, + m_aAttributeType, + ATTRIBUTE_BOOLEAN_FALSE ); + } + + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->startElement( ELEMENT_NS_STATUSBARITEM, pList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_STATUSBARITEM ); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/xml/toolboxconfiguration.cxx b/framework/source/fwe/xml/toolboxconfiguration.cxx new file mode 100644 index 000000000..d9e34baab --- /dev/null +++ b/framework/source/fwe/xml/toolboxconfiguration.cxx @@ -0,0 +1,106 @@ +/* -*- 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 <toolboxconfiguration.hxx> +#include <xml/toolboxdocumenthandler.hxx> +#include <xml/saxnamespacefilter.hxx> + +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XInputStream.hpp> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::container; + +namespace framework +{ +bool ToolBoxConfiguration::LoadToolBox( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::io::XInputStream>& rInputStream, + const css::uno::Reference<css::container::XIndexContainer>& rToolbarConfiguration) +{ + Reference<XParser> xParser = Parser::create(rxContext); + + // connect stream to input stream to the parser + InputSource aInputSource; + + aInputSource.aInputStream = rInputStream; + + // create namespace filter and set menudocument handler inside to support xml namespaces + Reference<XDocumentHandler> xDocHandler(new OReadToolBoxDocumentHandler(rToolbarConfiguration)); + Reference<XDocumentHandler> xFilter(new SaxNamespaceFilter(xDocHandler)); + + // connect parser and filter + xParser->setDocumentHandler(xFilter); + + try + { + xParser->parseStream(aInputSource); + return true; + } + catch (const RuntimeException&) + { + return false; + } + catch (const SAXException&) + { + return false; + } + catch (const css::io::IOException&) + { + return false; + } +} + +bool ToolBoxConfiguration::StoreToolBox( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::io::XOutputStream>& rOutputStream, + const css::uno::Reference<css::container::XIndexAccess>& rToolbarConfiguration) +{ + Reference<XWriter> xWriter = Writer::create(rxContext); + xWriter->setOutputStream(rOutputStream); + + try + { + Reference<XDocumentHandler> xHandler(xWriter, UNO_QUERY_THROW); + OWriteToolBoxDocumentHandler aWriteToolBoxDocumentHandler(rToolbarConfiguration, xHandler); + aWriteToolBoxDocumentHandler.WriteToolBoxDocument(); + return true; + } + catch (const RuntimeException&) + { + return false; + } + catch (const SAXException&) + { + return false; + } + catch (const css::io::IOException&) + { + return false; + } +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/xml/toolboxdocumenthandler.cxx b/framework/source/fwe/xml/toolboxdocumenthandler.cxx new file mode 100644 index 000000000..62202dea6 --- /dev/null +++ b/framework/source/fwe/xml/toolboxdocumenthandler.cxx @@ -0,0 +1,734 @@ +/* -*- 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 <xml/toolboxdocumenthandler.hxx> +#include <xml/toolboxconfigurationdefines.hxx> + +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <com/sun/star/xml/sax/XExtendedDocumentHandler.hpp> +#include <com/sun/star/ui/ItemType.hpp> +#include <com/sun/star/ui/ItemStyle.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> + +#include <sal/config.h> +#include <sal/macros.h> +#include <vcl/settings.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> + +#include <comphelper/attributelist.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::xml::sax; + +constexpr OUStringLiteral TOOLBAR_DOCTYPE = u"<!DOCTYPE toolbar:toolbar PUBLIC \"-//OpenOffice.org//DTD OfficeDocument 1.0//EN\" \"toolbar.dtd\">"; + +namespace framework +{ + +// Property names of a menu/menu item ItemDescriptor +constexpr OUStringLiteral ITEM_DESCRIPTOR_COMMANDURL = u"CommandURL"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_LABEL = u"Label"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_TYPE = u"Type"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_STYLE = u"Style"; +constexpr OUStringLiteral ITEM_DESCRIPTOR_VISIBLE = u"IsVisible"; + +static void ExtractToolbarParameters( const Sequence< PropertyValue >& rProp, + OUString& rCommandURL, + OUString& rLabel, + sal_Int16& rStyle, + bool& rVisible, + sal_Int16& rType ) +{ + for ( const PropertyValue& rEntry : rProp ) + { + if ( rEntry.Name == ITEM_DESCRIPTOR_COMMANDURL ) + { + rEntry.Value >>= rCommandURL; + rCommandURL = rCommandURL.intern(); + } + else if ( rEntry.Name == ITEM_DESCRIPTOR_LABEL ) + rEntry.Value >>= rLabel; + else if ( rEntry.Name == ITEM_DESCRIPTOR_TYPE ) + rEntry.Value >>= rType; + else if ( rEntry.Name == ITEM_DESCRIPTOR_VISIBLE ) + rEntry.Value >>= rVisible; + else if ( rEntry.Name == ITEM_DESCRIPTOR_STYLE ) + rEntry.Value >>= rStyle; + } +} + +namespace { + +struct ToolboxStyleItem +{ + sal_Int16 nBit; + rtl::OUStringConstExpr attrName; +}; + +} + +constexpr ToolboxStyleItem Styles[ ] = { + { css::ui::ItemStyle::RADIO_CHECK, ATTRIBUTE_ITEMSTYLE_RADIO }, + { css::ui::ItemStyle::ALIGN_LEFT, ATTRIBUTE_ITEMSTYLE_LEFT }, + { css::ui::ItemStyle::AUTO_SIZE, ATTRIBUTE_ITEMSTYLE_AUTO }, + { css::ui::ItemStyle::REPEAT, ATTRIBUTE_ITEMSTYLE_REPEAT }, + { css::ui::ItemStyle::DROPDOWN_ONLY, ATTRIBUTE_ITEMSTYLE_DROPDOWNONLY }, + { css::ui::ItemStyle::DROP_DOWN, ATTRIBUTE_ITEMSTYLE_DROPDOWN }, + { css::ui::ItemStyle::ICON, ATTRIBUTE_ITEMSTYLE_IMAGE }, + { css::ui::ItemStyle::TEXT, ATTRIBUTE_ITEMSTYLE_TEXT }, +}; + +sal_Int32 const nStyleItemEntries = SAL_N_ELEMENTS(Styles); + +namespace { + +struct ToolBarEntryProperty +{ + OReadToolBoxDocumentHandler::ToolBox_XML_Namespace nNamespace; + char aEntryName[20]; +}; + +} + +ToolBarEntryProperty const ToolBoxEntries[OReadToolBoxDocumentHandler::TB_XML_ENTRY_COUNT] = +{ + { OReadToolBoxDocumentHandler::TB_NS_TOOLBAR, ELEMENT_TOOLBAR }, + { OReadToolBoxDocumentHandler::TB_NS_TOOLBAR, ELEMENT_TOOLBARITEM }, + { OReadToolBoxDocumentHandler::TB_NS_TOOLBAR, ELEMENT_TOOLBARSPACE }, + { OReadToolBoxDocumentHandler::TB_NS_TOOLBAR, ELEMENT_TOOLBARBREAK }, + { OReadToolBoxDocumentHandler::TB_NS_TOOLBAR, ELEMENT_TOOLBARSEPARATOR }, + { OReadToolBoxDocumentHandler::TB_NS_TOOLBAR, ATTRIBUTE_TEXT }, + { OReadToolBoxDocumentHandler::TB_NS_XLINK, ATTRIBUTE_URL }, + { OReadToolBoxDocumentHandler::TB_NS_TOOLBAR, ATTRIBUTE_VISIBLE }, + { OReadToolBoxDocumentHandler::TB_NS_TOOLBAR, ATTRIBUTE_ITEMSTYLE }, + { OReadToolBoxDocumentHandler::TB_NS_TOOLBAR, ATTRIBUTE_UINAME }, +}; + +OReadToolBoxDocumentHandler::OReadToolBoxDocumentHandler( const Reference< XIndexContainer >& rItemContainer ) : + m_rItemContainer( rItemContainer ), + m_aType( ITEM_DESCRIPTOR_TYPE ), + m_aLabel( ITEM_DESCRIPTOR_LABEL ), + m_aStyle( ITEM_DESCRIPTOR_STYLE ), + m_aIsVisible( ITEM_DESCRIPTOR_VISIBLE ), + m_aCommandURL( ITEM_DESCRIPTOR_COMMANDURL ) + { + // create hash map + for ( int i = 0; i < TB_XML_ENTRY_COUNT; i++ ) + { + if ( ToolBoxEntries[i].nNamespace == TB_NS_TOOLBAR ) + { + OUString temp = XMLNS_TOOLBAR XMLNS_FILTER_SEPARATOR + + OUString::createFromAscii( ToolBoxEntries[i].aEntryName ); + m_aToolBoxMap.emplace( temp, static_cast<ToolBox_XML_Entry>(i) ); + } + else + { + OUString temp = XMLNS_XLINK XMLNS_FILTER_SEPARATOR + + OUString::createFromAscii( ToolBoxEntries[i].aEntryName ); + m_aToolBoxMap.emplace( temp, static_cast<ToolBox_XML_Entry>(i) ); + } + } + + // pre-calculate a hash code for all style strings to speed up xml read process + m_nHashCode_Style_Radio = OUString( ATTRIBUTE_ITEMSTYLE_RADIO ).hashCode(); + m_nHashCode_Style_Left = OUString( ATTRIBUTE_ITEMSTYLE_LEFT ).hashCode(); + m_nHashCode_Style_AutoSize = OUString( ATTRIBUTE_ITEMSTYLE_AUTOSIZE ).hashCode(); + m_nHashCode_Style_DropDown = OUString( ATTRIBUTE_ITEMSTYLE_DROPDOWN ).hashCode(); + m_nHashCode_Style_Repeat = OUString( ATTRIBUTE_ITEMSTYLE_REPEAT ).hashCode(); + m_nHashCode_Style_DropDownOnly = OUString( ATTRIBUTE_ITEMSTYLE_DROPDOWNONLY ).hashCode(); + m_nHashCode_Style_Text = OUString( ATTRIBUTE_ITEMSTYLE_TEXT ).hashCode(); + m_nHashCode_Style_Image = OUString( ATTRIBUTE_ITEMSTYLE_IMAGE ).hashCode(); + + m_bToolBarStartFound = false; + m_bToolBarItemStartFound = false; + m_bToolBarSpaceStartFound = false; + m_bToolBarBreakStartFound = false; + m_bToolBarSeparatorStartFound = false; +} + +OReadToolBoxDocumentHandler::~OReadToolBoxDocumentHandler() +{ +} + +// XDocumentHandler +void SAL_CALL OReadToolBoxDocumentHandler::startDocument() +{ +} + +void SAL_CALL OReadToolBoxDocumentHandler::endDocument() +{ + if ( m_bToolBarStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "No matching start or end element 'toolbar' found!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } +} + +void SAL_CALL OReadToolBoxDocumentHandler::startElement( + const OUString& aName, const Reference< XAttributeList > &xAttribs ) +{ + ToolBoxHashMap::const_iterator pToolBoxEntry = m_aToolBoxMap.find( aName ); + if ( pToolBoxEntry == m_aToolBoxMap.end() ) + return; + + switch ( pToolBoxEntry->second ) + { + case TB_ELEMENT_TOOLBAR: + { + if ( m_bToolBarStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element 'toolbar:toolbar' cannot be embedded into 'toolbar:toolbar'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + else + { + // Check if we have a UI name set in our XML file + OUString aUIName; + for ( sal_Int16 n = 0; n < xAttribs->getLength(); n++ ) + { + pToolBoxEntry = m_aToolBoxMap.find( xAttribs->getNameByIndex( n ) ); + if ( pToolBoxEntry != m_aToolBoxMap.end() ) + { + switch ( pToolBoxEntry->second ) + { + case TB_ATTRIBUTE_UINAME: + aUIName = xAttribs->getValueByIndex( n ); + break; + default: + break; + } + } + } + if ( !aUIName.isEmpty() ) + { + // Try to set UI name as a container property + Reference< XPropertySet > xPropSet( m_rItemContainer, UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + xPropSet->setPropertyValue("UIName", Any( aUIName ) ); + } + catch ( const UnknownPropertyException& ) + { + } + } + + } + } + m_bToolBarStartFound = true; + } + break; + + case TB_ELEMENT_TOOLBARITEM: + { + if ( !m_bToolBarStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element 'toolbar:toolbaritem' must be embedded into element 'toolbar:toolbar'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + if ( m_bToolBarSeparatorStartFound || + m_bToolBarBreakStartFound || + m_bToolBarSpaceStartFound || + m_bToolBarItemStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element toolbar:toolbaritem is not a container!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + bool bAttributeURL = false; + + m_bToolBarItemStartFound = true; + OUString aLabel; + OUString aCommandURL; + sal_uInt16 nItemBits( 0 ); + bool bVisible( true ); + + for ( sal_Int16 n = 0; n < xAttribs->getLength(); n++ ) + { + pToolBoxEntry = m_aToolBoxMap.find( xAttribs->getNameByIndex( n ) ); + if ( pToolBoxEntry != m_aToolBoxMap.end() ) + { + switch ( pToolBoxEntry->second ) + { + case TB_ATTRIBUTE_TEXT: + { + aLabel = xAttribs->getValueByIndex( n ); + } + break; + + case TB_ATTRIBUTE_URL: + { + bAttributeURL = true; + aCommandURL = xAttribs->getValueByIndex( n ).intern(); + } + break; + + case TB_ATTRIBUTE_VISIBLE: + { + if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_BOOLEAN_TRUE ) + bVisible = true; + else if ( xAttribs->getValueByIndex( n ) == ATTRIBUTE_BOOLEAN_FALSE ) + bVisible = false; + else + { + OUString aErrorMessage = getErrorLineString() + "Attribute toolbar:visible must have value 'true' or 'false'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + } + break; + + case TB_ATTRIBUTE_STYLE: + { + // read space separated item style list + OUString aTemp = xAttribs->getValueByIndex( n ); + sal_Int32 nIndex = 0; + + do + { + OUString aToken = aTemp.getToken( 0, ' ', nIndex ); + if ( !aToken.isEmpty() ) + { + sal_Int32 nHashCode = aToken.hashCode(); + if ( nHashCode == m_nHashCode_Style_Radio ) + nItemBits |= css::ui::ItemStyle::RADIO_CHECK; + else if ( nHashCode == m_nHashCode_Style_Left ) + nItemBits |= css::ui::ItemStyle::ALIGN_LEFT; + else if ( nHashCode == m_nHashCode_Style_AutoSize ) + nItemBits |= css::ui::ItemStyle::AUTO_SIZE; + else if ( nHashCode == m_nHashCode_Style_Repeat ) + nItemBits |= css::ui::ItemStyle::REPEAT; + else if ( nHashCode == m_nHashCode_Style_DropDownOnly ) + nItemBits |= css::ui::ItemStyle::DROPDOWN_ONLY; + else if ( nHashCode == m_nHashCode_Style_DropDown ) + nItemBits |= css::ui::ItemStyle::DROP_DOWN; + else if ( nHashCode == m_nHashCode_Style_Text ) + nItemBits |= css::ui::ItemStyle::TEXT; + else if ( nHashCode == m_nHashCode_Style_Image ) + nItemBits |= css::ui::ItemStyle::ICON; + } + } + while ( nIndex >= 0 ); + } + break; + + default: + break; + } + } + } // for + + if ( !bAttributeURL ) + { + OUString aErrorMessage = getErrorLineString() + "Required attribute toolbar:url must have a value!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + if ( !aCommandURL.isEmpty() ) + { + //fix for fdo#39370 + /// check whether RTL interface or not + if(AllSettings::GetLayoutRTL()){ + if (aCommandURL == ".uno:ParaLeftToRight") + aCommandURL = ".uno:ParaRightToLeft"; + else if (aCommandURL == ".uno:ParaRightToLeft") + aCommandURL = ".uno:ParaLeftToRight"; + else if (aCommandURL == ".uno:LeftPara") + aCommandURL = ".uno:RightPara"; + else if (aCommandURL == ".uno:RightPara") + aCommandURL = ".uno:LeftPara"; + else if (aCommandURL == ".uno:AlignLeft") + aCommandURL = ".uno:AlignRight"; + else if (aCommandURL == ".uno:AlignRight") + aCommandURL = ".uno:AlignLeft"; + else if (aCommandURL == ".uno:WrapLeft") + aCommandURL = ".uno:WrapRight"; + else if (aCommandURL == ".uno:WrapRight") + aCommandURL = ".uno:WrapLeft"; + } + + auto aToolbarItemProp( comphelper::InitPropertySequence( { + { m_aCommandURL, css::uno::Any( aCommandURL ) }, + { m_aLabel, css::uno::Any( aLabel ) }, + { m_aType, css::uno::Any( css::ui::ItemType::DEFAULT ) }, + { m_aStyle, css::uno::Any( nItemBits ) }, + { m_aIsVisible, css::uno::Any( bVisible ) }, + } ) ); + + m_rItemContainer->insertByIndex( m_rItemContainer->getCount(), Any( aToolbarItemProp ) ); + } + } + break; + + case TB_ELEMENT_TOOLBARSPACE: + { + if ( m_bToolBarSeparatorStartFound || + m_bToolBarBreakStartFound || + m_bToolBarSpaceStartFound || + m_bToolBarItemStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element toolbar:toolbarspace is not a container!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bToolBarSpaceStartFound = true; + + Sequence< PropertyValue > aToolbarItemProp{ + comphelper::makePropertyValue(m_aCommandURL, OUString()), + comphelper::makePropertyValue(m_aType, css::ui::ItemType::SEPARATOR_SPACE) + }; + + m_rItemContainer->insertByIndex( m_rItemContainer->getCount(), Any( aToolbarItemProp ) ); + } + break; + + case TB_ELEMENT_TOOLBARBREAK: + { + if ( m_bToolBarSeparatorStartFound || + m_bToolBarBreakStartFound || + m_bToolBarSpaceStartFound || + m_bToolBarItemStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element toolbar:toolbarbreak is not a container!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bToolBarBreakStartFound = true; + + Sequence< PropertyValue > aToolbarItemProp{ + comphelper::makePropertyValue(m_aCommandURL, OUString()), + comphelper::makePropertyValue(m_aType, css::ui::ItemType::SEPARATOR_LINEBREAK) + }; + + m_rItemContainer->insertByIndex( m_rItemContainer->getCount(), Any( aToolbarItemProp ) ); + } + break; + + case TB_ELEMENT_TOOLBARSEPARATOR: + { + if ( m_bToolBarSeparatorStartFound || + m_bToolBarBreakStartFound || + m_bToolBarSpaceStartFound || + m_bToolBarItemStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element toolbar:toolbarseparator is not a container!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bToolBarSeparatorStartFound = true; + + Sequence< PropertyValue > aToolbarItemProp{ + comphelper::makePropertyValue(m_aCommandURL, OUString()), + comphelper::makePropertyValue(m_aType, css::ui::ItemType::SEPARATOR_LINE) + }; + + m_rItemContainer->insertByIndex( m_rItemContainer->getCount(), Any( aToolbarItemProp ) ); + } + break; + + default: + break; + } +} + +void SAL_CALL OReadToolBoxDocumentHandler::endElement(const OUString& aName) +{ + ToolBoxHashMap::const_iterator pToolBoxEntry = m_aToolBoxMap.find( aName ); + if ( pToolBoxEntry == m_aToolBoxMap.end() ) + return; + + switch ( pToolBoxEntry->second ) + { + case TB_ELEMENT_TOOLBAR: + { + if ( !m_bToolBarStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "End element 'toolbar' found, but no start element 'toolbar'"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bToolBarStartFound = false; + } + break; + + case TB_ELEMENT_TOOLBARITEM: + { + if ( !m_bToolBarItemStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "End element 'toolbar:toolbaritem' found, but no start element 'toolbar:toolbaritem'"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bToolBarItemStartFound = false; + } + break; + + case TB_ELEMENT_TOOLBARBREAK: + { + if ( !m_bToolBarBreakStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "End element 'toolbar:toolbarbreak' found, but no start element 'toolbar:toolbarbreak'"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bToolBarBreakStartFound = false; + } + break; + + case TB_ELEMENT_TOOLBARSPACE: + { + if ( !m_bToolBarSpaceStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "End element 'toolbar:toolbarspace' found, but no start element 'toolbar:toolbarspace'"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bToolBarSpaceStartFound = false; + } + break; + + case TB_ELEMENT_TOOLBARSEPARATOR: + { + if ( !m_bToolBarSeparatorStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "End element 'toolbar:toolbarseparator' found, but no start element 'toolbar:toolbarseparator'"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bToolBarSeparatorStartFound = false; + } + break; + + default: break; + } +} + +void SAL_CALL OReadToolBoxDocumentHandler::characters(const OUString&) +{ +} + +void SAL_CALL OReadToolBoxDocumentHandler::ignorableWhitespace(const OUString&) +{ +} + +void SAL_CALL OReadToolBoxDocumentHandler::processingInstruction( + const OUString& /*aTarget*/, const OUString& /*aData*/ ) +{ +} + +void SAL_CALL OReadToolBoxDocumentHandler::setDocumentLocator( + const Reference< XLocator > &xLocator) +{ + m_xLocator = xLocator; +} + +OUString OReadToolBoxDocumentHandler::getErrorLineString() +{ + if ( m_xLocator.is() ) + return "Line: " + OUString::number( m_xLocator->getLineNumber() ) + " - "; + else + return OUString(); +} + +// OWriteToolBoxDocumentHandler + +OWriteToolBoxDocumentHandler::OWriteToolBoxDocumentHandler( + const Reference< XIndexAccess >& rItemAccess, + Reference< XDocumentHandler > const & rWriteDocumentHandler ) : + m_xWriteDocumentHandler( rWriteDocumentHandler ), + m_rItemAccess( rItemAccess ) +{ + m_xEmptyList = new ::comphelper::AttributeList; + m_aAttributeType = ATTRIBUTE_TYPE_CDATA; + m_aXMLXlinkNS = XMLNS_XLINK_PREFIX; + m_aXMLToolbarNS = XMLNS_TOOLBAR_PREFIX; +} + +OWriteToolBoxDocumentHandler::~OWriteToolBoxDocumentHandler() +{ +} + +void OWriteToolBoxDocumentHandler::WriteToolBoxDocument() +{ + m_xWriteDocumentHandler->startDocument(); + + // write DOCTYPE line! + Reference< XExtendedDocumentHandler > xExtendedDocHandler( m_xWriteDocumentHandler, UNO_QUERY ); + if ( xExtendedDocHandler.is() ) + { + xExtendedDocHandler->unknown( TOOLBAR_DOCTYPE ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + } + + OUString aUIName; + Reference< XPropertySet > xPropSet( m_rItemAccess, UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + xPropSet->getPropertyValue("UIName") >>= aUIName; + } + catch ( const UnknownPropertyException& ) + { + } + } + + rtl::Reference<::comphelper::AttributeList> pList = new ::comphelper::AttributeList; + + pList->AddAttribute( ATTRIBUTE_XMLNS_TOOLBAR, + m_aAttributeType, + XMLNS_TOOLBAR ); + + pList->AddAttribute( ATTRIBUTE_XMLNS_XLINK, + m_aAttributeType, + XMLNS_XLINK ); + + if ( !aUIName.isEmpty() ) + pList->AddAttribute( m_aXMLToolbarNS + ATTRIBUTE_UINAME, + m_aAttributeType, + aUIName ); + + m_xWriteDocumentHandler->startElement( ELEMENT_NS_TOOLBAR, pList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + + sal_Int32 nItemCount = m_rItemAccess->getCount(); + Any aAny; + + for ( sal_Int32 nItemPos = 0; nItemPos < nItemCount; nItemPos++ ) + { + Sequence< PropertyValue > aProps; + aAny = m_rItemAccess->getByIndex( nItemPos ); + if ( aAny >>= aProps ) + { + OUString aCommandURL; + OUString aLabel; + bool bVisible( true ); + sal_Int16 nType( css::ui::ItemType::DEFAULT ); + sal_Int16 nStyle( 0 ); + + ExtractToolbarParameters( aProps, aCommandURL, aLabel, nStyle, bVisible, nType ); + if ( nType == css::ui::ItemType::DEFAULT ) + WriteToolBoxItem( aCommandURL, aLabel, nStyle, bVisible ); + else if ( nType == css::ui::ItemType::SEPARATOR_SPACE ) + WriteToolBoxSpace(); + else if ( nType == css::ui::ItemType::SEPARATOR_LINE ) + WriteToolBoxSeparator(); + else if ( nType == css::ui::ItemType::SEPARATOR_LINEBREAK ) + WriteToolBoxBreak(); + } + } + + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_TOOLBAR ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endDocument(); +} + +// protected member functions + +void OWriteToolBoxDocumentHandler::WriteToolBoxItem( + const OUString& rCommandURL, + const OUString& rLabel, + sal_Int16 nStyle, + bool bVisible ) +{ + rtl::Reference<::comphelper::AttributeList> pList = new ::comphelper::AttributeList; + + if ( m_aAttributeURL.isEmpty() ) + { + m_aAttributeURL = m_aXMLXlinkNS + ATTRIBUTE_URL; + } + + // save required attribute (URL) + pList->AddAttribute( m_aAttributeURL, m_aAttributeType, rCommandURL ); + + if ( !rLabel.isEmpty() ) + { + pList->AddAttribute( m_aXMLToolbarNS + ATTRIBUTE_TEXT, + m_aAttributeType, + rLabel ); + } + + if ( !bVisible ) + { + pList->AddAttribute( m_aXMLToolbarNS + ATTRIBUTE_VISIBLE, + m_aAttributeType, + ATTRIBUTE_BOOLEAN_FALSE ); + } + + if ( nStyle > 0 ) + { + OUStringBuffer aValue; + const ToolboxStyleItem* pStyle = Styles; + + for ( sal_Int32 nIndex = 0; nIndex < nStyleItemEntries; ++nIndex, ++pStyle ) + { + if ( nStyle & pStyle->nBit ) + { + if ( !aValue.isEmpty() ) + aValue.append(" "); + aValue.append( OUString(pStyle->attrName) ); + } + } + pList->AddAttribute( m_aXMLToolbarNS + ATTRIBUTE_ITEMSTYLE, + m_aAttributeType, + aValue.makeStringAndClear() ); + } + + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->startElement( ELEMENT_NS_TOOLBARITEM, pList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_TOOLBARITEM ); +} + +void OWriteToolBoxDocumentHandler::WriteToolBoxSpace() +{ + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->startElement( ELEMENT_NS_TOOLBARSPACE, m_xEmptyList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_TOOLBARSPACE ); +} + +void OWriteToolBoxDocumentHandler::WriteToolBoxBreak() +{ + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->startElement( ELEMENT_NS_TOOLBARBREAK, m_xEmptyList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_TOOLBARBREAK ); +} + +void OWriteToolBoxDocumentHandler::WriteToolBoxSeparator() +{ + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->startElement( ELEMENT_NS_TOOLBARSEPARATOR, m_xEmptyList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_TOOLBARSEPARATOR ); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwe/xml/xmlnamespaces.cxx b/framework/source/fwe/xml/xmlnamespaces.cxx new file mode 100644 index 000000000..7faa6c48d --- /dev/null +++ b/framework/source/fwe/xml/xmlnamespaces.cxx @@ -0,0 +1,152 @@ +/* -*- 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 <xml/xmlnamespaces.hxx> + +#include <com/sun/star/xml/sax/SAXException.hpp> + +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star::uno; + +namespace framework +{ + +void XMLNamespaces::addNamespace( const OUString& aName, const OUString& aValue ) +{ + NamespaceMap::iterator p; + OUString aNamespaceName( aName ); + + // delete preceding "xmlns" + constexpr char aXMLAttributeNamespace[] = "xmlns"; + if ( aNamespaceName.startsWith( aXMLAttributeNamespace ) ) + { + constexpr sal_Int32 nXMLNamespaceLength = RTL_CONSTASCII_LENGTH(aXMLAttributeNamespace); + if ( aNamespaceName.getLength() == nXMLNamespaceLength ) + { + aNamespaceName.clear(); + } + else if ( aNamespaceName.getLength() >= nXMLNamespaceLength+2 ) + { + aNamespaceName = aNamespaceName.copy( nXMLNamespaceLength+1 ); + } + else + { + // a xml namespace without name is not allowed (e.g. "xmlns:" ) + throw SAXException( "A xml namespace without name is not allowed!", Reference< XInterface >(), Any() ); + } + } + + if ( aValue.isEmpty() && !aNamespaceName.isEmpty() ) + { + // namespace should be reset - as xml draft states this is only allowed + // for the default namespace - check and throw exception if check fails + throw SAXException( "Clearing xml namespace only allowed for default namespace!", Reference< XInterface >(), Any() ); + } + + if ( aNamespaceName.isEmpty() ) + m_aDefaultNamespace = aValue; + else + { + p = m_aNamespaceMap.find( aNamespaceName ); + if ( p != m_aNamespaceMap.end() ) + { + // replace current namespace definition + m_aNamespaceMap.erase( p ); + m_aNamespaceMap.emplace( aNamespaceName, aValue ); + } + else + { + m_aNamespaceMap.emplace( aNamespaceName, aValue ); + } + } +} + +OUString XMLNamespaces::applyNSToAttributeName( const OUString& aName ) const +{ + // xml draft: there is no default namespace for attributes! + + int index; + if (( index = aName.indexOf( ':' )) > 0 ) + { + if ( aName.getLength() <= index+1 ) + { + // attribute with namespace but without name "namespace:" is not allowed!! + throw SAXException( "Attribute has no name only preceding namespace!", Reference< XInterface >(), Any() ); + } + OUString aAttributeName = getNamespaceValue( aName.copy( 0, index )) + "^" + aName.subView( index+1); + return aAttributeName; + } + + return aName; +} + +OUString XMLNamespaces::applyNSToElementName( const OUString& aName ) const +{ + // xml draft: element names can have a default namespace + + int index = aName.indexOf( ':' ); + OUString aNamespace; + OUString aElementName = aName; + + if ( index > 0 ) + aNamespace = getNamespaceValue( aName.copy( 0, index ) ); + else + aNamespace = m_aDefaultNamespace; + + if ( !aNamespace.isEmpty() ) + { + aElementName = aNamespace + "^"; + } + else + return aName; + + if ( index > 0 ) + { + if ( aName.getLength() <= index+1 ) + { + // attribute with namespace but without a name is not allowed (e.g. "cfg:" ) + throw SAXException( "Attribute has no name only preceding namespace!", Reference< XInterface >(), Any() ); + } + aElementName += aName.subView( index+1 ); + } + else + aElementName += aName; + + return aElementName; +} + +OUString const & XMLNamespaces::getNamespaceValue( const OUString& aNamespace ) const +{ + if ( aNamespace.isEmpty() ) + return m_aDefaultNamespace; + else + { + NamespaceMap::const_iterator p = m_aNamespaceMap.find( aNamespace ); + if ( p == m_aNamespaceMap.end() ) + { + // namespace not defined => throw exception! + throw SAXException( "XML namespace used but not defined!", Reference< XInterface >(), Any() ); + } + return p->second; + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwi/classes/converter.cxx b/framework/source/fwi/classes/converter.cxx new file mode 100644 index 000000000..a29c5b127 --- /dev/null +++ b/framework/source/fwi/classes/converter.cxx @@ -0,0 +1,116 @@ +/* -*- 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 <classes/converter.hxx> +#include <rtl/ustrbuf.hxx> + +namespace framework{ + +/** + * converts a sequence of PropertyValue to a sequence of NamedValue. + */ +css::uno::Sequence< css::beans::NamedValue > Converter::convert_seqPropVal2seqNamedVal( const css::uno::Sequence< css::beans::PropertyValue >& lSource ) +{ + sal_Int32 nCount = lSource.getLength(); + css::uno::Sequence< css::beans::NamedValue > lDestination(nCount); + auto lDestinationRange = asNonConstRange(lDestination); + for (sal_Int32 nItem=0; nItem<nCount; ++nItem) + { + lDestinationRange[nItem].Name = lSource[nItem].Name; + lDestinationRange[nItem].Value = lSource[nItem].Value; + } + return lDestination; +} + +/** + * converts a sequence of unicode strings into a vector of such items + */ +std::vector<OUString> Converter::convert_seqOUString2OUStringList( const css::uno::Sequence< OUString >& lSource ) +{ + std::vector<OUString> lDestination; + sal_Int32 nCount = lSource.getLength(); + + lDestination.reserve(nCount); + for (sal_Int32 nItem = 0; nItem < nCount; ++nItem) + { + lDestination.push_back(lSource[nItem]); + } + + return lDestination; +} + +OUString Converter::convert_DateTime2ISO8601( const DateTime& aSource ) +{ + OUStringBuffer sBuffer(25); + + sal_Int32 nYear = aSource.GetYear(); + sal_Int32 nMonth = aSource.GetMonth(); + sal_Int32 nDay = aSource.GetDay(); + + sal_Int32 nHour = aSource.GetHour(); + sal_Int32 nMin = aSource.GetMin(); + sal_Int32 nSec = aSource.GetSec(); + + // write year formatted as "YYYY" + if (nYear<10) + sBuffer.append("000"); + else if (nYear<100) + sBuffer.append("00"); + else if (nYear<1000) + sBuffer.append("0"); + sBuffer.append( nYear ); + + sBuffer.append("-"); + // write month formatted as "MM" + if (nMonth<10) + sBuffer.append("0"); + sBuffer.append( nMonth ); + + sBuffer.append("-"); + // write day formatted as "DD" + if (nDay<10) + sBuffer.append("0"); + sBuffer.append( nDay ); + + sBuffer.append("T"); + // write hours formatted as "hh" + if (nHour<10) + sBuffer.append("0"); + sBuffer.append( nHour ); + + sBuffer.append(":"); + // write min formatted as "mm" + if (nMin<10) + sBuffer.append("0"); + sBuffer.append( nMin ); + + sBuffer.append(":"); + // write sec formatted as "ss" + if (nSec<10) + sBuffer.append("0"); + sBuffer.append( nSec ); + + sBuffer.append("Z"); + + return sBuffer.makeStringAndClear(); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwi/classes/protocolhandlercache.cxx b/framework/source/fwi/classes/protocolhandlercache.cxx new file mode 100644 index 000000000..7619d4323 --- /dev/null +++ b/framework/source/fwi/classes/protocolhandlercache.cxx @@ -0,0 +1,257 @@ +/* -*- 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 . + */ + +/*TODO + - change "singleton" behaviour by using new helper ::comhelper::SingletonRef + - rename method exist() to existHandlerForURL() or similar one + - may it's a good idea to replace struct ProtocolHandler by css::beans::NamedValue type?! +*/ + +#include <classes/protocolhandlercache.hxx> +#include <classes/converter.hxx> + +#include <tools/wldcrd.hxx> +#include <unotools/configpaths.hxx> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> + +constexpr OUStringLiteral SETNAME_HANDLER = u"HandlerSet"; // name of configuration set inside package + +namespace framework{ + +/** + @short overloaded index operator of hash map to support pattern key search + @descr All keys inside this hash map are URL pattern which points to a uno + implementation name of a protocol handler service which is registered + for this pattern. This operator makes it easy to find such registered + handler by using a full qualified URL and compare it with all pattern + keys. + + @param sURL + the full qualified URL which should match to a registered pattern + + @return An iterator which points to the found item inside the hash or PatternHash::end() + if no pattern match this given <var>sURL</var>. + */ +namespace { + +PatternHash::const_iterator findPatternKey(PatternHash const * hash, const OUString& sURL) +{ + return std::find_if(hash->begin(), hash->end(), + [&sURL](const PatternHash::value_type& rEntry) { + WildCard aPattern(rEntry.first); + return aPattern.Matches(sURL); + }); +} + +} + +/** + @short initialize static member of class HandlerCache + @descr We use a singleton pattern to implement this handler cache. + That means it use two static member list to hold all necessary information + and a ref count mechanism to create/destroy it on demand. + */ +std::optional<HandlerHash> HandlerCache::s_pHandler; +std::optional<PatternHash> HandlerCache::s_pPattern; +sal_Int32 HandlerCache::m_nRefCount = 0; +HandlerCFGAccess* HandlerCache::s_pConfig = nullptr; + +/** + @short ctor of the cache of all registered protocol handler + @descr It tries to open the right configuration package automatically + and fill the internal structures. After that the cache can be + used for read access on this data and perform some search + operations on it. + */ +HandlerCache::HandlerCache() +{ + SolarMutexGuard aGuard; + + if (m_nRefCount==0) + { + s_pHandler.emplace(); + s_pPattern.emplace(); + s_pConfig = new HandlerCFGAccess(PACKAGENAME_PROTOCOLHANDLER); + s_pConfig->read(*s_pHandler, *s_pPattern); + s_pConfig->setCache(this); + } + + ++m_nRefCount; +} + +/** + @short dtor of the cache + @descr It frees all used memory. In further implementations (may if we support write access too) + it's a good place to flush changes back to the configuration - but not needed yet. + */ +HandlerCache::~HandlerCache() +{ + SolarMutexGuard aGuard; + + if( m_nRefCount==1) + { + s_pConfig->setCache(nullptr); + + delete s_pConfig; + s_pConfig = nullptr; + s_pHandler.reset(); + s_pPattern.reset(); + } + + --m_nRefCount; +} + +/** + @short dtor of the cache + @descr It frees all used memory. In further implementations (may if we support write access too) + it's a good place to flush changes back to the configuration - but not needed yet. + */ +bool HandlerCache::search( const OUString& sURL, ProtocolHandler* pReturn ) const +{ + bool bFound = false; + + SolarMutexGuard aGuard; + + PatternHash::const_iterator pItem = findPatternKey(s_pPattern ? &*s_pPattern : nullptr, sURL); + if (pItem != s_pPattern->end()) + { + *pReturn = (*s_pHandler)[pItem->second]; + bFound = true; + } + + return bFound; +} + +/** + @short search for a registered handler by using a URL struct + @descr We combine necessary parts of this struct to a valid URL string + and call our other search method ... + It's a helper for outside code. + */ +bool HandlerCache::search( const css::util::URL& aURL, ProtocolHandler* pReturn ) const +{ + return search( aURL.Complete, pReturn ); +} + +void HandlerCache::takeOver(HandlerHash aHandler, PatternHash aPattern) +{ + SolarMutexGuard aGuard; + + s_pHandler = std::move(aHandler); + s_pPattern = std::move(aPattern); +} + +/** + @short dtor of the config access class + @descr It opens the configuration package automatically by using base class mechanism. + After that "read()" method of this class should be called to use it. + + @param sPackage + specifies the package name of the configuration data which should be used + */ +HandlerCFGAccess::HandlerCFGAccess( const OUString& sPackage ) + : ConfigItem(sPackage) + , m_pCache(nullptr) +{ + css::uno::Sequence< OUString > lListenPaths { SETNAME_HANDLER }; + EnableNotification(lListenPaths); +} + +/** + @short use base class mechanism to fill given structures + @descr User use us as a wrapper between configuration api and his internal structures. + He give us some pointer to his member and we fill it. + + @param rHandlerHash + list of protocol handler infos + + @param rPatternHash + reverse map of handler pattern to her uno names + */ +void HandlerCFGAccess::read( HandlerHash& rHandlerHash, PatternHash& rPatternHash ) +{ + // list of all uno implementation names without encoding + css::uno::Sequence< OUString > lNames = GetNodeNames( SETNAME_HANDLER, ::utl::ConfigNameFormat::LocalPath ); + sal_Int32 nSourceCount = lNames.getLength(); + sal_Int32 nTargetCount = nSourceCount; + // list of all full qualified path names of configuration entries + css::uno::Sequence< OUString > lFullNames ( nTargetCount ); + auto lFullNamesRange = asNonConstRange(lFullNames); + // expand names to full path names + sal_Int32 nSource=0; + sal_Int32 nTarget=0; + for( nSource=0; nSource<nSourceCount; ++nSource ) + { + lFullNamesRange[nTarget] = + SETNAME_HANDLER + + CFG_PATH_SEPARATOR + + lNames[nSource] + + CFG_PATH_SEPARATOR + PROPERTY_PROTOCOLS; + + ++nTarget; + } + + // get values at all + css::uno::Sequence< css::uno::Any > lValues = GetProperties( lFullNames ); + SAL_WARN_IF( lFullNames.getLength()!=lValues.getLength(), "fwk", "HandlerCFGAccess::read(): Miss some configuration values of handler set!" ); + + // fill structures + nSource = 0; + for( nTarget=0; nTarget<nTargetCount; ++nTarget ) + { + // create it new for every loop to guarantee a real empty object! + ProtocolHandler aHandler; + aHandler.m_sUNOName = ::utl::extractFirstFromConfigurationPath(lNames[nSource]); + + // unpack all values of this handler + css::uno::Sequence< OUString > lTemp; + lValues[nTarget] >>= lTemp; + aHandler.m_lProtocols = Converter::convert_seqOUString2OUStringList(lTemp); + + // register his pattern into the performance search hash + for (auto const& item : aHandler.m_lProtocols) + { + rPatternHash[item] = lNames[nSource]; + } + + // insert the handler info into the normal handler cache + rHandlerHash[lNames[nSource]] = aHandler; + ++nSource; + } +} + +void HandlerCFGAccess::Notify(const css::uno::Sequence< OUString >& /*lPropertyNames*/) +{ + HandlerHash aHandler; + PatternHash aPattern; + + read(aHandler, aPattern); + if (m_pCache) + m_pCache->takeOver(std::move(aHandler), std::move(aPattern)); +} + +void HandlerCFGAccess::ImplCommit() +{ +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwi/helper/mischelper.cxx b/framework/source/fwi/helper/mischelper.cxx new file mode 100644 index 000000000..e9c664d47 --- /dev/null +++ b/framework/source/fwi/helper/mischelper.cxx @@ -0,0 +1,154 @@ +/* -*- 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 <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/document/XDocumentLanguages.hpp> +#include <com/sun/star/linguistic2/LanguageGuessing.hpp> + +#include <sal/log.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <svtools/langtab.hxx> +#include <helper/mischelper.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; + +namespace framework +{ + +uno::Reference< linguistic2::XLanguageGuessing > const & LanguageGuessingHelper::GetGuesser() const +{ + if (!m_xLanguageGuesser.is()) + { + try + { + m_xLanguageGuesser = linguistic2::LanguageGuessing::create( m_xContext ); + } + catch (const uno::Exception &) + { + SAL_WARN( "fwk", "failed to get language guessing component" ); + } + } + return m_xLanguageGuesser; +} + +void FillLangItems( std::set< OUString > &rLangItems, + const uno::Reference< frame::XFrame > & rxFrame, + const LanguageGuessingHelper & rLangGuessHelper, + SvtScriptType nScriptType, + const OUString & rCurLang, + const OUString & rKeyboardLang, + const OUString & rGuessedTextLang ) +{ + rLangItems.clear(); + + //1--add current language + if( !rCurLang.isEmpty() && + LANGUAGE_DONTKNOW != SvtLanguageTable::GetLanguageType( rCurLang )) + rLangItems.insert( rCurLang ); + + //2--System + const AllSettings& rAllSettings = Application::GetSettings(); + LanguageType rSystemLanguage = rAllSettings.GetLanguageTag().getLanguageType(); + if( rSystemLanguage != LANGUAGE_DONTKNOW ) + { + if ( IsScriptTypeMatchingToLanguage( nScriptType, rSystemLanguage )) + rLangItems.insert( SvtLanguageTable::GetLanguageString( rSystemLanguage ) ); + } + + //3--UI + LanguageType rUILanguage = rAllSettings.GetUILanguageTag().getLanguageType(); + if( rUILanguage != LANGUAGE_DONTKNOW ) + { + if ( IsScriptTypeMatchingToLanguage( nScriptType, rUILanguage )) + rLangItems.insert( SvtLanguageTable::GetLanguageString( rUILanguage ) ); + } + + //4--guessed language + const uno::Reference< linguistic2::XLanguageGuessing >& xLangGuesser( rLangGuessHelper.GetGuesser() ); + if ( xLangGuesser.is() && !rGuessedTextLang.isEmpty()) + { + css::lang::Locale aLocale(xLangGuesser->guessPrimaryLanguage( rGuessedTextLang, 0, rGuessedTextLang.getLength()) ); + LanguageType nLang = LanguageTag( aLocale ).makeFallback().getLanguageType(); + if (nLang != LANGUAGE_DONTKNOW && nLang != LANGUAGE_NONE && nLang != LANGUAGE_SYSTEM + && IsScriptTypeMatchingToLanguage( nScriptType, nLang )) + rLangItems.insert( SvtLanguageTable::GetLanguageString( nLang )); + } + + //5--keyboard language + if( !rKeyboardLang.isEmpty() ) + { + if ( IsScriptTypeMatchingToLanguage( nScriptType, SvtLanguageTable::GetLanguageType( rKeyboardLang ))) + rLangItems.insert( rKeyboardLang ); + } + + //6--all languages used in current document + Reference< css::frame::XModel > xModel; + if ( rxFrame.is() ) + { + Reference< css::frame::XController > xController = rxFrame->getController(); + if ( xController.is() ) + xModel = xController->getModel(); + } + Reference< document::XDocumentLanguages > xDocumentLanguages( xModel, UNO_QUERY ); + /*the description of nScriptType + LATIN : 0x001 + ASIAN : 0x002 + COMPLEX: 0x004 + */ + const sal_Int16 nMaxCount = 7; + if ( !xDocumentLanguages.is() ) + return; + + const Sequence< Locale > rLocales( xDocumentLanguages->getDocumentLanguages( static_cast<sal_Int16>(nScriptType), nMaxCount )); + for ( const Locale& rLocale : rLocales ) + { + if ( rLangItems.size() == static_cast< size_t >(nMaxCount) ) + break; + if( IsScriptTypeMatchingToLanguage( nScriptType, SvtLanguageTable::GetLanguageType( rLocale.Language ))) + rLangItems.insert( rLocale.Language ); + } +} + +auto (*g_pGetMultiplexerListener)( + css::uno::Reference<css::uno::XComponentContext> const & xComponentContext, + uno::Reference<uno::XInterface> const&, + std::function<bool (uno::Reference<ui::XContextChangeEventListener> const&)> const&) + -> uno::Reference<ui::XContextChangeEventListener> = nullptr; + +uno::Reference<ui::XContextChangeEventListener> +GetFirstListenerWith_Impl( + css::uno::Reference<css::uno::XComponentContext> const & xComponentContext, + uno::Reference<uno::XInterface> const& xEventFocus, + std::function<bool (uno::Reference<ui::XContextChangeEventListener> const&)> const& rPredicate) +{ + assert(g_pGetMultiplexerListener != nullptr); // should not be called too early, nor too late + return g_pGetMultiplexerListener(xComponentContext, xEventFocus, rPredicate); +} + + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwi/helper/shareablemutex.cxx b/framework/source/fwi/helper/shareablemutex.cxx new file mode 100644 index 000000000..cd3b6bd18 --- /dev/null +++ b/framework/source/fwi/helper/shareablemutex.cxx @@ -0,0 +1,49 @@ +/* -*- 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 <helper/shareablemutex.hxx> + +namespace framework +{ +ShareableMutex::ShareableMutex() +{ + m_pMutexRef = new MutexRef; + m_pMutexRef->acquire(); +} + +ShareableMutex::ShareableMutex(const ShareableMutex& rShareableMutex) +{ + m_pMutexRef = rShareableMutex.m_pMutexRef; + m_pMutexRef->acquire(); +} + +ShareableMutex& ShareableMutex::operator=(const ShareableMutex& rShareableMutex) +{ + rShareableMutex.m_pMutexRef->acquire(); + m_pMutexRef->release(); + m_pMutexRef = rShareableMutex.m_pMutexRef; + return *this; +} + +void ShareableMutex::acquire() { m_pMutexRef->m_oslMutex.acquire(); } + +void ShareableMutex::release() { m_pMutexRef->m_oslMutex.release(); } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwi/jobs/configaccess.cxx b/framework/source/fwi/jobs/configaccess.cxx new file mode 100644 index 000000000..fde891293 --- /dev/null +++ b/framework/source/fwi/jobs/configaccess.cxx @@ -0,0 +1,184 @@ +/* -*- 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 <jobs/configaccess.hxx> +#include <services.h> + +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <utility> + +#include <tools/diagnose_ex.h> + +namespace framework{ + +/** + @short open the configuration of this job + @descr We open the configuration of this job only. Not the whole package or the whole + job set. We are interested on our own properties only. + We set the opened configuration access as our member. So any following method, + which needs cfg access, can use it. That prevent us against multiple open/close requests. + But you can use this method to upgrade an already opened configuration too. + + @param eMode + force opening of the configuration access in readonly or in read/write mode + */ +ConfigAccess::ConfigAccess( /*IN*/ css::uno::Reference< css::uno::XComponentContext > xContext, + /*IN*/ OUString sRoot ) + : m_xContext (std::move( xContext)) + , m_sRoot (std::move( sRoot )) + , m_eMode ( E_CLOSED ) +{ +} + +/** + @short last chance to close an open configuration access point + @descr In case our user forgot to close this configuration point + in the right way, normally he will run into some trouble - + e.g. losing data. + */ +ConfigAccess::~ConfigAccess() +{ + close(); +} + +/** + @short return the internal mode of this instance + @descr May be the outside user need any information about successfully opened + or closed config access point objects. He can control the internal mode to do so. + + @return The internal open state of this object. + */ +ConfigAccess::EOpenMode ConfigAccess::getMode() const +{ + std::unique_lock g(m_mutex); + return m_eMode; +} + +/** + @short open the configuration access in the specified mode + @descr We set the opened configuration access as our member. So any following method, + which needs cfg access, can use it. That prevent us against multiple open/close requests. + But you can use this method to upgrade an already opened configuration too. + It's possible to open a config access in READONLY mode first and "open" it at a second + time within the mode READWRITE. Then we will upgrade it. Downgrade will be possible too. + + But note: closing will be done explicitly by calling method close() ... not by + downgrading with mode CLOSED! + + @param eMode + force (re)opening of the configuration access in readonly or in read/write mode + */ +void ConfigAccess::open( /*IN*/ EOpenMode eMode ) +{ + std::unique_lock g(m_mutex); + + // check if configuration is already open in the right mode. + // By the way: Don't allow closing by using this method! + if ( eMode == E_CLOSED || m_eMode == eMode ) + return; + + // We have to close the old access point without any question here. + // It will be open again using the new mode. + // can be called without checks! It does the checks by itself ... + // e.g. for already closed or not opened configuration. + // Flushing of all made changes will be done here too. + closeImpl(); + + // create the configuration provider, which provides sub access points + css::uno::Reference< css::lang::XMultiServiceFactory > xConfigProvider = css::configuration::theDefaultProvider::get(m_xContext); + css::beans::PropertyValue aParam; + aParam.Name = "nodepath"; + aParam.Value <<= m_sRoot; + + css::uno::Sequence< css::uno::Any > lParams{ css::uno::Any(aParam) }; + + // open it + try + { + if (eMode==E_READONLY) + m_xConfig = xConfigProvider->createInstanceWithArguments(SERVICENAME_CFGREADACCESS , lParams); + else + if (eMode==E_READWRITE) + m_xConfig = xConfigProvider->createInstanceWithArguments(SERVICENAME_CFGUPDATEACCESS, lParams); + } + catch(const css::uno::Exception&) + { + TOOLS_INFO_EXCEPTION("fwk", "open config"); + } + + m_eMode = E_CLOSED; + if (m_xConfig.is()) + m_eMode = eMode; +} + +/** + @short close the internal opened configuration access and flush all changes + @descr It checks, if the given access is valid and react in the right way. + It flushes all changes ... so nobody else must know this state. + */ +void ConfigAccess::close() +{ + std::unique_lock g(m_mutex); + closeImpl(); +} + +void ConfigAccess::closeImpl() +{ + // check already closed configuration + if (m_xConfig.is()) + { + css::uno::Reference< css::util::XChangesBatch > xFlush(m_xConfig, css::uno::UNO_QUERY); + if (xFlush.is()) + xFlush->commitChanges(); + m_xConfig.clear(); + m_eMode = E_CLOSED; + } +} + +/** + @short provides an access to the internal wrapped configuration access + @descr It's not allowed to safe this c++ (!) reference outside. You have + to use it directly. Further you must use our public lock member m_aLock + to synchronize your code with our internal structures and our interface + methods. Acquire it before you call cfg() and release it afterwards immediately. + + E.g.: ConfigAccess aAccess(...); + Guard aReadLock(aAccess.m_aLock); + Reference< XPropertySet > xSet(aAccess.cfg(), UNO_QUERY); + Any aProp = xSet->getPropertyValue("..."); + aReadLock.unlock(); + + @attention During this time it's not allowed to call the methods open() or close()! + Otherwise you will change your own referenced config access. Anything will + be possible then. + + @return A c++(!) reference to the uno instance of the configuration access point. + */ +const css::uno::Reference< css::uno::XInterface >& ConfigAccess::cfg() +{ + // must be synchronized from outside! + // => no lock here ... + return m_xConfig; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwi/threadhelp/transactionmanager.cxx b/framework/source/fwi/threadhelp/transactionmanager.cxx new file mode 100644 index 000000000..86d5b354a --- /dev/null +++ b/framework/source/fwi/threadhelp/transactionmanager.cxx @@ -0,0 +1,219 @@ +/* -*- 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 <sal/log.hxx> + +#include <framework/transactionmanager.hxx> + +#include <com/sun/star/lang/DisposedException.hpp> + +namespace framework{ + +/*-************************************************************************************************************ + @short standard ctor + @descr Initialize instance with right start values for correct working. +*//*-*************************************************************************************************************/ +TransactionManager::TransactionManager() + : m_eWorkingMode ( E_INIT ) + , m_nTransactionCount ( 0 ) +{ + m_aBarrier.open(); +} + +/*-************************************************************************************************************ + @short standard dtor +*//*-*************************************************************************************************************/ +TransactionManager::~TransactionManager() +{ +} + +/*-**************************************************************************************************** + @short set new working mode + @descr These implementation knows for states of working: E_INIT, E_WORK, E_BEFORECLOSE, E_CLOSE + You can step during this ones only from the left to the right side and start at left side again! + (This is necessary e.g. for refcounted objects!) + This call will block till all current existing transactions was finished. + Following results can occur: + E_INIT : All requests on this implementation are refused. + It's your decision to react in a right way. + + E_WORK : The object can work now. The full functionality is available. + + E_BEFORECLOSE : The object start the closing mechanism ... but sometimes + e.g. the dispose() method need to call some private methods. + These some special methods should use E_SOFTEXCEPTIONS + to detect this special case! + + E_CLOSE : Object is already dead! All further requests will be refused. + It's your decision to react in a right way. + @param "eMode", is the new mode - but we don't accept setting mode in wrong order! + @onerror We do nothing. +*//*-*****************************************************************************************************/ +void TransactionManager::setWorkingMode( EWorkingMode eMode ) +{ + // Safe member access. + bool bWaitFor = false; + { + std::unique_lock aAccessGuard(m_aAccessLock); + // Change working mode first! + if ( + (m_eWorkingMode == E_INIT && eMode == E_WORK) || + ((m_eWorkingMode == E_WORK || m_eWorkingMode == E_INIT) && eMode == E_BEFORECLOSE) || + (m_eWorkingMode == E_BEFORECLOSE && eMode == E_CLOSE) || + (m_eWorkingMode == E_CLOSE && eMode == E_INIT) + ) + { + m_eWorkingMode = eMode; + if (m_eWorkingMode == E_BEFORECLOSE || m_eWorkingMode == E_CLOSE) + { + bWaitFor = true; + } + } + } + // Wait for current existing transactions then! + // (Only necessary for changing to E_BEFORECLOSE or E_CLOSE!... + // otherwise; if you wait at setting E_WORK another thread could finish an acquire-call during our unlock() and wait() call + // ... and we will wait forever here!!!) + // Don't forget to release access mutex before. + if( bWaitFor ) + { + m_aBarrier.wait(); + } +} + +/*-**************************************************************************************************** + @short get current working mode + @descr If you stand in your close() or init() method ... but don't know + if you called more than ones(!) ... you can use this function to get + right information. + e.g: You have a method init() which is used to change working mode from + E_INIT to E_WORK and should be used to initialize some member too ... + What should you do: + + void init( sal_Int32 nValue ) + { + // Reject this call if our transaction manager say: "Object already initialized!" + // Otherwise initialize your member. + if( m_aTransactionManager.getWorkingMode() == E_INIT ) + { + // Object is uninitialized ... + // Make member access threadsafe! + Guard aGuard( m_aMutex ); + + // Check working mode again .. because another instance could be faster. + // (It's possible to set this guard at first of this method too!) + if( m_aTransactionManager.getWorkingMode() == E_INIT ) + { + m_aMember = nValue; + + // Object is initialized now ... set working mode to E_WORK! + m_aTransactionManager.setWorkingMode( E_WORK ); + } + } + } + + @seealso method setWorkingMode() + @return Current set mode. + + @onerror No error should occur. +*//*-*****************************************************************************************************/ +EWorkingMode TransactionManager::getWorkingMode() const +{ + // Synchronize access to internal member! + std::unique_lock aAccessLock( m_aAccessLock ); + return m_eWorkingMode; +} + +/*-**************************************************************************************************** + @short start new transaction + @descr A guard should use this method to start a new transaction. He should look for rejected + calls to by using parameter eMode and eReason. + If call was not rejected your transaction will be non breakable during releasing your transaction + guard! BUT ... your code isn't threadsafe then! It's a transaction manager only... + + @seealso method unregisterTransaction() + + @param "eMode" ,used to enable/disable throwing exceptions automatically for rejected calls +*//*-*****************************************************************************************************/ +void TransactionManager::registerTransaction( EExceptionMode eMode ) +{ + std::unique_lock aAccessGuard( m_aAccessLock ); + switch( m_eWorkingMode ) + { + case E_INIT: + if( eMode == E_HARDEXCEPTIONS ) + { + // Help programmer to find out, why this exception is thrown! + SAL_WARN( "fwk", "TransactionManager...: Owner instance not correctly initialized yet. Call was rejected! Normally it's an algorithm error ... wrong use of class!" ); + //ATTENTION: temp. disabled - till all bad code positions are detected and changed! */ + // throw css::uno::RuntimeException( "TransactionManager.: Owner instance not right initialized yet. Call was rejected! Normally it's an algorithm error... wrong using of class!\n", css::uno::Reference< css::uno::XInterface >() ); + } + break; + case E_WORK: + break; + case E_BEFORECLOSE: + if( eMode == E_HARDEXCEPTIONS ) + { + // Help programmer to find out, why this exception is thrown! + SAL_WARN( "fwk", "TransactionManager...: Owner instance stand in close method. Call was rejected!" ); + throw css::lang::DisposedException( "TransactionManager: Owner instance stand in close method. Call was rejected!" ); + } + break; + case E_CLOSE: + // Help programmer to find out, why this exception is thrown! + SAL_WARN( "fwk", "TransactionManager...: Owner instance already closed. Call was rejected!" ); + throw css::lang::DisposedException( "TransactionManager: Owner instance already closed. Call was rejected!" ); + } + + // Register this new transaction. + // If it is the first one .. close gate to disable changing of working mode. + ++m_nTransactionCount; + if( m_nTransactionCount == 1 ) + { + m_aBarrier.close(); + } +} + +/*-**************************************************************************************************** + @short finish transaction + @descr A guard should call this method to release current transaction. + + @seealso method registerTransaction() +*//*-*****************************************************************************************************/ +void TransactionManager::unregisterTransaction() +{ + // This call could not rejected! + // Safe access to internal member. + std::unique_lock aAccessGuard( m_aAccessLock ); + + // Deregister this transaction. + // If it was the last one ... open gate to enable changing of working mode! + // (see setWorkingMode()) + + --m_nTransactionCount; + if( m_nTransactionCount == 0 ) + { + m_aBarrier.open(); + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwi/uielement/constitemcontainer.cxx b/framework/source/fwi/uielement/constitemcontainer.cxx new file mode 100644 index 000000000..7e43a5009 --- /dev/null +++ b/framework/source/fwi/uielement/constitemcontainer.cxx @@ -0,0 +1,288 @@ +/* -*- 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 <uielement/constitemcontainer.hxx> +#include <uielement/itemcontainer.hxx> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> + +#include <comphelper/propertysetinfo.hxx> +#include <comphelper/servicehelper.hxx> +#include <rtl/ref.hxx> + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::container; + +const int PROPHANDLE_UINAME = 1; +constexpr OUStringLiteral PROPNAME_UINAME = u"UIName"; + +namespace framework +{ + +ConstItemContainer::ConstItemContainer() +{ +} + +ConstItemContainer::ConstItemContainer( const ItemContainer& rItemContainer ) +{ + ShareGuard aLock( rItemContainer.m_aShareMutex ); + copyItemContainer( rItemContainer.m_aItemVector ); +} + +ConstItemContainer::ConstItemContainer( const Reference< XIndexAccess >& rSourceContainer, bool bFastCopy ) +{ + // We also have to copy the UIName property + try + { + Reference< XPropertySet > xPropSet( rSourceContainer, UNO_QUERY ); + if ( xPropSet.is() ) + { + xPropSet->getPropertyValue("UIName") >>= m_aUIName; + } + } + catch ( const Exception& ) + { + } + + if ( !rSourceContainer.is() ) + return; + + try + { + sal_Int32 nCount = rSourceContainer->getCount(); + m_aItemVector.reserve(nCount); + if ( bFastCopy ) + { + for ( sal_Int32 i = 0; i < nCount; i++ ) + { + Sequence< PropertyValue > aPropSeq; + if ( rSourceContainer->getByIndex( i ) >>= aPropSeq ) + m_aItemVector.push_back( aPropSeq ); + } + } + else + { + for ( sal_Int32 i = 0; i < nCount; i++ ) + { + Sequence< PropertyValue > aPropSeq; + if ( rSourceContainer->getByIndex( i ) >>= aPropSeq ) + { + sal_Int32 nContainerIndex = -1; + Reference< XIndexAccess > xIndexAccess; + for ( sal_Int32 j = 0; j < aPropSeq.getLength(); j++ ) + { + if ( aPropSeq[j].Name == "ItemDescriptorContainer" ) + { + aPropSeq[j].Value >>= xIndexAccess; + nContainerIndex = j; + break; + } + } + + if ( xIndexAccess.is() && nContainerIndex >= 0 ) + aPropSeq.getArray()[nContainerIndex].Value <<= deepCopyContainer( xIndexAccess ); + + m_aItemVector.push_back( aPropSeq ); + } + } + } + } + catch ( const IndexOutOfBoundsException& ) + { + } +} + +ConstItemContainer::~ConstItemContainer() +{ +} + +// private +void ConstItemContainer::copyItemContainer( const std::vector< Sequence< PropertyValue > >& rSourceVector ) +{ + const sal_uInt32 nCount = rSourceVector.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + sal_Int32 nContainerIndex = -1; + Sequence< PropertyValue > aPropSeq( rSourceVector[i] ); + Reference< XIndexAccess > xIndexAccess; + for ( sal_Int32 j = 0; j < aPropSeq.getLength(); j++ ) + { + if ( aPropSeq[j].Name == "ItemDescriptorContainer" ) + { + aPropSeq[j].Value >>= xIndexAccess; + nContainerIndex = j; + break; + } + } + + if ( xIndexAccess.is() && nContainerIndex >= 0 ) + aPropSeq.getArray()[nContainerIndex].Value <<= deepCopyContainer( xIndexAccess ); + + m_aItemVector.push_back( aPropSeq ); + } +} + +Reference< XIndexAccess > ConstItemContainer::deepCopyContainer( const Reference< XIndexAccess >& rSubContainer ) +{ + Reference< XIndexAccess > xReturn; + if ( rSubContainer.is() ) + { + ItemContainer* pSource = comphelper::getFromUnoTunnel<ItemContainer>( rSubContainer ); + rtl::Reference<ConstItemContainer> pSubContainer; + if ( pSource ) + pSubContainer = new ConstItemContainer( *pSource ); + else + pSubContainer = new ConstItemContainer( rSubContainer ); + xReturn = pSubContainer; + } + + return xReturn; +} + +// XUnoTunnel +sal_Int64 ConstItemContainer::getSomething( const css::uno::Sequence< sal_Int8 >& rIdentifier ) +{ + return comphelper::getSomethingImpl(rIdentifier, this); +} + +const Sequence< sal_Int8 >& ConstItemContainer::getUnoTunnelId() noexcept +{ + static const comphelper::UnoIdInit theConstItemContainerUnoTunnelId; + return theConstItemContainerUnoTunnelId.getSeq(); +} + +// XElementAccess +sal_Bool SAL_CALL ConstItemContainer::hasElements() +{ + return ( !m_aItemVector.empty() ); +} + +// XIndexAccess +sal_Int32 SAL_CALL ConstItemContainer::getCount() +{ + return m_aItemVector.size(); +} + +Any SAL_CALL ConstItemContainer::getByIndex( sal_Int32 Index ) +{ + if ( sal_Int32( m_aItemVector.size()) <= Index ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + return Any( m_aItemVector[Index] ); +} + +namespace +{ + std::vector<comphelper::PropertyMapEntry> makePropertyMap(const css::uno::Sequence<css::beans::Property>& rProps) + { + std::vector<comphelper::PropertyMapEntry> aEntries; + for (auto const& it : rProps) + aEntries.emplace_back(it.Name, it.Handle, it.Type, it.Attributes, 0); + return aEntries; + } +} + +// XPropertySet +Reference< XPropertySetInfo > SAL_CALL ConstItemContainer::getPropertySetInfo() +{ + // Create structure of propertysetinfo for baseclass "OPropertySetHelper". + // (Use method "getInfoHelper()".) + static std::vector<comphelper::PropertyMapEntry> aPropertyInfos(makePropertyMap(getInfoHelper().getProperties())); + static Reference< XPropertySetInfo > xInfo(new comphelper::PropertySetInfo(aPropertyInfos)); + + return xInfo; +} + +void SAL_CALL ConstItemContainer::setPropertyValue( const OUString&, const Any& ) +{ +} + +Any SAL_CALL ConstItemContainer::getPropertyValue( const OUString& PropertyName ) +{ + if ( PropertyName == PROPNAME_UINAME ) + return Any( m_aUIName ); + + throw UnknownPropertyException(PropertyName); +} + +void SAL_CALL ConstItemContainer::addPropertyChangeListener( const OUString&, const css::uno::Reference< css::beans::XPropertyChangeListener >& ) +{ +} + +void SAL_CALL ConstItemContainer::removePropertyChangeListener( const OUString&, const css::uno::Reference< css::beans::XPropertyChangeListener >& ) +{ + // Only read-only properties - do nothing +} + +void SAL_CALL ConstItemContainer::addVetoableChangeListener( const OUString&, const css::uno::Reference< css::beans::XVetoableChangeListener >& ) +{ + // Only read-only properties - do nothing +} + +void SAL_CALL ConstItemContainer::removeVetoableChangeListener( const OUString&, const css::uno::Reference< css::beans::XVetoableChangeListener >& ) +{ + // Only read-only properties - do nothing +} + +// XFastPropertySet +void SAL_CALL ConstItemContainer::setFastPropertyValue( sal_Int32, const css::uno::Any& ) +{ +} + +Any SAL_CALL ConstItemContainer::getFastPropertyValue( sal_Int32 nHandle ) +{ + if ( nHandle == PROPHANDLE_UINAME ) + return Any( m_aUIName ); + + throw UnknownPropertyException(OUString::number(nHandle)); +} + +::cppu::IPropertyArrayHelper& ConstItemContainer::getInfoHelper() +{ + // Define static member to give structure of properties to baseclass "OPropertySetHelper". + // "impl_getStaticPropertyDescriptor" is a non exported and static function, who will define a static propertytable. + // "true" say: Table is sorted by name. + static ::cppu::OPropertyArrayHelper ourInfoHelper( impl_getStaticPropertyDescriptor(), true ); + + return ourInfoHelper; +} + +css::uno::Sequence< css::beans::Property > ConstItemContainer::impl_getStaticPropertyDescriptor() +{ + // Create a property array to initialize sequence! + // Table of all predefined properties of this class. It's used from OPropertySetHelper-class! + // Don't forget to change the defines (see begin of this file), if you add, change or delete a property in this list!!! + // It's necessary for methods of OPropertySetHelper. + // ATTENTION: + // YOU MUST SORT FOLLOW TABLE BY NAME ALPHABETICAL !!! + + return + { + css::beans::Property( PROPNAME_UINAME, PROPHANDLE_UINAME , + cppu::UnoType<OUString>::get(), + css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ) + }; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwi/uielement/itemcontainer.cxx b/framework/source/fwi/uielement/itemcontainer.cxx new file mode 100644 index 000000000..389bbaeaa --- /dev/null +++ b/framework/source/fwi/uielement/itemcontainer.cxx @@ -0,0 +1,212 @@ +/* -*- 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 <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <uielement/itemcontainer.hxx> +#include <uielement/constitemcontainer.hxx> +#include <comphelper/servicehelper.hxx> +#include <rtl/ref.hxx> + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::container; + +constexpr OUStringLiteral WRONG_TYPE_EXCEPTION + = u"Type must be css::uno::Sequence< css::beans::PropertyValue >"; + +namespace framework +{ + +// XInterface, XTypeProvider + +ItemContainer::ItemContainer( const ShareableMutex& rMutex ) : + m_aShareMutex( rMutex ) +{ +} + +ItemContainer::ItemContainer( const ConstItemContainer& rConstItemContainer, const ShareableMutex& rMutex ) : m_aShareMutex( rMutex ) +{ + copyItemContainer( rConstItemContainer.m_aItemVector, rMutex ); +} + +ItemContainer::ItemContainer( const Reference< XIndexAccess >& rSourceContainer, const ShareableMutex& rMutex ) : + m_aShareMutex( rMutex ) +{ + if ( !rSourceContainer.is() ) + return; + + sal_Int32 nCount = rSourceContainer->getCount(); + try + { + for ( sal_Int32 i = 0; i < nCount; i++ ) + { + Sequence< PropertyValue > aPropSeq; + if ( rSourceContainer->getByIndex( i ) >>= aPropSeq ) + { + sal_Int32 nContainerIndex = -1; + Reference< XIndexAccess > xIndexAccess; + for ( sal_Int32 j = 0; j < aPropSeq.getLength(); j++ ) + { + if ( aPropSeq[j].Name == "ItemDescriptorContainer" ) + { + aPropSeq[j].Value >>= xIndexAccess; + nContainerIndex = j; + break; + } + } + + if ( xIndexAccess.is() && nContainerIndex >= 0 ) + aPropSeq.getArray()[nContainerIndex].Value <<= deepCopyContainer( xIndexAccess, rMutex ); + + m_aItemVector.push_back( aPropSeq ); + } + } + } + catch ( const IndexOutOfBoundsException& ) + { + } +} + +ItemContainer::~ItemContainer() +{ +} + +// private +void ItemContainer::copyItemContainer( const std::vector< Sequence< PropertyValue > >& rSourceVector, const ShareableMutex& rMutex ) +{ + const sal_uInt32 nCount = rSourceVector.size(); + for ( sal_uInt32 i = 0; i < nCount; ++i ) + { + sal_Int32 nContainerIndex = -1; + Sequence< PropertyValue > aPropSeq( rSourceVector[i] ); + Reference< XIndexAccess > xIndexAccess; + for ( sal_Int32 j = 0; j < aPropSeq.getLength(); j++ ) + { + if ( aPropSeq[j].Name == "ItemDescriptorContainer" ) + { + aPropSeq[j].Value >>= xIndexAccess; + nContainerIndex = j; + break; + } + } + + if ( xIndexAccess.is() && nContainerIndex >= 0 ) + aPropSeq.getArray()[nContainerIndex].Value <<= deepCopyContainer( xIndexAccess, rMutex ); + + m_aItemVector.push_back( aPropSeq ); + } +} + +Reference< XIndexAccess > ItemContainer::deepCopyContainer( const Reference< XIndexAccess >& rSubContainer, const ShareableMutex& rMutex ) +{ + Reference< XIndexAccess > xReturn; + if ( rSubContainer.is() ) + { + ConstItemContainer* pSource = comphelper::getFromUnoTunnel<ConstItemContainer>( rSubContainer ); + rtl::Reference<ItemContainer> pSubContainer; + if ( pSource ) + pSubContainer = new ItemContainer( *pSource, rMutex ); + else + pSubContainer = new ItemContainer( rSubContainer, rMutex ); + xReturn = pSubContainer; + } + + return xReturn; +} + +const Sequence< sal_Int8 >& ItemContainer::getUnoTunnelId() noexcept +{ + static const comphelper::UnoIdInit theItemContainerUnoTunnelId; + return theItemContainerUnoTunnelId.getSeq(); +} + +// XElementAccess +sal_Bool SAL_CALL ItemContainer::hasElements() +{ + ShareGuard aLock( m_aShareMutex ); + return ( !m_aItemVector.empty() ); +} + +// XIndexAccess +sal_Int32 SAL_CALL ItemContainer::getCount() +{ + ShareGuard aLock( m_aShareMutex ); + return m_aItemVector.size(); +} + +Any SAL_CALL ItemContainer::getByIndex( sal_Int32 Index ) +{ + ShareGuard aLock( m_aShareMutex ); + if ( sal_Int32( m_aItemVector.size()) <= Index ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + return Any( m_aItemVector[Index] ); +} + +// XIndexContainer +void SAL_CALL ItemContainer::insertByIndex( sal_Int32 Index, const Any& aItem ) +{ + Sequence< PropertyValue > aSeq; + if ( !(aItem >>= aSeq) ) + throw IllegalArgumentException( WRONG_TYPE_EXCEPTION, + static_cast<OWeakObject *>(this), 2 ); + + ShareGuard aLock( m_aShareMutex ); + if ( sal_Int32( m_aItemVector.size()) == Index ) + m_aItemVector.push_back( aSeq ); + else if ( sal_Int32( m_aItemVector.size()) >Index ) + { + std::vector< Sequence< PropertyValue > >::iterator aIter = m_aItemVector.begin(); + aIter += Index; + m_aItemVector.insert( aIter, aSeq ); + } + else + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); +} + +void SAL_CALL ItemContainer::removeByIndex( sal_Int32 nIndex ) +{ + ShareGuard aLock( m_aShareMutex ); + if ( static_cast<sal_Int32>(m_aItemVector.size()) <= nIndex ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + m_aItemVector.erase(m_aItemVector.begin() + nIndex); +} + +void SAL_CALL ItemContainer::replaceByIndex( sal_Int32 Index, const Any& aItem ) +{ + Sequence< PropertyValue > aSeq; + if ( !(aItem >>= aSeq) ) + throw IllegalArgumentException( WRONG_TYPE_EXCEPTION, + static_cast<OWeakObject *>(this), 2 ); + + ShareGuard aLock( m_aShareMutex ); + if ( sal_Int32( m_aItemVector.size()) <= Index ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + m_aItemVector[Index] = aSeq; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/fwi/uielement/rootitemcontainer.cxx b/framework/source/fwi/uielement/rootitemcontainer.cxx new file mode 100644 index 000000000..50ecd0d53 --- /dev/null +++ b/framework/source/fwi/uielement/rootitemcontainer.cxx @@ -0,0 +1,313 @@ +/* -*- 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 <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <comphelper/servicehelper.hxx> +#include <comphelper/sequence.hxx> +#include <uielement/rootitemcontainer.hxx> +#include <uielement/itemcontainer.hxx> +#include <uielement/constitemcontainer.hxx> +#include <properties.h> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <rtl/ref.hxx> + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::container; + +constexpr OUStringLiteral WRONG_TYPE_EXCEPTION + = u"Type must be css::uno::Sequence< css::beans::PropertyValue >"; + +const int PROPHANDLE_UINAME = 1; +constexpr OUStringLiteral PROPNAME_UINAME = u"UIName"; + +namespace framework +{ + +RootItemContainer::RootItemContainer() + : ::cppu::OBroadcastHelperVar< ::cppu::OMultiTypeInterfaceContainerHelper, ::cppu::OMultiTypeInterfaceContainerHelper::keyType >( m_aMutex ) + , ::cppu::OPropertySetHelper ( *static_cast< ::cppu::OBroadcastHelper* >(this) ) +{ +} + +RootItemContainer::RootItemContainer( const Reference< XIndexAccess >& rSourceContainer ) + : ::cppu::OBroadcastHelperVar< ::cppu::OMultiTypeInterfaceContainerHelper, ::cppu::OMultiTypeInterfaceContainerHelper::keyType >( m_aMutex ) + , ::cppu::OPropertySetHelper ( *static_cast< ::cppu::OBroadcastHelper* >(this) ) +{ + // We also have to copy the UIName property + try + { + Reference< XPropertySet > xPropSet( rSourceContainer, UNO_QUERY ); + if ( xPropSet.is() ) + { + xPropSet->getPropertyValue("UIName") >>= m_aUIName; + } + } + catch ( const Exception& ) + { + } + + if ( !rSourceContainer.is() ) + return; + + sal_Int32 nCount = rSourceContainer->getCount(); + try + { + for ( sal_Int32 i = 0; i < nCount; i++ ) + { + Sequence< PropertyValue > aPropSeq; + if ( rSourceContainer->getByIndex( i ) >>= aPropSeq ) + { + sal_Int32 nContainerIndex = -1; + Reference< XIndexAccess > xIndexAccess; + for ( sal_Int32 j = 0; j < aPropSeq.getLength(); j++ ) + { + if ( aPropSeq[j].Name == "ItemDescriptorContainer" ) + { + aPropSeq[j].Value >>= xIndexAccess; + nContainerIndex = j; + break; + } + } + + if ( xIndexAccess.is() && nContainerIndex >= 0 ) + aPropSeq.getArray()[nContainerIndex].Value <<= deepCopyContainer( xIndexAccess ); + + m_aItemVector.push_back( aPropSeq ); + } + } + } + catch ( const IndexOutOfBoundsException& ) + { + } +} + +RootItemContainer::~RootItemContainer() +{ +} + +Any SAL_CALL RootItemContainer::queryInterface( const Type& _rType ) +{ + Any aRet = RootItemContainer_BASE::queryInterface( _rType ); + if ( !aRet.hasValue() ) + aRet = OPropertySetHelper::queryInterface( _rType ); + return aRet; +} + +Sequence< Type > SAL_CALL RootItemContainer::getTypes( ) +{ + return comphelper::concatSequences( + RootItemContainer_BASE::getTypes(), + ::cppu::OPropertySetHelper::getTypes() + ); +} + +Reference< XIndexAccess > RootItemContainer::deepCopyContainer( const Reference< XIndexAccess >& rSubContainer ) +{ + Reference< XIndexAccess > xReturn; + if ( rSubContainer.is() ) + { + ConstItemContainer* pSource = comphelper::getFromUnoTunnel<ConstItemContainer>( rSubContainer ); + rtl::Reference<ItemContainer> pSubContainer; + if ( pSource ) + pSubContainer = new ItemContainer( *pSource, m_aShareMutex ); + else + pSubContainer = new ItemContainer( rSubContainer, m_aShareMutex ); + xReturn = pSubContainer; + } + + return xReturn; +} + +// XUnoTunnel +sal_Int64 RootItemContainer::getSomething( const css::uno::Sequence< sal_Int8 >& rIdentifier ) +{ + return comphelper::getSomethingImpl(rIdentifier, this); +} + +const Sequence< sal_Int8 >& RootItemContainer::getUnoTunnelId() noexcept +{ + static const comphelper::UnoIdInit theRootItemContainerUnoTunnelId; + return theRootItemContainerUnoTunnelId.getSeq(); +} + +// XElementAccess +sal_Bool SAL_CALL RootItemContainer::hasElements() +{ + ShareGuard aLock( m_aShareMutex ); + return ( !m_aItemVector.empty() ); +} + +// XIndexAccess +sal_Int32 SAL_CALL RootItemContainer::getCount() +{ + ShareGuard aLock( m_aShareMutex ); + return m_aItemVector.size(); +} + +Any SAL_CALL RootItemContainer::getByIndex( sal_Int32 Index ) +{ + ShareGuard aLock( m_aShareMutex ); + if ( sal_Int32( m_aItemVector.size()) <= Index ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + return Any( m_aItemVector[Index] ); +} + +// XIndexContainer +void SAL_CALL RootItemContainer::insertByIndex( sal_Int32 Index, const Any& aItem ) +{ + Sequence< PropertyValue > aSeq; + if ( !(aItem >>= aSeq) ) + throw IllegalArgumentException( WRONG_TYPE_EXCEPTION, static_cast<OWeakObject *>(this), 2 ); + + ShareGuard aLock( m_aShareMutex ); + if ( sal_Int32( m_aItemVector.size()) == Index ) + m_aItemVector.push_back( aSeq ); + else if ( sal_Int32( m_aItemVector.size()) >Index ) + { + std::vector< Sequence< PropertyValue > >::iterator aIter = m_aItemVector.begin(); + aIter += Index; + m_aItemVector.insert( aIter, aSeq ); + } + else + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); +} + +void SAL_CALL RootItemContainer::removeByIndex( sal_Int32 nIndex ) +{ + ShareGuard aLock( m_aShareMutex ); + if ( static_cast<sal_Int32>(m_aItemVector.size()) <= nIndex ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + m_aItemVector.erase(m_aItemVector.begin() + nIndex); +} + +void SAL_CALL RootItemContainer::replaceByIndex( sal_Int32 Index, const Any& aItem ) +{ + Sequence< PropertyValue > aSeq; + if ( !(aItem >>= aSeq) ) + throw IllegalArgumentException( WRONG_TYPE_EXCEPTION, static_cast<OWeakObject *>(this), 2 ); + + ShareGuard aLock( m_aShareMutex ); + if ( sal_Int32( m_aItemVector.size()) <= Index ) + throw IndexOutOfBoundsException( OUString(), static_cast<OWeakObject *>(this) ); + + m_aItemVector[Index] = aSeq; +} + +Reference< XInterface > SAL_CALL RootItemContainer::createInstanceWithContext( const Reference< XComponentContext >& ) +{ + return static_cast<OWeakObject *>(new ItemContainer( m_aShareMutex )); +} + +Reference< XInterface > SAL_CALL RootItemContainer::createInstanceWithArgumentsAndContext( const Sequence< Any >&, const Reference< XComponentContext >& ) +{ + return static_cast<OWeakObject *>(new ItemContainer( m_aShareMutex )); +} + +// XPropertySet helper +sal_Bool SAL_CALL RootItemContainer::convertFastPropertyValue( Any& aConvertedValue , + Any& aOldValue , + sal_Int32 nHandle , + const Any& aValue ) +{ + // Initialize state with sal_False !!! + // (Handle can be invalid) + bool bReturn = false; + + switch( nHandle ) + { + case PROPHANDLE_UINAME: + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(m_aUIName), + aValue, + aOldValue, + aConvertedValue); + break; + } + + // Return state of operation. + return bReturn; +} + +void SAL_CALL RootItemContainer::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle , + const css::uno::Any& aValue ) +{ + switch( nHandle ) + { + case PROPHANDLE_UINAME: + aValue >>= m_aUIName; + break; + } +} + +void SAL_CALL RootItemContainer::getFastPropertyValue( css::uno::Any& aValue , + sal_Int32 nHandle ) const +{ + switch( nHandle ) + { + case PROPHANDLE_UINAME: + aValue <<= m_aUIName; + break; + } +} + +::cppu::IPropertyArrayHelper& SAL_CALL RootItemContainer::getInfoHelper() +{ + // Define static member to give structure of properties to baseclass "OPropertySetHelper". + // "impl_getStaticPropertyDescriptor" is a non exported and static function, who will define a static propertytable. + // "true" say: Table is sorted by name. + static ::cppu::OPropertyArrayHelper ourInfoHelper( impl_getStaticPropertyDescriptor(), true ); + + return ourInfoHelper; +} + +css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL RootItemContainer::getPropertySetInfo() +{ + // Create structure of propertysetinfo for baseclass "OPropertySetHelper". + // (Use method "getInfoHelper()".) + static css::uno::Reference< css::beans::XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) ); + + return xInfo; +} + +css::uno::Sequence< css::beans::Property > RootItemContainer::impl_getStaticPropertyDescriptor() +{ + // Create a property array to initialize sequence! + // Table of all predefined properties of this class. It's used from OPropertySetHelper-class! + // Don't forget to change the defines (see begin of this file), if you add, change or delete a property in this list!!! + // It's necessary for methods of OPropertySetHelper. + // ATTENTION: + // YOU MUST SORT FOLLOW TABLE BY NAME ALPHABETICAL !!! + + return + { + css::beans::Property( PROPNAME_UINAME, PROPHANDLE_UINAME , + cppu::UnoType<OUString>::get(), + css::beans::PropertyAttribute::TRANSIENT ) + }; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/dockingareadefaultacceptor.cxx b/framework/source/helper/dockingareadefaultacceptor.cxx new file mode 100644 index 000000000..59a43a1e3 --- /dev/null +++ b/framework/source/helper/dockingareadefaultacceptor.cxx @@ -0,0 +1,129 @@ +/* -*- 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 <helper/dockingareadefaultacceptor.hxx> + +#include <com/sun/star/awt/XDevice.hpp> +#include <com/sun/star/awt/PosSize.hpp> + +#include <vcl/svapp.hxx> + +namespace framework{ + +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::cppu; +using namespace ::osl; + +// constructor + +DockingAreaDefaultAcceptor::DockingAreaDefaultAcceptor( const css::uno::Reference< XFrame >& xOwner ) + : m_xOwner ( xOwner ) +{ +} + +// destructor + +DockingAreaDefaultAcceptor::~DockingAreaDefaultAcceptor() +{ +} + +// XDockingAreaAcceptor +css::uno::Reference< css::awt::XWindow > SAL_CALL DockingAreaDefaultAcceptor::getContainerWindow() +{ + SolarMutexGuard g; + + // Try to "lock" the frame for access to taskscontainer. + css::uno::Reference< XFrame > xFrame( m_xOwner ); + //TODO: check xFrame for null? + css::uno::Reference< css::awt::XWindow > xContainerWindow( xFrame->getContainerWindow() ); + + return xContainerWindow; +} + +sal_Bool SAL_CALL DockingAreaDefaultAcceptor::requestDockingAreaSpace( const css::awt::Rectangle& RequestedSpace ) +{ + // Try to "lock" the frame for access to taskscontainer. + css::uno::Reference< XFrame > xFrame( m_xOwner ); + + if ( !xFrame.is() ) + return false; + + css::uno::Reference< css::awt::XWindow > xContainerWindow( xFrame->getContainerWindow() ); + css::uno::Reference< css::awt::XWindow > xComponentWindow( xFrame->getComponentWindow() ); + + if ( !xContainerWindow.is() || !xComponentWindow.is() ) + return false; + + css::uno::Reference< css::awt::XDevice > xDevice( xContainerWindow, css::uno::UNO_QUERY ); + // Convert relative size to output size. + css::awt::Rectangle aRectangle = xContainerWindow->getPosSize(); + css::awt::DeviceInfo aInfo = xDevice->getInfo(); + css::awt::Size aSize ( aRectangle.Width - aInfo.LeftInset - aInfo.RightInset , + aRectangle.Height - aInfo.TopInset - aInfo.BottomInset ); + + css::awt::Size aMinSize( 0, 0 ); // = xLayoutConstraints->getMinimumSize(); + + // Check if request border space would decrease component window size below minimum size + if ((( aSize.Width - RequestedSpace.X - RequestedSpace.Width ) < aMinSize.Width ) || + (( aSize.Height - RequestedSpace.Y - RequestedSpace.Height ) < aMinSize.Height ) ) + return false; + + return true; +} + +void SAL_CALL DockingAreaDefaultAcceptor::setDockingAreaSpace( const css::awt::Rectangle& BorderSpace ) +{ + SolarMutexGuard g; + + // Try to "lock" the frame for access to taskscontainer. + css::uno::Reference< XFrame > xFrame( m_xOwner ); + if ( !xFrame.is() ) + return; + + css::uno::Reference< css::awt::XWindow > xContainerWindow( xFrame->getContainerWindow() ); + css::uno::Reference< css::awt::XWindow > xComponentWindow( xFrame->getComponentWindow() ); + + if ( !(xContainerWindow.is() && xComponentWindow.is()) ) + return; + + css::uno::Reference< css::awt::XDevice > xDevice( xContainerWindow, css::uno::UNO_QUERY ); + // Convert relative size to output size. + css::awt::Rectangle aRectangle = xContainerWindow->getPosSize(); + css::awt::DeviceInfo aInfo = xDevice->getInfo(); + css::awt::Size aSize ( aRectangle.Width - aInfo.LeftInset - aInfo.RightInset , + aRectangle.Height - aInfo.TopInset - aInfo.BottomInset ); + css::awt::Size aMinSize( 0, 0 );// = xLayoutConstraints->getMinimumSize(); + + // Check if request border space would decrease component window size below minimum size + sal_Int32 nWidth = aSize.Width - BorderSpace.X - BorderSpace.Width; + sal_Int32 nHeight = aSize.Height - BorderSpace.Y - BorderSpace.Height; + + if (( nWidth > aMinSize.Width ) && ( nHeight > aMinSize.Height )) + { + // Resize our component window. + xComponentWindow->setPosSize( BorderSpace.X, BorderSpace.Y, nWidth, nHeight, css::awt::PosSize::POSSIZE ); + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/ocomponentaccess.cxx b/framework/source/helper/ocomponentaccess.cxx new file mode 100644 index 000000000..ad7409990 --- /dev/null +++ b/framework/source/helper/ocomponentaccess.cxx @@ -0,0 +1,167 @@ +/* -*- 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 <helper/ocomponentaccess.hxx> +#include <helper/ocomponentenumeration.hxx> + +#include <com/sun/star/frame/FrameSearchFlag.hpp> + +#include <vcl/svapp.hxx> +#include <sal/log.hxx> + +namespace framework{ + +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::cppu; +using namespace ::osl; + +// constructor + +OComponentAccess::OComponentAccess( const css::uno::Reference< XDesktop >& xOwner ) + : m_xOwner ( xOwner ) +{ + // Safe impossible cases + SAL_WARN_IF( !xOwner.is(), "fwk", "OComponentAccess::OComponentAccess(): Invalid parameter detected!" ); +} + +// destructor + +OComponentAccess::~OComponentAccess() +{ +} + +// XEnumerationAccess +css::uno::Reference< XEnumeration > SAL_CALL OComponentAccess::createEnumeration() +{ + SolarMutexGuard g; + + // Set default return value, if method failed. + // If no desktop exist and there is no task container - return an empty enumeration! + css::uno::Reference< XEnumeration > xReturn; + + // Try to "lock" the desktop for access to task container. + css::uno::Reference< XInterface > xLock = m_xOwner.get(); + if ( xLock.is() ) + { + // Desktop exist => pointer to task container must be valid. + // Initialize a new enumeration ... if some tasks and his components exist! + // (OTasksEnumeration will make an assert, if we initialize the new instance without valid values!) + + std::vector< css::uno::Reference< XComponent > > seqComponents; + impl_collectAllChildComponents( css::uno::Reference< XFramesSupplier >( xLock, UNO_QUERY ), seqComponents ); + xReturn = new OComponentEnumeration( std::move(seqComponents) ); + } + + // Return result of this operation. + return xReturn; +} + +// XElementAccess +Type SAL_CALL OComponentAccess::getElementType() +{ + // Elements in list an enumeration are components! + // Return the uno-type of XComponent. + return cppu::UnoType<XComponent>::get(); +} + +// XElementAccess +sal_Bool SAL_CALL OComponentAccess::hasElements() +{ + SolarMutexGuard g; + + // Set default return value, if method failed. + bool bReturn = false; + + // Try to "lock" the desktop for access to task container. + css::uno::Reference< XFramesSupplier > xLock( m_xOwner.get(), UNO_QUERY ); + if ( xLock.is() ) + { + // Ask container of owner for existing elements. + bReturn = xLock->getFrames()->hasElements(); + } + + // Return result of this operation. + return bReturn; +} + + +void OComponentAccess::impl_collectAllChildComponents( const css::uno::Reference< XFramesSupplier >& xNode , + std::vector< css::uno::Reference< XComponent > >& seqComponents ) +{ + // If valid node was given ... + if( !xNode.is() ) + return; + + // ... continue collection at these. + + // Get the container of current node, collect the components of existing child frames + // and go down to next level in tree (recursive!). + + const css::uno::Reference< XFrames > xContainer = xNode->getFrames(); + const Sequence< css::uno::Reference< XFrame > > seqFrames = xContainer->queryFrames( FrameSearchFlag::CHILDREN ); + + const sal_Int32 nFrameCount = seqFrames.getLength(); + for( sal_Int32 nFrame=0; nFrame<nFrameCount; ++nFrame ) + { + css::uno::Reference< XComponent > xComponent = impl_getFrameComponent( seqFrames[nFrame] ); + if( xComponent.is() ) + { + seqComponents.push_back( xComponent ); + } + } + // ... otherwise break a recursive path and go back at current stack! +} + +css::uno::Reference< XComponent > OComponentAccess::impl_getFrameComponent( const css::uno::Reference< XFrame >& xFrame ) const +{ + // Set default return value, if method failed. + css::uno::Reference< XComponent > xComponent; + // Does no controller exists? + css::uno::Reference< XController > xController = xFrame->getController(); + if ( !xController.is() ) + { + // Controller not exist - use the VCL-component. + xComponent = xFrame->getComponentWindow(); + } + else + { + // Does no model exists? + css::uno::Reference< XModel > xModel = xController->getModel(); + if ( xModel.is() ) + { + // Model exist - use the model as component. + xComponent = xModel; + } + else + { + // Model not exist - use the controller as component. + xComponent = xController; + } + } + + return xComponent; +} + + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/ocomponentenumeration.cxx b/framework/source/helper/ocomponentenumeration.cxx new file mode 100644 index 000000000..3d00f75e5 --- /dev/null +++ b/framework/source/helper/ocomponentenumeration.cxx @@ -0,0 +1,117 @@ +/* -*- 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 <helper/ocomponentenumeration.hxx> + +#include <vcl/svapp.hxx> +#include <sal/log.hxx> + +namespace framework{ + +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::cppu; +using namespace ::osl; + +// constructor + +OComponentEnumeration::OComponentEnumeration( std::vector< css::uno::Reference< XComponent > >&& seqComponents ) + : m_nPosition ( 0 ) // 0 is the first position for a valid list and the right value for an invalid list to! + , m_seqComponents ( std::move(seqComponents) ) +{} + +// destructor + +OComponentEnumeration::~OComponentEnumeration() +{ + // Reset instance, free memory... + impl_resetObject(); +} + +// XEventListener +void SAL_CALL OComponentEnumeration::disposing( const EventObject& aEvent ) +{ + SolarMutexGuard g; + + // Safe impossible cases + // This method is not specified for all incoming parameters. + SAL_WARN_IF( !aEvent.Source.is(), "fwk", "OComponentEnumeration::disposing(): Invalid parameter detected!" ); + + // Reset instance to defaults, release references and free memory. + impl_resetObject(); +} + +// XEnumeration +sal_Bool SAL_CALL OComponentEnumeration::hasMoreElements() +{ + SolarMutexGuard g; + + // First position in a valid list is 0. + // => The last one is getLength() - 1! + // m_nPosition's current value is the position for the next element, which will be return, if user call "nextElement()" + // => We have more elements if current position less than the length of the list! + return ( m_nPosition < static_cast<sal_uInt32>(m_seqComponents.size()) ); +} + +// XEnumeration + +Any SAL_CALL OComponentEnumeration::nextElement() +{ + SolarMutexGuard g; + + // If we have no elements or end of enumeration is arrived ... + if ( !hasMoreElements() ) + { + // .. throw an exception! + throw NoSuchElementException(); + } + + // Else; Get next element from list ... + Any aComponent; + aComponent <<= m_seqComponents[m_nPosition]; + // ... and step to next element! + ++m_nPosition; + + // Return listitem. + return aComponent; +} + +// protected method + +void OComponentEnumeration::impl_resetObject() +{ + // Attention: + // Write this for multiple calls - NOT AT THE SAME TIME - but for more than one call again)! + // It exist two ways to call this method. From destructor and from disposing(). + // I can't say, which one is the first. Normally the disposing-call - but other way... + + // Delete list of components. + m_seqComponents.clear(); + // Reset position in list. + // The list has no elements anymore. m_nPosition is normally the current position in list for nextElement! + // But a position of 0 in a list of 0 items is an invalid state. This constellation can't work in future. + // End of enumeration is arrived! + // (see hasMoreElements() for more details...) + m_nPosition = 0; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/oframes.cxx b/framework/source/helper/oframes.cxx new file mode 100644 index 000000000..a58949852 --- /dev/null +++ b/framework/source/helper/oframes.cxx @@ -0,0 +1,370 @@ +/* -*- 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 <helper/oframes.hxx> + +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> + +namespace framework{ + +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::cppu; +using namespace ::osl; +using namespace ::std; + +// constructor + +OFrames::OFrames( const css::uno::Reference< XFrame >& xOwner , + FrameContainer* pFrameContainer ) + : m_xOwner ( xOwner ) + , m_pFrameContainer ( pFrameContainer ) + , m_bRecursiveSearchProtection( false ) +{ + // An instance of this class can only work with valid initialization. + // We share the mutex with our owner class, need a valid factory to instantiate new services and + // use the access to our owner for some operations. + SAL_WARN_IF( !xOwner.is() || !pFrameContainer, "fwk", "OFrames::OFrames(): Invalid parameter detected!" ); +} + +// (protected!) destructor + +OFrames::~OFrames() +{ + // Reset instance, free memory... + impl_resetObject(); +} + +// XFrames +void SAL_CALL OFrames::append( const css::uno::Reference< XFrame >& xFrame ) +{ + SolarMutexGuard g; + + // Safe impossible cases + // Method is not defined for ALL incoming parameters! + SAL_WARN_IF( !xFrame.is(), "fwk", "OFrames::append(): Invalid parameter detected!" ); + + // Do the follow only, if owner instance valid! + // Lock owner for follow operations - make a "hard reference"! + css::uno::Reference< XFramesSupplier > xOwner( m_xOwner.get(), UNO_QUERY ); + if ( xOwner.is() ) + { + // Append frame to the end of the container ... + m_pFrameContainer->append( xFrame ); + // Set owner of this instance as parent of the new frame in container! + xFrame->setCreator( xOwner ); + } + // Else; Do nothing! Our owner is dead. + SAL_WARN_IF( !xOwner.is(), "fwk", "OFrames::append():Our owner is dead - you can't append any frames ...!" ); +} + +// XFrames +void SAL_CALL OFrames::remove( const css::uno::Reference< XFrame >& xFrame ) +{ + SolarMutexGuard g; + + // Safe impossible cases + // Method is not defined for ALL incoming parameters! + SAL_WARN_IF( !xFrame.is(), "fwk", "OFrames::remove(): Invalid parameter detected!" ); + + // Do the follow only, if owner instance valid! + // Lock owner for follow operations - make a "hard reference"! + css::uno::Reference< XFramesSupplier > xOwner( m_xOwner.get(), UNO_QUERY ); + if ( xOwner.is() ) + { + // Search frame and remove it from container ... + m_pFrameContainer->remove( xFrame ); + // Don't reset owner-property of removed frame! + // This must do the caller of this method himself. + // See documentation of interface XFrames for further information. + } + // Else; Do nothing! Our owner is dead. + SAL_WARN_IF( !xOwner.is(), "fwk", "OFrames::remove(): Our owner is dead - you can't remove any frames ...!" ); +} + +// XFrames +Sequence< css::uno::Reference< XFrame > > SAL_CALL OFrames::queryFrames( sal_Int32 nSearchFlags ) +{ + SolarMutexGuard g; + + // Safe impossible cases + // Method is not defined for ALL incoming parameters! + SAL_WARN_IF( !impldbg_checkParameter_queryFrames( nSearchFlags ), "fwk", "OFrames::queryFrames(): Invalid parameter detected!" ); + + // Set default return value. (empty sequence) + Sequence< css::uno::Reference< XFrame > > seqFrames; + + // Do the follow only, if owner instance valid. + // Lock owner for follow operations - make a "hard reference"! + css::uno::Reference< XFrame > xOwner( m_xOwner.get(), UNO_QUERY ); + if ( xOwner.is() ) + { + // Work only, if search was not started here ...! + if( !m_bRecursiveSearchProtection ) + { + // This class is a helper for services, which must implement XFrames. + // His parent and children are MY parent and children to. + // All searchflags are supported by this implementation! + // If some flags should not be supported - don't call me with this flags!!! + + // Search with AUTO-flag is not supported yet! + // We think about right implementation. + SAL_WARN_IF( (nSearchFlags & FrameSearchFlag::AUTO), "fwk", "OFrames::queryFrames(): Search with AUTO-flag is not supported yet!" ); + + // Search for ALL and GLOBAL is superfluous! + // We support all necessary flags, from which these two flags are derived. + // ALL = PARENT + SELF + CHILDREN + SIBLINGS + // GLOBAL = ALL + TASKS + + // Add parent to list ... if any exist! + if( nSearchFlags & FrameSearchFlag::PARENT ) + { + css::uno::Reference< XFrame > xParent = xOwner->getCreator(); + if( xParent.is() ) + { + impl_appendSequence( seqFrames, { xParent } ); + } + } + + // Add owner to list if SELF is searched. + if( nSearchFlags & FrameSearchFlag::SELF ) + { + impl_appendSequence( seqFrames, { xOwner } ); + } + + // Add SIBLINGS to list. + if( nSearchFlags & FrameSearchFlag::SIBLINGS ) + { + // Else; start a new search. + // Protect this instance against recursive calls from parents. + m_bRecursiveSearchProtection = true; + // Ask parent of my owner for frames and append results to return list. + css::uno::Reference< XFramesSupplier > xParent = xOwner->getCreator(); + // If a parent exist ... + if ( xParent.is() ) + { + // ... ask him for right frames. + impl_appendSequence( seqFrames, xParent->getFrames()->queryFrames( nSearchFlags ) ); + } + // We have all searched information. + // Reset protection-mode. + m_bRecursiveSearchProtection = false; + } + + // If searched for children, step over all elements in container and collect the information. + if ( nSearchFlags & FrameSearchFlag::CHILDREN ) + { + // Don't search for parents, siblings and self at children! + // These things are supported by this instance himself. + sal_Int32 const nChildSearchFlags = FrameSearchFlag::SELF | FrameSearchFlag::CHILDREN; + // Step over all items of container and ask children for frames. + sal_uInt32 nCount = m_pFrameContainer->getCount(); + for ( sal_uInt32 nIndex=0; nIndex<nCount; ++nIndex ) + { + // We don't must control this conversion. + // We have done this at append()! + css::uno::Reference< XFramesSupplier > xItem( (*m_pFrameContainer)[nIndex], UNO_QUERY ); + impl_appendSequence( seqFrames, xItem->getFrames()->queryFrames( nChildSearchFlags ) ); + } + } + } + } + // Else; Do nothing! Our owner is dead. + SAL_WARN_IF( !xOwner.is(), "fwk", "OFrames::queryFrames(): Our owner is dead - you can't query for frames ...!" ); + + // Return result of this operation. + return seqFrames; +} + +// XIndexAccess +sal_Int32 SAL_CALL OFrames::getCount() +{ + SolarMutexGuard g; + + // Set default return value. + sal_Int32 nCount = 0; + + // Do the follow only, if owner instance valid. + // Lock owner for follow operations - make a "hard reference"! + css::uno::Reference< XFrame > xOwner( m_xOwner.get(), UNO_QUERY ); + if ( xOwner.is() ) + { + // Set CURRENT size of container for return. + nCount = m_pFrameContainer->getCount(); + } + + // Return result. + return nCount; +} + +// XIndexAccess + +Any SAL_CALL OFrames::getByIndex( sal_Int32 nIndex ) +{ + SolarMutexGuard g; + + sal_uInt32 nCount = m_pFrameContainer->getCount(); + if ( nIndex < 0 || ( sal::static_int_cast< sal_uInt32 >( nIndex ) >= nCount )) + throw IndexOutOfBoundsException("OFrames::getByIndex - Index out of bounds", + static_cast<OWeakObject *>(this) ); + + // Set default return value. + Any aReturnValue; + + // Do the follow only, if owner instance valid. + // Lock owner for follow operations - make a "hard reference"! + css::uno::Reference< XFrame > xOwner( m_xOwner.get(), UNO_QUERY ); + if ( xOwner.is() ) + { + // Get element form container. + // (If index not valid, FrameContainer return NULL!) + aReturnValue <<= (*m_pFrameContainer)[nIndex]; + } + + // Return result of this operation. + return aReturnValue; +} + +// XElementAccess +Type SAL_CALL OFrames::getElementType() +{ + // This "container" support XFrame-interfaces only! + return cppu::UnoType<XFrame>::get(); +} + +// XElementAccess +sal_Bool SAL_CALL OFrames::hasElements() +{ + SolarMutexGuard g; + + // Set default return value. + bool bHasElements = false; + // Do the follow only, if owner instance valid. + // Lock owner for follow operations - make a "hard reference"! + css::uno::Reference< XFrame > xOwner( m_xOwner.get(), UNO_QUERY ); + if ( xOwner.is() ) + { + // If some elements exist ... + if ( m_pFrameContainer->getCount() > 0 ) + { + // ... change this state value! + bHasElements = true; + } + } + // Return result of this operation. + return bHasElements; +} + +// protected method + +void OFrames::impl_resetObject() +{ + // Attention: + // Write this for multiple calls - NOT AT THE SAME TIME - but for more than one call again)! + // It exist two ways to call this method. From destructor and from disposing(). + // I can't say, which one is the first. Normally the disposing-call - but other way... + + // This instance can't work if the weakreference to owner is invalid! + // Destroy this to reset this object. + m_xOwner.clear(); + // Reset pointer to shared container to! + m_pFrameContainer = nullptr; +} + +void OFrames::impl_appendSequence( Sequence< css::uno::Reference< XFrame > >& seqDestination , + const Sequence< css::uno::Reference< XFrame > >& seqSource ) +{ + // Get some information about the sequences. + sal_Int32 nSourceCount = seqSource.getLength(); + sal_Int32 nDestinationCount = seqDestination.getLength(); + const css::uno::Reference< XFrame >* pSourceAccess = seqSource.getConstArray(); + css::uno::Reference< XFrame >* pDestinationAccess = seqDestination.getArray(); + + // Get memory for result list. + Sequence< css::uno::Reference< XFrame > > seqResult ( nSourceCount + nDestinationCount ); + css::uno::Reference< XFrame >* pResultAccess = seqResult.getArray(); + sal_Int32 nResultPosition = 0; + + // Copy all items from first sequence. + for ( sal_Int32 nSourcePosition=0; nSourcePosition<nSourceCount; ++nSourcePosition ) + { + pResultAccess[nResultPosition] = pSourceAccess[nSourcePosition]; + ++nResultPosition; + } + + // Don't manipulate nResultPosition between these two loops! + // It's the current position in the result list. + + // Copy all items from second sequence. + for ( sal_Int32 nDestinationPosition=0; nDestinationPosition<nDestinationCount; ++nDestinationPosition ) + { + pResultAccess[nResultPosition] = pDestinationAccess[nDestinationPosition]; + ++nResultPosition; + } + + // Return result of this operation. + seqDestination.realloc( 0 ); + seqDestination = seqResult; +} + +// debug methods + +/*----------------------------------------------------------------------------------------------------------------- + The follow methods checks the parameter for other functions. If a parameter or his value is non valid, + we return "sal_False". (else sal_True) This mechanism is used to throw an ASSERT! + + ATTENTION + + If you miss a test for one of this parameters, contact the author or add it himself !(?) + But ... look for right testing! See using of this methods! +-----------------------------------------------------------------------------------------------------------------*/ + +// A search for frames must initiate with right flags. +// Some one are superfluous and not supported yet. But here we control only the range of incoming parameter! +bool OFrames::impldbg_checkParameter_queryFrames( sal_Int32 nSearchFlags ) +{ + // Set default return value. + bool bOK = true; + // Check parameter. + if ( + ( nSearchFlags != FrameSearchFlag::AUTO ) && + ( !( nSearchFlags & FrameSearchFlag::PARENT ) ) && + ( !( nSearchFlags & FrameSearchFlag::SELF ) ) && + ( !( nSearchFlags & FrameSearchFlag::CHILDREN ) ) && + ( !( nSearchFlags & FrameSearchFlag::CREATE ) ) && + ( !( nSearchFlags & FrameSearchFlag::SIBLINGS ) ) && + ( !( nSearchFlags & FrameSearchFlag::TASKS ) ) && + ( !( nSearchFlags & FrameSearchFlag::ALL ) ) && + ( !( nSearchFlags & FrameSearchFlag::GLOBAL ) ) + ) + { + bOK = false; + } + // Return result of check. + return bOK; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/persistentwindowstate.cxx b/framework/source/helper/persistentwindowstate.cxx new file mode 100644 index 000000000..2f7c99719 --- /dev/null +++ b/framework/source/helper/persistentwindowstate.cxx @@ -0,0 +1,267 @@ +/* -*- 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 <helper/persistentwindowstate.hxx> + +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> + +#include <comphelper/lok.hxx> +#include <comphelper/configurationhelper.hxx> +#include <utility> +#include <vcl/window.hxx> +#include <vcl/syswin.hxx> + +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/wrkwin.hxx> + +namespace framework{ + +PersistentWindowState::PersistentWindowState(css::uno::Reference< css::uno::XComponentContext > xContext) + : m_xContext (std::move(xContext )) + , m_bWindowStateAlreadySet(false ) +{ +} + +PersistentWindowState::~PersistentWindowState() +{ +} + +void SAL_CALL PersistentWindowState::initialize(const css::uno::Sequence< css::uno::Any >& lArguments) +{ + // check arguments + css::uno::Reference< css::frame::XFrame > xFrame; + if (!lArguments.hasElements()) + throw css::lang::IllegalArgumentException( + "Empty argument list!", + static_cast< ::cppu::OWeakObject* >(this), + 1); + + lArguments[0] >>= xFrame; + if (!xFrame.is()) + throw css::lang::IllegalArgumentException( + "No valid frame specified!", + static_cast< ::cppu::OWeakObject* >(this), + 1); + + { + SolarMutexGuard g; + m_xFrame = xFrame; + } + + // start listening + xFrame->addFrameActionListener(this); +} + +void SAL_CALL PersistentWindowState::frameAction(const css::frame::FrameActionEvent& aEvent) +{ + // We don't want to do this stuff when being used through LibreOfficeKit + if( comphelper::LibreOfficeKit::isActive() ) + return; + + css::uno::Reference< css::uno::XComponentContext > xContext; + css::uno::Reference< css::frame::XFrame > xFrame; + bool bRestoreWindowState; + { + SolarMutexGuard g; + xContext = m_xContext; + xFrame.set(m_xFrame.get(), css::uno::UNO_QUERY); + bRestoreWindowState = !m_bWindowStateAlreadySet; + } + + // frame already gone ? We hold it weak only ... + if (!xFrame.is()) + return; + + // no window -> no position and size available + css::uno::Reference< css::awt::XWindow > xWindow = xFrame->getContainerWindow(); + if (!xWindow.is()) + return; + + // unknown module -> no configuration available! + OUString sModuleName = PersistentWindowState::implst_identifyModule(xContext, xFrame); + if (sModuleName.isEmpty()) + return; + + switch(aEvent.Action) + { + case css::frame::FrameAction_COMPONENT_ATTACHED : + { + if (bRestoreWindowState) + { + OUString sWindowState = PersistentWindowState::implst_getWindowStateFromConfig(xContext, sModuleName); + PersistentWindowState::implst_setWindowStateOnWindow(xWindow,sWindowState); + SolarMutexGuard g; + m_bWindowStateAlreadySet = true; + } + } + break; + + case css::frame::FrameAction_COMPONENT_REATTACHED : + { + // nothing todo here, because it's not allowed to change position and size + // of an already existing frame! + } + break; + + case css::frame::FrameAction_COMPONENT_DETACHING : + { + OUString sWindowState = PersistentWindowState::implst_getWindowStateFromWindow(xWindow); + PersistentWindowState::implst_setWindowStateOnConfig(xContext, sModuleName, sWindowState); + } + break; + default: + break; + } +} + +void SAL_CALL PersistentWindowState::disposing(const css::lang::EventObject&) +{ + css::uno::Reference< css::frame::XFrame > xFrame(m_xFrame.get(), css::uno::UNO_QUERY); + if (xFrame.is()) + xFrame->removeFrameActionListener(this); + + // nothing todo here - because we hold the frame as weak reference only +} + +OUString PersistentWindowState::implst_identifyModule(const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + OUString sModuleName; + + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager = + css::frame::ModuleManager::create( rxContext ); + + try + { + sModuleName = xModuleManager->identify(xFrame); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { sModuleName.clear(); } + + return sModuleName; +} + +OUString PersistentWindowState::implst_getWindowStateFromConfig( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + std::u16string_view sModuleName) +{ + OUString sWindowState; + try + { + ::comphelper::ConfigurationHelper::readDirectKey(rxContext, + "org.openoffice.Setup/", + OUString::Concat("Office/Factories/*[\"") + sModuleName + "\"]", + "ooSetupFactoryWindowAttributes", + ::comphelper::EConfigurationModes::ReadOnly) >>= sWindowState; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { sWindowState.clear(); } + + return sWindowState; +} + +void PersistentWindowState::implst_setWindowStateOnConfig( + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + std::u16string_view sModuleName, const OUString& sWindowState) +{ + try + { + ::comphelper::ConfigurationHelper::writeDirectKey(rxContext, + "org.openoffice.Setup/", + OUString::Concat("Office/Factories/*[\"") + sModuleName + "\"]", + "ooSetupFactoryWindowAttributes", + css::uno::Any(sWindowState), + ::comphelper::EConfigurationModes::Standard); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + {} +} + +OUString PersistentWindowState::implst_getWindowStateFromWindow(const css::uno::Reference< css::awt::XWindow >& xWindow) +{ + OUString sWindowState; + + if (xWindow.is()) + { + // SOLAR SAFE -> ------------------------ + SolarMutexGuard aSolarGuard; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + // check for system window is necessary to guarantee correct pointer cast! + if ( pWindow && pWindow->IsSystemWindow() ) + { + WindowStateMask const nMask = WindowStateMask::All & ~WindowStateMask::Minimized; + sWindowState = OStringToOUString( + static_cast<SystemWindow*>(pWindow.get())->GetWindowState(nMask), + RTL_TEXTENCODING_UTF8); + } + // <- SOLAR SAFE ------------------------ + } + + return sWindowState; +} + +void PersistentWindowState::implst_setWindowStateOnWindow(const css::uno::Reference< css::awt::XWindow >& xWindow , + std::u16string_view sWindowState) +{ + if ( + (!xWindow.is() ) || + ( sWindowState.empty() ) + ) + return; + + // SOLAR SAFE -> ------------------------ + SolarMutexGuard aSolarGuard; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + if (!pWindow) + return; + + // check for system and work window - it's necessary to guarantee correct pointer cast! + bool bSystemWindow = pWindow->IsSystemWindow(); + bool bWorkWindow = (pWindow->GetType() == WindowType::WORKWINDOW); + + if (!bSystemWindow && !bWorkWindow) + return; + + SystemWindow* pSystemWindow = static_cast<SystemWindow*>(pWindow.get()); + WorkWindow* pWorkWindow = static_cast<WorkWindow* >(pWindow.get()); + + // don't save this special state! + if (pWorkWindow->IsMinimized()) + return; + + OUString sOldWindowState = OStringToOUString( pSystemWindow->GetWindowState(), RTL_TEXTENCODING_ASCII_US ); + if ( sOldWindowState != sWindowState ) + pSystemWindow->SetWindowState(OUStringToOString(sWindowState,RTL_TEXTENCODING_UTF8)); + // <- SOLAR SAFE ------------------------ +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/statusindicator.cxx b/framework/source/helper/statusindicator.cxx new file mode 100644 index 000000000..2deb4a0ae --- /dev/null +++ b/framework/source/helper/statusindicator.cxx @@ -0,0 +1,128 @@ +/* -*- 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 <comphelper/lok.hxx> +#include <helper/statusindicator.hxx> + +namespace framework +{ +StatusIndicator::StatusIndicator(StatusIndicatorFactory* pFactory) + : m_xFactory(pFactory) + , m_nRange(100) + , m_nLastCallbackPercent(-1) +{ +} + +StatusIndicator::~StatusIndicator() {} + +void SAL_CALL StatusIndicator::start(const OUString& sText, sal_Int32 nRange) +{ + if (comphelper::LibreOfficeKit::isActive()) + { + m_nRange = nRange; + m_nLastCallbackPercent = -1; + + comphelper::LibreOfficeKit::statusIndicatorStart(sText); + } +#if !defined(IOS) && !defined(ANDROID) + css::uno::Reference<css::task::XStatusIndicatorFactory> xFactory(m_xFactory); + if (xFactory.is()) + { + StatusIndicatorFactory* pFactory = static_cast<StatusIndicatorFactory*>(xFactory.get()); + pFactory->start(this, sText, nRange); + } +#else + (void)sText; +#endif +} + +void SAL_CALL StatusIndicator::end() +{ + if (comphelper::LibreOfficeKit::isActive()) + { + comphelper::LibreOfficeKit::statusIndicatorFinish(); + } +#if !defined(IOS) && !defined(ANDROID) + css::uno::Reference<css::task::XStatusIndicatorFactory> xFactory(m_xFactory); + if (xFactory.is()) + { + StatusIndicatorFactory* pFactory = static_cast<StatusIndicatorFactory*>(xFactory.get()); + pFactory->end(this); + } +#endif +} + +void SAL_CALL StatusIndicator::reset() +{ + if (comphelper::LibreOfficeKit::isActive()) + return; +#if !defined(IOS) && !defined(ANDROID) + css::uno::Reference<css::task::XStatusIndicatorFactory> xFactory(m_xFactory); + if (xFactory.is()) + { + StatusIndicatorFactory* pFactory = static_cast<StatusIndicatorFactory*>(xFactory.get()); + pFactory->reset(this); + } +#endif +} + +void SAL_CALL StatusIndicator::setText(const OUString& sText) +{ + if (comphelper::LibreOfficeKit::isActive()) + return; +#if !defined(IOS) && !defined(ANDROID) + css::uno::Reference<css::task::XStatusIndicatorFactory> xFactory(m_xFactory); + if (xFactory.is()) + { + StatusIndicatorFactory* pFactory = static_cast<StatusIndicatorFactory*>(xFactory.get()); + pFactory->setText(this, sText); + } +#else + (void)sText; +#endif +} + +void SAL_CALL StatusIndicator::setValue(sal_Int32 nValue) +{ + if (comphelper::LibreOfficeKit::isActive()) + { + if (m_nRange > 0) + { + int nPercent = (100 * nValue) / m_nRange; + if (nPercent >= m_nLastCallbackPercent) + { + comphelper::LibreOfficeKit::statusIndicatorSetValue(nPercent); + m_nLastCallbackPercent = nPercent; + } + } + return; + } +#if !defined(IOS) && !defined(ANDROID) + css::uno::Reference<css::task::XStatusIndicatorFactory> xFactory(m_xFactory); + if (xFactory.is()) + { + StatusIndicatorFactory* pFactory = static_cast<StatusIndicatorFactory*>(xFactory.get()); + pFactory->setValue(this, nValue); + } +#endif +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/statusindicatorfactory.cxx b/framework/source/helper/statusindicatorfactory.cxx new file mode 100644 index 000000000..e9e54df6b --- /dev/null +++ b/framework/source/helper/statusindicatorfactory.cxx @@ -0,0 +1,577 @@ +/* -*- 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 <algorithm> +#include <utility> +#include <helper/statusindicatorfactory.hxx> +#include <helper/statusindicator.hxx> +#include <helper/vclstatusindicator.hxx> +#include <properties.h> + +#include <com/sun/star/awt/XWindow2.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XLayoutManager2.hpp> + +#include <toolkit/helper/vclunohelper.hxx> + +#include <comphelper/sequenceashashmap.hxx> +#include <unotools/mediadescriptor.hxx> +#include <vcl/svapp.hxx> +#include <mutex> +#include <rtl/ref.hxx> + +#include <officecfg/Office/Common.hxx> + +namespace framework{ + +sal_Int32 StatusIndicatorFactory::m_nInReschedule = 0; ///< static counter for rescheduling + +constexpr OUStringLiteral PROGRESS_RESOURCE = u"private:resource/progressbar/progressbar"; + +StatusIndicatorFactory::StatusIndicatorFactory(css::uno::Reference< css::uno::XComponentContext > xContext) + : m_xContext (std::move(xContext )) + , m_bAllowReschedule (false) + , m_bAllowParentShow (false) + , m_bDisableReschedule(false) +{ +} + +StatusIndicatorFactory::~StatusIndicatorFactory() +{ + impl_stopWakeUpThread(); +} + +void SAL_CALL StatusIndicatorFactory::initialize(const css::uno::Sequence< css::uno::Any >& lArguments) +{ + if (lArguments.hasElements()) { + std::scoped_lock g(m_mutex); + + css::uno::Reference< css::frame::XFrame > xTmpFrame; + css::uno::Reference< css::awt::XWindow > xTmpWindow; + bool b1 = lArguments[0] >>= xTmpFrame; + bool b2 = lArguments[0] >>= xTmpWindow; + if (lArguments.getLength() == 3 && b1) { + // it's the first service constructor "createWithFrame" + m_xFrame = xTmpFrame; + lArguments[1] >>= m_bDisableReschedule; + lArguments[2] >>= m_bAllowParentShow; + } else if (lArguments.getLength() == 3 && b2) { + // it's the second service constructor "createWithWindow" + m_xPluggWindow = xTmpWindow; + lArguments[1] >>= m_bDisableReschedule; + lArguments[2] >>= m_bAllowParentShow; + } else { + // it's an old-style initialisation using properties + ::comphelper::SequenceAsHashMap lArgs(lArguments); + + m_xFrame = lArgs.getUnpackedValueOrDefault("Frame" , css::uno::Reference< css::frame::XFrame >()); + m_xPluggWindow = lArgs.getUnpackedValueOrDefault("Window" , css::uno::Reference< css::awt::XWindow >() ); + m_bAllowParentShow = lArgs.getUnpackedValueOrDefault("AllowParentShow" , false ); + m_bDisableReschedule = lArgs.getUnpackedValueOrDefault("DisableReschedule", false ); + } + } + +#ifdef EMSCRIPTEN + m_bDisableReschedule = true; +#endif + impl_createProgress(); +} + +css::uno::Reference< css::task::XStatusIndicator > SAL_CALL StatusIndicatorFactory::createStatusIndicator() +{ + return new StatusIndicator(this); +} + +void SAL_CALL StatusIndicatorFactory::update() +{ + std::scoped_lock g(m_mutex); + m_bAllowReschedule = true; +} + +void StatusIndicatorFactory::start(const css::uno::Reference< css::task::XStatusIndicator >& xChild, + const OUString& sText , + sal_Int32 nRange) +{ + css::uno::Reference< css::task::XStatusIndicator > xProgress; + // SAFE -> ---------------------------------- + { + std::scoped_lock aWriteLock(m_mutex); + + // create new info structure for this child or move it to the front of our stack + IndicatorStack::iterator pItem = ::std::find(m_aStack.begin(), m_aStack.end(), xChild); + if (pItem != m_aStack.end()) + m_aStack.erase(pItem); + IndicatorInfo aInfo(xChild, sText); + m_aStack.push_back (aInfo ); + + m_xActiveChild = xChild; + xProgress = m_xProgress; + } + // <- SAFE ---------------------------------- + + implts_makeParentVisibleIfAllowed(); + + if (xProgress.is()) + xProgress->start(sText, nRange); + + impl_startWakeUpThread(); + impl_reschedule(true); +} + +void StatusIndicatorFactory::reset(const css::uno::Reference< css::task::XStatusIndicator >& xChild) +{ + css::uno::Reference< css::task::XStatusIndicator > xActive; + css::uno::Reference< css::task::XStatusIndicator > xProgress; + // SAFE -> ---------------------------------- + { + std::scoped_lock aReadLock(m_mutex); + + // reset the internal info structure related to this child + IndicatorStack::iterator pItem = ::std::find(m_aStack.begin(), m_aStack.end(), xChild); + if (pItem != m_aStack.end()) + { + pItem->m_nValue = 0; + pItem->m_sText.clear(); + } + + xActive = m_xActiveChild; + xProgress = m_xProgress; + } + // <- SAFE ---------------------------------- + + // not the top most child => don't change UI + // But don't forget Reschedule! + if ( + (xChild == xActive) && + (xProgress.is() ) + ) + xProgress->reset(); + + impl_reschedule(true); +} + +void StatusIndicatorFactory::end(const css::uno::Reference< css::task::XStatusIndicator >& xChild) +{ + css::uno::Reference< css::task::XStatusIndicator > xActive; + css::uno::Reference< css::task::XStatusIndicator > xProgress; + OUString sText; + sal_Int32 nValue = 0; + // SAFE -> ---------------------------------- + { + std::scoped_lock aWriteLock(m_mutex); + + // remove this child from our stack + IndicatorStack::iterator pItem = ::std::find(m_aStack.begin(), m_aStack.end(), xChild); + if (pItem != m_aStack.end()) + m_aStack.erase(pItem); + + // activate next child ... or finish the progress if there is no further one. + m_xActiveChild.clear(); + IndicatorStack::reverse_iterator pNext = m_aStack.rbegin(); + if (pNext != m_aStack.rend()) + { + m_xActiveChild = pNext->m_xIndicator; + sText = pNext->m_sText; + nValue = pNext->m_nValue; + } + + xActive = m_xActiveChild; + xProgress = m_xProgress; + } + // <- SAFE ---------------------------------- + + if (xActive.is()) + { + // There is at least one further child indicator. + // Actualize our progress, so it shows these values from now. + if (xProgress.is()) + { + xProgress->setText (sText ); + xProgress->setValue(nValue); + } + } + else + { + // Our stack is empty. No further child exists. + // Se we must "end" our progress really + if (xProgress.is()) + xProgress->end(); + // Now hide the progress bar again. + impl_hideProgress(); + + impl_stopWakeUpThread(); + } + + impl_reschedule(true); +} + +void StatusIndicatorFactory::setText(const css::uno::Reference< css::task::XStatusIndicator >& xChild, + const OUString& sText ) +{ + css::uno::Reference< css::task::XStatusIndicator > xActive; + css::uno::Reference< css::task::XStatusIndicator > xProgress; + // SAFE -> ---------------------------------- + { + std::scoped_lock aWriteLock(m_mutex); + + IndicatorStack::iterator pItem = ::std::find(m_aStack.begin(), m_aStack.end(), xChild); + if (pItem != m_aStack.end()) + pItem->m_sText = sText; + + xActive = m_xActiveChild; + xProgress = m_xProgress; + } + // SAFE -> ---------------------------------- + + // paint only the top most indicator + // but don't forget to Reschedule! + if ( + (xChild == xActive) && + (xProgress.is() ) + ) + { + xProgress->setText(sText); + } + + impl_reschedule(true); +} + +void StatusIndicatorFactory::setValue( const css::uno::Reference< css::task::XStatusIndicator >& xChild , + sal_Int32 nValue ) +{ + sal_Int32 nOldValue = 0; + css::uno::Reference< css::task::XStatusIndicator > xActive; + css::uno::Reference< css::task::XStatusIndicator > xProgress; + // SAFE -> ---------------------------------- + { + std::scoped_lock aWriteLock(m_mutex); + + IndicatorStack::iterator pItem = ::std::find(m_aStack.begin(), m_aStack.end(), xChild); + if (pItem != m_aStack.end()) + { + nOldValue = pItem->m_nValue; + pItem->m_nValue = nValue; + } + + xActive = m_xActiveChild; + xProgress = m_xProgress; + } + // SAFE -> ---------------------------------- + + if ( + (xChild == xActive) && + (nOldValue != nValue ) && + (xProgress.is() ) + ) + { + xProgress->setValue(nValue); + } + + impl_reschedule(false); +} + +void StatusIndicatorFactory::implts_makeParentVisibleIfAllowed() +{ + css::uno::Reference< css::frame::XFrame > xFrame; + css::uno::Reference< css::awt::XWindow > xPluggWindow; + css::uno::Reference< css::uno::XComponentContext > xContext; + // SAFE -> ---------------------------------- + { + std::scoped_lock aReadLock(m_mutex); + + if (!m_bAllowParentShow) + return; + + xFrame = m_xFrame; + xPluggWindow = m_xPluggWindow; + xContext = m_xContext; + } + // <- SAFE ---------------------------------- + + css::uno::Reference< css::awt::XWindow > xParentWindow; + if (xFrame.is()) + xParentWindow = xFrame->getContainerWindow(); + else + xParentWindow = xPluggWindow; + + // don't disturb user in case he put the loading document into the background! + // Suppress any setVisible() or toFront() call in case the initial show was + // already made. + css::uno::Reference< css::awt::XWindow2 > xVisibleCheck(xParentWindow, css::uno::UNO_QUERY); + bool bIsVisible = false; + if (xVisibleCheck.is()) + bIsVisible = xVisibleCheck->isVisible(); + + if (bIsVisible) + { + impl_showProgress(); + return; + } + + // Check if the layout manager has been set to invisible state. It this case we are also + // not allowed to set the frame visible! + css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY); + if (xPropSet.is()) + { + css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager; + xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager; + if (xLayoutManager.is()) + { + if ( !xLayoutManager->isVisible() ) + return; + } + } + + // Ok the window should be made visible... because it is not currently visible. + // BUT..! + // We need a Hack for our applications: They get her progress from the frame directly + // on saving documents. Because there is no progress set on the MediaDescriptor. + // But that's wrong. In case the document was opened hidden, they should not use any progress .-( + // They only possible workaround: don't show the parent window here, if the document was opened hidden. + bool bHiddenDoc = false; + if (xFrame.is()) + { + css::uno::Reference< css::frame::XController > xController; + css::uno::Reference< css::frame::XModel > xModel; + xController = xFrame->getController(); + if (xController.is()) + xModel = xController->getModel(); + if (xModel.is()) + { + utl::MediaDescriptor lDocArgs(xModel->getArgs()); + bHiddenDoc = lDocArgs.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_HIDDEN, + false); + } + } + + if (bHiddenDoc) + return; + + // OK: The document was not opened in hidden mode ... + // and the window isn't already visible. + // Show it and bring it to front. + // But before we have to be sure, that our internal used helper progress + // is visible too. + impl_showProgress(); + + SolarMutexGuard aSolarGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xParentWindow); + if ( pWindow ) + { + bool bForceFrontAndFocus(officecfg::Office::Common::View::NewDocumentHandling::ForceFocusAndToFront::get()); + pWindow->Show(true, bForceFrontAndFocus ? ShowFlags::ForegroundTask : ShowFlags::NONE ); + } + +} + +void StatusIndicatorFactory::impl_createProgress() +{ + css::uno::Reference< css::frame::XFrame > xFrame; + css::uno::Reference< css::awt::XWindow > xWindow; + // SAFE -> ---------------------------------- + { + std::scoped_lock aReadLock(m_mutex); + + xFrame = m_xFrame; + xWindow = m_xPluggWindow; + } + // <- SAFE ---------------------------------- + + css::uno::Reference< css::task::XStatusIndicator > xProgress; + + if (xWindow.is()) + { + // use vcl based progress implementation in plugged mode + xProgress = new VCLStatusIndicator(xWindow); + } + else if (xFrame.is()) + { + // use frame layouted progress implementation + css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY); + if (xPropSet.is()) + { + css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager; + xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager; + if (xLayoutManager.is()) + { + xLayoutManager->lock(); + OUString sPROGRESS_RESOURCE(PROGRESS_RESOURCE); + xLayoutManager->createElement( sPROGRESS_RESOURCE ); + xLayoutManager->hideElement( sPROGRESS_RESOURCE ); + + css::uno::Reference< css::ui::XUIElement > xProgressBar = xLayoutManager->getElement(sPROGRESS_RESOURCE); + if (xProgressBar.is()) + xProgress.set(xProgressBar->getRealInterface(), css::uno::UNO_QUERY); + xLayoutManager->unlock(); + } + } + } + + std::scoped_lock g(m_mutex); + m_xProgress = xProgress; +} + +void StatusIndicatorFactory::impl_showProgress() +{ + css::uno::Reference< css::frame::XFrame > xFrame; + // SAFE -> ---------------------------------- + { + std::scoped_lock aReadLock(m_mutex); + + xFrame = m_xFrame; + } + // <- SAFE ---------------------------------- + + css::uno::Reference< css::task::XStatusIndicator > xProgress; + + if (!xFrame.is()) + return; + + // use frame layouted progress implementation + css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY); + if (xPropSet.is()) + { + css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager; + xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager; + if (xLayoutManager.is()) + { + // Be sure that we have always a progress. It can be that our frame + // was recycled and therefore the progress was destroyed! + // CreateElement does nothing if there is already a valid progress. + OUString sPROGRESS_RESOURCE(PROGRESS_RESOURCE); + xLayoutManager->createElement( sPROGRESS_RESOURCE ); + xLayoutManager->showElement( sPROGRESS_RESOURCE ); + + css::uno::Reference< css::ui::XUIElement > xProgressBar = xLayoutManager->getElement(sPROGRESS_RESOURCE); + if (xProgressBar.is()) + xProgress.set(xProgressBar->getRealInterface(), css::uno::UNO_QUERY); + } + } + + std::scoped_lock g(m_mutex); + m_xProgress = xProgress; +} + +void StatusIndicatorFactory::impl_hideProgress() +{ + css::uno::Reference< css::frame::XFrame > xFrame; + // SAFE -> ---------------------------------- + { + std::scoped_lock aReadLock(m_mutex); + + xFrame = m_xFrame; + } + // <- SAFE ---------------------------------- + + if (xFrame.is()) + { + // use frame layouted progress implementation + css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY); + if (xPropSet.is()) + { + css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager; + xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager; + if (xLayoutManager.is()) + xLayoutManager->hideElement( PROGRESS_RESOURCE ); + } + } +} + +void StatusIndicatorFactory::impl_reschedule(bool bForce) +{ + // SAFE -> + { + std::scoped_lock aReadLock(m_mutex); + if (m_bDisableReschedule) + return; + } + // <- SAFE + + bool bReschedule = bForce; + if (!bReschedule) + { + std::scoped_lock g(m_mutex); + bReschedule = m_bAllowReschedule; + m_bAllowReschedule = false; + } + + if (!bReschedule) + return; + + static std::mutex rescheduleLock; + // SAFE -> + std::unique_lock aRescheduleGuard(rescheduleLock); + + if (m_nInReschedule != 0) + return; + + // coverity[missing_lock: FALSE] - coverity fails to see the aRescheduleGuard ctor as taking a lock + ++m_nInReschedule; + aRescheduleGuard.unlock(); + // <- SAFE + + { + SolarMutexGuard g; + Application::Reschedule(true); + } + + // SAFE -> + aRescheduleGuard.lock(); + --m_nInReschedule; +} + +void StatusIndicatorFactory::impl_startWakeUpThread() +{ + std::scoped_lock g(m_mutex); + + if (m_bDisableReschedule) + return; + + if (!m_pWakeUp.is()) + { + m_pWakeUp = new WakeUpThread(this); + m_pWakeUp->launch(); + } +} + +void StatusIndicatorFactory::impl_stopWakeUpThread() +{ + rtl::Reference<WakeUpThread> wakeUp; + { + std::scoped_lock g(m_mutex); + std::swap(wakeUp, m_pWakeUp); + } + if (wakeUp.is()) + { + wakeUp->stop(); + } +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_StatusIndicatorFactory_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new framework::StatusIndicatorFactory(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/tagwindowasmodified.cxx b/framework/source/helper/tagwindowasmodified.cxx new file mode 100644 index 000000000..b113c4b96 --- /dev/null +++ b/framework/source/helper/tagwindowasmodified.cxx @@ -0,0 +1,146 @@ +/* -*- 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 <helper/tagwindowasmodified.hxx> + +#include <com/sun/star/awt/XWindow.hpp> + +#include <com/sun/star/frame/FrameAction.hpp> + +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> +#include <tools/wintypes.hxx> + +namespace framework{ + +TagWindowAsModified::TagWindowAsModified() +{ +} + +TagWindowAsModified::~TagWindowAsModified() +{ +} + +void SAL_CALL TagWindowAsModified::initialize(const css::uno::Sequence< css::uno::Any >& lArguments) +{ + css::uno::Reference< css::frame::XFrame > xFrame; + + if (lArguments.hasElements()) + lArguments[0] >>= xFrame; + + if (!xFrame) + return; + + m_xFrame = xFrame; + xFrame->addFrameActionListener(this); + impl_update (xFrame); +} + +void SAL_CALL TagWindowAsModified::modified(const css::lang::EventObject& aEvent) +{ + if (!m_xModel || !m_xWindow || aEvent.Source != m_xModel) + return; + + bool bModified = m_xModel->isModified (); + + // SYNCHRONIZED -> + SolarMutexGuard aSolarGuard; + + if (m_xWindow->isDisposed()) + return; + + if (bModified) + m_xWindow->SetExtendedStyle(WindowExtendedStyle::DocModified); + else + m_xWindow->SetExtendedStyle(WindowExtendedStyle::NONE); + // <- SYNCHRONIZED +} + +void SAL_CALL TagWindowAsModified::frameAction(const css::frame::FrameActionEvent& aEvent) +{ + if ( + (aEvent.Action != css::frame::FrameAction_COMPONENT_REATTACHED) && + (aEvent.Action != css::frame::FrameAction_COMPONENT_ATTACHED ) + ) + return; + + if ( aEvent.Source != m_xFrame ) + return; + + impl_update (m_xFrame); +} + +void SAL_CALL TagWindowAsModified::disposing(const css::lang::EventObject& aEvent) +{ + SolarMutexGuard g; + + if (m_xFrame && aEvent.Source == m_xFrame) + { + m_xFrame->removeFrameActionListener(this); + m_xFrame.clear(); + return; + } + + if (m_xModel && aEvent.Source == m_xModel) + { + m_xModel->removeModifyListener(this); + m_xModel.clear(); + return; + } +} + +void TagWindowAsModified::impl_update (const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + if (!xFrame) + return; + + css::uno::Reference< css::awt::XWindow > xWindow = xFrame->getContainerWindow (); + css::uno::Reference< css::frame::XController > xController = xFrame->getController (); + css::uno::Reference< css::util::XModifiable > xModel; + if (xController.is ()) + xModel = css::uno::Reference< css::util::XModifiable >(xController->getModel(), css::uno::UNO_QUERY); + + if (!xWindow || !xModel) + return; + + { + SolarMutexGuard g; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + bool bSystemWindow = pWindow->IsSystemWindow(); + bool bWorkWindow = (pWindow->GetType() == WindowType::WORKWINDOW); + if (!bSystemWindow && !bWorkWindow) + return; + + if (m_xModel) + m_xModel->removeModifyListener (this); + + // Note: frame was set as member outside ! we have to refresh connections + // regarding window and model only here. + m_xWindow = pWindow; + m_xModel = xModel; + } + + m_xModel->addModifyListener (this); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/titlebarupdate.cxx b/framework/source/helper/titlebarupdate.cxx new file mode 100644 index 000000000..a9f27bd4a --- /dev/null +++ b/framework/source/helper/titlebarupdate.cxx @@ -0,0 +1,316 @@ +/* -*- 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 <helper/titlebarupdate.hxx> + +#include <properties.h> + +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XTitle.hpp> +#include <com/sun/star/frame/XTitleChangeBroadcaster.hpp> + +#include <comphelper/sequenceashashmap.hxx> +#include <unotools/configmgr.hxx> +#include <utility> +#include <vcl/window.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/wrkwin.hxx> +#include <tools/diagnose_ex.h> + +namespace framework{ + +const ::sal_Int32 INVALID_ICON_ID = -1; +const ::sal_Int32 DEFAULT_ICON_ID = 0; + +TitleBarUpdate::TitleBarUpdate(css::uno::Reference< css::uno::XComponentContext > xContext) + : m_xContext (std::move(xContext )) +{ +} + +TitleBarUpdate::~TitleBarUpdate() +{ +} + +void SAL_CALL TitleBarUpdate::initialize(const css::uno::Sequence< css::uno::Any >& lArguments) +{ + // check arguments + css::uno::Reference< css::frame::XFrame > xFrame; + if (!lArguments.hasElements()) + throw css::lang::IllegalArgumentException( + "Empty argument list!", + static_cast< ::cppu::OWeakObject* >(this), + 1); + + lArguments[0] >>= xFrame; + if (!xFrame.is()) + throw css::lang::IllegalArgumentException( + "No valid frame specified!", + static_cast< ::cppu::OWeakObject* >(this), + 1); + + { + SolarMutexGuard g; + // hold the frame as weak reference(!) so it can die everytimes :-) + m_xFrame = xFrame; + } + + // start listening + xFrame->addFrameActionListener(this); + + css::uno::Reference< css::frame::XTitleChangeBroadcaster > xBroadcaster(xFrame, css::uno::UNO_QUERY); + if (xBroadcaster.is ()) + xBroadcaster->addTitleChangeListener (this); +} + +void SAL_CALL TitleBarUpdate::frameAction(const css::frame::FrameActionEvent& aEvent) +{ + // we are interested on events only, which must trigger a title bar update + // because component was changed. + if ( + (aEvent.Action == css::frame::FrameAction_COMPONENT_ATTACHED ) || + (aEvent.Action == css::frame::FrameAction_COMPONENT_REATTACHED) || + (aEvent.Action == css::frame::FrameAction_COMPONENT_DETACHING ) + ) + { + impl_forceUpdate (); + } +} + +void SAL_CALL TitleBarUpdate::titleChanged(const css::frame::TitleChangedEvent& /* aEvent */) +{ + impl_forceUpdate (); +} + +void SAL_CALL TitleBarUpdate::disposing(const css::lang::EventObject&) +{ + css::uno::Reference< css::frame::XFrame > xFrame(m_xFrame.get(), css::uno::UNO_QUERY); + if (xFrame.is()) + xFrame->removeFrameActionListener(this); + + // nothing todo here - because we hold the frame as weak reference only +} + +//http://live.gnome.org/GnomeShell/ApplicationBased +//http://msdn.microsoft.com/en-us/library/dd378459(v=VS.85).aspx +void TitleBarUpdate::impl_updateApplicationID(const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + css::uno::Reference< css::awt::XWindow > xWindow = xFrame->getContainerWindow (); + if ( ! xWindow.is() ) + return; + +#if !defined(MACOSX) + OUString sApplicationID; + try + { + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager = + css::frame::ModuleManager::create( m_xContext ); + + OUString sDesktopName; + OUString aModuleId = xModuleManager->identify(xFrame); + if ( aModuleId.startsWith("com.sun.star.text.") || aModuleId.startsWith("com.sun.star.xforms.") ) + sDesktopName = "Writer"; + else if ( aModuleId.startsWith("com.sun.star.sheet.") ) + sDesktopName = "Calc"; + else if ( aModuleId.startsWith("com.sun.star.presentation.") ) + sDesktopName = "Impress"; + else if ( aModuleId.startsWith("com.sun.star.drawing." ) ) + sDesktopName = "Draw"; + else if ( aModuleId.startsWith("com.sun.star.formula." ) ) + sDesktopName = "Math"; + else if ( aModuleId.startsWith("com.sun.star.sdb.") ) + sDesktopName = "Base"; + else + sDesktopName = "Startcenter"; +#if defined(_WIN32) + // We use a hardcoded product name matching the registry keys so applications can be associated with file types + sApplicationID = "TheDocumentFoundation.LibreOffice." + sDesktopName; +#else + sApplicationID = utl::ConfigManager::getProductName().toAsciiLowerCase() + "-" + sDesktopName.toAsciiLowerCase(); +#endif + } + catch(const css::uno::Exception&) + { + } +#else + OUString const sApplicationID; +#endif + + // VCL SYNCHRONIZED -> + SolarMutexGuard aSolarGuard; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->GetType() == WindowType::WORKWINDOW ) + { + WorkWindow* pWorkWindow = static_cast<WorkWindow*>(pWindow.get()); + pWorkWindow->SetApplicationID( sApplicationID ); + } + // <- VCL SYNCHRONIZED +} + +bool TitleBarUpdate::implst_getModuleInfo(const css::uno::Reference< css::frame::XFrame >& xFrame, + TModuleInfo& rInfo ) +{ + if ( ! xFrame.is ()) + return false; + + try + { + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager = + css::frame::ModuleManager::create( m_xContext ); + + rInfo.sID = xModuleManager->identify(xFrame); + ::comphelper::SequenceAsHashMap lProps = xModuleManager->getByName (rInfo.sID); + + rInfo.nIcon = lProps.getUnpackedValueOrDefault (OFFICEFACTORY_PROPNAME_ASCII_ICON , INVALID_ICON_ID ); + + // Note: If we could retrieve a module id ... everything is OK. + // UIName and Icon ID are optional values ! + bool bSuccess = !rInfo.sID.isEmpty(); + return bSuccess; + } + catch(const css::uno::Exception&) + {} + + return false; +} + +void TitleBarUpdate::impl_forceUpdate() +{ + css::uno::Reference< css::frame::XFrame > xFrame; + { + SolarMutexGuard g; + xFrame.set(m_xFrame.get(), css::uno::UNO_QUERY); + } + + // frame already gone ? We hold it weak only ... + if ( ! xFrame.is()) + return; + + // no window -> no chance to set/update title and icon + css::uno::Reference< css::awt::XWindow > xWindow = xFrame->getContainerWindow(); + if ( ! xWindow.is()) + return; + + impl_updateIcon (xFrame); + impl_updateTitle (xFrame); +#if !defined(MACOSX) + impl_updateApplicationID (xFrame); +#endif +} + +void TitleBarUpdate::impl_updateIcon(const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + css::uno::Reference< css::frame::XController > xController = xFrame->getController (); + css::uno::Reference< css::awt::XWindow > xWindow = xFrame->getContainerWindow (); + + if ( + ( ! xController.is() ) || + ( ! xWindow.is() ) + ) + return; + + // a) set default value to an invalid one. So we can start further searches for right icon id, if + // first steps failed! + sal_Int32 nIcon = INVALID_ICON_ID; + + // b) try to find information on controller property set directly + // Don't forget to catch possible exceptions - because these property is an optional one! + css::uno::Reference< css::beans::XPropertySet > xSet( xController, css::uno::UNO_QUERY ); + if ( xSet.is() ) + { + try + { + css::uno::Reference< css::beans::XPropertySetInfo > const xPSI( xSet->getPropertySetInfo(), css::uno::UNO_SET_THROW ); + if ( xPSI->hasPropertyByName( "IconId" ) ) + xSet->getPropertyValue( "IconId" ) >>= nIcon; + } + catch(const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + } + + // c) if b) failed ... identify the used module and retrieve set icon from module config. + // Tirck :-) Module was already specified outside and aInfo contains all needed information. + if ( nIcon == INVALID_ICON_ID ) + { + TModuleInfo aInfo; + if (implst_getModuleInfo(xFrame, aInfo)) + nIcon = aInfo.nIcon; + } + + // d) if all steps failed - use fallback :-) + // ... means using the global staroffice icon + if( nIcon == INVALID_ICON_ID ) + nIcon = DEFAULT_ICON_ID; + + // e) set icon on container window now + // Don't forget SolarMutex! We use vcl directly :-( + // Check window pointer for right WorkWindow class too!!! + + // VCL SYNCHRONIZED -> + SolarMutexGuard aSolarGuard; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && ( pWindow->GetType() == WindowType::WORKWINDOW ) ) + { + WorkWindow* pWorkWindow = static_cast<WorkWindow*>(pWindow.get()); + pWorkWindow->SetIcon( static_cast<sal_uInt16>(nIcon) ); + + css::uno::Reference< css::frame::XModel > xModel = xController->getModel(); + OUString aURL; + if( xModel.is() ) + aURL = xModel->getURL(); + pWorkWindow->SetRepresentedURL( aURL ); + } + // <- VCL SYNCHRONIZED +} + +void TitleBarUpdate::impl_updateTitle(const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + // no window ... no chance to set any title -> return + css::uno::Reference< css::awt::XWindow > xWindow = xFrame->getContainerWindow (); + if ( ! xWindow.is() ) + return; + + css::uno::Reference< css::frame::XTitle > xTitle(xFrame, css::uno::UNO_QUERY); + if ( ! xTitle.is() ) + return; + + const OUString sTitle = xTitle->getTitle (); + + // VCL SYNCHRONIZED -> + SolarMutexGuard aSolarGuard; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && ( pWindow->GetType() == WindowType::WORKWINDOW ) ) + { + WorkWindow* pWorkWindow = static_cast<WorkWindow*>(pWindow.get()); + pWorkWindow->SetText( sTitle ); + } + // <- VCL SYNCHRONIZED +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/uiconfigelementwrapperbase.cxx b/framework/source/helper/uiconfigelementwrapperbase.cxx new file mode 100644 index 000000000..95d5b4c65 --- /dev/null +++ b/framework/source/helper/uiconfigelementwrapperbase.cxx @@ -0,0 +1,481 @@ +/* -*- 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 <helper/uiconfigelementwrapperbase.hxx> +#include <properties.h> +#include <uielement/constitemcontainer.hxx> +#include <uielement/rootitemcontainer.hxx> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/ui/XUIConfiguration.hpp> + +#include <vcl/svapp.hxx> +#include <comphelper/sequence.hxx> + +const int UIELEMENT_PROPHANDLE_CONFIGSOURCE = 1; +const int UIELEMENT_PROPHANDLE_FRAME = 2; +const int UIELEMENT_PROPHANDLE_PERSISTENT = 3; +const int UIELEMENT_PROPHANDLE_RESOURCEURL = 4; +const int UIELEMENT_PROPHANDLE_TYPE = 5; +const int UIELEMENT_PROPHANDLE_XMENUBAR = 6; +const int UIELEMENT_PROPHANDLE_CONFIGLISTENER = 7; +const int UIELEMENT_PROPHANDLE_NOCLOSE = 8; +constexpr OUStringLiteral UIELEMENT_PROPNAME_CONFIGLISTENER = u"ConfigListener"; +constexpr OUStringLiteral UIELEMENT_PROPNAME_CONFIGSOURCE = u"ConfigurationSource"; +constexpr OUStringLiteral UIELEMENT_PROPNAME_FRAME = u"Frame"; +constexpr OUStringLiteral UIELEMENT_PROPNAME_PERSISTENT = u"Persistent"; +constexpr OUStringLiteral UIELEMENT_PROPNAME_RESOURCEURL = u"ResourceURL"; +constexpr OUStringLiteral UIELEMENT_PROPNAME_TYPE = u"Type"; +constexpr OUStringLiteral UIELEMENT_PROPNAME_XMENUBAR = u"XMenuBar"; +constexpr OUStringLiteral UIELEMENT_PROPNAME_NOCLOSE = u"NoClose"; +using namespace com::sun::star::beans; +using namespace com::sun::star::uno; +using namespace com::sun::star::frame; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace ::com::sun::star::ui; + +namespace framework +{ + +UIConfigElementWrapperBase::UIConfigElementWrapperBase( sal_Int16 nType ) + : ::cppu::OBroadcastHelperVar< ::cppu::OMultiTypeInterfaceContainerHelper, ::cppu::OMultiTypeInterfaceContainerHelper::keyType >( m_aMutex ) + , ::cppu::OPropertySetHelper ( *static_cast< ::cppu::OBroadcastHelper* >(this) ) + , m_nType ( nType ) + , m_bPersistent ( true ) + , m_bInitialized ( false ) + , m_bConfigListener ( false ) + , m_bConfigListening ( false ) + , m_bDisposed ( false ) + , m_bNoClose ( false ) + , m_aListenerContainer ( m_aMutex ) +{ +} + +UIConfigElementWrapperBase::~UIConfigElementWrapperBase() +{ +} + +Any SAL_CALL UIConfigElementWrapperBase::queryInterface( const Type& _rType ) +{ + Any aRet = UIConfigElementWrapperBase_BASE::queryInterface( _rType ); + if ( !aRet.hasValue() ) + aRet = OPropertySetHelper::queryInterface( _rType ); + return aRet; +} + +Sequence< Type > SAL_CALL UIConfigElementWrapperBase::getTypes( ) +{ + return comphelper::concatSequences( + UIConfigElementWrapperBase_BASE::getTypes(), + ::cppu::OPropertySetHelper::getTypes() + ); +} + +// XComponent +void SAL_CALL UIConfigElementWrapperBase::addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) +{ + m_aListenerContainer.addInterface( cppu::UnoType<css::lang::XEventListener>::get(), xListener ); +} + +void SAL_CALL UIConfigElementWrapperBase::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) +{ + m_aListenerContainer.removeInterface( cppu::UnoType<css::lang::XEventListener>::get(), aListener ); +} + +// XEventListener +void SAL_CALL UIConfigElementWrapperBase::disposing( const EventObject& ) +{ + SolarMutexGuard g; + m_xConfigSource.clear(); +} + +void SAL_CALL UIConfigElementWrapperBase::initialize( const Sequence< Any >& aArguments ) +{ + SolarMutexGuard g; + + if ( m_bInitialized ) + return; + + for ( const Any& rArg : aArguments ) + { + PropertyValue aPropValue; + if ( rArg >>= aPropValue ) + { + if ( aPropValue.Name == UIELEMENT_PROPNAME_CONFIGSOURCE ) + setFastPropertyValue_NoBroadcast( UIELEMENT_PROPHANDLE_CONFIGSOURCE, aPropValue.Value ); + else if ( aPropValue.Name == UIELEMENT_PROPNAME_FRAME ) + setFastPropertyValue_NoBroadcast( UIELEMENT_PROPHANDLE_FRAME, aPropValue.Value ); + else if ( aPropValue.Name == UIELEMENT_PROPNAME_PERSISTENT ) + setFastPropertyValue_NoBroadcast( UIELEMENT_PROPHANDLE_PERSISTENT, aPropValue.Value ); + else if ( aPropValue.Name == UIELEMENT_PROPNAME_RESOURCEURL ) + setFastPropertyValue_NoBroadcast( UIELEMENT_PROPHANDLE_RESOURCEURL, aPropValue.Value ); + else if ( aPropValue.Name == UIELEMENT_PROPNAME_TYPE ) + setFastPropertyValue_NoBroadcast( UIELEMENT_PROPHANDLE_TYPE, aPropValue.Value ); + else if ( aPropValue.Name == UIELEMENT_PROPNAME_CONFIGLISTENER ) + setFastPropertyValue_NoBroadcast( UIELEMENT_PROPHANDLE_CONFIGLISTENER, aPropValue.Value ); + else if ( aPropValue.Name == UIELEMENT_PROPNAME_NOCLOSE ) + setFastPropertyValue_NoBroadcast( UIELEMENT_PROPHANDLE_NOCLOSE, aPropValue.Value ); + } + } + + m_bInitialized = true; +} + +// XUpdatable +void SAL_CALL UIConfigElementWrapperBase::update() +{ + // can be implemented by derived class +} + +void SAL_CALL UIConfigElementWrapperBase::elementInserted( const css::ui::ConfigurationEvent& ) +{ + // can be implemented by derived class +} + +void SAL_CALL UIConfigElementWrapperBase::elementRemoved( const css::ui::ConfigurationEvent& ) +{ + // can be implemented by derived class +} + +void SAL_CALL UIConfigElementWrapperBase::elementReplaced( const css::ui::ConfigurationEvent& ) +{ + // can be implemented by derived class +} + +// XPropertySet helper +sal_Bool SAL_CALL UIConfigElementWrapperBase::convertFastPropertyValue( Any& aConvertedValue , + Any& aOldValue , + sal_Int32 nHandle , + const Any& aValue ) +{ + // Initialize state with sal_False !!! + // (Handle can be invalid) + bool bReturn = false; + + switch( nHandle ) + { + case UIELEMENT_PROPHANDLE_CONFIGLISTENER: + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(m_bConfigListener), + aValue, + aOldValue, + aConvertedValue); + break; + + case UIELEMENT_PROPHANDLE_CONFIGSOURCE: + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(m_xConfigSource), + aValue, + aOldValue, + aConvertedValue); + break; + + case UIELEMENT_PROPHANDLE_FRAME: + { + Reference< XFrame > xFrame( m_xWeakFrame ); + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(xFrame), + aValue, + aOldValue, + aConvertedValue); + } + break; + + case UIELEMENT_PROPHANDLE_PERSISTENT: + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(m_bPersistent), + aValue, + aOldValue, + aConvertedValue); + break; + + case UIELEMENT_PROPHANDLE_RESOURCEURL: + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(m_aResourceURL), + aValue, + aOldValue, + aConvertedValue); + break; + + case UIELEMENT_PROPHANDLE_TYPE : + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(m_nType), + aValue, + aOldValue, + aConvertedValue); + break; + + case UIELEMENT_PROPHANDLE_XMENUBAR : + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(m_xMenuBar), + aValue, + aOldValue, + aConvertedValue); + break; + + case UIELEMENT_PROPHANDLE_NOCLOSE: + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(m_bNoClose), + aValue, + aOldValue, + aConvertedValue); + break; + } + + // Return state of operation. + return bReturn; +} + +void SAL_CALL UIConfigElementWrapperBase::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle , + const css::uno::Any& aValue ) +{ + switch( nHandle ) + { + case UIELEMENT_PROPHANDLE_CONFIGLISTENER: + { + bool bBool( m_bConfigListener ); + aValue >>= bBool; + if ( m_bConfigListener != bBool ) + { + if ( m_bConfigListening ) + { + if ( m_xConfigSource.is() && !bBool ) + { + try + { + Reference< XUIConfiguration > xUIConfig( m_xConfigSource, UNO_QUERY ); + if ( xUIConfig.is() ) + { + xUIConfig->removeConfigurationListener( Reference< XUIConfigurationListener >(this) ); + m_bConfigListening = false; + } + } + catch ( const Exception& ) + { + } + } + } + else + { + if ( m_xConfigSource.is() && bBool ) + { + try + { + Reference< XUIConfiguration > xUIConfig( m_xConfigSource, UNO_QUERY ); + if ( xUIConfig.is() ) + { + xUIConfig->addConfigurationListener( Reference< XUIConfigurationListener >(this) ); + m_bConfigListening = true; + } + } + catch ( const Exception& ) + { + } + } + } + + m_bConfigListener = bBool; + } + } + break; + case UIELEMENT_PROPHANDLE_CONFIGSOURCE: + aValue >>= m_xConfigSource; + break; + case UIELEMENT_PROPHANDLE_FRAME: + { + Reference< XFrame > xFrame; + + aValue >>= xFrame; + m_xWeakFrame = xFrame; + break; + } + case UIELEMENT_PROPHANDLE_PERSISTENT: + { + bool bBool( m_bPersistent ); + aValue >>= bBool; + m_bPersistent = bBool; + break; + } + case UIELEMENT_PROPHANDLE_RESOURCEURL: + aValue >>= m_aResourceURL; + break; + case UIELEMENT_PROPHANDLE_TYPE: + aValue >>= m_nType; + break; + case UIELEMENT_PROPHANDLE_XMENUBAR: + aValue >>= m_xMenuBar; + break; + case UIELEMENT_PROPHANDLE_NOCLOSE: + { + bool bBool( m_bNoClose ); + aValue >>= bBool; + m_bNoClose = bBool; + break; + } + } +} + +void SAL_CALL UIConfigElementWrapperBase::getFastPropertyValue( css::uno::Any& aValue , + sal_Int32 nHandle ) const +{ + switch( nHandle ) + { + case UIELEMENT_PROPHANDLE_CONFIGLISTENER: + aValue <<= m_bConfigListener; + break; + case UIELEMENT_PROPHANDLE_CONFIGSOURCE: + aValue <<= m_xConfigSource; + break; + case UIELEMENT_PROPHANDLE_FRAME: + { + Reference< XFrame > xFrame( m_xWeakFrame ); + aValue <<= xFrame; + break; + } + case UIELEMENT_PROPHANDLE_PERSISTENT: + aValue <<= m_bPersistent; + break; + case UIELEMENT_PROPHANDLE_RESOURCEURL: + aValue <<= m_aResourceURL; + break; + case UIELEMENT_PROPHANDLE_TYPE: + aValue <<= m_nType; + break; + case UIELEMENT_PROPHANDLE_XMENUBAR: + aValue <<= m_xMenuBar; + break; + case UIELEMENT_PROPHANDLE_NOCLOSE: + aValue <<= m_bNoClose; + break; + } +} + +::cppu::IPropertyArrayHelper& SAL_CALL UIConfigElementWrapperBase::getInfoHelper() +{ + // Define static member to give structure of properties to baseclass "OPropertySetHelper". + // "impl_getStaticPropertyDescriptor" is a non exported and static function, who will define a static propertytable. + // "true" say: Table is sorted by name. + static ::cppu::OPropertyArrayHelper ourInfoHelper( impl_getStaticPropertyDescriptor(), true ); + + return ourInfoHelper; +} + +css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL UIConfigElementWrapperBase::getPropertySetInfo() +{ + // Create structure of propertysetinfo for baseclass "OPropertySetHelper". + // (Use method "getInfoHelper()".) + static css::uno::Reference< css::beans::XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) ); + + return xInfo; +} + +css::uno::Sequence< css::beans::Property > UIConfigElementWrapperBase::impl_getStaticPropertyDescriptor() +{ + // Create property array to initialize sequence! + // Table of all predefined properties of this class. It's used from OPropertySetHelper-class! + // Don't forget to change the defines (see begin of this file), if you add, change or delete a property in this list!!! + // It's necessary for methods of OPropertySetHelper. + // ATTENTION: + // YOU MUST SORT FOLLOW TABLE BY NAME ALPHABETICAL !!! + + return + { + css::beans::Property( UIELEMENT_PROPNAME_CONFIGLISTENER, UIELEMENT_PROPHANDLE_CONFIGLISTENER , cppu::UnoType<sal_Bool>::get(), css::beans::PropertyAttribute::TRANSIENT ), + css::beans::Property( UIELEMENT_PROPNAME_CONFIGSOURCE, UIELEMENT_PROPHANDLE_CONFIGSOURCE , cppu::UnoType<css::ui::XUIConfigurationManager>::get(), css::beans::PropertyAttribute::TRANSIENT ), + css::beans::Property( UIELEMENT_PROPNAME_FRAME, UIELEMENT_PROPHANDLE_FRAME , cppu::UnoType<css::frame::XFrame>::get(), css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ), + css::beans::Property( UIELEMENT_PROPNAME_NOCLOSE, UIELEMENT_PROPHANDLE_NOCLOSE , cppu::UnoType<sal_Bool>::get(), css::beans::PropertyAttribute::TRANSIENT ), + css::beans::Property( UIELEMENT_PROPNAME_PERSISTENT, UIELEMENT_PROPHANDLE_PERSISTENT , cppu::UnoType<sal_Bool>::get(), css::beans::PropertyAttribute::TRANSIENT ), + css::beans::Property( UIELEMENT_PROPNAME_RESOURCEURL, UIELEMENT_PROPHANDLE_RESOURCEURL , cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ), + css::beans::Property( UIELEMENT_PROPNAME_TYPE, UIELEMENT_PROPHANDLE_TYPE , cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ), + css::beans::Property( UIELEMENT_PROPNAME_XMENUBAR, UIELEMENT_PROPHANDLE_XMENUBAR , cppu::UnoType<css::awt::XMenuBar>::get(), css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ) + }; +} + +void SAL_CALL UIConfigElementWrapperBase::setSettings( const Reference< XIndexAccess >& xSettings ) +{ + SolarMutexClearableGuard aLock; + + if ( !xSettings.is() ) + return; + + // Create a copy of the data if the container is not const + Reference< XIndexReplace > xReplace( xSettings, UNO_QUERY ); + if ( xReplace.is() ) + m_xConfigData = new ConstItemContainer( xSettings ); + else + m_xConfigData = xSettings; + + if ( m_xConfigSource.is() && m_bPersistent ) + { + OUString aResourceURL( m_aResourceURL ); + Reference< XUIConfigurationManager > xUICfgMgr( m_xConfigSource ); + + aLock.clear(); + + try + { + xUICfgMgr->replaceSettings( aResourceURL, m_xConfigData ); + } + catch( const NoSuchElementException& ) + { + } + } + else if ( !m_bPersistent ) + { + // Transient menubar => Fill menubar with new data + impl_fillNewData(); + } +} +void UIConfigElementWrapperBase::impl_fillNewData() +{ +} +Reference< XIndexAccess > SAL_CALL UIConfigElementWrapperBase::getSettings( sal_Bool bWriteable ) +{ + SolarMutexGuard g; + + if ( bWriteable ) + return Reference< XIndexAccess >( new RootItemContainer( m_xConfigData ) ); + + return m_xConfigData; +} + +Reference< XFrame > SAL_CALL UIConfigElementWrapperBase::getFrame() +{ + SolarMutexGuard g; + Reference< XFrame > xFrame( m_xWeakFrame ); + return xFrame; +} + +OUString SAL_CALL UIConfigElementWrapperBase::getResourceURL() +{ + SolarMutexGuard g; + return m_aResourceURL; +} + +::sal_Int16 SAL_CALL UIConfigElementWrapperBase::getType() +{ + SolarMutexGuard g; + return m_nType; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/uielementwrapperbase.cxx b/framework/source/helper/uielementwrapperbase.cxx new file mode 100644 index 000000000..829e4f952 --- /dev/null +++ b/framework/source/helper/uielementwrapperbase.cxx @@ -0,0 +1,203 @@ +/* -*- 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 <helper/uielementwrapperbase.hxx> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <vcl/svapp.hxx> +#include <comphelper/sequence.hxx> + +const int UIELEMENT_PROPHANDLE_RESOURCEURL = 1; +const int UIELEMENT_PROPHANDLE_TYPE = 2; +const int UIELEMENT_PROPHANDLE_FRAME = 3; +constexpr OUStringLiteral UIELEMENT_PROPNAME_RESOURCEURL = u"ResourceURL"; +constexpr OUStringLiteral UIELEMENT_PROPNAME_TYPE = u"Type"; +constexpr OUStringLiteral UIELEMENT_PROPNAME_FRAME = u"Frame"; + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::frame; + +namespace framework +{ + +UIElementWrapperBase::UIElementWrapperBase( sal_Int16 nType ) + : ::cppu::OBroadcastHelperVar< ::cppu::OMultiTypeInterfaceContainerHelper, ::cppu::OMultiTypeInterfaceContainerHelper::keyType >( m_aMutex ) + , ::cppu::OPropertySetHelper ( *static_cast< ::cppu::OBroadcastHelper* >(this) ) + , m_aListenerContainer ( m_aMutex ) + , m_nType ( nType ) + , m_bInitialized ( false ) + , m_bDisposed ( false ) +{ +} + +UIElementWrapperBase::~UIElementWrapperBase() +{ +} + +Any SAL_CALL UIElementWrapperBase::queryInterface( const Type& _rType ) +{ + Any aRet = UIElementWrapperBase_BASE::queryInterface( _rType ); + if ( !aRet.hasValue() ) + aRet = OPropertySetHelper::queryInterface( _rType ); + return aRet; +} + +Sequence< Type > SAL_CALL UIElementWrapperBase::getTypes( ) +{ + return comphelper::concatSequences( + UIElementWrapperBase_BASE::getTypes(), + ::cppu::OPropertySetHelper::getTypes() + ); +} + +void SAL_CALL UIElementWrapperBase::addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) +{ + m_aListenerContainer.addInterface( cppu::UnoType<css::lang::XEventListener>::get(), xListener ); +} + +void SAL_CALL UIElementWrapperBase::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) +{ + m_aListenerContainer.removeInterface( cppu::UnoType<css::lang::XEventListener>::get(), xListener ); +} + +void SAL_CALL UIElementWrapperBase::initialize( const Sequence< Any >& aArguments ) +{ + SolarMutexGuard g; + + if ( m_bInitialized ) + return; + + for ( const Any& rArg : aArguments ) + { + PropertyValue aPropValue; + if ( rArg >>= aPropValue ) + { + if ( aPropValue.Name == "ResourceURL" ) + aPropValue.Value >>= m_aResourceURL; + else if ( aPropValue.Name == "Frame" ) + { + Reference< XFrame > xFrame; + aPropValue.Value >>= xFrame; + m_xWeakFrame = xFrame; + } + } + } + + m_bInitialized = true; +} + +// XUIElement +css::uno::Reference< css::frame::XFrame > SAL_CALL UIElementWrapperBase::getFrame() +{ + css::uno::Reference< css::frame::XFrame > xFrame( m_xWeakFrame ); + return xFrame; +} + +OUString SAL_CALL UIElementWrapperBase::getResourceURL() +{ + return m_aResourceURL; +} + +::sal_Int16 SAL_CALL UIElementWrapperBase::getType() +{ + return m_nType; +} + +// XUpdatable +void SAL_CALL UIElementWrapperBase::update() +{ + // can be implemented by derived class +} + +// XPropertySet helper +sal_Bool SAL_CALL UIElementWrapperBase::convertFastPropertyValue( Any& /*aConvertedValue*/ , + Any& /*aOldValue*/ , + sal_Int32 /*nHandle*/ , + const Any& /*aValue*/ ) +{ + // Initialize state with sal_False !!! + // (Handle can be invalid) + return false; +} + +void SAL_CALL UIElementWrapperBase::setFastPropertyValue_NoBroadcast( sal_Int32 /*nHandle*/ , + const css::uno::Any& /*aValue*/ ) +{ +} + +void SAL_CALL UIElementWrapperBase::getFastPropertyValue( css::uno::Any& aValue , + sal_Int32 nHandle ) const +{ + switch( nHandle ) + { + case UIELEMENT_PROPHANDLE_RESOURCEURL: + aValue <<= m_aResourceURL; + break; + case UIELEMENT_PROPHANDLE_TYPE: + aValue <<= m_nType; + break; + case UIELEMENT_PROPHANDLE_FRAME: + Reference< XFrame > xFrame( m_xWeakFrame ); + aValue <<= xFrame; + break; + } +} + +::cppu::IPropertyArrayHelper& SAL_CALL UIElementWrapperBase::getInfoHelper() +{ + // Define static member to give structure of properties to baseclass "OPropertySetHelper". + // "impl_getStaticPropertyDescriptor" is a non exported and static function, who will define a static propertytable. + // "true" say: Table is sorted by name. + static ::cppu::OPropertyArrayHelper ourInfoHelper( impl_getStaticPropertyDescriptor(), true ); + + return ourInfoHelper; +} + +css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL UIElementWrapperBase::getPropertySetInfo() +{ + // Create structure of propertysetinfo for baseclass "OPropertySetHelper". + // (Use method "getInfoHelper()".) + static css::uno::Reference< css::beans::XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) ); + + return xInfo; +} + +css::uno::Sequence< css::beans::Property > UIElementWrapperBase::impl_getStaticPropertyDescriptor() +{ + // Create a property array to initialize sequence! + // Table of all predefined properties of this class. It's used from OPropertySetHelper-class! + // Don't forget to change the defines (see begin of this file), if you add, change or delete a property in this list!!! + // It's necessary for methods of OPropertySetHelper. + // ATTENTION: + // YOU MUST SORT FOLLOW TABLE BY NAME ALPHABETICAL !!! + + return + { + css::beans::Property( UIELEMENT_PROPNAME_FRAME, UIELEMENT_PROPHANDLE_FRAME , cppu::UnoType<XFrame>::get(), css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ), + css::beans::Property( UIELEMENT_PROPNAME_RESOURCEURL, UIELEMENT_PROPHANDLE_RESOURCEURL , cppu::UnoType<sal_Int16>::get(), css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ), + css::beans::Property( UIELEMENT_PROPNAME_TYPE, UIELEMENT_PROPHANDLE_TYPE , cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ) + }; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/vclstatusindicator.cxx b/framework/source/helper/vclstatusindicator.cxx new file mode 100644 index 000000000..b493eacc7 --- /dev/null +++ b/framework/source/helper/vclstatusindicator.cxx @@ -0,0 +1,140 @@ +/* -*- 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 <helper/vclstatusindicator.hxx> + +#include <toolkit/helper/vclunohelper.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +namespace framework { + +VCLStatusIndicator::VCLStatusIndicator(css::uno::Reference< css::awt::XWindow > xParentWindow) + : m_xParentWindow (std::move(xParentWindow )) + , m_pStatusBar (nullptr ) + , m_nRange (0 ) + , m_nValue (0 ) +{ + if (!m_xParentWindow.is()) + throw css::uno::RuntimeException( + "Can't work without a parent window!", + static_cast< css::task::XStatusIndicator* >(this)); +} + +VCLStatusIndicator::~VCLStatusIndicator() +{ +} + +void SAL_CALL VCLStatusIndicator::start(const OUString& sText , + sal_Int32 nRange) +{ + SolarMutexGuard aSolarGuard; + + VclPtr<vcl::Window> pParentWindow = VCLUnoHelper::GetWindow(m_xParentWindow); + if (!m_pStatusBar) + m_pStatusBar = VclPtr<StatusBar>::Create(pParentWindow, WB_3DLOOK|WB_BORDER); + + VCLStatusIndicator::impl_recalcLayout(m_pStatusBar, pParentWindow); + + m_pStatusBar->Show(); + m_pStatusBar->StartProgressMode(sText); + m_pStatusBar->SetProgressValue(0); + + // force repaint! + pParentWindow->Show(); + pParentWindow->Invalidate(InvalidateFlags::Children); + pParentWindow->GetOutDev()->Flush(); + + m_nRange = nRange; + m_nValue = 0; +} + +void SAL_CALL VCLStatusIndicator::reset() +{ + SolarMutexGuard aSolarGuard; + if (m_pStatusBar) + { + m_pStatusBar->SetProgressValue(0); + m_pStatusBar->SetText(OUString()); + } +} + +void SAL_CALL VCLStatusIndicator::end() +{ + SolarMutexGuard aSolarGuard; + + m_nRange = 0; + m_nValue = 0; + + if (m_pStatusBar) + { + m_pStatusBar->EndProgressMode(); + m_pStatusBar->Show(false); + + m_pStatusBar.disposeAndClear(); + } +} + +void SAL_CALL VCLStatusIndicator::setText(const OUString& sText) +{ + SolarMutexGuard aSolarGuard; + if (m_pStatusBar) + m_pStatusBar->SetText(sText); +} + +void SAL_CALL VCLStatusIndicator::setValue(sal_Int32 nValue) +{ + SolarMutexGuard aSolarGuard; + + if (nValue <= m_nRange) + m_nValue = nValue; + else + m_nValue = m_nRange; + + sal_Int32 nRange = m_nRange; + nValue = m_nValue; + + // normalize value to fit the range of 0-100% + sal_uInt16 nPercent = sal::static_int_cast< sal_uInt16 >( + ::std::min( + ((nValue*100) / ::std::max(nRange,sal_Int32(1))), sal_Int32(100))); + + if (m_pStatusBar) + m_pStatusBar->SetProgressValue(nPercent); +} + +void VCLStatusIndicator::impl_recalcLayout(vcl::Window* pStatusBar , + vcl::Window const * pParentWindow) +{ + if ( + (!pStatusBar ) || + (!pParentWindow) + ) + return; + + Size aParentSize = pParentWindow->GetSizePixel(); + pStatusBar->setPosSizePixel(0, + 0, + aParentSize.Width(), + aParentSize.Height()); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/helper/wakeupthread.cxx b/framework/source/helper/wakeupthread.cxx new file mode 100644 index 000000000..40487c83b --- /dev/null +++ b/framework/source/helper/wakeupthread.cxx @@ -0,0 +1,60 @@ +/* -*- 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 <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/util/XUpdatable.hpp> + +#include <helper/wakeupthread.hxx> +#include <chrono> + +using namespace std::chrono_literals; + +void framework::WakeUpThread::execute() { + for (;;) { + { + std::unique_lock g(mutex_); + condition_.wait_for(g, 25ms, [this] { return terminate_; }); + if (terminate_) { + break; + } + } + css::uno::Reference<css::util::XUpdatable> up(updatable_); + if (up.is()) { + up->update(); + } + } +} + +framework::WakeUpThread::WakeUpThread( + css::uno::Reference<css::util::XUpdatable> const & updatable): + Thread("WakeUpThread"), updatable_(updatable), terminate_(false) +{} + +void framework::WakeUpThread::stop() { + { + std::unique_lock g(mutex_); + terminate_ = true; + } + condition_.notify_one(); + join(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/accelerators/acceleratorcache.hxx b/framework/source/inc/accelerators/acceleratorcache.hxx new file mode 100644 index 000000000..adfdf8fe5 --- /dev/null +++ b/framework/source/inc/accelerators/acceleratorcache.hxx @@ -0,0 +1,115 @@ +/* -*- 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 . + */ + +#pragma once + +#include <stdtypes.h> + +#include <com/sun/star/awt/KeyEvent.hpp> + +#include <unordered_map> +#include <vector> + +// definition + +namespace framework +{ + +/** + @short implements a cache for any accelerator configuration. + + @descr It's implemented threadsafe, supports copy-on-write pattern + and a flush mechanism to support concurrent access to the same + configuration. + + copy-on-write ... How? Do the following: + */ +class AcceleratorCache +{ + public: + + /** + commands -> keys + */ + typedef ::std::vector< css::awt::KeyEvent > TKeyList; + + private: + + typedef std::unordered_map<OUString, TKeyList> TCommand2Keys; + + /** + keys -> commands + */ + typedef std::unordered_map< css::awt::KeyEvent , + OUString , + KeyEventHashCode , + KeyEventEqualsFunc > TKey2Commands; + + /** map commands to keys in relation 1:n. + First key is interpreted as preferred one! */ + TCommand2Keys m_lCommand2Keys; + + /** map keys to commands in relation 1:1. */ + TKey2Commands m_lKey2Commands; + + public: + /** @short checks if the specified key exists. + + @param aKey + the key, which should be checked. + + @return [bool] + sal_True if the specified key exists inside this container. + */ + bool hasKey(const css::awt::KeyEvent& aKey) const; + bool hasCommand(const OUString& sCommand) const; + + TKeyList getAllKeys() const; + + /** @short add a new or change an existing key-command pair + of this container. + + @param aKey + describe the key. + + @param sCommand + describe the command. + */ + void setKeyCommandPair(const css::awt::KeyEvent& aKey , + const OUString& sCommand); + + /** @short returns the list of keys, which are registered + for this command. + + @param sCommand + describe the command. + + @return [TKeyList] + the list of registered keys. Can be empty! + */ + TKeyList getKeysByCommand(const OUString& sCommand) const; + + OUString getCommandByKey(const css::awt::KeyEvent& aKey) const; + void removeKey(const css::awt::KeyEvent& aKey); + void removeCommand(const OUString& sCommand); +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/accelerators/acceleratorconfiguration.hxx b/framework/source/inc/accelerators/acceleratorconfiguration.hxx new file mode 100644 index 000000000..725148ebb --- /dev/null +++ b/framework/source/inc/accelerators/acceleratorconfiguration.hxx @@ -0,0 +1,312 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <accelerators/presethandler.hxx> +#include <accelerators/acceleratorcache.hxx> + +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/ui/XAcceleratorConfiguration.hpp> + +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/util/XChangesListener.hpp> + +// TODO use XPresetHandler interface instead if available +#include <com/sun/star/form/XReset.hpp> + +#include <cppuhelper/implbase.hxx> + +// definition + +namespace framework +{ + +inline constexpr OUStringLiteral CFG_ENTRY_PRIMARY = u"PrimaryKeys"; +inline constexpr OUStringLiteral CFG_ENTRY_GLOBAL = u"Global"; +inline constexpr OUStringLiteral CFG_ENTRY_MODULES = u"Modules"; + +/** + implements a read/write access to the accelerator configuration. + */ +class XMLBasedAcceleratorConfiguration : public ::cppu::WeakImplHelper< + css::form::XReset, // TODO use XPresetHandler instead if available + css::ui::XAcceleratorConfiguration > // => css::ui::XUIConfigurationPersistence + // css::ui::XUIConfigurationStorage + // css::ui::XUIConfiguration +{ + + // member + + protected: + + /** the global uno service manager. + Must be used to create own needed services. */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + /** used to: + i ) copy configuration files from the share to the user layer + ii ) provide access to these config files + iii) cache all sub storages on the path from the top to the bottom(!) + iv ) provide commit for changes. */ + PresetHandler m_aPresetHandler; + + /** contains the cached configuration data */ + AcceleratorCache m_aReadCache; + + /** used to implement the copy on write pattern! */ + std::unique_ptr<AcceleratorCache> m_pWriteCache; + + // native interface! + + public: + + XMLBasedAcceleratorConfiguration( const css::uno::Reference< css::uno::XComponentContext >& xContext); + virtual ~XMLBasedAcceleratorConfiguration( ) override; + + // uno interface! + + public: + + // XAcceleratorConfiguration + virtual css::uno::Sequence< css::awt::KeyEvent > SAL_CALL getAllKeyEvents() override; + + virtual OUString SAL_CALL getCommandByKeyEvent(const css::awt::KeyEvent& aKeyEvent) override; + + virtual void SAL_CALL setKeyEvent(const css::awt::KeyEvent& aKeyEvent, + const OUString& sCommand ) override; + + virtual void SAL_CALL removeKeyEvent(const css::awt::KeyEvent& aKeyEvent) override; + + virtual css::uno::Sequence< css::awt::KeyEvent > SAL_CALL getKeyEventsByCommand(const OUString& sCommand) override; + + virtual css::uno::Sequence< css::uno::Any > SAL_CALL getPreferredKeyEventsForCommandList(const css::uno::Sequence< OUString >& lCommandList) override; + + virtual void SAL_CALL removeCommandFromAllKeyEvents(const OUString& sCommand) override; + + // XUIConfigurationPersistence + virtual void SAL_CALL reload() override; + + virtual void SAL_CALL store() override; + + virtual void SAL_CALL storeToStorage(const css::uno::Reference< css::embed::XStorage >& xStorage) override; + + virtual sal_Bool SAL_CALL isModified() override; + + virtual sal_Bool SAL_CALL isReadOnly() override; + + // XUIConfigurationStorage + virtual void SAL_CALL setStorage(const css::uno::Reference< css::embed::XStorage >& xStorage) override; + + virtual sal_Bool SAL_CALL hasStorage() override; + + // XUIConfiguration + virtual void SAL_CALL addConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& xListener) override; + + virtual void SAL_CALL removeConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& xListener) override; + + // XReset + // TODO use XPresetHandler instead if available + virtual void SAL_CALL reset() override; + + virtual void SAL_CALL addResetListener(const css::uno::Reference< css::form::XResetListener >& xListener) override; + + virtual void SAL_CALL removeResetListener(const css::uno::Reference< css::form::XResetListener >& xListener) override; + + // called when changes occurred in the storage + void changesOccurred(); + + // helper for derived classes + + protected: + + /** @short return the current office locale. + + @descr We do not cache this value, because we are not listen + for changes on the configuration layer ... + + @return OUString + The current office locale as BCP47 string. + */ + OUString impl_ts_getLocale() const; + + // helper + + private: + + /** @short load a configuration set, using the given stream. + + @param xStream + provides the XML structure as stream. + */ + void impl_ts_load(const css::uno::Reference< css::io::XInputStream >& xStream); + + /** @short save a configuration set, using the given stream. + + @param xStream + the XML structure can be written there. + */ + void impl_ts_save(const css::uno::Reference< css::io::XOutputStream >& xStream); + + /** @short returns a reference to one of our internal cache members. + + @descr We implement the copy-on-write pattern. Doing so + we know two caches internally. The second one is used + only, if the container was changed. + + This method here returns access to one of these + caches - depending on the change state of this + configuration service. + + @param bWriteAccessRequested + if the outside code wish to change the container + it must call this method with "sal_True". So the internal + cache can be prepared for that (means copy-on-write ...). + + @return [AcceleratorCache] + c++ reference(!) to one of our internal caches. + */ + AcceleratorCache& impl_getCFG(bool bWriteAccessRequested = false); + +}; + +class XCUBasedAcceleratorConfiguration : public ::cppu::WeakImplHelper< + css::util::XChangesListener, + css::form::XReset, // TODO use XPresetHandler instead if available + css::ui::XAcceleratorConfiguration > // => css::ui::XUIConfigurationPersistence + // css::ui::XUIConfigurationStorage + // css::ui::XUIConfiguration +{ + + // member + + protected: + + /** the global uno service manager. + Must be used to create own needed services. */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + css::uno::Reference< css::container::XNameAccess > m_xCfg; + AcceleratorCache m_aPrimaryReadCache; + AcceleratorCache m_aSecondaryReadCache; + std::unique_ptr<AcceleratorCache> m_pPrimaryWriteCache; + std::unique_ptr<AcceleratorCache> m_pSecondaryWriteCache; + + OUString m_sGlobalOrModules; + OUString m_sModuleCFG; + + // native interface! + + public: + + XCUBasedAcceleratorConfiguration( css::uno::Reference< css::uno::XComponentContext > xContext ); + virtual ~XCUBasedAcceleratorConfiguration( ) override; + + // uno interface! + + public: + + // XAcceleratorConfiguration + virtual css::uno::Sequence< css::awt::KeyEvent > SAL_CALL getAllKeyEvents() override; + + virtual OUString SAL_CALL getCommandByKeyEvent(const css::awt::KeyEvent& aKeyEvent) override; + + virtual void SAL_CALL setKeyEvent(const css::awt::KeyEvent& aKeyEvent, + const OUString& sCommand ) override; + + virtual void SAL_CALL removeKeyEvent(const css::awt::KeyEvent& aKeyEvent) override; + + virtual css::uno::Sequence< css::awt::KeyEvent > SAL_CALL getKeyEventsByCommand(const OUString& sCommand) override; + + virtual css::uno::Sequence< css::uno::Any > SAL_CALL getPreferredKeyEventsForCommandList(const css::uno::Sequence< OUString >& lCommandList) override; + + virtual void SAL_CALL removeCommandFromAllKeyEvents(const OUString& sCommand) override; + + // XUIConfigurationPersistence + virtual void SAL_CALL reload() override; + + virtual void SAL_CALL store() override; + + virtual void SAL_CALL storeToStorage(const css::uno::Reference< css::embed::XStorage >& xStorage) override; + + virtual sal_Bool SAL_CALL isModified() override; + + virtual sal_Bool SAL_CALL isReadOnly() override; + + // XUIConfigurationStorage + virtual void SAL_CALL setStorage(const css::uno::Reference< css::embed::XStorage >& xStorage) override; + + virtual sal_Bool SAL_CALL hasStorage() override; + + // XUIConfiguration + virtual void SAL_CALL addConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& xListener) override; + + virtual void SAL_CALL removeConfigurationListener(const css::uno::Reference< css::ui::XUIConfigurationListener >& xListener) override; + + // XReset + // TODO use XPresetHandler instead if available + virtual void SAL_CALL reset() override; + + virtual void SAL_CALL addResetListener(const css::uno::Reference< css::form::XResetListener >& xListener) override; + + virtual void SAL_CALL removeResetListener(const css::uno::Reference< css::form::XResetListener >& xListener) override; + + // css.util.XChangesListener + virtual void SAL_CALL changesOccurred(const css::util::ChangesEvent& aEvent) override; + + // css.lang.XEventListener + virtual void SAL_CALL disposing(const css::lang::EventObject& aEvent) override; + + // helper for derived classes + + protected: + + /** @short return the current office locale. + + @descr We do not cache this value, because we are not listen + for changes on the configuration layer ... + + @return OUString + The current office locale as BCP47 string. + */ + OUString impl_ts_getLocale() const; + + // helper + + private: + + void impl_ts_load(bool bPreferred, const css::uno::Reference< css::container::XNameAccess >& xCfg); + void impl_ts_save(bool bPreferred); + + void insertKeyToConfiguration(const css::awt::KeyEvent& aKeyEvent, const OUString& sCommand, const bool bPreferred); + void removeKeyFromConfiguration(const css::awt::KeyEvent& aKeyEvent, const bool bPreferred); + + void reloadChanged(const OUString& sPrimarySecondary, std::u16string_view sGlobalModules, const OUString& sModule, const OUString& sKey); + AcceleratorCache& impl_getCFG(bool bPreferred, bool bWriteAccessRequested = false); + +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/accelerators/keymapping.hxx b/framework/source/inc/accelerators/keymapping.hxx new file mode 100644 index 000000000..88429a012 --- /dev/null +++ b/framework/source/inc/accelerators/keymapping.hxx @@ -0,0 +1,126 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <unordered_map> + +// definition + +namespace framework +{ + +/** + can be used to map key identifier to the + corresponding key codes ... + */ +class KeyMapping +{ + + // const, types + + private: + + /** @short is used to map a key code + to the right key identifier, which is + used to make the xml file "human readable" + */ + struct KeyIdentifierInfo + { + sal_Int16 Code; + const char* Identifier; + }; + + /** @short hash structure to map identifier to key codes. */ + typedef std::unordered_map<OUString, sal_Int16> Identifier2CodeHash; + + /** @short hash structure to map key codes to identifier. */ + typedef std::unordered_map<sal_Int16, OUString> Code2IdentifierHash; + + // member + + private: + + static KeyIdentifierInfo const KeyIdentifierMap[]; + + /** @short hash to map identifier to key codes. */ + Identifier2CodeHash m_lIdentifierHash; + + /** @short hash to map key codes to identifier. */ + Code2IdentifierHash m_lCodeHash; + + // interface + + public: + + KeyMapping(); + + static KeyMapping & get(); + + /** @short return a suitable key code + for the specified key identifier. + + @param sIdentifier + string value, which describe the key. + + @return [css::awt::KeyEvent] + the corresponding key code as + short value. + + @throw [css::lang::IllegalArgumentException] + if the given identifier does not describe + a well known key code. + */ + sal_uInt16 mapIdentifierToCode(const OUString& sIdentifier); + + /** @short return a suitable key identifier + for the specified key code. + + @param nCode + short value, which describe the key. + + @return The corresponding string identifier. + */ + OUString mapCodeToIdentifier(sal_uInt16 nCode); + + // helper + + private: + + /** @short check if the given string describe a numeric + value ... and convert it. + + @param sIdentifier + the string value, which should be converted. + + @param rCode + contains the converted code, but is defined only + if this method returns sal_True! + + @return [boolean] + sal_True if conversion was successful. + */ + bool impl_st_interpretIdentifierAsPureKeyCode(std::u16string_view sIdentifier, + sal_uInt16& rCode ); +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/accelerators/presethandler.hxx b/framework/source/inc/accelerators/presethandler.hxx new file mode 100644 index 000000000..b0fee38b4 --- /dev/null +++ b/framework/source/inc/accelerators/presethandler.hxx @@ -0,0 +1,378 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <accelerators/storageholder.hxx> + +#include <com/sun/star/embed/XStorage.hpp> + +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <i18nlangtag/languagetag.hxx> + +namespace framework +{ +/** + TODO document me + + <layer>/global/<resourcetype>/<preset>.xml + <layer>/modules/<moduleid>/<resourcetype>/<preset>.xml + + RESOURCETYPE PRESET TARGET + (share) (user) + "accelerator" "default" "current" + "word" + "excel" + + "menubar" "default" "menubar" + + */ +class PresetHandler +{ + public: + + /** @short this handler can provide different + types of configuration. + + @descr Means: a global or a module dependent + or ... configuration. + */ + enum EConfigType + { + E_GLOBAL, + E_MODULES, + E_DOCUMENT + }; + + private: + + /** @short can be used to create on needed uno resources. */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + /** @short knows the type of provided configuration. + + @descr e.g. global, modules, ... + */ + EConfigType m_eConfigType; + + /** @short if we run in document mode, we can't use the global root storages! + We have to use a special document storage explicitly. */ + StorageHolder m_lDocumentStorages; + + /** @short holds the folder storage of the share layer alive, + where the current configuration set exists. + + @descr Note: If this preset handler works in document mode + this member is meant relative to the document root... + not to the share layer root! + + Further is defined, that m_xWorkingStorageUser + is equals to m_xWorkingStorageShare then! + */ + css::uno::Reference< css::embed::XStorage > m_xWorkingStorageShare; + + /** @short global language-independent storage + */ + css::uno::Reference< css::embed::XStorage > m_xWorkingStorageNoLang; + + /** @short holds the folder storage of the user layer alive, + where the current configuration set exists. + + @descr Note: If this preset handler works in document mode + this member is meant relative to the document root... + not to the user layer root! + + Further is defined, that m_xWorkingStorageUser + is equals to m_xWorkingStorageShare then! + */ + css::uno::Reference< css::embed::XStorage > m_xWorkingStorageUser; + + /** @short knows the relative path from the root. */ + OUString m_sRelPathShare; + OUString m_sRelPathUser; + + // native interface + + public: + + /** @short does nothing real. + + @param xContext + points to a uno service manager, which is used internally + to create own needed uno resources. + */ + PresetHandler(css::uno::Reference< css::uno::XComponentContext > xContext); + + /** @short copy ctor */ + PresetHandler(const PresetHandler& rCopy); + + /** @short closes all open storages ... if user forgot that .-) */ + ~PresetHandler(); + + /** @short free all currently cache(!) storages. */ + void forgetCachedStorages(); + + /** @short return access to the internally used and cached root storage. + + @descr These root storages are the base of all further opened + presets and targets. They are provided here only, to support + older implementations, which base on them ... + + getOrCreate...() - What does it mean? + Such root storage will be created one times only and + cached then internally till the last instance of such PresetHandler + dies. + + @return css::embed::XStorage + which represent a root storage. + */ + css::uno::Reference< css::embed::XStorage > getOrCreateRootStorageShare(); + css::uno::Reference< css::embed::XStorage > getOrCreateRootStorageUser(); + + /** @short provides access to the current working storages. + + @descr Working storages are the "lowest" storages, where the + preset and target files exists. + + @return css::embed::XStorage + which the current working storage. + */ + css::uno::Reference< css::embed::XStorage > getWorkingStorageUser() const; + + /** @short check if there is a parent storage well known for + the specified child storage and return it. + + @param xChild + the child storage where a paranet storage should be searched for. + + @return css::embed::XStorage + A valid storage if a paranet exists. NULL otherwise. + */ + css::uno::Reference< css::embed::XStorage > getParentStorageShare(); + css::uno::Reference< css::embed::XStorage > getParentStorageUser (); + + /** @short free all internal structures and let this handler + work on a new type of configuration sets. + + @param eConfigType + differ between global or module dependent configuration. + + @param sResourceType + differ between menubar/toolbar/accelerator/... configuration. + + @param sModule + if sResourceType is set to a module dependent configuration, + it address the current application module. + + @param xDocumentRoot + if sResourceType is set to E_DOCUMENT, this value points to the + root storage inside the document, where we can save our + configuration files. Note: that's not the real root of the document... + its only a sub storage. But we interpret it as our root storage. + + @param rLanguageTag + in case this configuration supports localized entries, + the current locale must be set. + + Localization will be represented as directory structure + of provided presets. Means: you call us with a preset name "default"; + and we use e.g. "/en-US/default.xml" internally. + + If no localization exists for this preset set, this class + will work in default mode - means "no locale" - automatically. + e.g. "/default.xml" + + @throw css::uno::RuntimeException(!) + if the specified resource couldn't be located. + */ + void connectToResource( EConfigType eConfigType , + std::u16string_view sResourceType , + std::u16string_view sModule , + const css::uno::Reference< css::embed::XStorage >& xDocumentRoot , + const LanguageTag& rLanguageTag = LanguageTag(LANGUAGE_USER_PRIV_NOTRANSLATE)); + + /** @short try to copy the specified preset from the share + layer to the user layer and establish it as the + specified target. + + @descr Means: copy share/.../<preset>.xml user/.../<target>.xml + Note: The target will be overwritten completely or + created as new by this operation! + + @param sPreset + the ALIAS name of an existing preset. + + @param sTarget + the ALIAS name of the target. + + @throw css::container::NoSuchElementException + if the specified preset does not exists. + + @throw css::io::IOException + if copying failed. + */ + void copyPresetToTarget(std::u16string_view sPreset, + std::u16string_view sTarget); + + /** @short open the specified preset as stream object + and return it. + + @descr Note: Because presets resist inside the share + layer, they will be opened readonly every time. + + @param sPreset + the ALIAS name of an existing preset. + + Accesses the global language-independent storage instead of the preset storage + + @return The opened preset stream ... or NULL if the preset does not exists. + */ + css::uno::Reference< css::io::XStream > openPreset(std::u16string_view sPreset); + + /** @short open the specified target as stream object + and return it. + + @descr Note: Targets resist inside the user + layer. Normally they are opened in read/write mode. + But it will be opened readonly automatically if that isn't possible + (may be the file is write protected on the system ...). + + @param sTarget + the ALIAS name of the target. + + @return The opened target stream ... or NULL if the target does not exists + or couldn't be created as new one. + */ + css::uno::Reference< css::io::XStream > openTarget( + std::u16string_view sTarget, sal_Int32 nMode); + + /** @short do anything which is necessary to flush all changes + back to disk. + + @descr We have to call commit on all cached sub storages on the + path from the root storage upside down to the working storage + (which are not really used, but required to be holded alive!). + */ + void commitUserChanges(); + + /** TODO */ + void addStorageListener(XMLBasedAcceleratorConfiguration* pListener); + void removeStorageListener(XMLBasedAcceleratorConfiguration* pListener); + + // helper + + private: + + /** @short open a config path ignoring errors (catching exceptions). + + @descr We catch only normal exceptions here - no runtime exceptions. + + @param sPath + the configuration path, which should be opened. + + @param eMode + the open mode (READ/READWRITE) + + @param bShare + force using of the share layer instead of the user layer. + + @return An opened storage in case method was successful - null otherwise. + */ + css::uno::Reference< css::embed::XStorage > impl_openPathIgnoringErrors(const OUString& sPath , + sal_Int32 eMode , + bool bShare); + + /** @short try to find the specified locale inside list of possible ones. + + @descr The list of possible locale values was e.g. retrieved from the system + (configuration, directory listing etcpp). The locale normally represent + the current office locale. This method search for a suitable item by using + different algorithm. + a) exact search + b) search with using fallbacks + + @param lLocalizedValues + list of BCP47 language tags / locale codes + + @param rLanguageTag + [IN ] the current office locale, which should be searched inside lLocalizedValues. + [OUT] in case fallbacks was allowed, it contains afterwards the fallback locale. + + @param bAllowFallbacks + enable/disable using of fallbacks + + @return An iterator, which points directly into lLocalizedValue list. + As a negative result the special iterator lLocalizedValues.end() will be returned. + */ + ::std::vector< OUString >::const_iterator impl_findMatchingLocalizedValue(const ::std::vector< OUString >& lLocalizedValues, + OUString& rLanguageTag , + bool bAllowFallbacks ); + + /** @short open a config path ignoring errors (catching exceptions). + + @descr We catch only normal exceptions here - no runtime exceptions. + Further the path itself is tries in different versions (using locale + specific attributes). + e.g. "path/e-US" => "path/en" => "path/de" + + @param sPath + the configuration path, which should be opened. + It's further used as out parameter too, so we can return the localized + path! + + @param eMode + the open mode (READ/READWRITE) + + @param bShare + force using of the share layer instead of the user layer. + + @param rLanguageTag + [IN ] contains the start locale for searching localized sub dirs. + [OUT] contains the locale of a found localized sub dir + + @param bAllowFallback + enable/disable fallback handling for locales + + @return An opened storage in case method was successful - null otherwise. + */ + css::uno::Reference< css::embed::XStorage > impl_openLocalizedPathIgnoringErrors(OUString& sPath , + sal_Int32 eMode , + bool bShare , + OUString& rLanguageTag , + bool bAllowFallback); + + /** @short returns the names of all sub storages of specified storage. + + @param xFolder + the base storage for this operation. + + @return [vector< string >] + a list of folder names. + */ + ::std::vector< OUString > impl_getSubFolderNames(const css::uno::Reference< css::embed::XStorage >& xFolder); +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/accelerators/storageholder.hxx b/framework/source/inc/accelerators/storageholder.hxx new file mode 100644 index 000000000..355bedeaa --- /dev/null +++ b/framework/source/inc/accelerators/storageholder.hxx @@ -0,0 +1,182 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/embed/XStorage.hpp> + +#include <mutex> +#include <unordered_map> +#include <vector> + +namespace framework +{ + +class XMLBasedAcceleratorConfiguration; +/** + TODO document me + */ +class StorageHolder final +{ + + // types + public: + + /** @short TODO */ + typedef ::std::vector< css::uno::Reference< css::embed::XStorage > > TStorageList; + + typedef ::std::vector< XMLBasedAcceleratorConfiguration* > TStorageListenerList; + + struct TStorageInfo + { + public: + css::uno::Reference< css::embed::XStorage > Storage; + sal_Int32 UseCount; + TStorageListenerList Listener; + + TStorageInfo() + : UseCount(0) + {} + }; + + /** @short TODO */ + typedef std::unordered_map< OUString, + TStorageInfo > TPath2StorageInfo; + + // member + private: + mutable std::mutex m_mutex; + + /** @short TODO */ + css::uno::Reference< css::embed::XStorage > m_xRoot; + + /** @short TODO */ + TPath2StorageInfo m_lStorages; + + // interface + public: + + /** @short TODO + */ + StorageHolder(); + + /** @short TODO + */ + ~StorageHolder(); + + /** @short TODO + */ + void forgetCachedStorages(); + + /** @short TODO + */ + void setRootStorage(const css::uno::Reference< css::embed::XStorage >& xRoot); + + /** @short TODO + */ + css::uno::Reference< css::embed::XStorage > getRootStorage() const; + + /** @short TODO + open or get! + */ + css::uno::Reference< css::embed::XStorage > openPath(const OUString& sPath , + sal_Int32 nOpenMode); + + /** @short TODO + */ + StorageHolder::TStorageList getAllPathStorages(const OUString& sPath); + + /** @short TODO + */ + void commitPath(const OUString& sPath); + + /** @short TODO + */ + void closePath(const OUString& sPath); + + /** @short TODO + */ + void notifyPath(const OUString& sPath); + + /** @short TODO + */ + void addStorageListener( XMLBasedAcceleratorConfiguration* pListener, + const OUString& sPath ); + + /** @short TODO + */ + void removeStorageListener( XMLBasedAcceleratorConfiguration* pListener, + const OUString& sPath ); + + /** @short TODO + */ + OUString getPathOfStorage(const css::uno::Reference< css::embed::XStorage >& xStorage); + + /** @short TODO + */ + css::uno::Reference< css::embed::XStorage > getParentStorage(const css::uno::Reference< css::embed::XStorage >& xChild); + + /** @short TODO + */ + css::uno::Reference< css::embed::XStorage > getParentStorage(const OUString& sChildPath); + + /** @short TODO + */ + StorageHolder& operator=(const StorageHolder& rCopy); + + /** @short opens a sub element of the specified base storage. + If eOpenMode contains an ELEMENT_WRITE flag remove it and try it with the rest of eOpenMode flags + again. + + @descr First this method try to open the requested sub element + using the given open mode. If it failed there is second step, + which tries to do the same again ... but removing a might existing + WRITE flag from the open mode. The user can suppress this fallback + handling by setting the parameter bAllowFallback to sal_False. + + @param xBaseStorage + the storage, where the sub element should be searched. + + @param sSubElement + the full name of the sub element. + e.g. "default.xml" + + @param eOpenMode + a flag field, which set the open mode for this operation. + + */ + static css::uno::Reference< css::embed::XStorage > openSubStorageWithFallback(const css::uno::Reference< css::embed::XStorage >& xBaseStorage , + const OUString& sSubStorage , + sal_Int32 eOpenMode); + + // helper + private: + + /** @short TODO + */ + static OUString impl_st_normPath(const OUString& sPath); + + /** @short TODO + */ + static std::vector<OUString> impl_st_parsePath(std::u16string_view sPath); +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/dispatch/dispatchdisabler.hxx b/framework/source/inc/dispatch/dispatchdisabler.hxx new file mode 100644 index 000000000..662eeb5d7 --- /dev/null +++ b/framework/source/inc/dispatch/dispatchdisabler.hxx @@ -0,0 +1,96 @@ +/* -*- 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/. + */ +#pragma once + +#include <set> + +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XInterceptorInfo.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XDispatchProviderInterceptor.hpp> + +namespace framework { + +/** + * Implementation of a service to make it easy to disable a whole + * suite of UNO commands in a batch - and have that act in-process. + * + * Often external re-use of LibreOffice wants a very cut-down set + * of functionality included, and disabling elements remotely one + * by one performs poorly. + */ +class DispatchDisabler final : public ::cppu::WeakImplHelper< + css::lang::XInitialization, + css::container::XNameContainer, + css::frame::XDispatchProviderInterceptor, + css::frame::XInterceptorInfo, + css::lang::XServiceInfo > +{ + std::set<OUString> maDisabledURLs; + css::uno::Reference< css::frame::XDispatchProvider > mxSlave; + css::uno::Reference< css::frame::XDispatchProvider > mxMaster; +public: + DispatchDisabler(const css::uno::Reference< css::uno::XComponentContext >& rxContext); + + // XInitialization + virtual void SAL_CALL initialize( const ::css::uno::Sequence< ::css::uno::Any >& aArguments ) override; + + // XDispatchProvider + virtual ::css::uno::Reference< ::css::frame::XDispatch > SAL_CALL + queryDispatch( const ::css::util::URL& URL, + const OUString& TargetFrameName, + ::sal_Int32 SearchFlags ) override; + virtual ::css::uno::Sequence< ::css::uno::Reference< ::css::frame::XDispatch > > SAL_CALL + queryDispatches( const ::css::uno::Sequence< ::css::frame::DispatchDescriptor >& Requests ) override; + + // XDispatchProviderInterceptor + virtual ::css::uno::Reference< ::css::frame::XDispatchProvider > SAL_CALL + getSlaveDispatchProvider() override; + virtual void SAL_CALL + setSlaveDispatchProvider( const ::css::uno::Reference< ::css::frame::XDispatchProvider >& NewDispatchProvider ) override; + virtual ::css::uno::Reference< ::css::frame::XDispatchProvider > SAL_CALL + getMasterDispatchProvider() override; + virtual void SAL_CALL + setMasterDispatchProvider( const ::css::uno::Reference< ::css::frame::XDispatchProvider >& NewSupplier ) override; + + // XInterceptorInfo + virtual ::css::uno::Sequence< OUString > SAL_CALL + getInterceptedURLs() override; + + // XElementAccess + virtual ::css::uno::Type SAL_CALL getElementType() override; + virtual ::sal_Bool SAL_CALL hasElements() override; + + // XNameAccess + virtual ::css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + virtual ::css::uno::Sequence< OUString > SAL_CALL getElementNames() override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& aName, const ::css::uno::Any& aElement ) override; + + // XNameContainer + virtual void SAL_CALL insertByName( const OUString& aName, const ::css::uno::Any& aElement ) override; + virtual void SAL_CALL removeByName( const OUString& Name ) override; + + /* interface XServiceInfo */ + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& sServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/dispatch/loaddispatcher.hxx b/framework/source/inc/dispatch/loaddispatcher.hxx new file mode 100644 index 000000000..195a46af8 --- /dev/null +++ b/framework/source/inc/dispatch/loaddispatcher.hxx @@ -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 . + */ + +#pragma once + +#include <loadenv/loadenv.hxx> + +#include <com/sun/star/frame/XNotifyingDispatch.hpp> +#include <com/sun/star/frame/XSynchronousDispatch.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/weakref.hxx> + +namespace framework{ + +/** @short implements a dispatch object which can be used to load + non-visible components (by using the mechanism of ContentHandler) + or visible-components (by using the mechanism of FrameLoader). + */ +class LoadDispatcher final : public ::cppu::WeakImplHelper< css::frame::XNotifyingDispatch, // => XDispatch => XInterface + css::frame::XSynchronousDispatch > +{ + + // member + + private: + osl::Mutex m_mutex; + + /** @short TODO document me */ + css::uno::WeakReference< css::frame::XFrame > m_xOwnerFrame; + + /** @short TODO document me */ + OUString m_sTarget; + + /** @short TODO document me */ + sal_Int32 m_nSearchFlags; + + /** @short TODO document me */ + LoadEnv m_aLoader; + + // native interface + + public: + + /** @short creates a new instance and initialize it with all necessary parameters. + + @descr Every instance of such LoadDispatcher can be used for the specified context only. + That means: it can be used to load any further requested content into the here(!) + specified target frame. + + @param xContext + will be used to create own needed services on demand. + + @param xOwnerFrame + used as startpoint to locate the right target frame. + + @param sTargetName + the name or the target frame for loading or a special qualifier + which define such target. + + @param nSearchFlags + used in case sTargetFrame isn't a special one. + */ + LoadDispatcher(const css::uno::Reference< css::uno::XComponentContext >& xContext, + const css::uno::Reference< css::frame::XFrame >& xOwnerFrame , + OUString sTargetName , + sal_Int32 nSearchFlags); + + /** @short used to free internal resources. + */ + virtual ~LoadDispatcher() override; + + // uno interface + + public: + + // XNotifyingDispatch + virtual void SAL_CALL dispatchWithNotification(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) override; + + // XDispatch + virtual void SAL_CALL dispatch(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments) override; + + virtual void SAL_CALL addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener, + const css::util::URL& aURL ) override; + + virtual void SAL_CALL removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener, + const css::util::URL& aURL ) override; + + // XSynchronousDispatch + virtual css::uno::Any SAL_CALL dispatchWithReturnValue( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) override; + + private: + css::uno::Any impl_dispatch( const css::util::URL& rURL, + const css::uno::Sequence< css::beans::PropertyValue >& lArguments, + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ); +}; // class LoadDispatcher + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/dispatch/windowcommanddispatch.hxx b/framework/source/inc/dispatch/windowcommanddispatch.hxx new file mode 100644 index 000000000..4a8a22d81 --- /dev/null +++ b/framework/source/inc/dispatch/windowcommanddispatch.hxx @@ -0,0 +1,109 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/frame/XFrame.hpp> + +#include <cppuhelper/weakref.hxx> +#include <tools/link.hxx> +#include <mutex> + +namespace com::sun::star::uno { + class XComponentContext; +} +class VclWindowEvent; + +namespace framework{ + +/** @short internal helper to bind e.g. MAC-Menu events to our internal dispatch API. + + @descr On e.g. MAC platform system menus are merged together with some fix entries as + e.g. "Pereferences" or "About". These menu entries trigger hard coded commands. + Here we map these commands to the right URLs and dispatch them. + + This helper knows a frame and its container window (where VCL provide the hard coded + commands). We hold those objects weak so there is no need to react for complex UNO dispose/ing() + scenarios. On the other side VCL does not hold us alive (because it doesn't know our UNO reference). + So we register at the VCL level as an event listener and + */ +class WindowCommandDispatch final +{ + private: + std::mutex m_mutex; + + /// can be used to create own needed services on demand. + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + /// knows the frame, where we dispatch our commands as weak reference + css::uno::WeakReference< css::frame::XFrame > m_xFrame; + + /// knows the VCL window (where the hard coded commands occurred) as weak XWindow reference + css::uno::WeakReference< css::awt::XWindow > m_xWindow; + + // native interface + + public: + + /** @short creates a new instance and initialize it with all necessary parameters. + + @descr Every instance of such MACDispatch can be used for the specified context only. + Means: 1 MACDispatch object is bound to 1 Frame/Window pair in which context + the detected commands will be executed. + + @param xContext + will be used to create own needed services on demand. + + @param xFrame + used as for new detected commands. + */ + WindowCommandDispatch(css::uno::Reference< css::uno::XComponentContext > xContext , + const css::uno::Reference< css::frame::XFrame >& xFrame); + + /** @short used to free internal resources. + */ + ~WindowCommandDispatch(); + + // implementation + + private: + + /** @short establish all listener connections we need. + + @descr Those listener connections will be created one times only (see ctor). + Afterwards we listen for incoming events till our referred frame/window pair + will be closed. + */ + void impl_startListening(); + + /** @short drop all listener connections we need. + + */ + void impl_stopListening(); + + /** @short callback from VCL to notify new commands + */ + DECL_LINK( impl_notifyCommand, VclWindowEvent&, void ); + +}; // class WindowCommandDispatch + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/loadenv/actionlockguard.hxx b/framework/source/inc/loadenv/actionlockguard.hxx new file mode 100644 index 000000000..ee52fcc0d --- /dev/null +++ b/framework/source/inc/loadenv/actionlockguard.hxx @@ -0,0 +1,141 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/document/XActionLockable.hpp> +#include <mutex> + +namespace framework{ + +/** @short implements a guard, which can use the interface + <type scope="css::document">XActionLockable</type>. + + @descr This guard should be used to be sure, that any lock will be + released. Otherwise the locked document can hinder the office on shutdown! +*/ +class ActionLockGuard final +{ + + // member + + private: + std::mutex m_mutex; + + /** @short points to the object, which can be locked from outside. */ + css::uno::Reference< css::document::XActionLockable > m_xActionLock; + + /** @short knows if a lock exists on the internal lock object + forced by this guard instance. */ + bool m_bActionLocked; + + // interface + + public: + + /** @short default ctor to initialize a "non working guard". + + @descr That can be useful in cases, where no resource still exists, + but will be available next time. Then this guard can be used + in a mode "use guard for more than one resources". + */ + ActionLockGuard() + : m_bActionLocked(false) + { + } + + /** @short release this guard instance and make sure, that no lock + will exist afterwards on the internal wrapped resource. + */ + ~ActionLockGuard() + { + unlock(); + } + + /** @short set a new resource for locking at this guard. + + @descr This call will fail, if an internal resource already exists + and is currently locked. + + @param xLock + points to the outside resource, which should be locked. + + @return sal_True, if new resource could be set and locked. + sal_False otherwise. + */ + bool setResource(const css::uno::Reference< css::document::XActionLockable >& xLock) + { + std::unique_lock g(m_mutex); + + if (m_bActionLocked || !xLock.is()) + return false; + + m_xActionLock = xLock; + m_xActionLock->addActionLock(); + m_bActionLocked = m_xActionLock->isActionLocked(); + + return true; + } + + /** @short set a new resource for locking at this guard. + + @descr This call will fail, if an internal resource already exists + and is currently locked. + + @param xLock + points to the outside resource, which should be locked. + + @return sal_True, if new resource could be set and locked. + sal_False otherwise. + */ + void freeResource() + { + // SAFE -> .......................... + std::unique_lock aMutexLock(m_mutex); + + css::uno::Reference< css::document::XActionLockable > xLock = m_xActionLock; + bool bLocked = m_bActionLocked; + + m_xActionLock.clear(); + m_bActionLocked = false; + + aMutexLock.unlock(); + // <- SAFE .......................... + + if (bLocked && xLock.is()) + xLock->removeActionLock(); + } + + /** @short unlock the internal wrapped resource, if it's not already done. */ + void unlock() + { + std::unique_lock g(m_mutex); + if (m_bActionLocked && m_xActionLock.is()) + { + m_xActionLock->removeActionLock(); + // don't check for any locks here ... + // May another guard use the same lock object :-( + m_bActionLocked = false; + } + } +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/loadenv/loadenv.hxx b/framework/source/inc/loadenv/loadenv.hxx new file mode 100644 index 000000000..351d39b94 --- /dev/null +++ b/framework/source/inc/loadenv/loadenv.hxx @@ -0,0 +1,540 @@ +/* -*- 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 . + */ +#pragma once + +#include <loadenv/actionlockguard.hxx> + +#include <com/sun/star/frame/XComponentLoader.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/util/URL.hpp> +#include <rtl/ref.hxx> +#include <unotools/mediadescriptor.hxx> +#include <o3tl/typed_flags_set.hxx> + + +/** @short enable/disable special features + of a load request. + + @desrc Such features must outcome without + any special parameters. + To make enabling/disabling of + features very easy (e.g. at the ctor of + this class) these values must be combinable + as flags. That means: its values must be in + range of [2^n]! + */ +enum class LoadEnvFeatures +{ + /// we should be informed, if no feature is enabled :-) + NONE = 0, + /// enable using of UI elements during loading (means progress, interaction handler etcpp.) + WorkWithUI = 1, + /// enable loading of resources, which are not related to a target frame! (see concept of ContentHandler) + AllowContentHandler = 2 +}; +namespace o3tl { + template<> struct typed_flags<LoadEnvFeatures> : is_typed_flags<LoadEnvFeatures, 0x3> {}; +} + + +namespace framework { + +class QuietInteraction; + +/** @short implements general mechanism for loading documents. + + @descr An instance of this class can be used inside the API calls + XComponentLoader::loadComponentFromURL() and + XDispatch::dispatch(). + */ +class LoadEnv +{ +public: + /** @short classify a content. + + @descr The load environment must know, if a content + is related to a target frame or not. Only "visible" + components, which fulfill the requirements of the + model-controller-view paradigm can be loaded into a frame. + Such contents are classified as E_CAN_BE_LOADED. + + But e.g. for the dispatch framework exists special ContentHandler + objects, which can load a content in "non visible" mode ... + and do not need a target frame for its operation. Such + ContentHandler e.g. plays sounds. + Such contents are classified as E_CAN_BE_HANDLED. + + And last but not least a content can be "not valid" in general. + */ + enum EContentType + { + /// identifies a content, which seems to be invalid in general + E_UNSUPPORTED_CONTENT, + /// identifies a content, which can be used with a ContentHandler and is not related to a target frame + E_CAN_BE_HANDLED, + /// identifies a content, which can be loaded into a target frame + E_CAN_BE_LOADED, + /// special mode for non real loading, In such case the model is given directly! + E_CAN_BE_SET + }; + +private: + mutable osl::Mutex m_mutex; + + /** @short reference to a uno service manager, which must be used + to created on needed services on demand. + */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + /** @short points to the frame, which uses this LoadEnv object + and must be used to start target search there. + */ + css::uno::Reference< css::frame::XFrame > m_xBaseFrame; + + /** @short points to the frame, into which the new component was loaded. + + @descr Note: This reference will be empty if loading failed + or a non visible content was loaded! + It can be the same frame as m_xBaseFrame it describe, in case + the target "_self", "" or the search flag "SELF" was used. + Otherwise it's the new created or recycled frame, which was + used for loading and contains further the new component. + + Please use method getTarget() or getTargetComponent() + to return the frame/controller or model to any interested + user of the results of this load request. + */ + css::uno::Reference< css::frame::XFrame > m_xTargetFrame; + + /** @short contains the name of the target, in which the specified resource + of this instance must be loaded. + */ + OUString m_sTarget; + + /** @short if m_sTarget is not a special one, this flags regulate searching + of a suitable one. + */ + sal_Int32 m_nSearchFlags; + + /** @short contains all needed information about the resource, + which should be loaded. + + @descr Inside this struct e.g. the URL, its type and filter name, + the stream or a model directly are saved. + */ + utl::MediaDescriptor m_lMediaDescriptor; + + /** @short because the mediadescriptor contains the complete URL ... but + some functionality need the structured version, we hold it twice :-(. + */ + css::util::URL m_aURL; + + /** @short enable/disable special features of a load request. */ + LoadEnvFeatures m_eFeature; + + /** @short classify the content, which should be loaded by this instance. */ + EContentType m_eContentType; + + /** @short it indicates, that the member m_xTargetFrame was new created for this + load request and must be closed in case loading (not handling!) + operation failed. The default value is sal_False! + */ + bool m_bCloseFrameOnError; + + /** @short it indicates, that the old document (which was located inside m_xBaseFrame + in combination with the m_sTarget value "_self") was suspended. + Normally it will be replaced by the new loaded document. But in case + loading (not handling!) failed, it must be reactivated. + The default value is sal_False! + */ + bool m_bReactivateControllerOnError; + + /** @short it holds one (!) asynchronous used contenthandler or frameloader + alive, till the asynchronous operation will be finished. + */ + css::uno::Reference< css::uno::XInterface > m_xAsynchronousJob; + + /** @short holds the information about the finished load process. + + @descr The content of m_xTargetFrame can't be used as valid indicator, + (in case the existing old document was reactivated) + we must hold the result of the load process explicitly. + */ + bool m_bLoaded; + + /** @short holds an XActionLock on the internal used task member. + + @seealso m_xTargetFrame + */ + ActionLockGuard m_aTargetLock; + + rtl::Reference<QuietInteraction> m_pQuietInteraction; + +public: + + /** @short initialize a new instance of this load environment. + + @param xContext + reference to a uno service manager, which can be used internally + to create on needed services on demand. + + @throw Currently there is no reason to throw such exception! + + @throw A RuntimeException in case any internal process indicates, that + the whole runtime can't be used any longer. + */ + LoadEnv(css::uno::Reference< css::uno::XComponentContext > xContext); + + /** @short deinitialize an instance of this class in the right way. + */ + ~LoadEnv(); + + /// @throws css::lang::IllegalArgumentException + /// @throws css::io::IOException + /// @throws css::uno::RuntimeException + static css::uno::Reference< css::lang::XComponent > loadComponentFromURL(const css::uno::Reference< css::frame::XComponentLoader >& xLoader, + const css::uno::Reference< css::uno::XComponentContext >& xContext, + const OUString& sURL , + const OUString& sTarget, + sal_Int32 nFlags , + const css::uno::Sequence< css::beans::PropertyValue >& lArgs ); + + /** @short start loading of a resource + + @descr The parameter for targeting, the content description, and + some environment specifier (UI, dispatch functionality) + can be set here. Of course a still running load request + will be detected here and a suitable exception will be thrown. + Such constellation can be detected outside by using provided + synchronisation methods or callbacks. + + There is no direct return value possible here. Because it depends + from the usage of this instance! E.g. for loading a "visible component" + a frame with a controller/model inside can be possible. For loading + of a "non visible component" only an information about a successfully start + can be provided. + Further it can't be guaranteed, that the internal process runs synchronous. + that's why we prefer using of specialized methods afterwards e.g. to: + - wait till the internal job will be finished + and get the results + - or to let it run without any further control from outside. + + @param sURL + points to the resource, which should be loaded. + + @param lMediaDescriptor + contains additional information for the following load request. + + @param xBaseFrame + points to the frame which must be used as start point for target search. + + @param sTarget + regulate searching/creating of frames, which should contain the + new loaded component afterwards. + + @param nSearchFlags + regulate searching of targets, if sTarget is not a special one. + + @param eFeature + flag field, which enable/disable special features of this + new instance for following load call. + + @throw A LoadEnvException e.g. if another load operation is till in progress + or initialization of a new one fail by other reasons. + The real reason, a suitable message and ID will be given here immediately. + + @throw A RuntimeException in case any internal process indicates, that + the whole runtime can't be used any longer. + */ + void startLoading(const OUString& sURL , + const css::uno::Sequence< css::beans::PropertyValue >& lMediaDescriptor, + const css::uno::Reference< css::frame::XFrame >& xBaseFrame , + const OUString& sTarget , + sal_Int32 nSearchFlags , + LoadEnvFeatures eFeature = LoadEnvFeatures::NONE); + + /** @short wait for an already running load request (started by calling + startLoading() before). + + @descr The timeout parameter can be used to wait some times only + or forever. The return value indicates if the load request + was finished during the specified timeout period. + But it indicates not, if the load request was successful or not! + + @param nTimeout + specify a timeout in [ms]. + A value 0 let it wait forever! + + @return sal_True if the started load process could be finished in time; + sal_False if the specified time was over. + + @throw ... currently not used :-) + + @throw A RuntimeException in case any internal process indicates, that + the whole runtime can't be used any longer. + */ + bool waitWhileLoading(sal_uInt32 nTimeout = 0); + + /** TODO document me ... */ + css::uno::Reference< css::lang::XComponent > getTargetComponent() const; + +public: + + /** @short checks if the specified content can be handled by a + ContentHandler only and is not related to a target frame, + or if it can be loaded by a FrameLoader into a target frame + as "visible" component. + + @descr using: + switch(classifyContent(...)) + { + case E_CAN_BE_HANDLED : + handleIt(...); + break; + + case E_CAN_BE_LOADED : + xFrame = locateTargetFrame(); + loadIt(xFrame); + break; + + case E_NOT_A_CONTENT : + default : throw ...; + } + + @param sURL + describe the content. + + @param lMediaDescriptor + describe the content more detailed! + + @return A suitable enum value, which classify the specified content. + */ + static EContentType classifyContent(const OUString& sURL , + const css::uno::Sequence< css::beans::PropertyValue >& lMediaDescriptor); + + /** TODO document me ... */ + static void initializeUIDefaults( + const css::uno::Reference< css::uno::XComponentContext >& i_rxContext, + utl::MediaDescriptor& io_lMediaDescriptor, + const bool _bUIMode, + rtl::Reference<QuietInteraction>* o_ppQuiteInteraction + ); + + /** TODO document me ... */ + void impl_setResult(bool bResult); + + /** TODO document me ... */ + css::uno::Reference< css::uno::XInterface > impl_searchLoader(); + + /** @short it means; show the frame, bring it to front, + might set the right icon etcpp. in case loading was + successfully or reactivate a might existing old document or + close the frame if it was created before in case loading failed. + + @throw A LoadEnvException only in cases, where an internal error indicates, + that the complete load environment seems to be not usable in general. + In such cases a RuntimeException would be to hard for the outside code :-) + + @throw A RuntimeException in case any internal process indicates, that + the whole runtime can't be used any longer. + */ + void impl_reactForLoadingState(); + +private: + void start(); + + /** @short tries to detect the type and the filter of the specified content. + + @descr This method update the available media descriptor of this instance, + so it contains the right type, a corresponding filter, may a + valid frame loader etc. In case detection failed, this descriptor + is corrected first, before a suitable exception will be thrown. + (Excepting a RuntimeException occurrence!) + + @attention Not all types we know, are supported by filters. So it does not + indicates an error, if no suitable filter(loader etcpp will be found + for a type. But a type must be detected for the specified content. + Otherwise it's an error and loading can't be finished successfully. + + @throw A LoadEnvException if detection failed. + + @throw A RuntimeException in case any internal process indicates, that + the whole runtime can't be used any longer. + */ + void impl_detectTypeAndFilter(); + + /** @short tries to use ContentHandler objects for loading. + + @descr It searches for a suitable content handler object, registered + for the detected content type (must be done before by calling + impl_detectTypeAndFilter()). Because such handler does not depend + from a real target frame, location of such frame will be + suppressed here. + In case handle failed all new created resources will be + removed before a suitable exception is thrown. + (Excepting a RuntimeException occurrence!) + + @return TODO + + @throw A LoadEnvException if handling failed. + + @throw A RuntimeException in case any internal process indicates, that + the whole runtime can't be used any longer. + */ + bool impl_handleContent(); + + /** @short tries to use FrameLoader objects for loading. + + @descr First the target frame will be located. If it could be found + or new created a filter/frame loader will be instantiated and + used to load the content into this frame. + In case loading failed all new created resources will be + removed before a suitable exception is thrown. + (Excepting a RuntimeException occurrence!) + + @return TODO + + @throw A LoadEnvException if loading failed. + + @throw A RuntimeException in case any internal process indicates, that + the whole runtime can't be used any longer. + */ + bool impl_loadContent(); + + /** @short checks if the specified content is already loaded. + + @descr It depends from the set target information, if such + search is allowed or not! So this method checks first, + if the target is the special one "_default". + If not it returns with an empty result immediately! + In case search is allowed, an existing document with the + same URL is searched. If it could be found, the corresponding + view will get the focus and this method return the corresponding frame. + Optional jumpmarks will be accepted here too. So the + view of the document will be updated to show the position + inside the document, which is related to the jumpmark. + + @return A valid reference to the target frame, which contains the already loaded content + and could be activated successfully. An empty reference otherwise. + + @throw A LoadEnvException only in cases, where an internal error indicates, + that the complete load environment seems to be not usable in general. + In such cases a RuntimeException would be to hard for the outside code :-) + + @throw A RuntimeException in case any internal process indicates, that + the whole runtime can't be used any longer. + */ + css::uno::Reference< css::frame::XFrame > impl_searchAlreadyLoaded(); + + /** @short search for any target frame, which seems to be usable + for this load request. + + @descr Because this special feature is bound to the target specifier "_default" + its checked inside first. If it's not set => this method return an empty + reference. Otherwise any currently existing frame will be analyzed, if + it can be used here. The following rules exists: + + <ul> + <li>The frame must be empty ...</li> + <li>or contains an empty document of the same application module + which the new document will have (Note: the filter of the new content + must be well known here!)</li> + <li>and(!) this target must not be already used by any other load request.</li> + </ul> + + If a suitable target is located it will be locked. That's why the last rule + exists! If this method returns a valid frame reference, it was locked to be usable + for this load request only. (Don't forget to reset this state later!) + Concurrent LoadEnv instances can synchronize her work be using such locks :-) HOPEFULLY + + @throw A LoadEnvException only in cases, where an internal error indicates, + that the complete load environment seems to be not usable in general. + In such cases a RuntimeException would be to hard for the outside code :-) + + @throw A RuntimeException in case any internal process indicates, that + the whole runtime can't be used any longer. + */ + css::uno::Reference< css::frame::XFrame > impl_searchRecycleTarget(); + + /** @short because showing of a frame is needed more than once... + it's implemented as a separate method .-) + + @descr Note: Showing of a frame is bound to a special feature... + a) If we recycle any existing frame, we must bring it to front. + Showing of such frame is not needed really... because we recycle + visible frames only! + b) If the document was already shown (e.g. by our progress implementation) + we do nothing here. The reason behind: The document was already shown... + and it was already make a top window... + If the user activated another frame inbetween (because loading needed some time) + it's not allowed to disturb the user again. Then the frame must resists in the background. + c) If the frame was not shown before... but loading of a visible document into this frame + was finished... we need both actions: setVisible() and toFront(). + + @param xWindow + points to the container window of a frame. + + @param bForceToFront + if it's set to sal_False... showing of the window is done more intelligent. + setVisible() is called only if the window was not shown before. + This mode is needed by b) and c) + If it's set to sal_True... both actions has to be done: setVisible(), toFront()! + This mode is needed by a) + */ + void impl_makeFrameWindowVisible(const css::uno::Reference< css::awt::XWindow >& xWindow , + bool bForceToFront); + + /** @short checks whether a frame is already used for another load request or not. + + @descr Such frames can't be used for our "recycle feature"! + + @param xFrame + the frame, which should be checked. + + @return [sal_Bool] + sal_True if this frame is already used for loading, + sal_False otherwise. + */ + bool impl_isFrameAlreadyUsedForLoading(const css::uno::Reference< css::frame::XFrame >& xFrame) const; + + /** @short try to determine the used application module + of this load request and apply right position and size + for this document window... hopefully before we show it .-) + */ + void impl_applyPersistentWindowState(const css::uno::Reference< css::awt::XWindow >& xWindow); + + /** @short determine if it's allowed to open new document frames. + */ + bool impl_furtherDocsAllowed(); + + /** @short jumps to the requested bookmark inside a given document. + */ + void impl_jumpToMark(const css::uno::Reference< css::frame::XFrame >& xFrame, + const css::util::URL& aURL ); + + /** @short determine if this loader has an interactive dialog shown before + loading the document. + */ + bool impl_filterHasInteractiveDialog() const; +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/loadenv/loadenvexception.hxx b/framework/source/inc/loadenv/loadenvexception.hxx new file mode 100644 index 000000000..62a17d2ac --- /dev/null +++ b/framework/source/inc/loadenv/loadenvexception.hxx @@ -0,0 +1,87 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Any.hxx> +#include <utility> + +namespace framework{ + +/** @short specify an exception, which can be used inside the + load environment only. + + @descr Of course outside code must wrap it, to transport + the occurred information to its caller. + */ +class LoadEnvException +{ + public: + /** @short Can be used as an ID for an instance of a LoadEnvException. + @descr To prevent errors on adding/removing/changing such IDs here, + an enum field is used. Its int values are self organized... + */ + enum EIDs + { + /** @short The specified URL/Stream/etcpp. can not be handled by a LoadEnv instance. */ + ID_UNSUPPORTED_CONTENT, + + /** @short indicates a corrupted media descriptor. + @descr Some parts are required - some other ones are optional. Such exception + should be thrown, if a required item does not exists. */ + ID_INVALID_MEDIADESCRIPTOR, + + /** @short Its similar to a uno::RuntimeException... + @descr But such runtime exception can break the whole office code. + So its capsulated to this specialized load environment only. + Mostly it indicates a missing but needed resource ... e.g the + global desktop reference! */ + ID_INVALID_ENVIRONMENT, + + /** @short indicates a failed search for the right target frame. */ + ID_NO_TARGET_FOUND, + + /** @short TODO */ + ID_COULD_NOT_REACTIVATE_CONTROLLER, + + /** @short indicates an already running load operation. Of course the same + instance can't be used for multiple load requests at the same time. + */ + ID_STILL_RUNNING, + + /** @short sometimes we can't specify the reason for an error, because we + was interrupted by a called code in an unexpected way ... + */ + ID_GENERAL_ERROR + }; + + sal_Int32 m_nID; + OUString m_sMessage; + css::uno::Any m_exOriginal; + + LoadEnvException( + sal_Int32 id, OUString message = OUString(), + css::uno::Any original = css::uno::Any()): + m_nID(id), m_sMessage(std::move(message)), m_exOriginal(std::move(original)) + {} +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/loadenv/targethelper.hxx b/framework/source/inc/loadenv/targethelper.hxx new file mode 100644 index 000000000..1f0d93d7e --- /dev/null +++ b/framework/source/inc/loadenv/targethelper.hxx @@ -0,0 +1,90 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> + +namespace framework{ + +/** @short can be used to detect, if a target name (used e.g. for XFrame.findFrame()) + has a special meaning or can be used as normal frame name (e.g. for XFrame.setName()). + */ +class TargetHelper +{ + + public: + + /** @short it's used at the following interfaces to classify + target names. + */ + enum class ESpecialTarget + { + Blank, + Default, + Beamer, + HelpTask + }; + + // interface + + public: + + /** @short it checks the given unknown target name, + if it's the expected special one. + + @note An empty target is similar to "_self"! + + @param sCheckTarget + must be the unknown target name, which should be checked. + + @param eSpecialTarget + represent the expected target. + + @return It returns <TRUE/> if <var>sCheckTarget</var> represent + the expected <var>eSpecialTarget</var> value; <FALSE/> otherwise. + */ + static bool matchSpecialTarget(std::u16string_view sCheckTarget , + ESpecialTarget eSpecialTarget); + + /** @short it checks, if the given name can be used + to set it at a frame using XFrame.setName() method. + + @descr Because we handle special targets in a hard coded way + (means we do not check the real name of a frame then) + such named frames will never be found! + + And in case such special names can exists one times only + by definition inside the same frame tree (e.g. _beamer and + OFFICE_HELP_TASK) it's not a good idea to allow anything here :-) + + Of course we can't check unknown names, which are not special ones. + But we decide, that it's not allowed to use "_" as first sign + (because we reserve this letter for our own purposes!) + and the value must not a well known special target. + + @param sName + the new frame name, which should be checked. + */ + static bool isValidNameForFrame(std::u16string_view sName); +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/pattern/frame.hxx b/framework/source/inc/pattern/frame.hxx new file mode 100644 index 000000000..947a03668 --- /dev/null +++ b/framework/source/inc/pattern/frame.hxx @@ -0,0 +1,79 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/util/XCloseable.hpp> + +// namespaces + +namespace framework::pattern::frame{ + +/** @short close (or dispose) the given resource. + + @descr It try to close the given resource first. + Delegating of the ownership can be influenced from + outside. If closing isn't possible (because the + needed interface isn't available) dispose() is tried instead. + All possible exceptions are handled inside. + So the user of this method has to look for the return value only. + + @attention The given resource will not be cleared. + But later using of it can produce an exception! + + @param xResource + the object, which should be closed here. + + @return [bool] + sal_True if closing failed. + */ +inline bool closeIt(const css::uno::Reference< css::uno::XInterface >& xResource) +{ + css::uno::Reference< css::util::XCloseable > xClose (xResource, css::uno::UNO_QUERY); + css::uno::Reference< css::lang::XComponent > xDispose(xResource, css::uno::UNO_QUERY); + + try + { + if (xClose.is()) + xClose->close(false/*bDelegateOwnership*/); + else + if (xDispose.is()) + xDispose->dispose(); + else + return false; + } + catch(const css::util::CloseVetoException&) + { return false; } + catch(const css::lang::DisposedException&) + {} // disposed is closed is ... + catch(const css::uno::RuntimeException&) + { throw; } // should not be suppressed! + catch(const css::uno::Exception&) + { return false; } // ??? We defined to return a boolean value instead of throwing exceptions... + // (OK: RuntimeExceptions should not be caught inside the core..) + + return true; +} + +} // namespace framework::pattern::frame + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/inc/pattern/window.hxx b/framework/source/inc/pattern/window.hxx new file mode 100644 index 000000000..92134915d --- /dev/null +++ b/framework/source/inc/pattern/window.hxx @@ -0,0 +1,62 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/XTopWindow.hpp> + +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> + +// namespaces + +namespace framework{ + +class WindowHelper +{ + public: + + +static bool isTopWindow(const css::uno::Reference< css::awt::XWindow >& xWindow) +{ + // even child frame containing top level windows (e.g. query designer of database) will be closed + css::uno::Reference< css::awt::XTopWindow > xTopWindowCheck(xWindow, css::uno::UNO_QUERY); + if (xTopWindowCheck.is()) + { + // Note: Toolkit interface XTopWindow sometimes is used by real VCL-child-windows also .-) + // Be sure that these window is really a "top system window". + // Attention ! Checking Window->GetParent() is not the right approach here. + // Because sometimes VCL create "implicit border windows" as parents even we created + // a simple XWindow using the toolkit only .-( + SolarMutexGuard aSolarGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->IsSystemWindow() ) + return true; + } + + return false; +} + +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/interaction/quietinteraction.cxx b/framework/source/interaction/quietinteraction.cxx new file mode 100644 index 000000000..e9dc21857 --- /dev/null +++ b/framework/source/interaction/quietinteraction.cxx @@ -0,0 +1,129 @@ +/* -*- 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 <interaction/quietinteraction.hxx> + +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/document/XInteractionFilterSelect.hpp> +#include <com/sun/star/document/XInteractionFilterOptions.hpp> +#include <com/sun/star/document/FilterOptionsRequest.hpp> +#include <com/sun/star/task/ErrorCodeRequest.hpp> + +#include <com/sun/star/document/LockedDocumentRequest.hpp> + +#include <vcl/errcode.hxx> +#include <vcl/svapp.hxx> + +namespace framework{ + +QuietInteraction::QuietInteraction() +{ +} + +void SAL_CALL QuietInteraction::handle( const css::uno::Reference< css::task::XInteractionRequest >& xRequest ) +{ + // safe the request for outside analyzing every time! + css::uno::Any aRequest = xRequest->getRequest(); + { + SolarMutexGuard g; + m_aRequest = aRequest; + } + + // analyze the request + // We need XAbort as possible continuation as minimum! + // An optional filter selection we can handle too. + css::uno::Sequence< css::uno::Reference< css::task::XInteractionContinuation > > lContinuations = xRequest->getContinuations(); + css::uno::Reference< css::task::XInteractionAbort > xAbort; + css::uno::Reference< css::task::XInteractionApprove > xApprove; + css::uno::Reference< css::document::XInteractionFilterSelect > xFilter; + css::uno::Reference< css::document::XInteractionFilterOptions > xFOptions; + + sal_Int32 nCount=lContinuations.getLength(); + for (sal_Int32 i=0; i<nCount; ++i) + { + if ( ! xAbort.is() ) + xAbort.set( lContinuations[i], css::uno::UNO_QUERY ); + + if( ! xApprove.is() ) + xApprove.set( lContinuations[i], css::uno::UNO_QUERY ); + + if ( ! xFilter.is() ) + xFilter.set( lContinuations[i], css::uno::UNO_QUERY ); + + if ( ! xFOptions.is() ) + xFOptions.set( lContinuations[i], css::uno::UNO_QUERY ); + } + + // differ between abortable interactions (error, unknown filter...) + // and other ones (ambiguous but not unknown filter...) + css::task::ErrorCodeRequest aErrorCodeRequest; + css::document::LockedDocumentRequest aLockedDocumentRequest; + css::document::FilterOptionsRequest aFilterOptionsRequest; + + if( aRequest >>= aErrorCodeRequest ) + { + // warnings can be ignored => approve + // errors must break loading => abort + bool bWarning = ErrCode(aErrorCodeRequest.ErrCode).IsWarning(); + if (xApprove.is() && bWarning) + xApprove->select(); + else + if (xAbort.is()) + xAbort->select(); + } + else + if( aRequest >>= aLockedDocumentRequest ) + { + // the locked document should be opened readonly by default + if (xApprove.is()) + xApprove->select(); + else + if (xAbort.is()) + xAbort->select(); + } + else + if (aRequest>>=aFilterOptionsRequest) + { + if (xFOptions.is()) + { + // let the default filter options be used + xFOptions->select(); + } + } + else + if (xAbort.is()) + xAbort->select(); +} + +css::uno::Any QuietInteraction::getRequest() const +{ + SolarMutexGuard g; + return m_aRequest; +} + +bool QuietInteraction::wasUsed() const +{ + SolarMutexGuard g; + return m_aRequest.hasValue(); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/jobs/helponstartup.cxx b/framework/source/jobs/helponstartup.cxx new file mode 100644 index 000000000..2795a3f45 --- /dev/null +++ b/framework/source/jobs/helponstartup.cxx @@ -0,0 +1,336 @@ +/* -*- 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 own header +#include <jobs/helponstartup.hxx> +#include <services.h> +#include <targets.h> + +#include <officecfg/Office/Common.hxx> +#include <officecfg/Setup.hxx> + +// include others +#include <comphelper/sequenceashashmap.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/help.hxx> + +// include interfaces +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XFramesSupplier.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <cppuhelper/supportsservice.hxx> + +namespace framework{ + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL HelpOnStartup::getImplementationName() +{ + return "com.sun.star.comp.framework.HelpOnStartup"; +} + +sal_Bool SAL_CALL HelpOnStartup::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL HelpOnStartup::getSupportedServiceNames() +{ + return { SERVICENAME_JOB }; +} + +HelpOnStartup::HelpOnStartup(css::uno::Reference< css::uno::XComponentContext > xContext) + : m_xContext (std::move(xContext)) +{ + // create some needed uno services and cache it + m_xModuleManager = css::frame::ModuleManager::create( m_xContext ); + + m_xDesktop = css::frame::Desktop::create(m_xContext); + + // ask for office locale + m_sLocale = officecfg::Setup::L10N::ooLocale::get(); + + // detect system + m_sSystem = officecfg::Office::Common::Help::System::get(); + + // Start listening for disposing events of these services, + // so we can react e.g. for an office shutdown + css::uno::Reference< css::lang::XComponent > xComponent; + xComponent.set(m_xModuleManager, css::uno::UNO_QUERY); + if (xComponent.is()) + xComponent->addEventListener(static_cast< css::lang::XEventListener* >(this)); + if (m_xDesktop.is()) + m_xDesktop->addEventListener(static_cast< css::lang::XEventListener* >(this)); + xComponent.set(m_xConfig, css::uno::UNO_QUERY); + if (xComponent.is()) + xComponent->addEventListener(static_cast< css::lang::XEventListener* >(this)); +} + +HelpOnStartup::~HelpOnStartup() +{ +} + +// css.task.XJob +css::uno::Any SAL_CALL HelpOnStartup::execute(const css::uno::Sequence< css::beans::NamedValue >& lArguments) +{ + // Analyze the given arguments; try to locate a model there and + // classify it's used application module. + OUString sModule = its_getModuleIdFromEnv(lArguments); + + // Attention: we are bound to events for opening any document inside the office. + // That includes e.g. the help module itself. But we have to do nothing then! + if (sModule.isEmpty()) + return css::uno::Any(); + + // check current state of the help module + // a) help isn't open => show default page for the detected module + // b) help shows any other default page(!) => show default page for the detected module + // c) help shows any other content => do nothing (user travelled to any other content and leaved the set of default pages) + OUString sCurrentHelpURL = its_getCurrentHelpURL(); + bool bCurrentHelpURLIsAnyDefaultURL = its_isHelpUrlADefaultOne(sCurrentHelpURL); + bool bShowIt = false; + + // a) + if (sCurrentHelpURL.isEmpty()) + bShowIt = true; + // b) + else if (bCurrentHelpURLIsAnyDefaultURL) + bShowIt = true; + + if (bShowIt) + { + // retrieve the help URL for the detected application module + OUString sModuleDependentHelpURL = its_checkIfHelpEnabledAndGetURL(sModule); + if (!sModuleDependentHelpURL.isEmpty()) + { + // Show this help page. + // Note: The help window brings itself to front ... + Help* pHelp = Application::GetHelp(); + if (pHelp) + pHelp->Start(sModuleDependentHelpURL); + } + } + + return css::uno::Any(); +} + +void SAL_CALL HelpOnStartup::disposing(const css::lang::EventObject& aEvent) +{ + std::unique_lock g(m_mutex); + if (aEvent.Source == m_xModuleManager) + m_xModuleManager.clear(); + else if (aEvent.Source == m_xDesktop) + m_xDesktop.clear(); + else if (aEvent.Source == m_xConfig) + m_xConfig.clear(); +} + +OUString HelpOnStartup::its_getModuleIdFromEnv(const css::uno::Sequence< css::beans::NamedValue >& lArguments) +{ + ::comphelper::SequenceAsHashMap lArgs (lArguments); + ::comphelper::SequenceAsHashMap lEnvironment = lArgs.getUnpackedValueOrDefault("Environment", css::uno::Sequence< css::beans::NamedValue >()); + + // check for right environment. + // If it's not a DocumentEvent, which triggered this job, + // we can't work correctly! => return immediately and do nothing + OUString sEnvType = lEnvironment.getUnpackedValueOrDefault("EnvType", OUString()); + if (sEnvType != "DOCUMENTEVENT") + return OUString(); + + css::uno::Reference< css::frame::XModel > xDoc = lEnvironment.getUnpackedValueOrDefault("Model", css::uno::Reference< css::frame::XModel >()); + if (!xDoc.is()) + return OUString(); + + // be sure that we work on top level documents only, which are registered + // on the desktop instance. Ignore e.g. life previews, which are top frames too ... + // but not registered at this global desktop instance. + css::uno::Reference< css::frame::XDesktop > xDesktopCheck; + css::uno::Reference< css::frame::XFrame > xFrame; + css::uno::Reference< css::frame::XController > xController = xDoc->getCurrentController(); + if (xController.is()) + xFrame = xController->getFrame(); + if (xFrame.is() && xFrame->isTop()) + xDesktopCheck.set(xFrame->getCreator(), css::uno::UNO_QUERY); + if (!xDesktopCheck.is()) + return OUString(); + + // OK - now we are sure this document is a top level document. + // Classify it. + // SAFE -> + std::unique_lock aLock(m_mutex); + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager = m_xModuleManager; + aLock.unlock(); + // <- SAFE + + OUString sModuleId; + try + { + sModuleId = xModuleManager->identify(xDoc); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { sModuleId.clear(); } + + return sModuleId; +} + +OUString HelpOnStartup::its_getCurrentHelpURL() +{ + // SAFE -> + std::unique_lock aLock(m_mutex); + css::uno::Reference< css::frame::XDesktop2 > xDesktop = m_xDesktop; + aLock.unlock(); + // <- SAFE + + if (!xDesktop.is()) + return OUString(); + + css::uno::Reference< css::frame::XFrame > xHelp = xDesktop->findFrame(SPECIALTARGET_HELPTASK, css::frame::FrameSearchFlag::CHILDREN); + if (!xHelp.is()) + return OUString(); + + OUString sCurrentHelpURL; + try + { + css::uno::Reference< css::frame::XFramesSupplier > xHelpRoot (xHelp , css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::container::XIndexAccess > xHelpChildren(xHelpRoot->getFrames(), css::uno::UNO_QUERY_THROW); + + css::uno::Reference< css::frame::XFrame > xHelpChild; + css::uno::Reference< css::frame::XController > xHelpView; + css::uno::Reference< css::frame::XModel > xHelpContent; + + xHelpChildren->getByIndex(0) >>= xHelpChild; + if (xHelpChild.is()) + xHelpView = xHelpChild->getController(); + if (xHelpView.is()) + xHelpContent = xHelpView->getModel(); + if (xHelpContent.is()) + sCurrentHelpURL = xHelpContent->getURL(); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { sCurrentHelpURL.clear(); } + + return sCurrentHelpURL; +} + +bool HelpOnStartup::its_isHelpUrlADefaultOne(std::u16string_view sHelpURL) +{ + if (sHelpURL.empty()) + return false; + + // SAFE -> + std::unique_lock aLock(m_mutex); + css::uno::Reference< css::container::XNameAccess > xConfig = m_xConfig; + OUString sLocale = m_sLocale; + OUString sSystem = m_sSystem; + aLock.unlock(); + // <- SAFE + + if (!xConfig.is()) + return false; + + // check given help url against all default ones + const css::uno::Sequence< OUString > lModules = xConfig->getElementNames(); + const OUString* pModules = lModules.getConstArray(); + ::sal_Int32 c = lModules.getLength(); + ::sal_Int32 i = 0; + + for (i=0; i<c; ++i) + { + try + { + css::uno::Reference< css::container::XNameAccess > xModuleConfig; + xConfig->getByName(pModules[i]) >>= xModuleConfig; + if (!xModuleConfig.is()) + continue; + + OUString sHelpBaseURL; + xModuleConfig->getByName("ooSetupFactoryHelpBaseURL") >>= sHelpBaseURL; + OUString sHelpURLForModule = HelpOnStartup::ist_createHelpURL(sHelpBaseURL, sLocale, sSystem); + if (sHelpURL == sHelpURLForModule) + return true; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + {} + } + + return false; +} + +OUString HelpOnStartup::its_checkIfHelpEnabledAndGetURL(const OUString& sModule) +{ + // SAFE -> + std::unique_lock aLock(m_mutex); + css::uno::Reference< css::container::XNameAccess > xConfig = m_xConfig; + OUString sLocale = m_sLocale; + OUString sSystem = m_sSystem; + aLock.unlock(); + // <- SAFE + + OUString sHelpURL; + + try + { + css::uno::Reference< css::container::XNameAccess > xModuleConfig; + if (xConfig.is()) + xConfig->getByName(sModule) >>= xModuleConfig; + + bool bHelpEnabled = false; + if (xModuleConfig.is()) + xModuleConfig->getByName("ooSetupFactoryHelpOnOpen") >>= bHelpEnabled; + + if (bHelpEnabled) + { + OUString sHelpBaseURL; + xModuleConfig->getByName("ooSetupFactoryHelpBaseURL") >>= sHelpBaseURL; + sHelpURL = HelpOnStartup::ist_createHelpURL(sHelpBaseURL, sLocale, sSystem); + } + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { sHelpURL.clear(); } + + return sHelpURL; +} + +OUString HelpOnStartup::ist_createHelpURL(std::u16string_view sBaseURL, + std::u16string_view sLocale , + std::u16string_view sSystem ) +{ + return OUString::Concat(sBaseURL) + "?Language=" + sLocale + "&System=" + sSystem; +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_HelpOnStartup_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::HelpOnStartup(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/jobs/job.cxx b/framework/source/jobs/job.cxx new file mode 100644 index 000000000..726c037cf --- /dev/null +++ b/framework/source/jobs/job.cxx @@ -0,0 +1,857 @@ +/* -*- 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 <jobs/job.hxx> +#include <jobs/jobresult.hxx> + +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/TerminationVetoException.hpp> +#include <com/sun/star/task/XJob.hpp> +#include <com/sun/star/task/XAsyncJob.hpp> +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/util/XCloseBroadcaster.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/lang/DisposedException.hpp> + +#include <comphelper/sequence.hxx> +#include <sal/log.hxx> +#include <tools/diagnose_ex.h> +#include <utility> +#include <vcl/svapp.hxx> + +namespace framework{ + +/** + @short standard ctor + @descr It initialize this new instance. But it set some generic parameters here only. + Specialized information (e.g. the alias or service name ofthis job) will be set + later using the method setJobData(). + + @param xContext + reference to the uno service manager + + @param xFrame + reference to the frame, in which environment we run + (May be null!) +*/ +Job::Job( /*IN*/ const css::uno::Reference< css::uno::XComponentContext >& xContext , + /*IN*/ css::uno::Reference< css::frame::XFrame > xFrame ) + : m_aJobCfg (xContext ) + , m_xContext (xContext ) + , m_xFrame (std::move(xFrame )) + , m_bListenOnDesktop (false ) + , m_bListenOnFrame (false ) + , m_bListenOnModel (false ) + , m_bPendingCloseFrame (false ) + , m_bPendingCloseModel (false ) + , m_eRunState (E_NEW ) +{ +} + +/** + @short standard ctor + @descr It initialize this new instance. But it set some generic parameters here only. + Specialized information (e.g. the alias or service name ofthis job) will be set + later using the method setJobData(). + + @param xContext + reference to the uno service manager + + @param xModel + reference to the model, in which environment we run + (May be null!) +*/ +Job::Job( /*IN*/ const css::uno::Reference< css::uno::XComponentContext >& xContext , + /*IN*/ css::uno::Reference< css::frame::XModel > xModel ) + : m_aJobCfg (xContext ) + , m_xContext (xContext ) + , m_xModel (std::move(xModel )) + , m_bListenOnDesktop (false ) + , m_bListenOnFrame (false ) + , m_bListenOnModel (false ) + , m_bPendingCloseFrame (false ) + , m_bPendingCloseModel (false ) + , m_eRunState (E_NEW ) +{ +} + +/** + @short superfluous! + @descr Releasing of memory and reference must be done inside die() call. + Otherwise it's a bug. +*/ +Job::~Job() +{ +} + +/** + @short set (or delete) a listener for sending dispatch result events + @descr Because this object is used in a wrapped mode ... the original listener + for such events can't be registered here directly. Because the + listener expect to get the original object given as source of the event. + That's why we get this source here too, to fake(!) it at sending time! + + @param xListener + the original listener for dispatch result events + + @param xSourceFake + our user, which got the registration request for this listener +*/ +void Job::setDispatchResultFake( /*IN*/ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener , + /*IN*/ const css::uno::Reference< css::uno::XInterface >& xSourceFake ) +{ + SolarMutexGuard g; + + // reject dangerous calls + if (m_eRunState != E_NEW) + { + SAL_INFO("fwk", "Job::setJobData(): job may still running or already finished"); + return; + } + + m_xResultListener = xListener; + m_xResultSourceFake = xSourceFake; +} + +void Job::setJobData( const JobData& aData ) +{ + SolarMutexGuard g; + + // reject dangerous calls + if (m_eRunState != E_NEW) + { + SAL_INFO("fwk", "Job::setJobData(): job may still running or already finished"); + return; + } + + m_aJobCfg = aData; +} + +/** + @short runs the job + @descr It doesn't matter, if the job is an asynchronous or + synchronous one. This method returns only if it was finished + or cancelled. + + @param lDynamicArgs + optional arguments for job execution + In case the represented job is a configured one (which uses static + arguments too) all information will be merged! +*/ +void Job::execute( /*IN*/ const css::uno::Sequence< css::beans::NamedValue >& lDynamicArgs ) +{ + /* SAFE { */ + class SolarMutexAntiGuard { + SolarMutexResettableGuard & m_rGuard; + public: + SolarMutexAntiGuard(SolarMutexResettableGuard & rGuard) : m_rGuard(rGuard) + { + m_rGuard.clear(); + } + ~SolarMutexAntiGuard() + { + m_rGuard.reset(); + } + }; + SolarMutexResettableGuard aWriteLock; + + // reject dangerous calls + if (m_eRunState != E_NEW) + { + SAL_INFO("fwk", "Job::execute(): job may still running or already finished"); + return; + } + + // create the environment and mark this job as running ... + m_eRunState = E_RUNNING; + impl_startListening(); + + css::uno::Reference< css::task::XAsyncJob > xAJob; + css::uno::Reference< css::task::XJob > xSJob; + css::uno::Sequence< css::beans::NamedValue > lJobArgs = impl_generateJobArgs(lDynamicArgs); + + // It's necessary to hold us self alive! + // Otherwise we might die by ref count ... + css::uno::Reference< css::task::XJobListener > xThis(this); + + try + { + // create the job + // We must check for the supported interface on demand! + // But we prefer the synchronous one ... + m_xJob = m_xContext->getServiceManager()->createInstanceWithContext(m_aJobCfg.getService(), m_xContext); + xSJob.set(m_xJob, css::uno::UNO_QUERY); + if (!xSJob.is()) + xAJob.set(m_xJob, css::uno::UNO_QUERY); + + // execute it asynchronous + if (xAJob.is()) + { + m_aAsyncWait.reset(); + SolarMutexAntiGuard const ag(aWriteLock); + /* } SAFE */ + xAJob->executeAsync(lJobArgs, xThis); + // wait for finishing this job - so this method + // does the same for synchronous and asynchronous jobs! + m_aAsyncWait.wait(); + /* SAFE { */ + // Note: Result handling was already done inside the callback! + } + // execute it synchron + else if (xSJob.is()) + { + css::uno::Any aResult; + { + SolarMutexAntiGuard const ag(aWriteLock); + /* } SAFE */ + aResult = xSJob->execute(lJobArgs); + } + /* SAFE { */ + impl_reactForJobResult(aResult); + } + } + #if OSL_DEBUG_LEVEL > 0 + catch(const css::uno::Exception&) + { + TOOLS_INFO_EXCEPTION("fwk", "Job::execute(): Got exception during job execution"); + } + #else + catch(const css::uno::Exception&) + {} + #endif + + // deinitialize the environment and mark this job as finished... + // but don't overwrite any information about STOPPED or might DISPOSED jobs! + impl_stopListening(); + if (m_eRunState == E_RUNNING) + m_eRunState = E_STOPPED_OR_FINISHED; + + // If we got a close request from our frame or model... + // but we disagreed with that by throwing a veto exception... + // and got the ownership... + // we have to close the resource frame or model now - + // and to disable ourself! + if (m_bPendingCloseFrame) + { + m_bPendingCloseFrame = false; + css::uno::Reference< css::util::XCloseable > xClose(m_xFrame, css::uno::UNO_QUERY); + if (xClose.is()) + { + try + { + xClose->close(true); + } + catch(const css::util::CloseVetoException&) {} + } + } + + if (m_bPendingCloseModel) + { + m_bPendingCloseModel = false; + css::uno::Reference< css::util::XCloseable > xClose(m_xModel, css::uno::UNO_QUERY); + if (xClose.is()) + { + try + { + xClose->close(true); + } + catch(const css::util::CloseVetoException&) {} + } + } + + aWriteLock.clear(); + /* SAFE { */ + + // release this instance ... + die(); +} + +/** + @short kill this job + @descr It doesn't matter if this request is called from inside or + from outside. We release our internal structures and stop + every activity. After doing so - this instance will not be + usable any longer! Of course we try to handle further requests + carefully. Maybe someone else holds a reference to us ... +*/ +void Job::die() +{ + SolarMutexGuard g; + + impl_stopListening(); + + if (m_eRunState != E_DISPOSED) + { + try + { + css::uno::Reference< css::lang::XComponent > xDispose(m_xJob, css::uno::UNO_QUERY); + if (xDispose.is()) + { + xDispose->dispose(); + m_eRunState = E_DISPOSED; + } + } + catch(const css::lang::DisposedException&) + { + m_eRunState = E_DISPOSED; + } + } + + m_xJob.clear(); + m_xFrame.clear(); + m_xModel.clear(); + m_xDesktop.clear(); + m_xResultListener.clear(); + m_xResultSourceFake.clear(); + m_bPendingCloseFrame = false; + m_bPendingCloseModel = false; +} + +/** + @short generates list of arguments for job execute + @descr There exist a set of information, which can be needed by a job. + a) it's static configuration data (Equals for all jobs. ) + b) it's specific configuration data (Different for every job.) + c) some environment values (e.g. the frame, for which this job was started) + d) any other dynamic data (e.g. parameters of a dispatch() request) + We collect all this information and generate one list which include all others. + + @param lDynamicArgs + list of dynamic arguments (given by a corresponding dispatch() call) + Can be empty too. + + @return A list which includes all mentioned sub lists. +*/ +css::uno::Sequence< css::beans::NamedValue > Job::impl_generateJobArgs( /*IN*/ const css::uno::Sequence< css::beans::NamedValue >& lDynamicArgs ) +{ + css::uno::Sequence< css::beans::NamedValue > lAllArgs; + + /* SAFE { */ + SolarMutexClearableGuard aReadLock; + + // the real structure of the returned list depends from the environment of this job! + JobData::EMode eMode = m_aJobCfg.getMode(); + + // Create list of environment variables. This list must be part of the + // returned structure every time... but some of its members are optional! + sal_Int32 nLen = 1; + if (m_xFrame.is()) + ++nLen; + if (m_xModel.is()) + ++nLen; + if (eMode==JobData::E_EVENT) + ++nLen; + css::uno::Sequence< css::beans::NamedValue > lEnvArgs(nLen); + auto plEnvArgs = lEnvArgs.getArray(); + plEnvArgs[0].Name = "EnvType"; + plEnvArgs[0].Value <<= m_aJobCfg.getEnvironmentDescriptor(); + + sal_Int32 i = 0; + if (m_xFrame.is()) + { + ++i; + plEnvArgs[i].Name = "Frame"; + plEnvArgs[i].Value <<= m_xFrame; + } + if (m_xModel.is()) + { + ++i; + plEnvArgs[i].Name = "Model"; + plEnvArgs[i].Value <<= m_xModel; + } + if (eMode==JobData::E_EVENT) + { + ++i; + plEnvArgs[i].Name = "EventName"; + plEnvArgs[i].Value <<= m_aJobCfg.getEvent(); + } + + // get the configuration data from the job data container ... if possible + // Means: if this job has any configuration data. Note: only really + // filled lists will be set to the return structure at the end of this method. + css::uno::Sequence< css::beans::NamedValue > lConfigArgs; + std::vector< css::beans::NamedValue > lJobConfigArgs; + if (eMode==JobData::E_ALIAS || eMode==JobData::E_EVENT) + { + lConfigArgs = m_aJobCfg.getConfig(); + lJobConfigArgs = m_aJobCfg.getJobConfig(); + } + + aReadLock.clear(); + /* } SAFE */ + + // Add all valid (not empty) lists to the return list + if (lConfigArgs.hasElements()) + { + sal_Int32 nLength = lAllArgs.getLength(); + lAllArgs.realloc(nLength+1); + auto plAllArgs = lAllArgs.getArray(); + plAllArgs[nLength].Name = "Config"; + plAllArgs[nLength].Value <<= lConfigArgs; + } + if (!lJobConfigArgs.empty()) + { + sal_Int32 nLength = lAllArgs.getLength(); + lAllArgs.realloc(nLength+1); + auto plAllArgs = lAllArgs.getArray(); + plAllArgs[nLength].Name = "JobConfig"; + plAllArgs[nLength].Value <<= comphelper::containerToSequence(lJobConfigArgs); + } + if (lEnvArgs.hasElements()) + { + sal_Int32 nLength = lAllArgs.getLength(); + lAllArgs.realloc(nLength+1); + auto plAllArgs = lAllArgs.getArray(); + plAllArgs[nLength].Name = "Environment"; + plAllArgs[nLength].Value <<= lEnvArgs; + } + if (lDynamicArgs.hasElements()) + { + sal_Int32 nLength = lAllArgs.getLength(); + lAllArgs.realloc(nLength+1); + auto plAllArgs = lAllArgs.getArray(); + plAllArgs[nLength].Name = "DynamicData"; + plAllArgs[nLength].Value <<= lDynamicArgs; + } + + return lAllArgs; +} + +/** + @short analyze the given job result and change the job configuration + @descr Note: Some results can be handled only, if this job has a valid configuration! + For "not configured jobs" (means pure services) they can be ignored. + But these cases are handled by our JobData member. We can call it every time. + It does the right things automatically. E.g. if the job has no configuration ... + it does nothing during setJobConfig()! + + @param aResult + the job result for analyzing +*/ +void Job::impl_reactForJobResult( /*IN*/ const css::uno::Any& aResult ) +{ + SolarMutexGuard g; + + // analyze the result set ... + JobResult aAnalyzedResult(aResult); + + // some of the following operations will be supported for different environments + // or different type of jobs only. + JobData::EEnvironment eEnvironment = m_aJobCfg.getEnvironment(); + + // write back the job specific configuration data ... + // If the environment allow it and if this job has a configuration! + if ( + (m_aJobCfg.hasConfig() ) && + (aAnalyzedResult.existPart(JobResult::E_ARGUMENTS)) + ) + { + m_aJobCfg.setJobConfig(aAnalyzedResult.getArguments()); + } + + // disable a job for further executions. + // Note: this option is available inside the environment EXECUTOR only + if ( +// (eEnvironment == JobData::E_EXECUTION ) && + (m_aJobCfg.hasConfig() ) && + (aAnalyzedResult.existPart(JobResult::E_DEACTIVATE)) + ) + { + m_aJobCfg.disableJob(); + } + + // notify any interested listener with the may given result state. + // Note: this option is available inside the environment DISPATCH only + if ( + (eEnvironment == JobData::E_DISPATCH ) && + (m_xResultListener.is() ) && + (aAnalyzedResult.existPart(JobResult::E_DISPATCHRESULT)) + ) + { + // Attention: Because the listener expect that the original object send this event ... + // and we nor the job are the right ones ... + // our user has set itself before. So we can fake this source address! + css::frame::DispatchResultEvent aEvent = aAnalyzedResult.getDispatchResult(); + aEvent.Source = m_xResultSourceFake; + m_xResultListener->dispatchFinished(aEvent); + } +} + +/** + @short starts listening for office shutdown and closing of our + given target frame (if it's a valid reference) + @descr We will register ourself as terminate listener + at the global desktop instance. That will hold us + alive and additional we get the information, if the + office wish to shutdown. If then an internal job + is running we will have the chance to suppress that + by throwing a veto exception. If our internal wrapped + job finished his work, we can release this listener + connection. + + Further we are listener for closing of the (possible valid) + given frame. We must be sure, that this resource won't be gone + if our internal job is still running. +*/ +void Job::impl_startListening() +{ + SolarMutexGuard g; + + // listening for office shutdown + if (!m_xDesktop.is() && !m_bListenOnDesktop) + { + try + { + m_xDesktop = css::frame::Desktop::create( m_xContext ); + css::uno::Reference< css::frame::XTerminateListener > xThis(this); + m_xDesktop->addTerminateListener(xThis); + m_bListenOnDesktop = true; + } + catch(const css::uno::Exception&) + { + m_xDesktop.clear(); + } + } + + // listening for frame closing + if (m_xFrame.is() && !m_bListenOnFrame) + { + try + { + css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xFrame , css::uno::UNO_QUERY); + css::uno::Reference< css::util::XCloseListener > xThis(this); + if (xCloseable.is()) + { + xCloseable->addCloseListener(xThis); + m_bListenOnFrame = true; + } + } + catch(const css::uno::Exception&) + { + m_bListenOnFrame = false; + } + } + + // listening for model closing + if (!m_xModel.is() || m_bListenOnModel) + return; + + try + { + css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xModel , css::uno::UNO_QUERY); + css::uno::Reference< css::util::XCloseListener > xThis(this); + if (xCloseable.is()) + { + xCloseable->addCloseListener(xThis); + m_bListenOnModel = true; + } + } + catch(const css::uno::Exception&) + { + m_bListenOnModel = false; + } +} + +/** + @short release listener connection for office shutdown + @descr see description of impl_startListening() +*/ +void Job::impl_stopListening() +{ + SolarMutexGuard g; + + // stop listening for office shutdown + if (m_xDesktop.is() && m_bListenOnDesktop) + { + try + { + css::uno::Reference< css::frame::XTerminateListener > xThis(this); + m_xDesktop->removeTerminateListener(xThis); + m_xDesktop.clear(); + m_bListenOnDesktop = false; + } + catch(const css::uno::Exception&) + { + } + } + + // stop listening for frame closing + if (m_xFrame.is() && m_bListenOnFrame) + { + try + { + css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xFrame , css::uno::UNO_QUERY); + css::uno::Reference< css::util::XCloseListener > xThis(this); + if (xCloseable.is()) + { + xCloseable->removeCloseListener(xThis); + m_bListenOnFrame = false; + } + } + catch(const css::uno::Exception&) + { + } + } + + // stop listening for model closing + if (!(m_xModel.is() && m_bListenOnModel)) + return; + + try + { + css::uno::Reference< css::util::XCloseBroadcaster > xCloseable(m_xModel , css::uno::UNO_QUERY); + css::uno::Reference< css::util::XCloseListener > xThis(this); + if (xCloseable.is()) + { + xCloseable->removeCloseListener(xThis); + m_bListenOnModel = false; + } + } + catch(const css::uno::Exception&) + { + } +} + +/** + @short callback from any asynchronous executed job + + @descr Our execute() method waits for this callback. + We have to react for the possible results here, + to kill the running job and disable the blocked condition + so execute() can be finished too. + + @param xJob + the job, which was running and inform us now + + @param aResult + its results +*/ +void SAL_CALL Job::jobFinished( /*IN*/ const css::uno::Reference< css::task::XAsyncJob >& xJob , + /*IN*/ const css::uno::Any& aResult ) +{ + SolarMutexGuard g; + + // It's necessary to check this. + // May this job was cancelled by any other reason + // some milliseconds before. :-) + if (m_xJob.is() && m_xJob==xJob) + { + // react for his results + // (means enable/disable it for further requests + // or save arguments or notify listener ...) + impl_reactForJobResult(aResult); + + // Let the job die! + m_xJob.clear(); + } + + // And let the start method "execute()" finishing it's job. + // But do it every time. So any outside blocking code can finish + // his work too. + m_aAsyncWait.set(); +} + +/** + @short prevent internal wrapped job against office termination + @descr This event is broadcasted by the desktop instance and ask for an office termination. + If the internal wrapped job is still in progress, we disagree with that by throwing the + right veto exception. If not - we agree. But then we must be aware, that another event + notifyTermination() can follow. Then we have no chance to do the same. Then we have to + accept that and stop our work instandly. + + @param aEvent + describes the broadcaster and must be the desktop instance + + @throw TerminateVetoException + if our internal wrapped job is still running. + */ +void SAL_CALL Job::queryTermination( /*IN*/ const css::lang::EventObject& ) +{ + SolarMutexGuard g; + + // Otherwise try to close() it + css::uno::Reference< css::util::XCloseable > xClose(m_xJob, css::uno::UNO_QUERY); + if (xClose.is()) + { + try + { + xClose->close(false); + m_eRunState = E_STOPPED_OR_FINISHED; + } + catch(const css::util::CloseVetoException&) {} + } + + if (m_eRunState != E_STOPPED_OR_FINISHED) + { + css::uno::Reference< css::uno::XInterface > xThis(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY); + throw css::frame::TerminationVetoException("job still in progress", xThis); + } +} + +/** + @short inform us about office termination + @descr Instead of the method queryTermination(), here is no chance to disagree with that. + We have to accept it and cancel all current processes inside. + It can occur only, if job was not already started if queryTermination() was called here. + Then we had not thrown a veto exception. But now we must agree with this situation and break + all our internal processes. It's not a good idea to mark this instance as non startable any longer + inside queryTermination() if no job was running too. Because that would disable this job and may + the office does not really shutdown, because another listener has thrown the suitable exception. + + @param aEvent + describes the broadcaster and must be the desktop instance + */ +void SAL_CALL Job::notifyTermination( /*IN*/ const css::lang::EventObject& ) +{ + die(); + // Do nothing else here. Our internal resources was released ... +} + +/** + @short prevent internal wrapped job against frame closing + @descr This event is broadcasted by the frame instance and ask for closing. + If the internal wrapped job is still in progress, we disagree with that by throwing the + right veto exception. If not - we agree. But then we must be aware, that another event + notifyClosing() can follow. Then we have no chance to do the same. Then we have to + accept that and stop our work instandly. + + @param aEvent + describes the broadcaster and must be the frame instance + + @param bGetsOwnership + If it's set to <sal_True> and we throw the right veto exception, we have to close this frame later + if our internal processes will be finished. If it's set to <FALSE/> we can ignore it. + + @throw CloseVetoException + if our internal wrapped job is still running. + */ +void SAL_CALL Job::queryClosing( const css::lang::EventObject& aEvent , + sal_Bool bGetsOwnership ) +{ + SolarMutexGuard g; + + // do nothing, if no internal job is still running ... + // The frame or model can be closed then successfully. + if (m_eRunState != E_RUNNING) + return; + + // try close() first at the job. + // The job can agree or disagree with this request. + css::uno::Reference< css::util::XCloseable > xClose(m_xJob, css::uno::UNO_QUERY); + if (xClose.is()) + { + xClose->close(bGetsOwnership); + // Here we can say: "this job was stopped successfully". Because + // no veto exception was thrown! + m_eRunState = E_STOPPED_OR_FINISHED; + return; + } + + // try dispose() then + // Here the job has no chance for a veto. + // But we must be aware of an "already disposed exception"... + try + { + css::uno::Reference< css::lang::XComponent > xDispose(m_xJob, css::uno::UNO_QUERY); + if (xDispose.is()) + { + xDispose->dispose(); + m_eRunState = E_DISPOSED; + } + } + catch(const css::lang::DisposedException&) + { + // the job was already disposed by any other mechanism !? + // But it's not interesting for us. For us this job is stopped now. + m_eRunState = E_DISPOSED; + } + + if (m_eRunState != E_DISPOSED) + { + // analyze event source - to find out, which resource called queryClosing() at this + // job wrapper. We must bind a "pending close" request to this resource. + // Closing of the corresponding resource will be done if our internal job finish it's work. + m_bPendingCloseFrame = (m_xFrame.is() && aEvent.Source == m_xFrame); + m_bPendingCloseModel = (m_xModel.is() && aEvent.Source == m_xModel); + + // throw suitable veto exception - because the internal job could not be cancelled. + css::uno::Reference< css::uno::XInterface > xThis(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY); + throw css::util::CloseVetoException("job still in progress", xThis); + } + + // No veto ... + // But don't call die() here or free our internal member. + // This must be done inside notifyClosing() only. Otherwise the + // might stopped job has no chance to return its results or + // call us back. We must give him the chance to finish it's work successfully. +} + +/** + @short inform us about frame closing + @descr Instead of the method queryClosing(), here is no chance to disagree with that. + We have to accept it and cancel all current processes inside. + + @param aEvent + describes the broadcaster and must be the frame or model instance we know + */ +void SAL_CALL Job::notifyClosing( const css::lang::EventObject& ) +{ + die(); + // Do nothing else here. Our internal resources was released ... +} + +/** + @short shouldn't be called normally + @descr But it doesn't matter, who called it. We have to kill our internal + running processes hardly. + + @param aEvent + describe the broadcaster +*/ +void SAL_CALL Job::disposing( const css::lang::EventObject& aEvent ) +{ + /* SAFE { */ + { + SolarMutexGuard aWriteLock; + + if (m_xDesktop.is() && aEvent.Source == m_xDesktop) + { + m_xDesktop.clear(); + m_bListenOnDesktop = false; + } + else if (m_xFrame.is() && aEvent.Source == m_xFrame) + { + m_xFrame.clear(); + m_bListenOnFrame = false; + } + else if (m_xModel.is() && aEvent.Source == m_xModel) + { + m_xModel.clear(); + m_bListenOnModel = false; + } + } + /* } SAFE */ + + die(); + // Do nothing else here. Our internal resources was released ... +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/jobs/jobdata.cxx b/framework/source/jobs/jobdata.cxx new file mode 100644 index 000000000..863bc8c4b --- /dev/null +++ b/framework/source/jobs/jobdata.cxx @@ -0,0 +1,563 @@ +/* -*- 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 <string_view> + +#include <jobs/configaccess.hxx> +#include <jobs/jobdata.hxx> +#include <classes/converter.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XMultiHierarchicalPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> + +#include <tools/wldcrd.hxx> +#include <unotools/configpaths.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +namespace framework{ + +/** + @short standard ctor + @descr It initialize this new instance. + But for real working it's necessary to call setAlias() or setService() later. + Because we need the job data ... + + @param rxContext + reference to the uno service manager +*/ +JobData::JobData( css::uno::Reference< css::uno::XComponentContext > xContext ) + : m_xContext (std::move(xContext )) +{ + // share code for member initialization with defaults! + impl_reset(); +} + +/** + @short copy ctor + @descr Sometimes such job data container must be moved from one using place + to another one. Then a copy ctor and copy operator must be available. + + @param rCopy + the original instance, from which we must copy all data +*/ +JobData::JobData( const JobData& rCopy ) +{ + // use the copy operator to share the same code + *this = rCopy; +} + +/** + @short operator for copying JobData instances + @descr Sometimes such job data container must be moved from one using place + to another one. Then a copy ctor and copy operator must be available. + + @param rCopy + the original instance, from which we must copy all data +*/ +JobData& JobData::operator=( const JobData& rCopy ) +{ + SolarMutexGuard g; + // Please don't copy the uno service manager reference. + // That can change the uno context, which isn't a good idea! + m_eMode = rCopy.m_eMode; + m_eEnvironment = rCopy.m_eEnvironment; + m_sAlias = rCopy.m_sAlias; + m_sService = rCopy.m_sService; + m_sContext = rCopy.m_sContext; + m_sEvent = rCopy.m_sEvent; + m_lArguments = rCopy.m_lArguments; + return *this; +} + +/** + @short let this instance die + @descr There is no chance any longer to work. We have to + release all used resources and free used memory. +*/ +JobData::~JobData() +{ + impl_reset(); +} + +/** + @short initialize this instance as a job with configuration + @descr They given alias can be used to address some configuration data. + We read it and fill our internal structures. Of course old information + will be lost doing so. + + @param sAlias + the alias name of this job, used to locate job properties inside cfg +*/ +void JobData::setAlias( const OUString& sAlias ) +{ + SolarMutexGuard g; + // delete all old information! Otherwise we mix it with the new one ... + impl_reset(); + + // take over the new information + m_sAlias = sAlias; + m_eMode = E_ALIAS; + + // try to open the configuration set of this job directly and get a property access to it + // We open it readonly here + ConfigAccess aConfig( + m_xContext, + ("/org.openoffice.Office.Jobs/Jobs/" + + utl::wrapConfigurationElementName(m_sAlias))); + aConfig.open(ConfigAccess::E_READONLY); + if (aConfig.getMode()==ConfigAccess::E_CLOSED) + { + impl_reset(); + return; + } + + css::uno::Reference< css::beans::XPropertySet > xJobProperties(aConfig.cfg(), css::uno::UNO_QUERY); + if (xJobProperties.is()) + { + css::uno::Any aValue; + + // read uno implementation name + aValue = xJobProperties->getPropertyValue("Service"); + aValue >>= m_sService; + + // read module context list + aValue = xJobProperties->getPropertyValue("Context"); + aValue >>= m_sContext; + + // read whole argument list + aValue = xJobProperties->getPropertyValue("Arguments"); + css::uno::Reference< css::container::XNameAccess > xArgumentList; + if ( + (aValue >>= xArgumentList) && + (xArgumentList.is() ) + ) + { + css::uno::Sequence< OUString > lArgumentNames = xArgumentList->getElementNames(); + sal_Int32 nCount = lArgumentNames.getLength(); + m_lArguments.resize(nCount); + for (sal_Int32 i=0; i<nCount; ++i) + { + m_lArguments[i].Name = lArgumentNames[i]; + m_lArguments[i].Value = xArgumentList->getByName(m_lArguments[i].Name); + } + } + } + + aConfig.close(); +} + +/** + @short initialize this instance as a job without configuration + @descr This job has no configuration data. We have to forget all old information + and set only some of them new, so this instance can work. + + @param sService + the uno service name of this "non configured" job +*/ +void JobData::setService( const OUString& sService ) +{ + SolarMutexGuard g; + // delete all old information! Otherwise we mix it with the new one ... + impl_reset(); + // take over the new information + m_sService = sService; + m_eMode = E_SERVICE; +} + +/** + @short initialize this instance with new job values. + @descr It reads automatically all properties of the specified + job (using it's alias name) and "register it" for the + given event. This registration will not be validated against + the underlying configuration! (That must be done from outside. + Because the caller must have the configuration already open to + get the values for sEvent and sAlias! And doing so it can perform + only, if the time stamp values are read outside too. + Further it makes no sense to initialize and start a disabled job. + So this initialization method will be called for enabled jobs only.) + + @param sEvent + the triggered event, for which this job should be started + + @param sAlias + mark the required job inside event registration list +*/ +void JobData::setEvent( const OUString& sEvent , + const OUString& sAlias ) +{ + // share code to read all job properties! + setAlias(sAlias); + + SolarMutexGuard g; + // take over the new information - which differ against set one of method setAlias()! + m_sEvent = sEvent; + m_eMode = E_EVENT; +} + +/** + @short set the new job specific arguments + @descr If a job finish his work, it can give us a new list of arguments (which + will not interpreted by us). We write it back to the configuration only + (if this job has its own configuration!). + So a job can have persistent data without implementing anything + or define own config areas for that. + + @param lArguments + list of arguments, which should be set for this job + */ +void JobData::setJobConfig( std::vector< css::beans::NamedValue >&& lArguments ) +{ + SolarMutexGuard g; + + // update member + m_lArguments = std::move(lArguments); + + // update the configuration ... if possible! + if (m_eMode!=E_ALIAS) + return; + + // It doesn't matter if this config object was already opened before. + // It doesn nothing here then ... or it change the mode automatically, if + // it was opened using another one before. + ConfigAccess aConfig( + m_xContext, + ("/org.openoffice.Office.Jobs/Jobs/" + + utl::wrapConfigurationElementName(m_sAlias))); + aConfig.open(ConfigAccess::E_READWRITE); + if (aConfig.getMode()==ConfigAccess::E_CLOSED) + return; + + css::uno::Reference< css::beans::XMultiHierarchicalPropertySet > xArgumentList(aConfig.cfg(), css::uno::UNO_QUERY); + if (xArgumentList.is()) + { + sal_Int32 nCount = m_lArguments.size(); + css::uno::Sequence< OUString > lNames (nCount); + auto lNamesRange = asNonConstRange(lNames); + css::uno::Sequence< css::uno::Any > lValues(nCount); + auto lValuesRange = asNonConstRange(lValues); + + for (sal_Int32 i=0; i<nCount; ++i) + { + lNamesRange [i] = m_lArguments[i].Name; + lValuesRange[i] = m_lArguments[i].Value; + } + + xArgumentList->setHierarchicalPropertyValues(lNames, lValues); + } + aConfig.close(); +} + +/** + @short set a new environment descriptor for this job + @descr It must(!) be done every time this container is initialized + with new job data e.g.: setAlias()/setEvent()/setService() ... + Otherwise the environment will be unknown! + */ +void JobData::setEnvironment( EEnvironment eEnvironment ) +{ + SolarMutexGuard g; + m_eEnvironment = eEnvironment; +} + +/** + @short these functions provides access to our internal members + @descr These member represent any information about the job + and can be used from outside to e.g. start a job. + */ +JobData::EMode JobData::getMode() const +{ + SolarMutexGuard g; + return m_eMode; +} + +JobData::EEnvironment JobData::getEnvironment() const +{ + SolarMutexGuard g; + return m_eEnvironment; +} + +OUString JobData::getEnvironmentDescriptor() const +{ + OUString sDescriptor; + SolarMutexGuard g; + switch(m_eEnvironment) + { + case E_EXECUTION : + sDescriptor = "EXECUTOR"; + break; + + case E_DISPATCH : + sDescriptor = "DISPATCH"; + break; + + case E_DOCUMENTEVENT : + sDescriptor = "DOCUMENTEVENT"; + break; + default: + break; + } + return sDescriptor; +} + +OUString JobData::getService() const +{ + SolarMutexGuard g; + return m_sService; +} + +OUString JobData::getEvent() const +{ + SolarMutexGuard g; + return m_sEvent; +} + +std::vector< css::beans::NamedValue > JobData::getJobConfig() const +{ + SolarMutexGuard g; + return m_lArguments; +} + +css::uno::Sequence< css::beans::NamedValue > JobData::getConfig() const +{ + SolarMutexGuard g; + css::uno::Sequence< css::beans::NamedValue > lConfig; + if (m_eMode==E_ALIAS) + { + lConfig = { { "Alias", css::uno::Any(m_sAlias) }, + { "Service", css::uno::Any(m_sService) }, + { "Context", css::uno::Any(m_sContext) } }; + } + return lConfig; +} + +/** + @short return information, if this job is part of the global configuration package + org.openoffice.Office.Jobs + @descr Because jobs can be executed by the dispatch framework using a uno service name + directly - an executed job must not have any configuration really. Such jobs + must provide the right interfaces only! But after finishing jobs can return + some information (e.g. for updating her configuration ...). We must know + if such request is valid or not then. + + @return sal_True if the represented job is part of the underlying configuration package. + */ +bool JobData::hasConfig() const +{ + SolarMutexGuard g; + return (m_eMode==E_ALIAS || m_eMode==E_EVENT); +} + +/** + @short mark a job as non startable for further requests + @descr We don't remove the configuration entry! We set a timestamp value only. + And there exist two of them: one for an administrator... and one for the + current user. We change it for the user layer only. So this JobDispatch can't be + started any more... till the administrator change his timestamp. + That can be useful for post setup scenarios, which must run one time only. + + Note: This method don't do anything, if this represented job doesn't have a configuration! + */ +void JobData::disableJob() +{ + SolarMutexGuard g; + + // No configuration - not used from EXECUTOR and not triggered from an event => no chance! + if (m_eMode!=E_EVENT) + return; + + // update the configuration + // It doesn't matter if this config object was already opened before. + // It doesn nothing here then ... or it change the mode automatically, if + // it was opened using another one before. + ConfigAccess aConfig( + m_xContext, + ("/org.openoffice.Office.Jobs/Events/" + + utl::wrapConfigurationElementName(m_sEvent) + "/JobList/" + + utl::wrapConfigurationElementName(m_sAlias))); + aConfig.open(ConfigAccess::E_READWRITE); + if (aConfig.getMode()==ConfigAccess::E_CLOSED) + return; + + css::uno::Reference< css::beans::XPropertySet > xPropSet(aConfig.cfg(), css::uno::UNO_QUERY); + if (xPropSet.is()) + { + // Convert and write the user timestamp to the configuration. + css::uno::Any aValue; + aValue <<= Converter::convert_DateTime2ISO8601(DateTime( DateTime::SYSTEM)); + xPropSet->setPropertyValue("UserTime", aValue); + } + + aConfig.close(); +} + +static bool isEnabled( std::u16string_view sAdminTime , + std::u16string_view sUserTime ) +{ + /*Attention! + To prevent interpreting of TriGraphs inside next const string value, + we have to encode all '?' signs. Otherwise e.g. "??-" will be translated + to "~" ... + */ + WildCard aISOPattern(u"\?\?\?\?-\?\?-\?\?*"); + + bool bValidAdmin = aISOPattern.Matches(sAdminTime); + bool bValidUser = aISOPattern.Matches(sUserTime ); + + // We check for "isEnabled()" here only. + // Note further: ISO8601 formatted strings can be compared as strings directly! + // FIXME: this is not true! "T1215" is the same time as "T12:15" or "T121500" + return ( + (!bValidAdmin && !bValidUser ) || + ( bValidAdmin && bValidUser && sAdminTime>=sUserTime) + ); +} + +void JobData::appendEnabledJobsForEvent( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const OUString& sEvent , + ::std::vector< JobData::TJob2DocEventBinding >& lJobs ) +{ + std::vector< OUString > lAdditionalJobs = JobData::getEnabledJobsForEvent(rxContext, sEvent); + sal_Int32 c = lAdditionalJobs.size(); + sal_Int32 i = 0; + + for (i=0; i<c; ++i) + { + JobData::TJob2DocEventBinding aBinding(lAdditionalJobs[i], sEvent); + lJobs.push_back(aBinding); + } +} + +bool JobData::hasCorrectContext(const OUString& rModuleIdent) const +{ + sal_Int32 nContextLen = m_sContext.getLength(); + sal_Int32 nModuleIdLen = rModuleIdent.getLength(); + + if ( nContextLen == 0 ) + return true; + + if ( nModuleIdLen > 0 ) + { + sal_Int32 nIndex = m_sContext.indexOf( rModuleIdent ); + if ( nIndex >= 0 && ( nIndex+nModuleIdLen <= nContextLen )) + { + std::u16string_view sContextModule = m_sContext.subView( nIndex, nModuleIdLen ); + return sContextModule == rModuleIdent; + } + } + + return false; +} + +std::vector< OUString > JobData::getEnabledJobsForEvent( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + std::u16string_view sEvent ) +{ + // create a config access to "/org.openoffice.Office.Jobs/Events" + ConfigAccess aConfig(rxContext, "/org.openoffice.Office.Jobs/Events"); + aConfig.open(ConfigAccess::E_READONLY); + if (aConfig.getMode()==ConfigAccess::E_CLOSED) + return std::vector< OUString >(); + + css::uno::Reference< css::container::XHierarchicalNameAccess > xEventRegistry(aConfig.cfg(), css::uno::UNO_QUERY); + if (!xEventRegistry.is()) + return std::vector< OUString >(); + + // check if the given event exist inside list of registered ones + OUString sPath(OUString::Concat(sEvent) + "/JobList"); + if (!xEventRegistry->hasByHierarchicalName(sPath)) + return std::vector< OUString >(); + + // step to the job list, which is a child of the event node inside cfg + // e.g. "/org.openoffice.Office.Jobs/Events/<event name>/JobList" + css::uno::Any aJobList = xEventRegistry->getByHierarchicalName(sPath); + css::uno::Reference< css::container::XNameAccess > xJobList; + if (!(aJobList >>= xJobList) || !xJobList.is()) + return std::vector< OUString >(); + + // get all alias names of jobs, which are part of this job list + // But Some of them can be disabled by its timestamp values. + // We create an additional job name list with the same size, then the original list... + // step over all job entries... check her timestamps... and put only job names to the + // destination list, which represent an enabled job. + const css::uno::Sequence< OUString > lAllJobs = xJobList->getElementNames(); + sal_Int32 c = lAllJobs.getLength(); + + std::vector< OUString > lEnabledJobs(c); + sal_Int32 d = 0; + + for (OUString const & jobName : lAllJobs) + { + css::uno::Reference< css::beans::XPropertySet > xJob; + if ( + !(xJobList->getByName(jobName) >>= xJob) || + !(xJob.is() ) + ) + { + continue; + } + + OUString sAdminTime; + xJob->getPropertyValue("AdminTime") >>= sAdminTime; + + OUString sUserTime; + xJob->getPropertyValue("UserTime") >>= sUserTime; + + if (!isEnabled(sAdminTime, sUserTime)) + continue; + + lEnabledJobs[d] = jobName; + ++d; + } + lEnabledJobs.resize(d); + + aConfig.close(); + + return lEnabledJobs; +} + +/** + @short reset all internal structures + @descr If someone recycles this instance, he can switch from one + using mode to another one. But then we have to reset all currently + used information. Otherwise we mix it and they can make trouble. + + But note: that does not set defaults for internal used members, which + does not relate to any job property! e.g. the reference to the global + uno service manager. Such information is used for internal processes only + and are necessary for our work. + */ +void JobData::impl_reset() +{ + SolarMutexGuard g; + m_eMode = E_UNKNOWN_MODE; + m_eEnvironment = E_UNKNOWN_ENVIRONMENT; + m_sAlias.clear(); + m_sService.clear(); + m_sContext.clear(); + m_sEvent.clear(); + m_lArguments.clear(); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/jobs/jobdispatch.cxx b/framework/source/jobs/jobdispatch.cxx new file mode 100644 index 000000000..d1e227b9a --- /dev/null +++ b/framework/source/jobs/jobdispatch.cxx @@ -0,0 +1,496 @@ +/* -*- 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 <jobs/configaccess.hxx> +#include <jobs/joburl.hxx> +#include <jobs/job.hxx> +#include <classes/converter.hxx> + +#include <com/sun/star/frame/DispatchResultEvent.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XNotifyingDispatch.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XStatusListener.hpp> +#include <com/sun/star/frame/XDispatchResultListener.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +using namespace framework; + +namespace { + +/** + @short implements a dispatch object for jobs + @descr Such dispatch object will be used by the generic dispatch mechanism if + a URL "vnd.sun.star.job:alias=<name>" occurs. + Then an instance of this class will be created and used. + This new instance will be called within his method + dispatch() or dispatchWithNotification() for executing the + real job. We do it, control the life cycle of this internal + wrapped job and inform any interested listener if it finish. + */ +class JobDispatch : public ::cppu::WeakImplHelper< + css::lang::XServiceInfo + , css::lang::XInitialization + , css::frame::XDispatchProvider + , css::frame::XNotifyingDispatch > // => XDispatch +{ +private: + + /** reference to the uno service manager */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + /** reference to the frame, inside which this dispatch is used */ + css::uno::Reference< css::frame::XFrame > m_xFrame; + + /** name of module (writer, impress etc.) the frame is for */ + OUString m_sModuleIdentifier; + +// native interface methods + +public: + + explicit JobDispatch(css::uno::Reference< css::uno::XComponentContext > xContext); + virtual ~JobDispatch() override; + + void impl_dispatchEvent ( const OUString& sEvent , + const css::uno::Sequence< css::beans::PropertyValue >& lArgs , + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ); + void impl_dispatchService( const OUString& sService , + const css::uno::Sequence< css::beans::PropertyValue >& lArgs , + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ); + void impl_dispatchAlias ( const OUString& sAlias , + const css::uno::Sequence< css::beans::PropertyValue >& lArgs , + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ); + +public: + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.jobs.JobDispatch"; + } + + 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.frame.ProtocolHandler"}; + } + + // Xinitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& lArguments ) override; + + // XDispatchProvider + virtual css::uno::Reference< css::frame::XDispatch > SAL_CALL queryDispatch ( const css::util::URL& aURL , + const OUString& sTargetFrameName , + sal_Int32 nSearchFlags ) override; + virtual css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptor ) override; + + // XNotifyingDispatch + virtual void SAL_CALL dispatchWithNotification( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArgs , + const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) override; + + // XDispatch + virtual void SAL_CALL dispatch ( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArgs ) override; + virtual void SAL_CALL addStatusListener ( const css::uno::Reference< css::frame::XStatusListener >& xListener , + const css::util::URL& aURL ) override; + virtual void SAL_CALL removeStatusListener( const css::uno::Reference< css::frame::XStatusListener >& xListener , + const css::util::URL& aURL ) override; +}; + +/** + @short standard ctor + @descr It initialize this new instance. + + @param xContext + reference to the uno service manager +*/ +JobDispatch::JobDispatch( /*IN*/ css::uno::Reference< css::uno::XComponentContext > xContext ) + : m_xContext (std::move(xContext )) +{ +} + +/** + @short let this instance die + @descr We have to release all used resources and free used memory. +*/ +JobDispatch::~JobDispatch() +{ + // release all used resources + m_xContext.clear(); + m_xFrame.clear(); +} + +/** + @short implementation of XInitialization + @descr A protocol handler can provide this functionality, if it wish to get additional information + about the context it runs. In this case the frame reference would be given by the outside code. + + @param lArguments + the list of initialization arguments + First parameter should be the frame reference we need. +*/ +void SAL_CALL JobDispatch::initialize( const css::uno::Sequence< css::uno::Any >& lArguments ) +{ + SolarMutexGuard g; + + for (int a=0; a<lArguments.getLength(); ++a) + { + if (a==0) + { + lArguments[a] >>= m_xFrame; + + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager = + css::frame::ModuleManager::create(m_xContext); + try + { + m_sModuleIdentifier = xModuleManager->identify( m_xFrame ); + } + catch( const css::uno::Exception& ) + {} + } + } +} + +/** + @short implementation of XDispatchProvider::queryDispatches() + @descr Every protocol handler will be asked for his agreement, if a URL was queried + for which this handler is registered. It's the chance for this handler to validate + the given URL and return a dispatch object (may be itself) or not. + + @param aURL + the queried URL, which should be checked + + @param sTargetFrameName + describes the target frame, in which context this handler will be used + Is mostly set to "", "_self", "_blank", "_default" or a non special one + using SELF/CREATE as search flags. + + @param nSearchFlags + Can be SELF or CREATE only and are set only if sTargetFrameName isn't a special target +*/ +css::uno::Reference< css::frame::XDispatch > SAL_CALL JobDispatch::queryDispatch( /*IN*/ const css::util::URL& aURL , + /*IN*/ const OUString& /*sTargetFrameName*/ , + /*IN*/ sal_Int32 /*nSearchFlags*/ ) +{ + css::uno::Reference< css::frame::XDispatch > xDispatch; + + JobURL aAnalyzedURL(aURL.Complete); + if (aAnalyzedURL.isValid()) + xDispatch = this; + + return xDispatch; +} + +/** + @short implementation of XDispatchProvider::queryDispatches() + @descr It's an optimized access for remote, so you can ask for + multiple dispatch objects at the same time. + + @param lDescriptor + a list of queryDispatch() parameter + + @return A list of corresponding dispatch objects. + NULL references are not skipped. Every result + match to one given descriptor item. +*/ +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL JobDispatch::queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptor ) +{ + // don't pack resulting list! + sal_Int32 nCount = lDescriptor.getLength(); + css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > lDispatches(nCount); + auto lDispatchesRange = asNonConstRange(lDispatches); + for (sal_Int32 i=0; i<nCount; ++i) + lDispatchesRange[i] = queryDispatch( lDescriptor[i].FeatureURL , + lDescriptor[i].FrameName , + lDescriptor[i].SearchFlags ); + return lDispatches; +} + +/** + @short implementation of XNotifyingDispatch::dispatchWithNotification() + @descr It creates the job service implementation and call execute on it. + Further it starts the life time control of it. (important for async job) + For synchronous job we react for the returned result directly ... for asynchronous + ones we do it later inside our callback method. But we use the same impl method + doing that to share the code. (see impl_finishJob()) + + If a job is already running, (it can only occur for asynchronous jobs) + don't start the same job a second time. Queue in the given dispatch parameter + and return immediately. If the current running job call us back, we will start this + new dispatch request. + If no job is running - queue the parameter too! But then start the new job immediately. + We have to queue it every time - because it hold us alive by ref count! + + @param aURL + describe the job(s), which should be started + + @param lArgs + optional arguments for this request + + @param xListener + an interested listener for possible results of this operation +*/ +void SAL_CALL JobDispatch::dispatchWithNotification( /*IN*/ const css::util::URL& aURL , + /*IN*/ const css::uno::Sequence< css::beans::PropertyValue >& lArgs , + /*IN*/ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + JobURL aAnalyzedURL(aURL.Complete); + if (aAnalyzedURL.isValid()) + { + OUString sRequest; + if (aAnalyzedURL.getEvent(sRequest)) + impl_dispatchEvent(sRequest, lArgs, xListener); + else + if (aAnalyzedURL.getService(sRequest)) + impl_dispatchService(sRequest, lArgs, xListener); + else + if (aAnalyzedURL.getAlias(sRequest)) + impl_dispatchAlias(sRequest, lArgs, xListener); + } +} + +/** + @short dispatch an event + @descr We search all registered jobs for this event and execute it. + After doing so, we inform the given listener about the results. + (There will be one notify for every executed job!) + + @param sEvent + the event, for which jobs can be registered + + @param lArgs + optional arguments for this request + Currently not used! + + @param xListener + an interested listener for possible results of this operation +*/ +void JobDispatch::impl_dispatchEvent( /*IN*/ const OUString& sEvent , + /*IN*/ const css::uno::Sequence< css::beans::PropertyValue >& lArgs , + /*IN*/ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + // get list of all enabled jobs + // The called static helper methods read it from the configuration and + // filter disabled jobs using it's time stamp values. + /* SAFE { */ + SolarMutexResettableGuard aReadLock; + std::vector< OUString > lJobs = JobData::getEnabledJobsForEvent(m_xContext, sEvent); + aReadLock.clear(); + /* } SAFE */ + + css::uno::Reference< css::frame::XDispatchResultListener > xThis( static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY ); + + // no jobs... no execution + // But a may given listener will know something... + // I think this operation was finished successfully. + // It's not really an error, if no registered jobs could be located. + // Step over all found jobs and execute it + int nExecutedJobs=0; + for (const OUString & lJob : lJobs) + { + /* SAFE { */ + aReadLock.reset(); + + JobData aCfg(m_xContext); + aCfg.setEvent(sEvent, lJob); + aCfg.setEnvironment(JobData::E_DISPATCH); + const bool bIsEnabled=aCfg.hasCorrectContext(m_sModuleIdentifier); + + rtl::Reference<Job> pJob = new Job(m_xContext, m_xFrame); + pJob->setJobData(aCfg); + + aReadLock.clear(); + /* } SAFE */ + + if (!bIsEnabled) + continue; + + // Special mode for listener. + // We don't notify it directly here. We delegate that + // to the job implementation. But we must set ourself there too. + // Because this job must fake the source address of the event. + // Otherwise the listener may ignore it. + if (xListener.is()) + pJob->setDispatchResultFake(xListener, xThis); + pJob->execute(Converter::convert_seqPropVal2seqNamedVal(lArgs)); + ++nExecutedJobs; + } + + if (nExecutedJobs<1 && xListener.is()) + { + css::frame::DispatchResultEvent aEvent; + aEvent.Source = xThis; + aEvent.State = css::frame::DispatchResultState::SUCCESS; + xListener->dispatchFinished(aEvent); + } +} + +/** + @short dispatch a service + @descr We use the given name only to create and if possible to initialize + it as a uno service. It can be useful for creating (caching?) + of e.g. one instance services. + + @param sService + the uno implementation or service name of the job, which should be instantiated + + @param lArgs + optional arguments for this request + Currently not used! + + @param xListener + an interested listener for possible results of this operation +*/ +void JobDispatch::impl_dispatchService( /*IN*/ const OUString& sService , + /*IN*/ const css::uno::Sequence< css::beans::PropertyValue >& lArgs , + /*IN*/ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + /* SAFE { */ + SolarMutexClearableGuard aReadLock; + + JobData aCfg(m_xContext); + aCfg.setService(sService); + aCfg.setEnvironment(JobData::E_DISPATCH); + + /*Attention! + Jobs implements interfaces and dies by ref count! + And freeing of such uno object is done by uno itself. + So we have to use dynamic memory everytimes. + */ + rtl::Reference<Job> pJob = new Job(m_xContext, m_xFrame); + pJob->setJobData(aCfg); + + aReadLock.clear(); + /* } SAFE */ + + css::uno::Reference< css::frame::XDispatchResultListener > xThis( static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY ); + + // Special mode for listener. + // We don't notify it directly here. We delegate that + // to the job implementation. But we must set ourself there too. + // Because this job must fake the source address of the event. + // Otherwise the listener may ignore it. + if (xListener.is()) + pJob->setDispatchResultFake(xListener, xThis); + pJob->execute(Converter::convert_seqPropVal2seqNamedVal(lArgs)); +} + +/** + @short dispatch an alias + @descr We use this alias to locate a job inside the configuration + and execute it. Further we inform the given listener about the results. + + @param sAlias + the alias name of the configured job + + @param lArgs + optional arguments for this request + Currently not used! + + @param xListener + an interested listener for possible results of this operation +*/ +void JobDispatch::impl_dispatchAlias( /*IN*/ const OUString& sAlias , + /*IN*/ const css::uno::Sequence< css::beans::PropertyValue >& lArgs , + /*IN*/ const css::uno::Reference< css::frame::XDispatchResultListener >& xListener ) +{ + /* SAFE { */ + SolarMutexClearableGuard aReadLock; + + JobData aCfg(m_xContext); + aCfg.setAlias(sAlias); + aCfg.setEnvironment(JobData::E_DISPATCH); + + rtl::Reference<Job> pJob = new Job(m_xContext, m_xFrame); + pJob->setJobData(aCfg); + + aReadLock.clear(); + /* } SAFE */ + + css::uno::Reference< css::frame::XDispatchResultListener > xThis( static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY ); + + // Special mode for listener. + // We don't notify it directly here. We delegate that + // to the job implementation. But we must set ourself there too. + // Because this job must fake the source address of the event. + // Otherwise the listener may ignore it. + if (xListener.is()) + pJob->setDispatchResultFake(xListener, xThis); + pJob->execute(Converter::convert_seqPropVal2seqNamedVal(lArgs)); +} + +/** + @short implementation of XDispatch::dispatch() + @descr Because the methods dispatch() and dispatchWithNotification() are different in her parameters + only, we can forward this request to dispatchWithNotification() by using an empty listener! + + @param aURL + describe the job(s), which should be started + + @param lArgs + optional arguments for this request + + @see dispatchWithNotification() +*/ +void SAL_CALL JobDispatch::dispatch( /*IN*/ const css::util::URL& aURL , + /*IN*/ const css::uno::Sequence< css::beans::PropertyValue >& lArgs ) +{ + dispatchWithNotification(aURL, lArgs, css::uno::Reference< css::frame::XDispatchResultListener >()); +} + +/** + @short not supported +*/ +void SAL_CALL JobDispatch::addStatusListener( /*IN*/ const css::uno::Reference< css::frame::XStatusListener >&, + /*IN*/ const css::util::URL& ) +{ +} + +/** + @short not supported +*/ +void SAL_CALL JobDispatch::removeStatusListener( /*IN*/ const css::uno::Reference< css::frame::XStatusListener >&, + /*IN*/ const css::util::URL& ) +{ +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_jobs_JobDispatch_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new JobDispatch(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/jobs/jobexecutor.cxx b/framework/source/jobs/jobexecutor.cxx new file mode 100644 index 000000000..5578b877b --- /dev/null +++ b/framework/source/jobs/jobexecutor.cxx @@ -0,0 +1,399 @@ +/* -*- 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 <jobs/job.hxx> +#include <jobs/configaccess.hxx> +#include <classes/converter.hxx> + +#include <helper/mischelper.hxx> + +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XContainer.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/task/XJobExecutor.hpp> +#include <com/sun/star/container/XContainerListener.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/document/XEventListener.hpp> + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/configpaths.hxx> +#include <rtl/ref.hxx> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> + +using namespace framework; + +namespace { + +typedef cppu::WeakComponentImplHelper< + css::lang::XServiceInfo + , css::task::XJobExecutor + , css::container::XContainerListener // => lang.XEventListener + , css::document::XEventListener > + Base; + +/** + @short implements a job executor, which can be triggered from any code + @descr It uses the given trigger event to locate any registered job service + inside the configuration and execute it. Of course it controls the + lifetime of such jobs too. + */ +class JobExecutor : private cppu::BaseMutex, public Base +{ +private: + + /** reference to the uno service manager */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + /** cached list of all registered event names of cfg for call optimization. */ + std::vector<OUString> m_lEvents; + + /** we listen at the configuration for changes at the event list. */ + ConfigAccess m_aConfig; + + /** helper to allow us listen to the configuration without a cyclic dependency */ + css::uno::Reference<css::container::XContainerListener> m_xConfigListener; + + virtual void SAL_CALL disposing() final override; + +public: + + explicit JobExecutor(const css::uno::Reference< css::uno::XComponentContext >& xContext); + virtual ~JobExecutor() override; + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.JobExecutor"; + } + + 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.task.JobExecutor"}; + } + + // task.XJobExecutor + virtual void SAL_CALL trigger( const OUString& sEvent ) override; + + /// Initialization function after having acquire()'d. + void initListeners(); + + // document.XEventListener + virtual void SAL_CALL notifyEvent( const css::document::EventObject& aEvent ) override; + + // container.XContainerListener + virtual void SAL_CALL elementInserted( const css::container::ContainerEvent& aEvent ) override; + virtual void SAL_CALL elementRemoved ( const css::container::ContainerEvent& aEvent ) override; + virtual void SAL_CALL elementReplaced( const css::container::ContainerEvent& aEvent ) override; + + // lang.XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& aEvent ) override; +}; + +/** + @short standard ctor + @descr It initialize this new instance. + + @param xContext + reference to the uno service manager + */ +JobExecutor::JobExecutor( /*IN*/ const css::uno::Reference< css::uno::XComponentContext >& xContext ) + : Base (m_aMutex) + , m_xContext (xContext ) + , m_aConfig (xContext, "/org.openoffice.Office.Jobs/Events") +{ +} + +void JobExecutor::initListeners() +{ + if (utl::ConfigManager::IsFuzzing()) + return; + + // read the list of all currently registered events inside configuration. + // e.g. "/org.openoffice.Office.Jobs/Events/<event name>" + // We need it later to check if an incoming event request can be executed successfully + // or must be rejected. It's an optimization! Of course we must implement updating of this + // list too ... Be listener at the configuration. + + m_aConfig.open(ConfigAccess::E_READONLY); + if (m_aConfig.getMode() != ConfigAccess::E_READONLY) + return; + + css::uno::Reference< css::container::XNameAccess > xRegistry( + m_aConfig.cfg(), css::uno::UNO_QUERY); + if (xRegistry.is()) + m_lEvents = Converter::convert_seqOUString2OUStringList( + xRegistry->getElementNames()); + + css::uno::Reference< css::container::XContainer > xNotifier( + m_aConfig.cfg(), css::uno::UNO_QUERY); + if (xNotifier.is()) + { + m_xConfigListener = new WeakContainerListener(this); + xNotifier->addContainerListener(m_xConfigListener); + } + + // don't close cfg here! + // It will be done inside disposing ... +} + +JobExecutor::~JobExecutor() +{ + disposing(); +} + +void JobExecutor::disposing() { + css::uno::Reference<css::container::XContainer> notifier; + css::uno::Reference<css::container::XContainerListener> listener; + { + osl::MutexGuard g(rBHelper.rMutex); + if (m_aConfig.getMode() != ConfigAccess::E_CLOSED) { + notifier.set(m_aConfig.cfg(), css::uno::UNO_QUERY); + listener = m_xConfigListener; + m_aConfig.close(); + } + m_xConfigListener.clear(); + } + if (notifier.is()) { + notifier->removeContainerListener(listener); + } +} + +/** + @short implementation of XJobExecutor interface + @descr We use the given event to locate any registered job inside our configuration + and execute it. Further we control the lifetime of it and suppress + shutdown of the office till all jobs was finished. + + @param sEvent + is used to locate registered jobs + */ +void SAL_CALL JobExecutor::trigger( const OUString& sEvent ) +{ + SAL_INFO( "fwk", "JobExecutor::trigger()"); + + std::vector< OUString > lJobs; + + /* SAFE */ { + osl::MutexGuard g(rBHelper.rMutex); + + // Optimization! + // Check if the given event name exist inside configuration and reject wrong requests. + // This optimization suppress using of the cfg api for getting event and job descriptions ... + if (std::find(m_lEvents.begin(), m_lEvents.end(), sEvent) == m_lEvents.end()) + return; + + // get list of all enabled jobs + // The called static helper methods read it from the configuration and + // filter disabled jobs using it's time stamp values. + lJobs = JobData::getEnabledJobsForEvent(m_xContext, sEvent); + } /* SAFE */ + + // step over all enabled jobs and execute it + size_t c = lJobs.size(); + for (size_t j=0; j<c; ++j) + { + rtl::Reference<Job> pJob; + + /* SAFE */ + { + SolarMutexGuard g2; + + JobData aCfg(m_xContext); + aCfg.setEvent(sEvent, lJobs[j]); + aCfg.setEnvironment(JobData::E_EXECUTION); + + /*Attention! + Jobs implements interfaces and dies by ref count! + And freeing of such uno object is done by uno itself. + So we have to use dynamic memory everytimes. + */ + pJob = new Job(m_xContext, css::uno::Reference< css::frame::XFrame >()); + pJob->setJobData(aCfg); + } /* SAFE */ + + pJob->execute(css::uno::Sequence< css::beans::NamedValue >()); + } +} + +void SAL_CALL JobExecutor::notifyEvent( const css::document::EventObject& aEvent ) +{ + static constexpr OUStringLiteral EVENT_ON_DOCUMENT_OPENED(u"onDocumentOpened"); // Job UI event : OnNew or OnLoad + static constexpr OUStringLiteral EVENT_ON_DOCUMENT_ADDED(u"onDocumentAdded"); // Job API event : OnCreate or OnLoadFinished + + OUString aModuleIdentifier; + ::std::vector< JobData::TJob2DocEventBinding > lJobs; + + /* SAFE */ { + osl::MutexGuard g(rBHelper.rMutex); + + // Optimization! + // Check if the given event name exist inside configuration and reject wrong requests. + // This optimization suppress using of the cfg api for getting event and job descriptions. + // see using of m_lEvents.find() below ... + + // retrieve event context from event source + try + { + aModuleIdentifier = css::frame::ModuleManager::create( m_xContext )->identify( aEvent.Source ); + } + catch( const css::uno::Exception& ) + {} + + // Special feature: If the events "OnNew" or "OnLoad" occurs - we generate our own event "onDocumentOpened". + if ( + (aEvent.EventName == "OnNew") || + (aEvent.EventName == "OnLoad") + ) + { + if (std::find(m_lEvents.begin(), m_lEvents.end(), EVENT_ON_DOCUMENT_OPENED) != m_lEvents.end()) + JobData::appendEnabledJobsForEvent(m_xContext, EVENT_ON_DOCUMENT_OPENED, lJobs); + } + + // Special feature: If the events "OnCreate" or "OnLoadFinished" occurs - we generate our own event "onDocumentAdded". + if ( + (aEvent.EventName == "OnCreate") || + (aEvent.EventName == "OnLoadFinished") + ) + { + if (std::find(m_lEvents.begin(), m_lEvents.end(), EVENT_ON_DOCUMENT_ADDED) != m_lEvents.end()) + JobData::appendEnabledJobsForEvent(m_xContext, EVENT_ON_DOCUMENT_ADDED, lJobs); + } + + // Add all jobs for "real" notified event too .-) + if (std::find(m_lEvents.begin(), m_lEvents.end(), aEvent.EventName) != m_lEvents.end()) + JobData::appendEnabledJobsForEvent(m_xContext, aEvent.EventName, lJobs); + } /* SAFE */ + + // step over all enabled jobs and execute it + for (auto const& lJob : lJobs) + { + rtl::Reference<Job> pJob; + + /* SAFE */ { + SolarMutexGuard g2; + + const JobData::TJob2DocEventBinding& rBinding = lJob; + + JobData aCfg(m_xContext); + aCfg.setEvent(rBinding.m_sDocEvent, rBinding.m_sJobName); + aCfg.setEnvironment(JobData::E_DOCUMENTEVENT); + + if (!aCfg.hasCorrectContext(aModuleIdentifier)) + continue; + + /*Attention! + Jobs implements interfaces and dies by ref count! + And freeing of such uno object is done by uno itself. + So we have to use dynamic memory everytimes. + */ + css::uno::Reference< css::frame::XModel > xModel(aEvent.Source, css::uno::UNO_QUERY); + pJob = new Job(m_xContext, xModel); + pJob->setJobData(aCfg); + } /* SAFE */ + + pJob->execute(css::uno::Sequence< css::beans::NamedValue >()); + } +} + +void SAL_CALL JobExecutor::elementInserted( const css::container::ContainerEvent& aEvent ) +{ + OUString sValue; + if (aEvent.Accessor >>= sValue) + { + OUString sEvent = ::utl::extractFirstFromConfigurationPath(sValue); + if (!sEvent.isEmpty()) + { + std::vector<OUString>::iterator pEvent = std::find(m_lEvents.begin(), m_lEvents.end(), sEvent); + if (pEvent == m_lEvents.end()) + m_lEvents.push_back(sEvent); + } + } +} + +void SAL_CALL JobExecutor::elementRemoved ( const css::container::ContainerEvent& aEvent ) +{ + OUString sValue; + if (aEvent.Accessor >>= sValue) + { + OUString sEvent = ::utl::extractFirstFromConfigurationPath(sValue); + if (!sEvent.isEmpty()) + { + std::vector<OUString>::iterator pEvent = std::find(m_lEvents.begin(), m_lEvents.end(), sEvent); + if (pEvent != m_lEvents.end()) + m_lEvents.erase(pEvent); + } + } +} + +void SAL_CALL JobExecutor::elementReplaced( const css::container::ContainerEvent& ) +{ + // I'm not interested on changed items :-) +} + +/** @short the used cfg changes notifier wish to be released in its reference. + + @descr We close our internal used configuration instance to + free this reference. + + @attention For the special feature "bind global document event broadcaster to job execution" + this job executor instance was registered from outside code as + css.document.XEventListener. So it can be, that this disposing call comes from + the global event broadcaster service. But we don't hold any reference to this service + which can or must be released. Because this broadcaster itself is a one instance service + too, we can ignore this request. On the other side we must release our internal CFG + reference... SOLUTION => check the given event source and react only, if it's our internal + hold configuration object! + */ +void SAL_CALL JobExecutor::disposing( const css::lang::EventObject& aEvent ) +{ + /* SAFE { */ + osl::MutexGuard g(rBHelper.rMutex); + css::uno::Reference< css::uno::XInterface > xCFG(m_aConfig.cfg(), css::uno::UNO_QUERY); + if ( + (xCFG == aEvent.Source ) && + (m_aConfig.getMode() != ConfigAccess::E_CLOSED) + ) + { + m_aConfig.close(); + } + /* } SAFE */ +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_JobExecutor_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + rtl::Reference<JobExecutor> xJobExec = new JobExecutor(context); + // 2nd phase initialization needed + xJobExec->initListeners(); + return cppu::acquire(xJobExec.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/jobs/jobresult.cxx b/framework/source/jobs/jobresult.cxx new file mode 100644 index 000000000..58cd2b594 --- /dev/null +++ b/framework/source/jobs/jobresult.cxx @@ -0,0 +1,192 @@ +/* -*- 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 <jobs/jobresult.hxx> +#include <jobs/jobconst.hxx> + +#include <vcl/svapp.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/sequence.hxx> + +namespace framework +{ +/** + @short special ctor + @descr It initialize this new instance with a pure job execution result + and analyze it. Doing so, we update our other members. + + <p> + It's a list of named values, packed inside this any. + Following protocol is used: + <p> + <ul> + <li> + "SaveArguments" [sequence< css.beans.NamedValue >] + <br> + The returned list of (for this generic implementation unknown!) + properties, will be written directly to the configuration and replace + any old values there. There will no check for changes and we don't + support any merge feature here. They are written only. The job has + to modify this list. + </li> + <li> + "SendDispatchResult" [css.frame.DispatchResultEvent] + <br> + The given event is send to all current registered listener. + But it's not guaranteed. In case no listener are available or + this job isn't part of the dispatch environment (because it was used + by the css..task.XJobExecutor->trigger() implementation) this option + will be ignored. + </li> + <li> + "Deactivate" [boolean] + <br> + The job wish to be disabled. But note: There is no way, to enable it later + again by using this implementation. It can be done by using the configuration + only. (Means to register this job again.) + If a job knows, that there exist some status or result listener, it must use + the options "SendDispatchStatus" and "SendDispatchResult" (see before) too, to + inform it about the deactivation of this service. + </li> + </ul> + + @param aResult + the job result +*/ +JobResult::JobResult(/*IN*/ const css::uno::Any& aResult) +{ + // reset the flag mask! + // It will reset the accessible state of this object. + // That can be useful if something will fail here ... + m_eParts = E_NOPART; + + // analyze the result and update our other members + ::comphelper::SequenceAsHashMap aProtocol(aResult); + if (aProtocol.empty()) + return; + + ::comphelper::SequenceAsHashMap::const_iterator pIt + = aProtocol.find(JobConst::ANSWER_DEACTIVATE_JOB); + if (pIt != aProtocol.end()) + { + /** + an executed job can force his deactivation + But we provide this information here only. + Doing so is part of any user of us. + */ + bool bDeactivate(false); + pIt->second >>= bDeactivate; + if (bDeactivate) + m_eParts |= E_DEACTIVATE; + } + + pIt = aProtocol.find(JobConst::ANSWER_SAVE_ARGUMENTS); + if (pIt != aProtocol.end()) + { + css::uno::Sequence<css::beans::NamedValue> aTmp; + pIt->second >>= aTmp; + comphelper::sequenceToContainer(m_lArguments, aTmp); + if (m_lArguments.empty()) + m_eParts |= E_ARGUMENTS; + } + + pIt = aProtocol.find(JobConst::ANSWER_SEND_DISPATCHRESULT); + if (pIt != aProtocol.end()) + { + if (pIt->second >>= m_aDispatchResult) + m_eParts |= E_DISPATCHRESULT; + } +} + +/** + @short copy dtor +*/ +JobResult::JobResult(const JobResult& rCopy) +{ + m_eParts = rCopy.m_eParts; + m_lArguments = rCopy.m_lArguments; + m_aDispatchResult = rCopy.m_aDispatchResult; +} + +/** + @short standard dtor + @descr Free all internally used resources at the end of living. +*/ +JobResult::~JobResult() +{ + // Nothing really to do here. +} + +/** + @short =operator + @descr Must be implemented to overwrite this instance with another one. + + @param rCopy + reference to the other instance, which should be used for copying. +*/ +JobResult& JobResult::operator=(const JobResult& rCopy) +{ + SolarMutexGuard g; + m_eParts = rCopy.m_eParts; + m_lArguments = rCopy.m_lArguments; + m_aDispatchResult = rCopy.m_aDispatchResult; + return *this; +} + +/** + @short checks for existing parts of the analyzed result + @descr The internal flag mask was set after analyzing of the pure result. + An user of us can check here, if the required part was really part + of this result. Otherwise it would use invalid information ... + by using our other members! + + @param eParts + a flag mask too, which will be compared with our internally set one. + + @return We return true only, if any set flag of the given mask match. +*/ +bool JobResult::existPart(sal_uInt32 eParts) const +{ + SolarMutexGuard g; + return ((m_eParts & eParts) == eParts); +} + +/** + @short provides access to our internal members + @descr The return value will be valid only in case a call of + existPart(E_...) before returned true! + + @return It returns the state of the internal member + without any checks! +*/ +std::vector<css::beans::NamedValue> JobResult::getArguments() const +{ + SolarMutexGuard g; + return m_lArguments; +} + +css::frame::DispatchResultEvent JobResult::getDispatchResult() const +{ + SolarMutexGuard g; + return m_aDispatchResult; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/jobs/joburl.cxx b/framework/source/jobs/joburl.cxx new file mode 100644 index 000000000..efff201a8 --- /dev/null +++ b/framework/source/jobs/joburl.cxx @@ -0,0 +1,253 @@ +/* -*- 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 <cstring> + +#include <jobs/joburl.hxx> + +#include <vcl/svapp.hxx> + +namespace framework{ + +/** + @short special ctor + @descr It initialize this new instance with a (hopefully) valid job URL. + This URL will be parsed. After that we set our members right, + so other interface methods of this class can be used to get + all items of this URL. Of course it will be possible to know, + if this URL was valid too. + + @param sURL + the job URL for parsing +*/ +JobURL::JobURL( /*IN*/ const OUString& sURL ) +{ + m_eRequest = E_UNKNOWN; + + // syntax: vnd.sun.star.job:{[event=<name>],[alias=<name>],[service=<name>]} + + // check for "vnd.sun.star.job:" + if (!sURL.startsWithIgnoreAsciiCase("vnd.sun.star.job:")) + return; + + sal_Int32 t = std::strlen("vnd.sun.star.job:"); + do + { + // separate all token of "{[event=<name>],[alias=<name>],[service=<name>]}" + OUString sToken = sURL.getToken(0, JOBURL_PART_SEPARATOR, t); + OUString sPartValue; + OUString sPartArguments; + + // check for "event=" + if ( + (JobURL::implst_split(sToken,JOBURL_EVENT_STR,JOBURL_EVENT_LEN,sPartValue,sPartArguments)) && + (!sPartValue.isEmpty()) + ) + { + // set the part value + m_sEvent = sPartValue; + m_eRequest |= E_EVENT; + } + else + // check for "alias=" + if ( + (JobURL::implst_split(sToken,JOBURL_ALIAS_STR,JOBURL_ALIAS_LEN,sPartValue,sPartArguments)) && + (!sPartValue.isEmpty()) + ) + { + // set the part value + m_sAlias = sPartValue; + m_eRequest |= E_ALIAS; + } + else + // check for "service=" + if ( + (JobURL::implst_split(sToken,JOBURL_SERVICE_STR,JOBURL_SERVICE_LEN,sPartValue,sPartArguments)) && + (!sPartValue.isEmpty()) + ) + { + // set the part value + m_sService = sPartValue; + m_eRequest |= E_SERVICE; + } + } + while(t!=-1); +} + +/** + @short knows, if this job URL object hold a valid URL inside + + @return <TRUE/> if it represent a valid job URL. +*/ +bool JobURL::isValid() const +{ + SolarMutexGuard g; + return (m_eRequest!=E_UNKNOWN); +} + +/** + @short get the event item of this job URL + @descr Because the three possible parts of such URL (event, alias, service) + can't be combined, this method can(!) return a valid value - but it's + not a must. That's why the return value must be used too, to detect a missing + event value. + + @param sEvent + returns the possible existing event value + e.g. "vnd.sun.star.job:event=myEvent" returns "myEvent" + + @return <TRUE/> if an event part of the job URL exist and the out parameter + sEvent was filled. + + @attention The out parameter will be reset every time. Don't use it if method returns <FALSE/>! +*/ +bool JobURL::getEvent( /*OUT*/ OUString& sEvent ) const +{ + SolarMutexGuard g; + + sEvent.clear(); + bool bSet = ((m_eRequest & E_EVENT) == E_EVENT); + if (bSet) + sEvent = m_sEvent; + + return bSet; +} + +/** + @short get the alias item of this job URL + @descr Because the three possible parts of such URL (event, alias, service) + can't be combined, this method can(!) return a valid value - but it's + not a must. that's why the return value must be used too, to detect a missing + alias value. + + @param sAlias + returns the possible existing alias value + e.g. "vnd.sun.star.job:alias=myAlias" returns "myAlias" + + @return <TRUE/> if an alias part of the job URL exist and the out parameter + sAlias was filled. + + @attention The out parameter will be reset every time. Don't use it if method returns <FALSE/>! +*/ +bool JobURL::getAlias( /*OUT*/ OUString& sAlias ) const +{ + SolarMutexGuard g; + + sAlias.clear(); + bool bSet = ((m_eRequest & E_ALIAS) == E_ALIAS); + if (bSet) + sAlias = m_sAlias; + + return bSet; +} + +/** + @short get the service item of this job URL + @descr Because the three possible parts of such URL (event, service, service) + can't be combined, this method can(!) return a valid value - but it's + not a must. That's why the return value must be used too, to detect a missing + service value. + + @param sAlias + returns the possible existing service value + e.g. "vnd.sun.star.job:service=com.sun.star.Service" returns "com.sun.star.Service" + + @return <TRUE/> if a service part of the job URL exist and the out parameter + sService was filled. + + @attention The out parameter will be reset every time. Don't use it if method returns <FALSE/>! +*/ +bool JobURL::getService( /*OUT*/ OUString& sService ) const +{ + SolarMutexGuard g; + + sService.clear(); + bool bSet = ((m_eRequest & E_SERVICE) == E_SERVICE); + if (bSet) + sService = m_sService; + + return bSet; +} + +/** + @short searches for a special identifier in the given string and split it + @descr If the given identifier could be found at the beginning of the given string, + this method split it into different parts and return it. + Following schema is used: <partidentifier>=<partvalue>[?<partarguments>] + + @param sPart + the string, which should be analyzed + + @param pPartIdentifier + the part identifier value, which must be found at the beginning of the + parameter <var>sPart</var> + + @param nPartLength + the length of the ascii value <var>pPartIdentifier</var> + + @param rPartValue + returns the part value if <var>sPart</var> was split successfully + + @param rPartArguments + returns the part arguments if <var>sPart</var> was split successfully + + @return <TRUE/> if the identifier could be found and the string was split. + <FALSE/> otherwise. +*/ +bool JobURL::implst_split( /*IN*/ const OUString& sPart , + /*IN*/ const char* pPartIdentifier , + /*IN*/ sal_Int32 nPartLength , + /*OUT*/ OUString& rPartValue , + /*OUT*/ OUString& rPartArguments ) +{ + // first search for the given identifier + bool bPartFound = sPart.matchIgnoreAsciiCaseAsciiL(pPartIdentifier,nPartLength); + + // If it exist - we can split the part and return sal_True. + // Otherwise we do nothing and return sal_False. + if (bPartFound) + { + // But may the part has optional arguments - separated by a "?". + // Do so - we set the return value with the whole part string. + // Arguments will be set to an empty string as default. + // If we detect the right sign - we split the arguments and overwrite the default. + OUString sValueAndArguments = sPart.copy(nPartLength); + OUString sValue = sValueAndArguments; + OUString sArguments; + + sal_Int32 nArgStart = sValueAndArguments.indexOf('?'); + if (nArgStart!=-1) + { + sValue = sValueAndArguments.copy(0,nArgStart); + ++nArgStart; // ignore '?'! + sArguments = sValueAndArguments.copy(nArgStart); + } + + rPartValue = sValue; + rPartArguments = sArguments; + } + + return bPartFound; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/jobs/shelljob.cxx b/framework/source/jobs/shelljob.cxx new file mode 100644 index 000000000..0c895db33 --- /dev/null +++ b/framework/source/jobs/shelljob.cxx @@ -0,0 +1,168 @@ +/* -*- 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 own header + +#include <jobs/shelljob.hxx> +#include <jobs/jobconst.hxx> +#include <services.h> + +// include others + +#include <osl/process.h> +#include <comphelper/sequenceashashmap.hxx> + +// include interfaces + +#include <com/sun/star/util/PathSubstitution.hpp> +#include <com/sun/star/util/XStringSubstitution.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <utility> + +namespace framework{ + + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL ShellJob::getImplementationName() +{ + return "com.sun.star.comp.framework.ShellJob"; +} + +sal_Bool SAL_CALL ShellJob::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL ShellJob::getSupportedServiceNames() +{ + return { SERVICENAME_JOB }; +} + + +ShellJob::ShellJob(css::uno::Reference< css::uno::XComponentContext > xContext) + : m_xContext (std::move(xContext)) +{ +} + +ShellJob::~ShellJob() +{ +} + +css::uno::Any SAL_CALL ShellJob::execute(const css::uno::Sequence< css::beans::NamedValue >& lJobArguments) +{ + ::comphelper::SequenceAsHashMap lArgs (lJobArguments); + /** address job configuration inside argument set provided on method execute(). */ + ::comphelper::SequenceAsHashMap lOwnCfg(lArgs.getUnpackedValueOrDefault("JobConfig", css::uno::Sequence< css::beans::NamedValue >())); + + const OUString sCommand = lOwnCfg.getUnpackedValueOrDefault("Command" , OUString()); + const css::uno::Sequence< OUString > lCommandArguments = lOwnCfg.getUnpackedValueOrDefault("Arguments" , css::uno::Sequence< OUString >()); + const bool bDeactivateJobIfDone = lOwnCfg.getUnpackedValueOrDefault("DeactivateJobIfDone" , true ); + const bool bCheckExitCode = lOwnCfg.getUnpackedValueOrDefault("CheckExitCode" , true ); + + // replace all might existing place holder. + OUString sRealCommand = impl_substituteCommandVariables(sCommand); + + // Command is required as minimum. + // If it does not exists ... we can't do our job. + // Deactivate such miss configured job silently .-) + if (sRealCommand.isEmpty()) + return ShellJob::impl_generateAnswer4Deactivation(); + + // do it + bool bDone = impl_execute(sRealCommand, lCommandArguments, bCheckExitCode); + if (! bDone) + return css::uno::Any(); + + // Job was done ... user configured deactivation of this job + // in such case. + if (bDeactivateJobIfDone) + return ShellJob::impl_generateAnswer4Deactivation(); + + // There was no decision about deactivation of this job. + // So we have to return nothing here ! + return css::uno::Any(); +} + +css::uno::Any ShellJob::impl_generateAnswer4Deactivation() +{ + css::uno::Sequence< css::beans::NamedValue > aAnswer { { JobConst::ANSWER_DEACTIVATE_JOB, css::uno::Any(true) } }; + return css::uno::Any(aAnswer); +} + +OUString ShellJob::impl_substituteCommandVariables(const OUString& sCommand) +{ + try + { + css::uno::Reference< css::util::XStringSubstitution > xSubst( css::util::PathSubstitution::create(m_xContext) ); + const bool bSubstRequired = true; + const OUString sCompleteCommand = xSubst->substituteVariables(sCommand, bSubstRequired); + + return sCompleteCommand; + } + catch(const css::uno::Exception&) + {} + + return OUString(); +} + +bool ShellJob::impl_execute(const OUString& sCommand , + const css::uno::Sequence< OUString >& lArguments , + bool bCheckExitCode) +{ + ::rtl_uString** pArgs = nullptr; + const ::sal_Int32 nArgs = lArguments.getLength (); + oslProcess hProcess(nullptr); + + if (nArgs > 0) + pArgs = reinterpret_cast< ::rtl_uString** >(const_cast< OUString* >(lArguments.getConstArray())); + + oslProcessError eError = osl_executeProcess(sCommand.pData, pArgs, nArgs, osl_Process_WAIT, nullptr, nullptr, nullptr, 0, &hProcess); + + // executable not found or couldn't be started + if (eError != osl_Process_E_None) + return false; + + bool bRet = true; + if (bCheckExitCode) + { + // check its return codes ... + oslProcessInfo aInfo; + aInfo.Size = sizeof (oslProcessInfo); + eError = osl_getProcessInfo(hProcess, osl_Process_EXITCODE, &aInfo); + + if (eError != osl_Process_E_None) + bRet = false; + else + bRet = (aInfo.Code == 0); + } + osl_freeProcessHandle(hProcess); + return bRet; +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_ShellJob_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::ShellJob(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/layoutmanager/helpers.cxx b/framework/source/layoutmanager/helpers.cxx new file mode 100644 index 000000000..7508d58cb --- /dev/null +++ b/framework/source/layoutmanager/helpers.cxx @@ -0,0 +1,340 @@ +/* -*- 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 "helpers.hxx" + +#include <com/sun/star/ui/DockingArea.hpp> +#include <com/sun/star/awt/Toolkit.hpp> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/frame/DispatchHelper.hpp> +#include <com/sun/star/awt/XDockableWindow.hpp> +#include <com/sun/star/awt/XDockableWindowListener.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/ui/XUIElement.hpp> + +#include <comphelper/lok.hxx> +#include <comphelper/propertyvalue.hxx> +#include <unotools/mediadescriptor.hxx> +#include <vcl/svapp.hxx> +#include <o3tl/string_view.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +using namespace com::sun::star; + +namespace framework +{ + +bool hasEmptySize( const css::awt::Size& rSize ) +{ + return ( rSize.Width == 0 ) && ( rSize.Height == 0 ); +} + +bool hasDefaultPosValue( const css::awt::Point& rPos ) +{ + return (( rPos.X == SAL_MAX_INT32 ) || ( rPos.Y == SAL_MAX_INT32 )); +} + +bool isDefaultPos( const css::awt::Point& rPos ) +{ + return (( rPos.X == SAL_MAX_INT32 ) && ( rPos.Y == SAL_MAX_INT32 )); +} + +bool isReverseOrderDockingArea( const sal_Int32 nDockArea ) +{ + ui::DockingArea eDockArea = static_cast< ui::DockingArea >( nDockArea ); + return (( eDockArea == ui::DockingArea_DOCKINGAREA_BOTTOM ) || + ( eDockArea == ui::DockingArea_DOCKINGAREA_RIGHT )); +} + +bool isToolboxHorizontalAligned( ToolBox const * pToolBox ) +{ + if ( pToolBox ) + return (( pToolBox->GetAlign() == WindowAlign::Top ) || ( pToolBox->GetAlign() == WindowAlign::Bottom )); + return false; +} + +bool isHorizontalDockingArea( const ui::DockingArea& nDockingArea ) +{ + return (( nDockingArea == ui::DockingArea_DOCKINGAREA_TOP ) || + ( nDockingArea == ui::DockingArea_DOCKINGAREA_BOTTOM )); +} + +bool isHorizontalDockingArea( const sal_Int32 nDockArea ) +{ + return isHorizontalDockingArea(static_cast< ui::DockingArea >( nDockArea )); +} + +OUString retrieveToolbarNameFromHelpURL( vcl::Window* pWindow ) +{ + OUString aToolbarName; + + if ( pWindow->GetType() == WindowType::TOOLBOX ) + { + ToolBox* pToolBox = dynamic_cast<ToolBox *>( pWindow ); + if ( pToolBox ) + { + aToolbarName = OStringToOUString( pToolBox->GetHelpId(), RTL_TEXTENCODING_UTF8 ); + sal_Int32 i = aToolbarName.lastIndexOf( ':' ); + if ( !aToolbarName.isEmpty() && ( i > 0 ) && (( i + 1 ) < aToolbarName.getLength() )) + aToolbarName = aToolbarName.copy( i+1 ); // Remove ".HelpId:" protocol from toolbar name + else + aToolbarName.clear(); + } + } + return aToolbarName; +} + +ToolBox* getToolboxPtr( vcl::Window* pWindow ) +{ + ToolBox* pToolbox(nullptr); + if ( pWindow->GetType() == WindowType::TOOLBOX ) + pToolbox = dynamic_cast<ToolBox*>( pWindow ); + return pToolbox; +} + +vcl::Window* getWindowFromXUIElement( const uno::Reference< ui::XUIElement >& xUIElement ) +{ + SolarMutexGuard aGuard; + uno::Reference< awt::XWindow > xWindow; + if ( xUIElement.is() ) + xWindow.set( xUIElement->getRealInterface(), uno::UNO_QUERY ); + return VCLUnoHelper::GetWindow( xWindow ); +} + +SystemWindow* getTopSystemWindow( const uno::Reference< awt::XWindow >& xWindow ) +{ + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + while ( pWindow && !pWindow->IsSystemWindow() ) + pWindow = pWindow->GetParent(); + + if ( pWindow ) + return static_cast<SystemWindow *>(pWindow.get()); + else + return nullptr; +} + +// ATTENTION! +// This value is directly copied from the sfx2 project. +// You have to change BOTH values, see sfx2/inc/sfx2/sfxsids.hrc (SID_DOCKWIN_START) +const sal_Int32 DOCKWIN_ID_BASE = 9800; + +bool lcl_checkUIElement(const uno::Reference< ui::XUIElement >& xUIElement, awt::Rectangle& _rPosSize, uno::Reference< awt::XWindow >& _xWindow) +{ + bool bRet = xUIElement.is(); + if ( bRet ) + { + SolarMutexGuard aGuard; + _xWindow.set( xUIElement->getRealInterface(), uno::UNO_QUERY ); + _rPosSize = _xWindow->getPosSize(); + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( _xWindow ); + if ( pWindow->GetType() == WindowType::TOOLBOX ) + { + ::Size aSize = static_cast<ToolBox*>(pWindow.get())->CalcWindowSizePixel( 1 ); + _rPosSize.Width = aSize.Width(); + _rPosSize.Height = aSize.Height(); + } + } // if ( xUIElement.is() ) + return bRet; +} + +uno::Reference< awt::XWindowPeer > createToolkitWindow( const uno::Reference< uno::XComponentContext >& rxContext, const uno::Reference< awt::XWindowPeer >& rParent, const char* pService ) +{ + uno::Reference< awt::XToolkit2 > xToolkit = awt::Toolkit::create( rxContext ); + + // describe window properties. + css::awt::WindowDescriptor aDescriptor; + aDescriptor.Type = awt::WindowClass_SIMPLE; + aDescriptor.WindowServiceName = OUString::createFromAscii( pService ); + aDescriptor.ParentIndex = -1; + aDescriptor.Parent = rParent; + aDescriptor.Bounds = awt::Rectangle(0,0,0,0); + aDescriptor.WindowAttributes = 0; + + // create an awt window + uno::Reference< awt::XWindowPeer > xPeer = xToolkit->createWindow( aDescriptor ); + + return xPeer; +} + +// convert alignment constant to vcl's WindowAlign type +WindowAlign ImplConvertAlignment( ui::DockingArea aAlignment ) +{ + if ( aAlignment == ui::DockingArea_DOCKINGAREA_LEFT ) + return WindowAlign::Left; + else if ( aAlignment == ui::DockingArea_DOCKINGAREA_RIGHT ) + return WindowAlign::Right; + else if ( aAlignment == ui::DockingArea_DOCKINGAREA_TOP ) + return WindowAlign::Top; + else + return WindowAlign::Bottom; +} + +std::u16string_view getElementTypeFromResourceURL( std::u16string_view aResourceURL ) +{ + if ( o3tl::starts_with(aResourceURL, UIRESOURCE_URL ) ) + { + sal_Int32 nIndex{ UIRESOURCE_URL.getLength() }; + return o3tl::getToken(aResourceURL, 1, '/', nIndex ); + } + + return std::u16string_view(); +} + +void parseResourceURL( std::u16string_view aResourceURL, OUString& aElementType, OUString& aElementName ) +{ + if ( o3tl::starts_with(aResourceURL, UIRESOURCE_URL) ) + { + sal_Int32 nIndex{ UIRESOURCE_URL.getLength() }; + aElementType = o3tl::getToken(aResourceURL, 1, '/', nIndex ); + aElementName = o3tl::getToken(aResourceURL, 0, '/', nIndex ); + } +} + +css::awt::Rectangle putRectangleValueToAWT( const ::tools::Rectangle& rRect ) +{ + css::awt::Rectangle aRect; + aRect.X = rRect.Left(); + aRect.Y = rRect.Top(); + aRect.Width = rRect.Right(); + aRect.Height = rRect.Bottom(); + + return aRect; +} + +::tools::Rectangle putAWTToRectangle( const css::awt::Rectangle& rRect ) +{ + ::tools::Rectangle aRect; + aRect.SetLeft( rRect.X ); + aRect.SetTop( rRect.Y ); + aRect.SetRight( rRect.Width ); + aRect.SetBottom( rRect.Height ); + + return aRect; +} + +bool equalRectangles( const css::awt::Rectangle& rRect1, + const css::awt::Rectangle& rRect2 ) +{ + return (( rRect1.X == rRect2.X ) && + ( rRect1.Y == rRect2.Y ) && + ( rRect1.Width == rRect2.Width ) && + ( rRect1.Height == rRect2.Height )); +} + +uno::Reference< frame::XModel > impl_getModelFromFrame( const uno::Reference< frame::XFrame >& rFrame ) +{ + // Query for the model to get check the context information + uno::Reference< frame::XModel > xModel; + if ( rFrame.is() ) + { + uno::Reference< frame::XController > xController = rFrame->getController(); + if ( xController.is() ) + xModel = xController->getModel(); + } + + return xModel; +} + +bool implts_isPreviewModel( const uno::Reference< frame::XModel >& xModel ) +{ + // the cost in calc of calling getArgs for this property + // includes measuring the entire sheet - which is extremely slow. + if (comphelper::LibreOfficeKit::isActive()) + return false; + + if ( xModel.is() ) + { + utl::MediaDescriptor aDesc( xModel->getArgs() ); + return aDesc.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_PREVIEW, false); + } + else + return false; +} + +bool implts_isFrameOrWindowTop( const uno::Reference< frame::XFrame >& xFrame ) +{ + if (xFrame->isTop()) + return true; + + uno::Reference< awt::XTopWindow > xWindowCheck(xFrame->getContainerWindow(), uno::UNO_QUERY); // don't use _THROW here ... it's a check only + if (xWindowCheck.is()) + { + // #i76867# top and system window is required. + SolarMutexGuard aGuard; + uno::Reference< awt::XWindow > xWindow( xWindowCheck, uno::UNO_QUERY ); + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + return pWindow && pWindow->IsSystemWindow(); + } + + return false; +} + +void impl_setDockingWindowVisibility( const css::uno::Reference< css::uno::XComponentContext>& rxContext, const css::uno::Reference< css::frame::XFrame >& rFrame, std::u16string_view rDockingWindowName, bool bVisible ) +{ + sal_Int32 nID = o3tl::toInt32(rDockingWindowName); + sal_Int32 nIndex = nID - DOCKWIN_ID_BASE; + + css::uno::Reference< css::frame::XDispatchProvider > xProvider(rFrame, css::uno::UNO_QUERY); + if ( !(nIndex >= 0 && xProvider.is()) ) + return; + + OUString aDockWinArgName = "DockingWindow" + OUString::number( nIndex ); + + css::uno::Sequence< css::beans::PropertyValue > aArgs{ comphelper::makePropertyValue( + aDockWinArgName, bVisible) }; + + css::uno::Reference< css::frame::XDispatchHelper > xDispatcher = css::frame::DispatchHelper::create( rxContext ); + + OUString aDockWinCommand = ".uno:" + aDockWinArgName; + xDispatcher->executeDispatch( + xProvider, + aDockWinCommand, + "_self", + 0, + aArgs); +} + +void impl_addWindowListeners( + const css::uno::Reference< css::uno::XInterface >& xThis, + const css::uno::Reference< css::ui::XUIElement >& xUIElement ) +{ + css::uno::Reference< css::awt::XWindow > xWindow( xUIElement->getRealInterface(), css::uno::UNO_QUERY ); + css::uno::Reference< css::awt::XDockableWindow > xDockWindow( xUIElement->getRealInterface(), css::uno::UNO_QUERY ); + if ( !(xDockWindow.is() && xWindow.is()) ) + return; + + try + { + xDockWindow->addDockableWindowListener( + css::uno::Reference< css::awt::XDockableWindowListener >( + xThis, css::uno::UNO_QUERY )); + xWindow->addWindowListener( + css::uno::Reference< css::awt::XWindowListener >( + xThis, css::uno::UNO_QUERY )); + xDockWindow->enableDocking( true ); + } + catch ( const css::uno::Exception& ) + { + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/layoutmanager/helpers.hxx b/framework/source/layoutmanager/helpers.hxx new file mode 100644 index 000000000..14f9642a3 --- /dev/null +++ b/framework/source/layoutmanager/helpers.hxx @@ -0,0 +1,67 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/ui/XUIElement.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/ui/DockingArea.hpp> +#include <com/sun/star/awt/Point.hpp> + +#include <vcl/window.hxx> +#include <vcl/toolbox.hxx> + +inline constexpr OUStringLiteral UIRESOURCE_URL = u"private:resource"; +inline constexpr OUStringLiteral UIRESOURCETYPE_TOOLBAR = u"toolbar"; +#define UIRESOURCETYPE_MENUBAR "menubar" + +namespace framework +{ + +bool hasEmptySize( const css::awt::Size& rSize ); +bool hasDefaultPosValue( const css::awt::Point& rPos ); +bool isDefaultPos( const css::awt::Point& rPos ); +bool isToolboxHorizontalAligned( ToolBox const * pToolBox ); +bool isReverseOrderDockingArea( const sal_Int32 nDockArea ); +bool isHorizontalDockingArea( const sal_Int32 nDockArea ); +bool isHorizontalDockingArea( const css::ui::DockingArea& nDockArea ); +OUString retrieveToolbarNameFromHelpURL( vcl::Window* pWindow ); +ToolBox* getToolboxPtr( vcl::Window* pWindow ); +vcl::Window* getWindowFromXUIElement( const css::uno::Reference< css::ui::XUIElement >& xUIElement ); +SystemWindow* getTopSystemWindow( const css::uno::Reference< css::awt::XWindow >& xWindow ); +bool equalRectangles( const css::awt::Rectangle& rRect1, const css::awt::Rectangle& rRect2 ); +bool lcl_checkUIElement(const css::uno::Reference< css::ui::XUIElement >& xUIElement,css::awt::Rectangle& _rPosSize, css::uno::Reference< css::awt::XWindow >& _xWindow); +css::uno::Reference< css::awt::XWindowPeer > createToolkitWindow( const css::uno::Reference< css::uno::XComponentContext >& rxContext, const css::uno::Reference< css::awt::XWindowPeer >& rParent, const char* pService ); +WindowAlign ImplConvertAlignment( css::ui::DockingArea aAlignment ); +std::u16string_view getElementTypeFromResourceURL( std::u16string_view aResourceURL ); +void parseResourceURL( std::u16string_view aResourceURL, OUString& aElementType, OUString& aElementName ); +::tools::Rectangle putAWTToRectangle( const css::awt::Rectangle& rRect ); +css::awt::Rectangle putRectangleValueToAWT( const ::tools::Rectangle& rRect ); +css::uno::Reference< css::frame::XModel > impl_getModelFromFrame( const css::uno::Reference< css::frame::XFrame >& rFrame ); +bool implts_isPreviewModel( const css::uno::Reference< css::frame::XModel >& xModel ); +bool implts_isFrameOrWindowTop( const css::uno::Reference< css::frame::XFrame >& xFrame ); +void impl_setDockingWindowVisibility( const css::uno::Reference< css::uno::XComponentContext>& rxContext, const css::uno::Reference< css::frame::XFrame >& rFrame, std::u16string_view rDockingWindowName, bool bVisible ); +void impl_addWindowListeners( const css::uno::Reference< css::uno::XInterface >& xThis, const css::uno::Reference< css::ui::XUIElement >& xUIElement ); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/layoutmanager/layoutmanager.cxx b/framework/source/layoutmanager/layoutmanager.cxx new file mode 100644 index 000000000..9220b805c --- /dev/null +++ b/framework/source/layoutmanager/layoutmanager.cxx @@ -0,0 +1,3079 @@ +/* -*- 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 <memory> +#include <config_feature_desktop.h> + +#include <properties.h> +#include <services/layoutmanager.hxx> +#include "helpers.hxx" + +#include <framework/sfxhelperfunctions.hxx> +#include <uielement/menubarwrapper.hxx> +#include <uielement/progressbarwrapper.hxx> +#include <uiconfiguration/globalsettings.hxx> +#include <uiconfiguration/windowstateproperties.hxx> +#include "toolbarlayoutmanager.hxx" + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/FrameAction.hpp> +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/awt/XDevice.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/theWindowStateConfiguration.hpp> +#include <com/sun/star/ui/theUIElementFactoryManager.hpp> +#include <com/sun/star/container/XNameReplace.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/frame/LayoutManagerEvents.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/DispatchHelper.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +#include <comphelper/lok.hxx> +#include <comphelper/propertyvalue.hxx> +#include <vcl/status.hxx> +#include <vcl/settings.hxx> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <toolkit/awt/vclxmenu.hxx> +#include <comphelper/uno3.hxx> +#include <officecfg/Office/Compatibility.hxx> + +#include <rtl/ref.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +#include <algorithm> + +// using namespace +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::ui; +using namespace ::com::sun::star::frame; + +constexpr OUStringLiteral STATUS_BAR_ALIAS = u"private:resource/statusbar/statusbar"; + +namespace framework +{ + +IMPLEMENT_FORWARD_XTYPEPROVIDER2( LayoutManager, LayoutManager_Base, LayoutManager_PBase ) +IMPLEMENT_FORWARD_XINTERFACE2( LayoutManager, LayoutManager_Base, LayoutManager_PBase ) + +LayoutManager::LayoutManager( const Reference< XComponentContext >& xContext ) : + ::cppu::OBroadcastHelperVar< ::cppu::OMultiTypeInterfaceContainerHelper, ::cppu::OMultiTypeInterfaceContainerHelper::keyType >(m_aMutex) + , LayoutManager_PBase( *static_cast< ::cppu::OBroadcastHelper* >(this) ) + , m_xContext( xContext ) + , m_xURLTransformer( URLTransformer::create(xContext) ) + , m_nLockCount( 0 ) + , m_bInplaceMenuSet( false ) + , m_bMenuVisible( true ) + , m_bVisible( true ) + , m_bParentWindowVisible( false ) + , m_bMustDoLayout( true ) +#if HAVE_FEATURE_DESKTOP + , m_bAutomaticToolbars( true ) +#else + , m_bAutomaticToolbars( false ) +#endif + , m_bHideCurrentUI( false ) + , m_bGlobalSettings( false ) + , m_bPreserveContentSize( false ) + , m_bMenuBarCloseButton( false ) + , m_xModuleManager( ModuleManager::create( xContext )) + , m_xUIElementFactoryManager( ui::theUIElementFactoryManager::get(xContext) ) + , m_xPersistentWindowStateSupplier( ui::theWindowStateConfiguration::get( xContext ) ) + , m_aAsyncLayoutTimer( "framework::LayoutManager m_aAsyncLayoutTimer" ) + , m_aListenerContainer( m_aMutex ) +{ + // Initialize statusbar member + m_aStatusBarElement.m_aType = "statusbar"; + m_aStatusBarElement.m_aName = STATUS_BAR_ALIAS; + + if (!comphelper::LibreOfficeKit::isActive()) + { + m_xToolbarManager = new ToolbarLayoutManager( xContext, Reference<XUIElementFactory>(m_xUIElementFactoryManager, UNO_QUERY_THROW), this ); + } + + m_aAsyncLayoutTimer.SetPriority( TaskPriority::HIGH_IDLE ); + m_aAsyncLayoutTimer.SetTimeout( 50 ); + m_aAsyncLayoutTimer.SetInvokeHandler( LINK( this, LayoutManager, AsyncLayoutHdl ) ); + + registerProperty( LAYOUTMANAGER_PROPNAME_ASCII_AUTOMATICTOOLBARS, LAYOUTMANAGER_PROPHANDLE_AUTOMATICTOOLBARS, css::beans::PropertyAttribute::TRANSIENT, &m_bAutomaticToolbars, cppu::UnoType<decltype(m_bAutomaticToolbars)>::get() ); + registerProperty( LAYOUTMANAGER_PROPNAME_ASCII_HIDECURRENTUI, LAYOUTMANAGER_PROPHANDLE_HIDECURRENTUI, beans::PropertyAttribute::TRANSIENT, &m_bHideCurrentUI, cppu::UnoType<decltype(m_bHideCurrentUI)>::get() ); + registerProperty( LAYOUTMANAGER_PROPNAME_ASCII_LOCKCOUNT, LAYOUTMANAGER_PROPHANDLE_LOCKCOUNT, beans::PropertyAttribute::TRANSIENT | beans::PropertyAttribute::READONLY, &m_nLockCount, cppu::UnoType<decltype(m_nLockCount)>::get() ); + registerProperty( LAYOUTMANAGER_PROPNAME_MENUBARCLOSER, LAYOUTMANAGER_PROPHANDLE_MENUBARCLOSER, beans::PropertyAttribute::TRANSIENT, &m_bMenuBarCloseButton, cppu::UnoType<decltype(m_bMenuBarCloseButton)>::get() ); + registerPropertyNoMember( LAYOUTMANAGER_PROPNAME_ASCII_REFRESHVISIBILITY, LAYOUTMANAGER_PROPHANDLE_REFRESHVISIBILITY, beans::PropertyAttribute::TRANSIENT, cppu::UnoType<bool>::get(), css::uno::Any(false) ); + registerProperty( LAYOUTMANAGER_PROPNAME_ASCII_PRESERVE_CONTENT_SIZE, LAYOUTMANAGER_PROPHANDLE_PRESERVE_CONTENT_SIZE, beans::PropertyAttribute::TRANSIENT, &m_bPreserveContentSize, cppu::UnoType<decltype(m_bPreserveContentSize)>::get() ); +} + +LayoutManager::~LayoutManager() +{ + m_aAsyncLayoutTimer.Stop(); + setDockingAreaAcceptor(nullptr); + m_pGlobalSettings.reset(); +} + +void LayoutManager::implts_createMenuBar(const OUString& rMenuBarName) +{ + SolarMutexGuard aWriteLock; + + // Create a customized menu if compatibility mode is on + if (m_aModuleIdentifier == "com.sun.star.text.TextDocument" && officecfg::Office::Compatibility::View::MSCompatibleFormsMenu::get()) + { + implts_createMSCompatibleMenuBar(rMenuBarName); + } + + // Create the default menubar otherwise + if (m_bInplaceMenuSet || m_xMenuBar.is()) + return; + + m_xMenuBar = implts_createElement( rMenuBarName ); + if ( !m_xMenuBar.is() ) + return; + + SystemWindow* pSysWindow = getTopSystemWindow( m_xContainerWindow ); + if ( !pSysWindow ) + return; + + Reference< awt::XMenuBar > xMenuBar; + + Reference< XPropertySet > xPropSet( m_xMenuBar, UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + xPropSet->getPropertyValue("XMenuBar") >>= xMenuBar; + } + catch (const beans::UnknownPropertyException&) + { + } + catch (const lang::WrappedTargetException&) + { + } + } + + if ( !xMenuBar.is() ) + return; + + VCLXMenu* pAwtMenuBar = comphelper::getFromUnoTunnel<VCLXMenu>( xMenuBar ); + if ( pAwtMenuBar ) + { + MenuBar* pMenuBar = static_cast<MenuBar*>(pAwtMenuBar->GetMenu()); + if ( pMenuBar ) + { + pSysWindow->SetMenuBar(pMenuBar); + pMenuBar->SetDisplayable( m_bMenuVisible ); + implts_updateMenuBarClose(); + } + } +} + +// Internal helper function +void LayoutManager::impl_clearUpMenuBar() +{ + implts_lock(); + + // Clear up VCL menu bar to prepare shutdown + if ( m_xContainerWindow.is() ) + { + SolarMutexGuard aGuard; + + SystemWindow* pSysWindow = getTopSystemWindow( m_xContainerWindow ); + if ( pSysWindow ) + { + MenuBar* pSetMenuBar = nullptr; + if ( m_xInplaceMenuBar.is() ) + pSetMenuBar = static_cast<MenuBar *>(m_xInplaceMenuBar->GetMenuBar()); + else + { + Reference< awt::XMenuBar > xMenuBar; + + Reference< XPropertySet > xPropSet( m_xMenuBar, UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + xPropSet->getPropertyValue("XMenuBar") >>= xMenuBar; + } + catch (const beans::UnknownPropertyException&) + { + } + catch (const lang::WrappedTargetException&) + { + } + } + + VCLXMenu* pAwtMenuBar = comphelper::getFromUnoTunnel<VCLXMenu>( xMenuBar ); + if ( pAwtMenuBar ) + pSetMenuBar = static_cast<MenuBar*>(pAwtMenuBar->GetMenu()); + } + + MenuBar* pTopMenuBar = pSysWindow->GetMenuBar(); + if ( pSetMenuBar == pTopMenuBar ) + pSysWindow->SetMenuBar( nullptr ); + } + } + + // reset inplace menubar manager + VclPtr<Menu> pMenuBar; + if (m_xInplaceMenuBar.is()) + { + pMenuBar = m_xInplaceMenuBar->GetMenuBar(); + m_xInplaceMenuBar->dispose(); + m_xInplaceMenuBar.clear(); + } + pMenuBar.disposeAndClear(); + m_bInplaceMenuSet = false; + + Reference< XComponent > xComp( m_xMenuBar, UNO_QUERY ); + if ( xComp.is() ) + xComp->dispose(); + m_xMenuBar.clear(); + implts_unlock(); +} + +void LayoutManager::implts_lock() +{ + SolarMutexGuard g; + ++m_nLockCount; +} + +bool LayoutManager::implts_unlock() +{ + SolarMutexGuard g; + m_nLockCount = std::max( m_nLockCount-1, static_cast<sal_Int32>(0) ); + return ( m_nLockCount == 0 ); +} + +void LayoutManager::implts_reset( bool bAttached ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aReadLock; + Reference< XFrame > xFrame = m_xFrame; + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + Reference< XUIConfiguration > xModuleCfgMgr( m_xModuleCfgMgr, UNO_QUERY ); + Reference< XUIConfiguration > xDocCfgMgr( m_xDocCfgMgr, UNO_QUERY ); + Reference< XNameAccess > xPersistentWindowState( m_xPersistentWindowState ); + Reference< XComponentContext > xContext( m_xContext ); + Reference< XNameAccess > xPersistentWindowStateSupplier( m_xPersistentWindowStateSupplier ); + rtl::Reference<ToolbarLayoutManager> xToolbarManager( m_xToolbarManager ); + OUString aModuleIdentifier( m_aModuleIdentifier ); + bool bAutomaticToolbars( m_bAutomaticToolbars ); + aReadLock.clear(); + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + implts_lock(); + + Reference< XModel > xModel; + if ( xFrame.is() ) + { + if ( bAttached ) + { + OUString aOldModuleIdentifier( aModuleIdentifier ); + try + { + aModuleIdentifier = m_xModuleManager->identify( xFrame ); + } + catch( const Exception& ) {} + + if ( !aModuleIdentifier.isEmpty() && aOldModuleIdentifier != aModuleIdentifier ) + { + Reference< XModuleUIConfigurationManagerSupplier > xModuleCfgSupplier; + if ( xContext.is() ) + xModuleCfgSupplier = theModuleUIConfigurationManagerSupplier::get( xContext ); + + if ( xModuleCfgMgr.is() ) + { + try + { + // Remove listener to old module ui configuration manager + xModuleCfgMgr->removeConfigurationListener( Reference< XUIConfigurationListener >(this) ); + } + catch (const Exception&) + { + } + } + + try + { + // Add listener to new module ui configuration manager + xModuleCfgMgr.set( xModuleCfgSupplier->getUIConfigurationManager( aModuleIdentifier ), UNO_QUERY ); + if ( xModuleCfgMgr.is() ) + xModuleCfgMgr->addConfigurationListener( Reference< XUIConfigurationListener >(this) ); + } + catch (const Exception&) + { + } + + try + { + // Retrieve persistent window state reference for our new module + if ( xPersistentWindowStateSupplier.is() ) + xPersistentWindowStateSupplier->getByName( aModuleIdentifier ) >>= xPersistentWindowState; + } + catch (const NoSuchElementException&) + { + } + catch (const WrappedTargetException&) + { + } + } + + xModel = impl_getModelFromFrame( xFrame ); + if ( xModel.is() ) + { + Reference< XUIConfigurationManagerSupplier > xUIConfigurationManagerSupplier( xModel, UNO_QUERY ); + if ( xUIConfigurationManagerSupplier.is() ) + { + if ( xDocCfgMgr.is() ) + { + try + { + // Remove listener to old ui configuration manager + xDocCfgMgr->removeConfigurationListener( Reference< XUIConfigurationListener >(this) ); + } + catch (const Exception&) + { + } + } + + try + { + xDocCfgMgr.set( xUIConfigurationManagerSupplier->getUIConfigurationManager(), UNO_QUERY ); + if ( xDocCfgMgr.is() ) + xDocCfgMgr->addConfigurationListener( Reference< XUIConfigurationListener >(this) ); + } + catch (const Exception&) + { + } + } + } + } + else + { + // Remove configuration listeners before we can release our references + if ( xModuleCfgMgr.is() ) + { + try + { + xModuleCfgMgr->removeConfigurationListener( + Reference< XUIConfigurationListener >(this) ); + } + catch (const Exception&) + { + } + } + + if ( xDocCfgMgr.is() ) + { + try + { + xDocCfgMgr->removeConfigurationListener( + Reference< XUIConfigurationListener >(this) ); + } + catch (const Exception&) + { + } + } + + // Release references to our configuration managers as we currently don't have + // an attached module. + xModuleCfgMgr.clear(); + xDocCfgMgr.clear(); + xPersistentWindowState.clear(); + aModuleIdentifier.clear(); + } + + Reference< XUIConfigurationManager > xModCfgMgr( xModuleCfgMgr, UNO_QUERY ); + Reference< XUIConfigurationManager > xDokCfgMgr( xDocCfgMgr, UNO_QUERY ); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aWriteLock; + m_aDockingArea = awt::Rectangle(); + m_aModuleIdentifier = aModuleIdentifier; + m_xModuleCfgMgr = xModCfgMgr; + m_xDocCfgMgr = xDokCfgMgr; + m_xPersistentWindowState = xPersistentWindowState; + m_aStatusBarElement.m_bStateRead = false; // reset state to read data again! + aWriteLock.clear(); + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + // reset/notify toolbar layout manager + if ( xToolbarManager.is() ) + { + if ( bAttached ) + { + xToolbarManager->attach( xFrame, xModCfgMgr, xDokCfgMgr, xPersistentWindowState ); + uno::Reference< awt::XWindowPeer > xParent( xContainerWindow, UNO_QUERY ); + xToolbarManager->setParentWindow( xParent ); + if ( bAutomaticToolbars ) + xToolbarManager->createStaticToolbars(); + } + else + { + xToolbarManager->reset(); + implts_destroyElements(); + } + } + } + + implts_unlock(); +} + +bool LayoutManager::implts_isEmbeddedLayoutManager() const +{ + SolarMutexClearableGuard aReadLock; + Reference< XFrame > xFrame = m_xFrame; + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + aReadLock.clear(); + + Reference< awt::XWindow > xFrameContainerWindow = xFrame->getContainerWindow(); + return xFrameContainerWindow != xContainerWindow; +} + +void LayoutManager::implts_destroyElements() +{ + SolarMutexResettableGuard aWriteLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aWriteLock.clear(); + + if ( pToolbarManager ) + pToolbarManager->destroyToolbars(); + + implts_destroyStatusBar(); + + aWriteLock.reset(); + impl_clearUpMenuBar(); + aWriteLock.clear(); +} + +void LayoutManager::implts_toggleFloatingUIElementsVisibility( bool bActive ) +{ + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + pToolbarManager->setFloatingToolbarsVisibility( bActive ); +} + +uno::Reference< ui::XUIElement > LayoutManager::implts_findElement( std::u16string_view aName ) +{ + OUString aElementType; + OUString aElementName; + + parseResourceURL( aName, aElementType, aElementName ); + if ( aElementType.equalsIgnoreAsciiCase("menubar") && + aElementName.equalsIgnoreAsciiCase("menubar") ) + return m_xMenuBar; + else if (( aElementType.equalsIgnoreAsciiCase("statusbar") && + aElementName.equalsIgnoreAsciiCase("statusbar") ) || + ( m_aStatusBarElement.m_aName == aName )) + return m_aStatusBarElement.m_xUIElement; + else if ( aElementType.equalsIgnoreAsciiCase("progressbar") && + aElementName.equalsIgnoreAsciiCase("progressbar") ) + return m_aProgressBarElement.m_xUIElement; + + return uno::Reference< ui::XUIElement >(); +} + +bool LayoutManager::implts_readWindowStateData( const OUString& aName, UIElement& rElementData ) +{ + return readWindowStateData( aName, rElementData, m_xPersistentWindowState, + m_pGlobalSettings, m_bGlobalSettings, m_xContext ); +} + +bool LayoutManager::readWindowStateData( const OUString& aName, UIElement& rElementData, + const Reference< XNameAccess > &rPersistentWindowState, + std::unique_ptr<GlobalSettings> &rGlobalSettings, bool &bInGlobalSettings, + const Reference< XComponentContext > &rComponentContext ) +{ + if ( !rPersistentWindowState.is() ) + return false; + + bool bGetSettingsState( false ); + + SolarMutexClearableGuard aWriteLock; + bool bGlobalSettings( bInGlobalSettings ); + if ( rGlobalSettings == nullptr ) + { + rGlobalSettings.reset( new GlobalSettings( rComponentContext ) ); + bGetSettingsState = true; + } + GlobalSettings* pGlobalSettings = rGlobalSettings.get(); + aWriteLock.clear(); + + try + { + Sequence< PropertyValue > aWindowState; + if ( rPersistentWindowState->hasByName( aName ) && (rPersistentWindowState->getByName( aName ) >>= aWindowState) ) + { + bool bValue( false ); + for ( PropertyValue const & rProp : std::as_const(aWindowState) ) + { + if ( rProp.Name == WINDOWSTATE_PROPERTY_DOCKED ) + { + if ( rProp.Value >>= bValue ) + rElementData.m_bFloating = !bValue; + } + else if ( rProp.Name == WINDOWSTATE_PROPERTY_VISIBLE ) + { + if ( rProp.Value >>= bValue ) + rElementData.m_bVisible = bValue; + } + else if ( rProp.Name == WINDOWSTATE_PROPERTY_DOCKINGAREA ) + { + ui::DockingArea eDockingArea; + if ( rProp.Value >>= eDockingArea ) + rElementData.m_aDockedData.m_nDockedArea = eDockingArea; + } + else if ( rProp.Name == WINDOWSTATE_PROPERTY_DOCKPOS ) + { + awt::Point aPoint; + if (rProp.Value >>= aPoint) + { + //tdf#90256 repair these broken Docking positions + if (aPoint.X < 0) + aPoint.X = SAL_MAX_INT32; + if (aPoint.Y < 0) + aPoint.Y = SAL_MAX_INT32; + rElementData.m_aDockedData.m_aPos = aPoint; + } + } + else if ( rProp.Name == WINDOWSTATE_PROPERTY_POS ) + { + awt::Point aPoint; + if ( rProp.Value >>= aPoint ) + rElementData.m_aFloatingData.m_aPos = aPoint; + } + else if ( rProp.Name == WINDOWSTATE_PROPERTY_SIZE ) + { + awt::Size aSize; + if ( rProp.Value >>= aSize ) + rElementData.m_aFloatingData.m_aSize = aSize; + } + else if ( rProp.Name == WINDOWSTATE_PROPERTY_UINAME ) + rProp.Value >>= rElementData.m_aUIName; + else if ( rProp.Name == WINDOWSTATE_PROPERTY_STYLE ) + { + sal_Int32 nStyle = 0; + if ( rProp.Value >>= nStyle ) + rElementData.m_nStyle = static_cast<ButtonType>( nStyle ); + } + else if ( rProp.Name == WINDOWSTATE_PROPERTY_LOCKED ) + { + if ( rProp.Value >>= bValue ) + rElementData.m_aDockedData.m_bLocked = bValue; + } + else if ( rProp.Name == WINDOWSTATE_PROPERTY_CONTEXT ) + { + if ( rProp.Value >>= bValue ) + rElementData.m_bContextSensitive = bValue; + } + else if ( rProp.Name == WINDOWSTATE_PROPERTY_NOCLOSE ) + { + if ( rProp.Value >>= bValue ) + rElementData.m_bNoClose = bValue; + } + } + } + + // oversteer values with global settings + if (bGetSettingsState || bGlobalSettings) + { + if ( pGlobalSettings->HasToolbarStatesInfo()) + { + { + SolarMutexGuard aWriteLock2; + bInGlobalSettings = true; + } + + uno::Any aValue; + if ( pGlobalSettings->GetToolbarStateInfo( + GlobalSettings::STATEINFO_LOCKED, + aValue )) + aValue >>= rElementData.m_aDockedData.m_bLocked; + if ( pGlobalSettings->GetToolbarStateInfo( + GlobalSettings::STATEINFO_DOCKED, + aValue )) + { + bool bValue; + if ( aValue >>= bValue ) + rElementData.m_bFloating = !bValue; + } + } + } + + const bool bDockingSupportCrippled = !StyleSettings::GetDockingFloatsSupported(); + if (bDockingSupportCrippled) + rElementData.m_bFloating = false; + + return true; + } + catch (const NoSuchElementException&) + { + } + + return false; +} + +void LayoutManager::implts_writeWindowStateData( const OUString& aName, const UIElement& rElementData ) +{ + SolarMutexClearableGuard aWriteLock; + Reference< XNameAccess > xPersistentWindowState( m_xPersistentWindowState ); + + aWriteLock.clear(); + + bool bPersistent( false ); + Reference< XPropertySet > xPropSet( rElementData.m_xUIElement, UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + // Check persistent flag of the user interface element + xPropSet->getPropertyValue("Persistent") >>= bPersistent; + } + catch (const beans::UnknownPropertyException&) + { + // Non-configurable elements should at least store their dimension/position + bPersistent = true; + } + catch (const lang::WrappedTargetException&) + { + } + } + + if ( !(bPersistent && xPersistentWindowState.is()) ) + return; + + try + { + Sequence< PropertyValue > aWindowState{ + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_DOCKED, !rElementData.m_bFloating), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_VISIBLE, rElementData.m_bVisible), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_DOCKINGAREA, + rElementData.m_aDockedData.m_nDockedArea), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_DOCKPOS, + rElementData.m_aDockedData.m_aPos), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_POS, + rElementData.m_aFloatingData.m_aPos), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_SIZE, + rElementData.m_aFloatingData.m_aSize), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_UINAME, rElementData.m_aUIName), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_LOCKED, + rElementData.m_aDockedData.m_bLocked) + }; + + if ( xPersistentWindowState->hasByName( aName )) + { + Reference< XNameReplace > xReplace( xPersistentWindowState, uno::UNO_QUERY ); + xReplace->replaceByName( aName, Any( aWindowState )); + } + else + { + Reference< XNameContainer > xInsert( xPersistentWindowState, uno::UNO_QUERY ); + xInsert->insertByName( aName, Any( aWindowState )); + } + } + catch (const Exception&) + { + } +} + +::Size LayoutManager::implts_getContainerWindowOutputSize() +{ + ::Size aContainerWinSize; + vcl::Window* pContainerWindow( nullptr ); + + // Retrieve output size from container Window + SolarMutexGuard aGuard; + pContainerWindow = VCLUnoHelper::GetWindow( m_xContainerWindow ); + if ( pContainerWindow ) + aContainerWinSize = pContainerWindow->GetOutputSizePixel(); + + return aContainerWinSize; +} + +Reference< XUIElement > LayoutManager::implts_createElement( const OUString& aName ) +{ + Reference< ui::XUIElement > xUIElement; + + SolarMutexGuard g; + Sequence< PropertyValue > aPropSeq{ comphelper::makePropertyValue("Frame", m_xFrame), + comphelper::makePropertyValue("Persistent", true) }; + + try + { + xUIElement = m_xUIElementFactoryManager->createUIElement( aName, aPropSeq ); + } + catch (const NoSuchElementException&) + { + } + catch (const IllegalArgumentException&) + { + } + + return xUIElement; +} + +void LayoutManager::implts_setVisibleState( bool bShow ) +{ + { + SolarMutexGuard aWriteLock; + m_aStatusBarElement.m_bMasterHide = !bShow; + } + + implts_updateUIElementsVisibleState( bShow ); +} + +void LayoutManager::implts_updateUIElementsVisibleState( bool bSetVisible ) +{ + // notify listeners + uno::Any a; + if ( bSetVisible ) + implts_notifyListeners( frame::LayoutManagerEvents::VISIBLE, a ); + else + implts_notifyListeners( frame::LayoutManagerEvents::INVISIBLE, a ); + + SolarMutexResettableGuard aWriteLock; + Reference< XUIElement > xMenuBar = m_xMenuBar; + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + rtl::Reference< MenuBarManager > xInplaceMenuBar( m_xInplaceMenuBar ); + aWriteLock.clear(); + + if (( xMenuBar.is() || xInplaceMenuBar.is() ) && xContainerWindow.is() ) + { + SolarMutexGuard aGuard; + + MenuBar* pMenuBar( nullptr ); + if ( xInplaceMenuBar.is() ) + pMenuBar = static_cast<MenuBar *>(xInplaceMenuBar->GetMenuBar()); + else + { + MenuBarWrapper* pMenuBarWrapper = static_cast< MenuBarWrapper* >(xMenuBar.get()); + pMenuBar = static_cast<MenuBar *>(pMenuBarWrapper->GetMenuBarManager()->GetMenuBar()); + } + + SystemWindow* pSysWindow = getTopSystemWindow( xContainerWindow ); + if ( pSysWindow ) + { + if ( bSetVisible ) + { + pSysWindow->SetMenuBar(pMenuBar); + } + else + pSysWindow->SetMenuBar( nullptr ); + } + } + + bool bMustDoLayout; + // Hide/show the statusbar according to bSetVisible + if ( bSetVisible ) + bMustDoLayout = !implts_showStatusBar(); + else + bMustDoLayout = !implts_hideStatusBar(); + + aWriteLock.reset(); + ToolbarLayoutManager* pToolbarManager( m_xToolbarManager.get() ); + aWriteLock.clear(); + + if ( pToolbarManager ) + { + pToolbarManager->setVisible( bSetVisible ); + bMustDoLayout = pToolbarManager->isLayoutDirty(); + } + + if ( bMustDoLayout ) + implts_doLayout_notify( false ); +} + +void LayoutManager::implts_setCurrentUIVisibility( bool bShow ) +{ + { + SolarMutexGuard aWriteLock; + if (!bShow && m_aStatusBarElement.m_bVisible && m_aStatusBarElement.m_xUIElement.is()) + m_aStatusBarElement.m_bMasterHide = true; + else if (bShow && m_aStatusBarElement.m_bVisible) + m_aStatusBarElement.m_bMasterHide = false; + } + + implts_updateUIElementsVisibleState( bShow ); +} + +void LayoutManager::implts_destroyStatusBar() +{ + Reference< XComponent > xCompStatusBar; + + SolarMutexClearableGuard aWriteLock; + m_aStatusBarElement.m_aName.clear(); + xCompStatusBar.set( m_aStatusBarElement.m_xUIElement, UNO_QUERY ); + m_aStatusBarElement.m_xUIElement.clear(); + aWriteLock.clear(); + + if ( xCompStatusBar.is() ) + xCompStatusBar->dispose(); + + implts_destroyProgressBar(); +} + +void LayoutManager::implts_createStatusBar( const OUString& aStatusBarName ) +{ + { + SolarMutexGuard aWriteLock; + if (!m_aStatusBarElement.m_xUIElement.is()) + { + implts_readStatusBarState(aStatusBarName); + m_aStatusBarElement.m_aName = aStatusBarName; + m_aStatusBarElement.m_xUIElement = implts_createElement(aStatusBarName); + } + } + + implts_createProgressBar(); +} + +void LayoutManager::implts_readStatusBarState( const OUString& rStatusBarName ) +{ + SolarMutexGuard g; + if ( !m_aStatusBarElement.m_bStateRead ) + { + // Read persistent data for status bar if not yet read! + if ( implts_readWindowStateData( rStatusBarName, m_aStatusBarElement )) + m_aStatusBarElement.m_bStateRead = true; + } +} + +void LayoutManager::implts_createProgressBar() +{ + Reference< XUIElement > xStatusBar; + Reference< XUIElement > xProgressBar; + Reference< XUIElement > xProgressBarBackup; + Reference< awt::XWindow > xContainerWindow; + + SolarMutexResettableGuard aWriteLock; + xStatusBar = m_aStatusBarElement.m_xUIElement; + xProgressBar = m_aProgressBarElement.m_xUIElement; + xProgressBarBackup = m_xProgressBarBackup; + m_xProgressBarBackup.clear(); + xContainerWindow = m_xContainerWindow; + aWriteLock.clear(); + + bool bRecycled = xProgressBarBackup.is(); + rtl::Reference<ProgressBarWrapper> pWrapper; + if ( bRecycled ) + pWrapper = static_cast<ProgressBarWrapper*>(xProgressBarBackup.get()); + else if ( xProgressBar.is() ) + pWrapper = static_cast<ProgressBarWrapper*>(xProgressBar.get()); + else + pWrapper = new ProgressBarWrapper(); + + if ( xStatusBar.is() ) + { + Reference< awt::XWindow > xWindow( xStatusBar->getRealInterface(), UNO_QUERY ); + pWrapper->setStatusBar( xWindow ); + } + else + { + Reference< awt::XWindow > xStatusBarWindow = pWrapper->getStatusBar(); + + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pStatusBarWnd = VCLUnoHelper::GetWindow( xStatusBarWindow ); + if ( !pStatusBarWnd ) + { + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xContainerWindow ); + if ( pWindow ) + { + VclPtrInstance<StatusBar> pStatusBar( pWindow, WinBits( WB_LEFT | WB_3DLOOK ) ); + Reference< awt::XWindow > xStatusBarWindow2( VCLUnoHelper::GetInterface( pStatusBar )); + pWrapper->setStatusBar( xStatusBarWindow2, true ); + } + } + } + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + aWriteLock.reset(); + m_aProgressBarElement.m_xUIElement = pWrapper; + aWriteLock.clear(); + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + if ( bRecycled ) + implts_showProgressBar(); +} + +void LayoutManager::implts_backupProgressBarWrapper() +{ + SolarMutexGuard g; + + if (m_xProgressBarBackup.is()) + return; + + // safe a backup copy of the current progress! + // This copy will be used automatically inside createProgressBar() which is called + // implicitly from implts_doLayout() .-) + m_xProgressBarBackup = m_aProgressBarElement.m_xUIElement; + + // remove the relation between this old progress bar and our old status bar. + // Otherwise we work on disposed items ... + // The internal used ProgressBarWrapper can handle a NULL reference. + if ( m_xProgressBarBackup.is() ) + { + ProgressBarWrapper* pWrapper = static_cast<ProgressBarWrapper*>(m_xProgressBarBackup.get()); + if ( pWrapper ) + pWrapper->setStatusBar( Reference< awt::XWindow >() ); + } + + // prevent us from dispose() the m_aProgressBarElement.m_xUIElement inside implts_reset() + m_aProgressBarElement.m_xUIElement.clear(); +} + +void LayoutManager::implts_destroyProgressBar() +{ + // don't remove the progressbar in general + // We must reuse it if a new status bar is created later. + // Of course there exists one backup only. + // And further this backup will be released inside our dtor. + implts_backupProgressBarWrapper(); +} + +void LayoutManager::implts_setStatusBarPosSize( const ::Point& rPos, const ::Size& rSize ) +{ + Reference< XUIElement > xStatusBar; + Reference< XUIElement > xProgressBar; + Reference< awt::XWindow > xContainerWindow; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aReadLock; + xStatusBar = m_aStatusBarElement.m_xUIElement; + xProgressBar = m_aProgressBarElement.m_xUIElement; + xContainerWindow = m_xContainerWindow; + + Reference< awt::XWindow > xWindow; + if ( xStatusBar.is() ) + xWindow.set( xStatusBar->getRealInterface(), UNO_QUERY ); + else if ( xProgressBar.is() ) + { + ProgressBarWrapper* pWrapper = static_cast<ProgressBarWrapper*>(xProgressBar.get()); + if ( pWrapper ) + xWindow = pWrapper->getStatusBar(); + } + aReadLock.clear(); + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + if ( !xWindow.is() ) + return; + + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pParentWindow = VCLUnoHelper::GetWindow( xContainerWindow ); + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pParentWindow && ( pWindow && pWindow->GetType() == WindowType::STATUSBAR )) + { + vcl::Window* pOldParentWindow = pWindow->GetParent(); + if ( pParentWindow != pOldParentWindow ) + pWindow->SetParent( pParentWindow ); + static_cast<StatusBar *>(pWindow.get())->SetPosSizePixel( rPos, rSize ); + } +} + +bool LayoutManager::implts_showProgressBar() +{ + Reference< XUIElement > xStatusBar; + Reference< XUIElement > xProgressBar; + Reference< awt::XWindow > xWindow; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexGuard aWriteLock; + xStatusBar = m_aStatusBarElement.m_xUIElement; + xProgressBar = m_aProgressBarElement.m_xUIElement; + bool bVisible( m_bVisible ); + + m_aProgressBarElement.m_bVisible = true; + if ( bVisible ) + { + if ( xStatusBar.is() && !m_aStatusBarElement.m_bMasterHide ) + { + xWindow.set( xStatusBar->getRealInterface(), UNO_QUERY ); + } + else if ( xProgressBar.is() ) + { + ProgressBarWrapper* pWrapper = static_cast<ProgressBarWrapper*>(xProgressBar.get()); + if ( pWrapper ) + xWindow = pWrapper->getStatusBar(); + } + } + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow ) + { + if ( !pWindow->IsVisible() ) + { + implts_setOffset( pWindow->GetSizePixel().Height() ); + pWindow->Show(); + implts_doLayout_notify( false ); + } + return true; + } + + return false; +} + +bool LayoutManager::implts_hideProgressBar() +{ + Reference< XUIElement > xProgressBar; + Reference< awt::XWindow > xWindow; + bool bHideStatusBar( false ); + + SolarMutexGuard g; + xProgressBar = m_aProgressBarElement.m_xUIElement; + + bool bInternalStatusBar( false ); + if ( xProgressBar.is() ) + { + Reference< awt::XWindow > xStatusBar; + ProgressBarWrapper* pWrapper = static_cast<ProgressBarWrapper*>(xProgressBar.get()); + if ( pWrapper ) + xWindow = pWrapper->getStatusBar(); + Reference< ui::XUIElement > xStatusBarElement = m_aStatusBarElement.m_xUIElement; + if ( xStatusBarElement.is() ) + xStatusBar.set( xStatusBarElement->getRealInterface(), UNO_QUERY ); + bInternalStatusBar = xStatusBar != xWindow; + } + m_aProgressBarElement.m_bVisible = false; + implts_readStatusBarState( STATUS_BAR_ALIAS ); + bHideStatusBar = !m_aStatusBarElement.m_bVisible; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->IsVisible() && ( bHideStatusBar || bInternalStatusBar )) + { + implts_setOffset( 0 ); + pWindow->Hide(); + implts_doLayout_notify( false ); + return true; + } + + return false; +} + +bool LayoutManager::implts_showStatusBar( bool bStoreState ) +{ + SolarMutexClearableGuard aWriteLock; + Reference< ui::XUIElement > xStatusBar = m_aStatusBarElement.m_xUIElement; + if ( bStoreState ) + m_aStatusBarElement.m_bVisible = true; + aWriteLock.clear(); + + if ( xStatusBar.is() ) + { + Reference< awt::XWindow > xWindow( xStatusBar->getRealInterface(), UNO_QUERY ); + + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && !pWindow->IsVisible() ) + { + implts_setOffset( pWindow->GetSizePixel().Height() ); + pWindow->Show(); + implts_doLayout_notify( false ); + return true; + } + } + + return false; +} + +bool LayoutManager::implts_hideStatusBar( bool bStoreState ) +{ + SolarMutexClearableGuard aWriteLock; + Reference< ui::XUIElement > xStatusBar = m_aStatusBarElement.m_xUIElement; + if ( bStoreState ) + m_aStatusBarElement.m_bVisible = false; + aWriteLock.clear(); + + if ( xStatusBar.is() ) + { + Reference< awt::XWindow > xWindow( xStatusBar->getRealInterface(), UNO_QUERY ); + + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->IsVisible() ) + { + implts_setOffset( 0 ); + pWindow->Hide(); + implts_doLayout_notify( false ); + return true; + } + } + + return false; +} + +void LayoutManager::implts_setOffset( const sal_Int32 nBottomOffset ) +{ + if ( m_xToolbarManager.is() ) + m_xToolbarManager->setDockingAreaOffsets({ 0, 0, 0, nBottomOffset }); +} + +void LayoutManager::implts_setInplaceMenuBar( const Reference< XIndexAccess >& xMergedMenuBar ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aWriteLock; + + if ( m_bInplaceMenuSet ) + return; + + SolarMutexGuard aGuard; + + // Reset old inplace menubar! + VclPtr<Menu> pOldMenuBar; + if (m_xInplaceMenuBar.is()) + { + pOldMenuBar = m_xInplaceMenuBar->GetMenuBar(); + m_xInplaceMenuBar->dispose(); + m_xInplaceMenuBar.clear(); + } + pOldMenuBar.disposeAndClear(); + + m_bInplaceMenuSet = false; + + if ( m_xFrame.is() && m_xContainerWindow.is() ) + { + Reference< XDispatchProvider > xDispatchProvider; + + VclPtr<MenuBar> pMenuBar = VclPtr<MenuBar>::Create(); + m_xInplaceMenuBar = new MenuBarManager( m_xContext, m_xFrame, m_xURLTransformer, xDispatchProvider, OUString(), pMenuBar, true ); + m_xInplaceMenuBar->SetItemContainer( xMergedMenuBar ); + + SystemWindow* pSysWindow = getTopSystemWindow( m_xContainerWindow ); + if ( pSysWindow ) + pSysWindow->SetMenuBar(pMenuBar); + + m_bInplaceMenuSet = true; + } + + aWriteLock.clear(); + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + implts_updateMenuBarClose(); +} + +void LayoutManager::implts_resetInplaceMenuBar() +{ + SolarMutexGuard g; + m_bInplaceMenuSet = false; + + if ( m_xContainerWindow.is() ) + { + SolarMutexGuard aGuard; + MenuBarWrapper* pMenuBarWrapper = static_cast< MenuBarWrapper* >(m_xMenuBar.get()); + SystemWindow* pSysWindow = getTopSystemWindow( m_xContainerWindow ); + if ( pSysWindow ) + { + if ( pMenuBarWrapper ) + pSysWindow->SetMenuBar(static_cast<MenuBar *>(pMenuBarWrapper->GetMenuBarManager()->GetMenuBar())); + else + pSysWindow->SetMenuBar(nullptr); + } + } + + // Remove inplace menu bar + VclPtr<Menu> pMenuBar; + if (m_xInplaceMenuBar.is()) + { + pMenuBar = m_xInplaceMenuBar->GetMenuBar(); + m_xInplaceMenuBar->dispose(); + m_xInplaceMenuBar.clear(); + } + pMenuBar.disposeAndClear(); +} + +void SAL_CALL LayoutManager::attachFrame( const Reference< XFrame >& xFrame ) +{ + SolarMutexGuard g; + m_xFrame = xFrame; +} + +void SAL_CALL LayoutManager::reset() +{ + implts_reset( true ); +} + +// XMenuBarMergingAcceptor + +sal_Bool SAL_CALL LayoutManager::setMergedMenuBar( + const Reference< XIndexAccess >& xMergedMenuBar ) +{ + implts_setInplaceMenuBar( xMergedMenuBar ); + + uno::Any a; + implts_notifyListeners( frame::LayoutManagerEvents::MERGEDMENUBAR, a ); + return true; +} + +void SAL_CALL LayoutManager::removeMergedMenuBar() +{ + implts_resetInplaceMenuBar(); +} + +awt::Rectangle SAL_CALL LayoutManager::getCurrentDockingArea() +{ + SolarMutexGuard g; + return m_aDockingArea; +} + +Reference< XDockingAreaAcceptor > SAL_CALL LayoutManager::getDockingAreaAcceptor() +{ + SolarMutexGuard g; + return m_xDockingAreaAcceptor; +} + +void SAL_CALL LayoutManager::setDockingAreaAcceptor( const Reference< ui::XDockingAreaAcceptor >& xDockingAreaAcceptor ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aWriteLock; + + if (( m_xDockingAreaAcceptor == xDockingAreaAcceptor ) || !m_xFrame.is() ) + return; + + // IMPORTANT: Be sure to stop layout timer if don't have a docking area acceptor! + if ( !xDockingAreaAcceptor.is() ) + m_aAsyncLayoutTimer.Stop(); + + bool bAutomaticToolbars( m_bAutomaticToolbars ); + + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + + if ( !xDockingAreaAcceptor.is() ) + m_aAsyncLayoutTimer.Stop(); + + // Remove listener from old docking area acceptor + if ( m_xDockingAreaAcceptor.is() ) + { + Reference< awt::XWindow > xWindow( m_xDockingAreaAcceptor->getContainerWindow() ); + if ( xWindow.is() && ( m_xFrame->getContainerWindow() != m_xContainerWindow || !xDockingAreaAcceptor.is() ) ) + xWindow->removeWindowListener( Reference< awt::XWindowListener >(this) ); + + m_aDockingArea = awt::Rectangle(); + if ( pToolbarManager ) + pToolbarManager->resetDockingArea(); + + VclPtr<vcl::Window> pContainerWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pContainerWindow ) + pContainerWindow->RemoveChildEventListener( LINK( this, LayoutManager, WindowEventListener ) ); + } + + m_xDockingAreaAcceptor = xDockingAreaAcceptor; + if ( m_xDockingAreaAcceptor.is() ) + { + m_aDockingArea = awt::Rectangle(); + m_xContainerWindow = m_xDockingAreaAcceptor->getContainerWindow(); + m_xContainerTopWindow.set( m_xContainerWindow, UNO_QUERY ); + m_xContainerWindow->addWindowListener( Reference< awt::XWindowListener >(this) ); + + // we always must keep a connection to the window of our frame for resize events + if ( m_xContainerWindow != m_xFrame->getContainerWindow() ) + m_xFrame->getContainerWindow()->addWindowListener( Reference< awt::XWindowListener >(this) ); + + // #i37884# set initial visibility state - in the plugin case the container window is already shown + // and we get no notification anymore + { + VclPtr<vcl::Window> pContainerWindow = VCLUnoHelper::GetWindow( m_xContainerWindow ); + if( pContainerWindow ) + m_bParentWindowVisible = pContainerWindow->IsVisible(); + } + } + + aWriteLock.clear(); + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + if ( xDockingAreaAcceptor.is() ) + { + SolarMutexGuard aGuard; + + // Add layout manager as listener to get notifications about toolbar button activities + VclPtr<vcl::Window> pContainerWindow = VCLUnoHelper::GetWindow( m_xContainerWindow ); + if ( pContainerWindow ) + pContainerWindow->AddChildEventListener( LINK( this, LayoutManager, WindowEventListener ) ); + + // We have now a new container window, reparent all child windows! + implts_reparentChildWindows(); + } + else + implts_destroyElements(); // remove all elements + + if ( pToolbarManager && xDockingAreaAcceptor.is() ) + { + if ( bAutomaticToolbars ) + { + lock(); + pToolbarManager->createStaticToolbars(); + unlock(); + } + implts_doLayout( true, false ); + } +} + +void LayoutManager::implts_reparentChildWindows() +{ + SolarMutexResettableGuard aWriteLock; + UIElement aStatusBarElement = m_aStatusBarElement; + uno::Reference< awt::XWindow > xContainerWindow = m_xContainerWindow; + aWriteLock.clear(); + + uno::Reference< awt::XWindow > xStatusBarWindow; + if ( aStatusBarElement.m_xUIElement.is() ) + { + try + { + xStatusBarWindow.set( aStatusBarElement.m_xUIElement->getRealInterface(), UNO_QUERY ); + } + catch (const RuntimeException&) + { + throw; + } + catch (const Exception&) + { + } + } + + if ( xStatusBarWindow.is() ) + { + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pContainerWindow = VCLUnoHelper::GetWindow( xContainerWindow ); + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xStatusBarWindow ); + if ( pWindow && pContainerWindow ) + pWindow->SetParent( pContainerWindow ); + } + + implts_resetMenuBar(); + + aWriteLock.reset(); + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + if ( pToolbarManager ) + pToolbarManager->setParentWindow( uno::Reference< awt::XWindowPeer >( xContainerWindow, uno::UNO_QUERY )); + aWriteLock.clear(); +} + +uno::Reference< ui::XUIElement > LayoutManager::implts_createDockingWindow( const OUString& aElementName ) +{ + Reference< XUIElement > xUIElement = implts_createElement( aElementName ); + return xUIElement; +} + +IMPL_LINK( LayoutManager, WindowEventListener, VclWindowEvent&, rEvent, void ) +{ + vcl::Window* pWindow = rEvent.GetWindow(); + if ( pWindow && pWindow->GetType() == WindowType::TOOLBOX ) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager( m_xToolbarManager.get() ); + aReadLock.clear(); + + if ( pToolbarManager ) + pToolbarManager->childWindowEvent( &rEvent ); + } +} + +void SAL_CALL LayoutManager::createElement( const OUString& aName ) +{ + SAL_INFO( "fwk", "LayoutManager::createElement " << aName ); + + SolarMutexClearableGuard aReadLock; + Reference< XFrame > xFrame = m_xFrame; + aReadLock.clear(); + + if ( !xFrame.is() ) + return; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aWriteLock; + + bool bMustBeLayouted( false ); + bool bNotify( false ); + + bool bPreviewFrame; + if (m_xToolbarManager.is()) + // Assumes that we created the ToolbarLayoutManager with our frame, if + // not then we're somewhat fouled up ... + bPreviewFrame = m_xToolbarManager->isPreviewFrame(); + else + { + Reference< XModel > xModel( impl_getModelFromFrame( xFrame ) ); + bPreviewFrame = implts_isPreviewModel( xModel ); + } + + if ( m_xContainerWindow.is() && !bPreviewFrame ) // no UI elements on preview frames + { + OUString aElementType; + OUString aElementName; + + parseResourceURL( aName, aElementType, aElementName ); + + if ( aElementType.equalsIgnoreAsciiCase( UIRESOURCETYPE_TOOLBAR ) && m_xToolbarManager.is() ) + { + bNotify = m_xToolbarManager->createToolbar( aName ); + bMustBeLayouted = m_xToolbarManager->isLayoutDirty(); + } + else if ( aElementType.equalsIgnoreAsciiCase("menubar") && + aElementName.equalsIgnoreAsciiCase("menubar") && + implts_isFrameOrWindowTop(xFrame) ) + { + implts_createMenuBar( aName ); + if (m_bMenuVisible) + bNotify = true; + + aWriteLock.clear(); + } + else if ( aElementType.equalsIgnoreAsciiCase("statusbar") && + ( implts_isFrameOrWindowTop(xFrame) || implts_isEmbeddedLayoutManager() )) + { + implts_createStatusBar( aName ); + bNotify = true; + } + else if ( aElementType.equalsIgnoreAsciiCase("progressbar") && + aElementName.equalsIgnoreAsciiCase("progressbar") && + implts_isFrameOrWindowTop(xFrame) ) + { + implts_createProgressBar(); + bNotify = true; + } + else if ( aElementType.equalsIgnoreAsciiCase("dockingwindow")) + { + // Add layout manager as listener for docking and other window events + uno::Reference< uno::XInterface > xThis( static_cast< OWeakObject* >(this), uno::UNO_QUERY ); + uno::Reference< ui::XUIElement > xUIElement( implts_createDockingWindow( aName )); + + if ( xUIElement.is() ) + { + impl_addWindowListeners( xThis, xUIElement ); + } + + // The docking window is created by a factory method located in the sfx2 library. +// CreateDockingWindow( xFrame, aElementName ); + } + } + + if ( bMustBeLayouted ) + implts_doLayout_notify( true ); + + if ( bNotify ) + { + // UI element is invisible - provide information to listeners + implts_notifyListeners( frame::LayoutManagerEvents::UIELEMENT_VISIBLE, uno::Any( aName ) ); + } +} + +void SAL_CALL LayoutManager::destroyElement( const OUString& aName ) +{ + SAL_INFO( "fwk", "LayoutManager::destroyElement " << aName ); + + bool bMustBeLayouted(false); + bool bNotify(false); + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + { + SolarMutexClearableGuard aWriteLock; + + OUString aElementType; + OUString aElementName; + + parseResourceURL(aName, aElementType, aElementName); + + if (aElementType.equalsIgnoreAsciiCase("menubar") + && aElementName.equalsIgnoreAsciiCase("menubar")) + { + if (!m_bInplaceMenuSet) + { + impl_clearUpMenuBar(); + m_xMenuBar.clear(); + bNotify = true; + } + } + else if ((aElementType.equalsIgnoreAsciiCase("statusbar") + && aElementName.equalsIgnoreAsciiCase("statusbar")) + || (m_aStatusBarElement.m_aName == aName)) + { + aWriteLock.clear(); + implts_destroyStatusBar(); + bMustBeLayouted = true; + bNotify = true; + } + else if (aElementType.equalsIgnoreAsciiCase("progressbar") + && aElementName.equalsIgnoreAsciiCase("progressbar")) + { + aWriteLock.clear(); + implts_createProgressBar(); + bMustBeLayouted = true; + bNotify = true; + } + else if (aElementType.equalsIgnoreAsciiCase(UIRESOURCETYPE_TOOLBAR) + && m_xToolbarManager.is()) + { + aWriteLock.clear(); + bNotify = m_xToolbarManager->destroyToolbar(aName); + bMustBeLayouted = m_xToolbarManager->isLayoutDirty(); + } + else if (aElementType.equalsIgnoreAsciiCase("dockingwindow")) + { + uno::Reference<frame::XFrame> xFrame(m_xFrame); + uno::Reference<XComponentContext> xContext(m_xContext); + aWriteLock.clear(); + + impl_setDockingWindowVisibility(xContext, xFrame, aElementName, false); + bMustBeLayouted = false; + bNotify = false; + } + } + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + if ( bMustBeLayouted ) + doLayout(); + + if ( bNotify ) + implts_notifyListeners( frame::LayoutManagerEvents::UIELEMENT_INVISIBLE, uno::Any( aName ) ); +} + +sal_Bool SAL_CALL LayoutManager::requestElement( const OUString& rResourceURL ) +{ + bool bResult( false ); + bool bNotify( false ); + OUString aElementType; + OUString aElementName; + + parseResourceURL( rResourceURL, aElementType, aElementName ); + + SolarMutexClearableGuard aWriteLock; + + OString aResName = OUStringToOString( aElementName, RTL_TEXTENCODING_ASCII_US ); + SAL_INFO( "fwk", "LayoutManager::requestElement " << aResName ); + + if (( aElementType.equalsIgnoreAsciiCase("statusbar") && + aElementName.equalsIgnoreAsciiCase("statusbar") ) || + ( m_aStatusBarElement.m_aName == rResourceURL )) + { + implts_readStatusBarState( rResourceURL ); + if ( m_aStatusBarElement.m_bVisible && !m_aStatusBarElement.m_bMasterHide ) + { + aWriteLock.clear(); + createElement( rResourceURL ); + + // There are some situation where we are not able to create an element. + // Therefore we have to check the reference before further action. + // See #i70019# + uno::Reference< ui::XUIElement > xUIElement( m_aStatusBarElement.m_xUIElement ); + if ( xUIElement.is() ) + { + // we need VCL here to pass special flags to Show() + SolarMutexGuard aGuard; + Reference< awt::XWindow > xWindow( xUIElement->getRealInterface(), UNO_QUERY ); + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow ) + { + pWindow->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); + bResult = true; + bNotify = true; + } + } + } + } + else if ( aElementType.equalsIgnoreAsciiCase("progressbar") && + aElementName.equalsIgnoreAsciiCase("progressbar") ) + { + aWriteLock.clear(); + implts_showProgressBar(); + bResult = true; + bNotify = true; + } + else if ( aElementType.equalsIgnoreAsciiCase( UIRESOURCETYPE_TOOLBAR ) && m_bVisible ) + { + bool bComponentAttached( !m_aModuleIdentifier.isEmpty() ); + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aWriteLock.clear(); + + if ( pToolbarManager && bComponentAttached ) + { + bNotify = pToolbarManager->requestToolbar( rResourceURL ); + } + } + else if ( aElementType.equalsIgnoreAsciiCase("dockingwindow")) + { + uno::Reference< frame::XFrame > xFrame( m_xFrame ); + aWriteLock.clear(); + + CreateDockingWindow( xFrame, aElementName ); + } + + if ( bNotify ) + implts_notifyListeners( frame::LayoutManagerEvents::UIELEMENT_VISIBLE, uno::Any( rResourceURL ) ); + + return bResult; +} + +Reference< XUIElement > SAL_CALL LayoutManager::getElement( const OUString& aName ) +{ + Reference< XUIElement > xUIElement = implts_findElement( aName ); + if ( !xUIElement.is() ) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager( m_xToolbarManager.get() ); + aReadLock.clear(); + + if ( pToolbarManager ) + xUIElement = pToolbarManager->getToolbar( aName ); + } + + return xUIElement; +} + +Sequence< Reference< ui::XUIElement > > SAL_CALL LayoutManager::getElements() +{ + SolarMutexClearableGuard aReadLock; + uno::Reference< ui::XUIElement > xMenuBar( m_xMenuBar ); + uno::Reference< ui::XUIElement > xStatusBar( m_aStatusBarElement.m_xUIElement ); + ToolbarLayoutManager* pToolbarManager( m_xToolbarManager.get() ); + aReadLock.clear(); + + Sequence< Reference< ui::XUIElement > > aSeq; + if ( pToolbarManager ) + aSeq = pToolbarManager->getToolbars(); + + sal_Int32 nSize = aSeq.getLength(); + sal_Int32 nMenuBarIndex(-1); + sal_Int32 nStatusBarIndex(-1); + if ( xMenuBar.is() ) + { + nMenuBarIndex = nSize; + ++nSize; + } + if ( xStatusBar.is() ) + { + nStatusBarIndex = nSize; + ++nSize; + } + + aSeq.realloc(nSize); + auto pSeq = aSeq.getArray(); + if ( nMenuBarIndex >= 0 ) + pSeq[nMenuBarIndex] = xMenuBar; + if ( nStatusBarIndex >= 0 ) + pSeq[nStatusBarIndex] = xStatusBar; + + return aSeq; +} + +sal_Bool SAL_CALL LayoutManager::showElement( const OUString& aName ) +{ + bool bResult( false ); + bool bNotify( false ); + bool bMustLayout( false ); + OUString aElementType; + OUString aElementName; + + parseResourceURL( aName, aElementType, aElementName ); + + OString aResName = OUStringToOString( aElementName, RTL_TEXTENCODING_ASCII_US ); + SAL_INFO( "fwk", "LayoutManager::showElement " << aResName ); + + if ( aElementType.equalsIgnoreAsciiCase("menubar") && + aElementName.equalsIgnoreAsciiCase("menubar") ) + { + { + SolarMutexGuard aWriteLock; + m_bMenuVisible = true; + } + + bResult = implts_resetMenuBar(); + bNotify = bResult; + } + else if (( aElementType.equalsIgnoreAsciiCase("statusbar") && + aElementName.equalsIgnoreAsciiCase("statusbar") ) || + ( m_aStatusBarElement.m_aName == aName )) + { + SolarMutexClearableGuard aWriteLock; + if ( m_aStatusBarElement.m_xUIElement.is() && !m_aStatusBarElement.m_bMasterHide && + implts_showStatusBar( true )) + { + aWriteLock.clear(); + + implts_writeWindowStateData( STATUS_BAR_ALIAS, m_aStatusBarElement ); + bMustLayout = true; + bResult = true; + bNotify = true; + } + } + else if ( aElementType.equalsIgnoreAsciiCase("progressbar") && + aElementName.equalsIgnoreAsciiCase("progressbar") ) + { + bNotify = bResult = implts_showProgressBar(); + } + else if ( aElementType.equalsIgnoreAsciiCase( UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + { + bNotify = pToolbarManager->showToolbar( aName ); + bMustLayout = pToolbarManager->isLayoutDirty(); + } + } + else if ( aElementType.equalsIgnoreAsciiCase("dockingwindow")) + { + SolarMutexClearableGuard aReadGuard; + uno::Reference< frame::XFrame > xFrame( m_xFrame ); + uno::Reference< XComponentContext > xContext( m_xContext ); + aReadGuard.clear(); + + impl_setDockingWindowVisibility( xContext, xFrame, aElementName, true ); + } + + if ( bMustLayout ) + doLayout(); + + if ( bNotify ) + implts_notifyListeners( frame::LayoutManagerEvents::UIELEMENT_VISIBLE, uno::Any( aName ) ); + + return bResult; +} + +sal_Bool SAL_CALL LayoutManager::hideElement( const OUString& aName ) +{ + bool bNotify( false ); + bool bMustLayout( false ); + OUString aElementType; + OUString aElementName; + + parseResourceURL( aName, aElementType, aElementName ); + OString aResName = OUStringToOString( aElementName, RTL_TEXTENCODING_ASCII_US ); + SAL_INFO( "fwk", "LayoutManager::hideElement " << aResName ); + + if ( aElementType.equalsIgnoreAsciiCase("menubar") && + aElementName.equalsIgnoreAsciiCase("menubar") ) + { + SolarMutexGuard g; + + if ( m_xContainerWindow.is() ) + { + m_bMenuVisible = false; + + SolarMutexGuard aGuard; + SystemWindow* pSysWindow = getTopSystemWindow( m_xContainerWindow ); + if ( pSysWindow ) + { + MenuBar* pMenuBar = pSysWindow->GetMenuBar(); + if ( pMenuBar ) + { + pMenuBar->SetDisplayable( false ); + bNotify = true; + } + } + } + } + else if (( aElementType.equalsIgnoreAsciiCase("statusbar") && + aElementName.equalsIgnoreAsciiCase("statusbar") ) || + ( m_aStatusBarElement.m_aName == aName )) + { + SolarMutexGuard g; + if ( m_aStatusBarElement.m_xUIElement.is() && !m_aStatusBarElement.m_bMasterHide && + implts_hideStatusBar( true )) + { + implts_writeWindowStateData( STATUS_BAR_ALIAS, m_aStatusBarElement ); + bMustLayout = true; + bNotify = true; + } + } + else if ( aElementType.equalsIgnoreAsciiCase("progressbar") && + aElementName.equalsIgnoreAsciiCase("progressbar") ) + { + bNotify = implts_hideProgressBar(); + } + else if ( aElementType.equalsIgnoreAsciiCase( UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + { + bNotify = pToolbarManager->hideToolbar( aName ); + bMustLayout = pToolbarManager->isLayoutDirty(); + } + } + else if ( aElementType.equalsIgnoreAsciiCase("dockingwindow")) + { + SolarMutexClearableGuard aReadGuard; + uno::Reference< frame::XFrame > xFrame( m_xFrame ); + uno::Reference< XComponentContext > xContext( m_xContext ); + aReadGuard.clear(); + + impl_setDockingWindowVisibility( xContext, xFrame, aElementName, false ); + } + + if ( bMustLayout ) + doLayout(); + + if ( bNotify ) + implts_notifyListeners( frame::LayoutManagerEvents::UIELEMENT_INVISIBLE, uno::Any( aName ) ); + + return false; +} + +sal_Bool SAL_CALL LayoutManager::dockWindow( const OUString& aName, DockingArea DockingArea, const awt::Point& Pos ) +{ + OUString aElementType; + OUString aElementName; + + parseResourceURL( aName, aElementType, aElementName ); + if ( aElementType.equalsIgnoreAsciiCase( UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + { + pToolbarManager->dockToolbar( aName, DockingArea, Pos ); + if ( pToolbarManager->isLayoutDirty() ) + doLayout(); + } + } + return false; +} + +sal_Bool SAL_CALL LayoutManager::dockAllWindows( ::sal_Int16 /*nElementType*/ ) +{ + SolarMutexClearableGuard aReadLock; + bool bResult( false ); + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + { + bResult = pToolbarManager->dockAllToolbars(); + if ( pToolbarManager->isLayoutDirty() ) + doLayout(); + } + return bResult; +} + +sal_Bool SAL_CALL LayoutManager::floatWindow( const OUString& aName ) +{ + bool bResult( false ); + if ( o3tl::equalsIgnoreAsciiCase(getElementTypeFromResourceURL( aName ), UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + { + bResult = pToolbarManager->floatToolbar( aName ); + if ( pToolbarManager->isLayoutDirty() ) + doLayout(); + } + } + return bResult; +} + +sal_Bool SAL_CALL LayoutManager::lockWindow( const OUString& aName ) +{ + bool bResult( false ); + if ( o3tl::equalsIgnoreAsciiCase(getElementTypeFromResourceURL( aName ), UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + { + bResult = pToolbarManager->lockToolbar( aName ); + if ( pToolbarManager->isLayoutDirty() ) + doLayout(); + } + } + return bResult; +} + +sal_Bool SAL_CALL LayoutManager::unlockWindow( const OUString& aName ) +{ + bool bResult( false ); + if ( o3tl::equalsIgnoreAsciiCase(getElementTypeFromResourceURL( aName ), UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + { + bResult = pToolbarManager->unlockToolbar( aName ); + if ( pToolbarManager->isLayoutDirty() ) + doLayout(); + } + } + return bResult; +} + +void SAL_CALL LayoutManager::setElementSize( const OUString& aName, const awt::Size& aSize ) +{ + if ( !o3tl::equalsIgnoreAsciiCase(getElementTypeFromResourceURL( aName ), UIRESOURCETYPE_TOOLBAR )) + return; + + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + { + pToolbarManager->setToolbarSize( aName, aSize ); + if ( pToolbarManager->isLayoutDirty() ) + doLayout(); + } +} + +void SAL_CALL LayoutManager::setElementPos( const OUString& aName, const awt::Point& aPos ) +{ + if ( !o3tl::equalsIgnoreAsciiCase(getElementTypeFromResourceURL( aName ), UIRESOURCETYPE_TOOLBAR )) + return; + + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager( m_xToolbarManager.get() ); + aReadLock.clear(); + + if ( pToolbarManager ) + { + pToolbarManager->setToolbarPos( aName, aPos ); + if ( pToolbarManager->isLayoutDirty() ) + doLayout(); + } +} + +void SAL_CALL LayoutManager::setElementPosSize( const OUString& aName, const awt::Point& aPos, const awt::Size& aSize ) +{ + if ( !o3tl::equalsIgnoreAsciiCase(getElementTypeFromResourceURL( aName ), UIRESOURCETYPE_TOOLBAR )) + return; + + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager( m_xToolbarManager.get() ); + aReadLock.clear(); + + if ( pToolbarManager ) + { + pToolbarManager->setToolbarPosSize( aName, aPos, aSize ); + if ( pToolbarManager->isLayoutDirty() ) + doLayout(); + } +} + +sal_Bool SAL_CALL LayoutManager::isElementVisible( const OUString& aName ) +{ + OUString aElementType; + OUString aElementName; + + parseResourceURL( aName, aElementType, aElementName ); + if ( aElementType.equalsIgnoreAsciiCase("menubar") && + aElementName.equalsIgnoreAsciiCase("menubar") ) + { + SolarMutexResettableGuard aReadLock; + if ( m_xContainerWindow.is() ) + { + aReadLock.clear(); + + SolarMutexGuard aGuard; + SystemWindow* pSysWindow = getTopSystemWindow( m_xContainerWindow ); + if ( pSysWindow ) + { + MenuBar* pMenuBar = pSysWindow->GetMenuBar(); + if ( pMenuBar && pMenuBar->IsDisplayable() ) + return true; + } + else + { + aReadLock.reset(); + return m_bMenuVisible; + } + } + } + else if (( aElementType.equalsIgnoreAsciiCase("statusbar") && + aElementName.equalsIgnoreAsciiCase("statusbar") ) || + ( m_aStatusBarElement.m_aName == aName )) + { + if ( m_aStatusBarElement.m_xUIElement.is() ) + { + Reference< awt::XWindow > xWindow( m_aStatusBarElement.m_xUIElement->getRealInterface(), UNO_QUERY ); + if ( xWindow.is() ) + { + SolarMutexGuard g; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->IsVisible() ) + return true; + else + return false; + } + } + } + else if ( aElementType.equalsIgnoreAsciiCase("progressbar") && + aElementName.equalsIgnoreAsciiCase("progressbar") ) + { + if ( m_aProgressBarElement.m_xUIElement.is() ) + return m_aProgressBarElement.m_bVisible; + } + else if ( aElementType.equalsIgnoreAsciiCase( UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + return pToolbarManager->isToolbarVisible( aName ); + } + else if ( aElementType.equalsIgnoreAsciiCase("dockingwindow")) + { + SolarMutexClearableGuard aReadGuard; + uno::Reference< frame::XFrame > xFrame( m_xFrame ); + aReadGuard.clear(); + + return IsDockingWindowVisible( xFrame, aElementName ); + } + + return false; +} + +sal_Bool SAL_CALL LayoutManager::isElementFloating( const OUString& aName ) +{ + if ( o3tl::equalsIgnoreAsciiCase(getElementTypeFromResourceURL( aName ), UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + return pToolbarManager->isToolbarFloating( aName ); + } + + return false; +} + +sal_Bool SAL_CALL LayoutManager::isElementDocked( const OUString& aName ) +{ + if ( o3tl::equalsIgnoreAsciiCase(getElementTypeFromResourceURL( aName ), UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + return pToolbarManager->isToolbarDocked( aName ); + } + + return false; +} + +sal_Bool SAL_CALL LayoutManager::isElementLocked( const OUString& aName ) +{ + if ( o3tl::equalsIgnoreAsciiCase(getElementTypeFromResourceURL( aName ), UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + return pToolbarManager->isToolbarLocked( aName ); + } + + return false; +} + +awt::Size SAL_CALL LayoutManager::getElementSize( const OUString& aName ) +{ + if ( o3tl::equalsIgnoreAsciiCase(getElementTypeFromResourceURL( aName ), UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + return pToolbarManager->getToolbarSize( aName ); + } + + return awt::Size(); +} + +awt::Point SAL_CALL LayoutManager::getElementPos( const OUString& aName ) +{ + if ( o3tl::equalsIgnoreAsciiCase(getElementTypeFromResourceURL( aName ), UIRESOURCETYPE_TOOLBAR )) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + aReadLock.clear(); + + if ( pToolbarManager ) + return pToolbarManager->getToolbarPos( aName ); + } + + return awt::Point(); +} + +void SAL_CALL LayoutManager::lock() +{ + implts_lock(); + + SolarMutexClearableGuard aReadLock; + sal_Int32 nLockCount( m_nLockCount ); + aReadLock.clear(); + + SAL_INFO( "fwk", "LayoutManager::lock " << reinterpret_cast<sal_Int64>(this) << " - " << nLockCount ); + + Any a( nLockCount ); + implts_notifyListeners( frame::LayoutManagerEvents::LOCK, a ); +} + +void SAL_CALL LayoutManager::unlock() +{ + bool bDoLayout( implts_unlock() ); + + SolarMutexClearableGuard aReadLock; + sal_Int32 nLockCount( m_nLockCount ); + aReadLock.clear(); + + SAL_INFO( "fwk", "LayoutManager::unlock " << reinterpret_cast<sal_Int64>(this) << " - " << nLockCount); + + // conform to documentation: unlock with lock count == 0 means force a layout + + { + SolarMutexGuard aWriteLock; + if (bDoLayout) + m_aAsyncLayoutTimer.Stop(); + } + + Any a( nLockCount ); + implts_notifyListeners( frame::LayoutManagerEvents::UNLOCK, a ); + + if ( bDoLayout ) + implts_doLayout_notify( true ); +} + +void SAL_CALL LayoutManager::doLayout() +{ + implts_doLayout_notify( true ); +} + +// ILayoutNotifications + +void LayoutManager::requestLayout() +{ + doLayout(); +} + +void LayoutManager::implts_doLayout_notify( bool bOuterResize ) +{ + bool bLayouted = implts_doLayout( false, bOuterResize ); + if ( bLayouted ) + implts_notifyListeners( frame::LayoutManagerEvents::LAYOUT, Any() ); +} + +bool LayoutManager::implts_doLayout( bool bForceRequestBorderSpace, bool bOuterResize ) +{ + SAL_INFO( "fwk", "LayoutManager::implts_doLayout" ); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aReadLock; + + if ( !m_xFrame.is() || !m_bParentWindowVisible ) + return false; + + bool bPreserveContentSize( m_bPreserveContentSize ); + bool bMustDoLayout( m_bMustDoLayout ); + bool bNoLock = ( m_nLockCount == 0 ); + awt::Rectangle aCurrBorderSpace( m_aDockingArea ); + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + Reference< awt::XTopWindow2 > xContainerTopWindow( m_xContainerTopWindow ); + Reference< awt::XWindow > xComponentWindow; + try { + xComponentWindow = m_xFrame->getComponentWindow(); + } catch (css::lang::DisposedException &) { + // There can be a race between one thread calling Frame::dispose + // (framework/source/services/frame.cxx) -> Frame::disableLayoutManager + // -> LayoutManager::attachFrame(null) setting m_xFrame to null, and + // the main thread firing the timer-triggered + // LayoutManager::AsyncLayoutHdl -> LayoutManager::implts_doLayout and + // calling into the in-dispose m_xFrame here, so silently ignore a + // DisposedException here: + return false; + } + Reference< XDockingAreaAcceptor > xDockingAreaAcceptor( m_xDockingAreaAcceptor ); + aReadLock.clear(); + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + bool bLayouted( false ); + + if ( bNoLock && xDockingAreaAcceptor.is() && xContainerWindow.is() && xComponentWindow.is() ) + { + bLayouted = true; + + awt::Rectangle aDockSpace( implts_calcDockingAreaSizes() ); + awt::Rectangle aBorderSpace( aDockSpace ); + bool bGotRequestedBorderSpace( true ); + + // We have to add the height of a possible status bar + aBorderSpace.Height += implts_getStatusBarSize().Height(); + + if ( !equalRectangles( aBorderSpace, aCurrBorderSpace ) || bForceRequestBorderSpace || bMustDoLayout ) + { + // we always resize the content window (instead of the complete container window) if we're not set up + // to (attempt to) preserve the content window's size + if ( bOuterResize && !bPreserveContentSize ) + bOuterResize = false; + + // maximized windows can resized their content window only, not their container window + if ( bOuterResize && xContainerTopWindow.is() && xContainerTopWindow->getIsMaximized() ) + bOuterResize = false; + + // if the component window does not have a size (yet), then we can't use it to calc the container + // window size + awt::Rectangle aComponentRect = xComponentWindow->getPosSize(); + if ( bOuterResize && ( aComponentRect.Width == 0 ) && ( aComponentRect.Height == 0 ) ) + bOuterResize = false; + + bGotRequestedBorderSpace = false; + if ( bOuterResize ) + { + Reference< awt::XDevice > xDevice( xContainerWindow, uno::UNO_QUERY ); + awt::DeviceInfo aContainerInfo = xDevice->getInfo(); + + awt::Size aRequestedSize( aComponentRect.Width + aContainerInfo.LeftInset + aContainerInfo.RightInset + aBorderSpace.X + aBorderSpace.Width, + aComponentRect.Height + aContainerInfo.TopInset + aContainerInfo.BottomInset + aBorderSpace.Y + aBorderSpace.Height ); + awt::Point aComponentPos( aBorderSpace.X, aBorderSpace.Y ); + + bGotRequestedBorderSpace = implts_resizeContainerWindow( aRequestedSize, aComponentPos ); + } + + // if we did not do a container window resize, or it failed, then use the DockingAcceptor as usual + if ( !bGotRequestedBorderSpace ) + bGotRequestedBorderSpace = xDockingAreaAcceptor->requestDockingAreaSpace( aBorderSpace ); + + if ( bGotRequestedBorderSpace ) + { + SolarMutexGuard aWriteGuard; + m_aDockingArea = aBorderSpace; + m_bMustDoLayout = false; + } + } + + if ( bGotRequestedBorderSpace ) + { + ::Size aContainerSize; + ::Size aStatusBarSize; + + // Interim solution to let the layout method within the + // toolbar layout manager. + implts_setOffset( implts_getStatusBarSize().Height() ); + if ( m_xToolbarManager.is() ) + m_xToolbarManager->setDockingArea( aDockSpace ); + + // Subtract status bar size from our container output size. Docking area windows + // don't contain the status bar! + aStatusBarSize = implts_getStatusBarSize(); + aContainerSize = implts_getContainerWindowOutputSize(); + aContainerSize.AdjustHeight( -(aStatusBarSize.Height()) ); + + if ( m_xToolbarManager.is() ) + m_xToolbarManager->doLayout(aContainerSize); + + // Position the status bar + if ( aStatusBarSize.Height() > 0 ) + { + implts_setStatusBarPosSize( ::Point( 0, std::max(( aContainerSize.Height() ), tools::Long( 0 ))), + ::Size( aContainerSize.Width(),aStatusBarSize.Height() )); + } + + xDockingAreaAcceptor->setDockingAreaSpace( aBorderSpace ); + } + } + + return bLayouted; +} + +bool LayoutManager::implts_resizeContainerWindow( const awt::Size& rContainerSize, + const awt::Point& rComponentPos ) +{ + SolarMutexClearableGuard aReadLock; + Reference< awt::XWindow > xContainerWindow = m_xContainerWindow; + Reference< awt::XTopWindow2 > xContainerTopWindow = m_xContainerTopWindow; + Reference< awt::XWindow > xComponentWindow = m_xFrame->getComponentWindow(); + aReadLock.clear(); + + // calculate the maximum size we have for the container window + sal_Int32 nDisplay = xContainerTopWindow->getDisplay(); + tools::Rectangle aWorkArea = Application::GetScreenPosSizePixel( nDisplay ); + + if (!aWorkArea.IsEmpty()) + { + if (( rContainerSize.Width > aWorkArea.GetWidth() ) || ( rContainerSize.Height > aWorkArea.GetHeight() )) + return false; + // Strictly, this is not correct. If we have a multi-screen display (css.awt.DisplayAccess.MultiDisplay == true), + // the "effective work area" would be much larger than the work area of a single display, since we could in theory + // position the container window across multiple screens. + // However, this should suffice as a heuristics here ... (nobody really wants to check whether the different screens are + // stacked horizontally or vertically, whether their work areas can really be combined, or are separated by non-work-areas, + // and the like ... right?) + } + + // resize our container window + xContainerWindow->setPosSize( 0, 0, rContainerSize.Width, rContainerSize.Height, awt::PosSize::SIZE ); + // position the component window + xComponentWindow->setPosSize( rComponentPos.X, rComponentPos.Y, 0, 0, awt::PosSize::POS ); + return true; +} + +void SAL_CALL LayoutManager::setVisible( sal_Bool bVisible ) +{ + SolarMutexClearableGuard aWriteLock; + bool bWasVisible( m_bVisible ); + m_bVisible = bVisible; + aWriteLock.clear(); + + if ( bWasVisible != bool(bVisible) ) + implts_setVisibleState( bVisible ); +} + +sal_Bool SAL_CALL LayoutManager::isVisible() +{ + SolarMutexGuard g; + return m_bVisible; +} + +::Size LayoutManager::implts_getStatusBarSize() +{ + SolarMutexClearableGuard aReadLock; + bool bStatusBarVisible( isElementVisible( STATUS_BAR_ALIAS )); + bool bProgressBarVisible( isElementVisible( "private:resource/progressbar/progressbar" )); + bool bVisible( m_bVisible ); + Reference< XUIElement > xStatusBar( m_aStatusBarElement.m_xUIElement ); + Reference< XUIElement > xProgressBar( m_aProgressBarElement.m_xUIElement ); + + Reference< awt::XWindow > xWindow; + if ( bStatusBarVisible && bVisible && xStatusBar.is() ) + xWindow.set( xStatusBar->getRealInterface(), UNO_QUERY ); + else if ( xProgressBar.is() && !xStatusBar.is() && bProgressBarVisible ) + { + ProgressBarWrapper* pWrapper = static_cast<ProgressBarWrapper*>(xProgressBar.get()); + if ( pWrapper ) + xWindow = pWrapper->getStatusBar(); + } + aReadLock.clear(); + + if ( xWindow.is() ) + { + awt::Rectangle aPosSize = xWindow->getPosSize(); + return ::Size( aPosSize.Width, aPosSize.Height ); + } + else + return ::Size(); +} + +awt::Rectangle LayoutManager::implts_calcDockingAreaSizes() +{ + SolarMutexClearableGuard aReadLock; + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + Reference< XDockingAreaAcceptor > xDockingAreaAcceptor( m_xDockingAreaAcceptor ); + aReadLock.clear(); + + awt::Rectangle aBorderSpace; + if ( m_xToolbarManager.is() && xDockingAreaAcceptor.is() && xContainerWindow.is() ) + aBorderSpace = m_xToolbarManager->getDockingArea(); + + return aBorderSpace; +} + +void LayoutManager::implts_setDockingAreaWindowSizes() +{ + SolarMutexClearableGuard aReadLock; + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + aReadLock.clear(); + + uno::Reference< awt::XDevice > xDevice( xContainerWindow, uno::UNO_QUERY ); + // Convert relative size to output size. + awt::Rectangle aRectangle = xContainerWindow->getPosSize(); + awt::DeviceInfo aInfo = xDevice->getInfo(); + awt::Size aContainerClientSize( aRectangle.Width - aInfo.LeftInset - aInfo.RightInset, + aRectangle.Height - aInfo.TopInset - aInfo.BottomInset ); + ::Size aStatusBarSize = implts_getStatusBarSize(); + + // Position the status bar + if ( aStatusBarSize.Height() > 0 ) + { + implts_setStatusBarPosSize( ::Point( 0, std::max(( aContainerClientSize.Height - aStatusBarSize.Height() ), tools::Long( 0 ))), + ::Size( aContainerClientSize.Width, aStatusBarSize.Height() )); + } +} + +void LayoutManager::implts_updateMenuBarClose() +{ + SolarMutexClearableGuard aWriteLock; + bool bShowCloseButton( m_bMenuBarCloseButton ); + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + aWriteLock.clear(); + + if ( !xContainerWindow.is() ) + return; + + SolarMutexGuard aGuard; + + SystemWindow* pSysWindow = getTopSystemWindow( xContainerWindow ); + if ( pSysWindow ) + { + MenuBar* pMenuBar = pSysWindow->GetMenuBar(); + if ( pMenuBar ) + { + // TODO remove link on sal_False ?! + pMenuBar->ShowCloseButton(bShowCloseButton); + pMenuBar->SetCloseButtonClickHdl(LINK(this, LayoutManager, MenuBarClose)); + } + } +} + +bool LayoutManager::implts_resetMenuBar() +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexGuard aWriteLock; + bool bMenuVisible( m_bMenuVisible ); + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + + MenuBar* pSetMenuBar = nullptr; + if ( m_xInplaceMenuBar.is() ) + pSetMenuBar = static_cast<MenuBar *>(m_xInplaceMenuBar->GetMenuBar()); + else + { + MenuBarWrapper* pMenuBarWrapper = static_cast< MenuBarWrapper* >( m_xMenuBar.get() ); + if ( pMenuBarWrapper ) + pSetMenuBar = static_cast<MenuBar*>(pMenuBarWrapper->GetMenuBarManager()->GetMenuBar()); + } + + SystemWindow* pSysWindow = getTopSystemWindow( xContainerWindow ); + if ( pSysWindow && bMenuVisible && pSetMenuBar ) + { + pSysWindow->SetMenuBar(pSetMenuBar); + pSetMenuBar->SetDisplayable( true ); + return true; + } + + return false; +} + +void LayoutManager::implts_createMSCompatibleMenuBar( const OUString& aName ) +{ + SolarMutexGuard aWriteLock; + + // Find Form menu in the original menubar + m_xMenuBar = implts_createElement( aName ); + uno::Reference< XUIElementSettings > xMenuBarSettings(m_xMenuBar, UNO_QUERY); + uno::Reference< container::XIndexReplace > xMenuIndex(xMenuBarSettings->getSettings(true), UNO_QUERY); + + sal_Int32 nFormsMenu = -1; + for (sal_Int32 nIndex = 0; nIndex < xMenuIndex->getCount(); ++nIndex) + { + uno::Sequence< beans::PropertyValue > aProps; + xMenuIndex->getByIndex( nIndex ) >>= aProps; + OUString aCommand; + for ( beans::PropertyValue const & rProp : std::as_const(aProps) ) + { + if (rProp.Name == "CommandURL") + { + rProp.Value >>= aCommand; + break; + } + } + + if (aCommand == ".uno:FormatFormMenu") + nFormsMenu = nIndex; + } + assert(nFormsMenu != -1); + + // Create the MS compatible Form menu + css::uno::Reference< css::ui::XUIElement > xFormsMenu = implts_createElement( "private:resource/menubar/mscompatibleformsmenu" ); + if(!xFormsMenu.is()) + return; + + // Merge the MS compatible Form menu into the menubar + uno::Reference< XUIElementSettings > xFormsMenuSettings(xFormsMenu, UNO_QUERY); + uno::Reference< container::XIndexAccess > xFormsMenuIndex(xFormsMenuSettings->getSettings(true)); + + assert(xFormsMenuIndex->getCount() >= 1); + uno::Sequence< beans::PropertyValue > aNewFormsMenu; + xFormsMenuIndex->getByIndex( 0 ) >>= aNewFormsMenu; + xMenuIndex->replaceByIndex(nFormsMenu, uno::Any(aNewFormsMenu)); + + setMergedMenuBar( xMenuIndex ); + + // Clear up the temporal forms menubar + Reference< XComponent > xFormsMenuComp( xFormsMenu, UNO_QUERY ); + if ( xFormsMenuComp.is() ) + xFormsMenuComp->dispose(); + xFormsMenu.clear(); +} + +IMPL_LINK_NOARG(LayoutManager, MenuBarClose, void*, void) +{ + SolarMutexClearableGuard aReadLock; + uno::Reference< frame::XDispatchProvider > xProvider(m_xFrame, uno::UNO_QUERY); + uno::Reference< XComponentContext > xContext( m_xContext ); + aReadLock.clear(); + + if ( !xProvider.is()) + return; + + uno::Reference< frame::XDispatchHelper > xDispatcher = frame::DispatchHelper::create( xContext ); + + xDispatcher->executeDispatch( + xProvider, + ".uno:CloseWin", + "_self", + 0, + uno::Sequence< beans::PropertyValue >()); +} + +// XLayoutManagerEventBroadcaster + +void SAL_CALL LayoutManager::addLayoutManagerEventListener( const uno::Reference< frame::XLayoutManagerListener >& xListener ) +{ + m_aListenerContainer.addInterface( cppu::UnoType<frame::XLayoutManagerListener>::get(), xListener ); +} + +void SAL_CALL LayoutManager::removeLayoutManagerEventListener( const uno::Reference< frame::XLayoutManagerListener >& xListener ) +{ + m_aListenerContainer.removeInterface( cppu::UnoType<frame::XLayoutManagerListener>::get(), xListener ); +} + +void LayoutManager::implts_notifyListeners(short nEvent, const uno::Any& rInfoParam) +{ + comphelper::OInterfaceContainerHelper2* pContainer = m_aListenerContainer.getContainer( cppu::UnoType<frame::XLayoutManagerListener>::get()); + if (pContainer==nullptr) + return; + + lang::EventObject aSource( static_cast< ::cppu::OWeakObject*>(this) ); + comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer); + while (pIterator.hasMoreElements()) + { + try + { + static_cast<frame::XLayoutManagerListener*>(pIterator.next())->layoutEvent(aSource, nEvent, rInfoParam); + } + catch( const uno::RuntimeException& ) + { + pIterator.remove(); + } + } +} + +// XWindowListener + +void SAL_CALL LayoutManager::windowResized( const awt::WindowEvent& aEvent ) +{ + SolarMutexGuard g; + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + + Reference< XInterface > xIfac( xContainerWindow, UNO_QUERY ); + if ( xIfac == aEvent.Source && m_bVisible ) + { + // We have to call our resize handler at least once synchronously, as some + // application modules need this. So we have to check if this is the first + // call after the async layout time expired. + m_bMustDoLayout = true; + if ( !m_aAsyncLayoutTimer.IsActive() ) + { + m_aAsyncLayoutTimer.Invoke(); + if ( m_nLockCount == 0 ) + m_aAsyncLayoutTimer.Start(); + } + } + else if ( m_xFrame.is() && aEvent.Source == m_xFrame->getContainerWindow() ) + { + // the container window of my DockingAreaAcceptor is not the same as of my frame + // I still have to resize my frames' window as nobody else will do it + Reference< awt::XWindow > xComponentWindow( m_xFrame->getComponentWindow() ); + if( xComponentWindow.is() ) + { + uno::Reference< awt::XDevice > xDevice( m_xFrame->getContainerWindow(), uno::UNO_QUERY ); + + // Convert relative size to output size. + awt::Rectangle aRectangle = m_xFrame->getContainerWindow()->getPosSize(); + awt::DeviceInfo aInfo = xDevice->getInfo(); + awt::Size aSize( aRectangle.Width - aInfo.LeftInset - aInfo.RightInset , + aRectangle.Height - aInfo.TopInset - aInfo.BottomInset ); + + // Resize our component window. + xComponentWindow->setPosSize( 0, 0, aSize.Width, aSize.Height, awt::PosSize::POSSIZE ); + } + } +} + +void SAL_CALL LayoutManager::windowMoved( const awt::WindowEvent& ) +{ +} + +void SAL_CALL LayoutManager::windowShown( const lang::EventObject& aEvent ) +{ + SolarMutexClearableGuard aReadLock; + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + bool bParentWindowVisible( m_bParentWindowVisible ); + aReadLock.clear(); + + Reference< XInterface > xIfac( xContainerWindow, UNO_QUERY ); + if ( xIfac == aEvent.Source ) + { + SolarMutexClearableGuard aWriteLock; + m_bParentWindowVisible = true; + bool bSetVisible = ( m_bParentWindowVisible != bParentWindowVisible ); + aWriteLock.clear(); + + if ( bSetVisible ) + implts_updateUIElementsVisibleState( true ); + } +} + +void SAL_CALL LayoutManager::windowHidden( const lang::EventObject& aEvent ) +{ + SolarMutexClearableGuard aReadLock; + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + bool bParentWindowVisible( m_bParentWindowVisible ); + aReadLock.clear(); + + Reference< XInterface > xIfac( xContainerWindow, UNO_QUERY ); + if ( xIfac == aEvent.Source ) + { + SolarMutexClearableGuard aWriteLock; + m_bParentWindowVisible = false; + bool bSetInvisible = ( m_bParentWindowVisible != bParentWindowVisible ); + aWriteLock.clear(); + + if ( bSetInvisible ) + implts_updateUIElementsVisibleState( false ); + } +} + +IMPL_LINK_NOARG(LayoutManager, AsyncLayoutHdl, Timer *, void) +{ + { + SolarMutexGuard aReadLock; + + if (!m_xContainerWindow.is()) + return; + } + + implts_setDockingAreaWindowSizes(); + implts_doLayout( true, false ); +} + +// XFrameActionListener + +void SAL_CALL LayoutManager::frameAction( const FrameActionEvent& aEvent ) +{ + if (( aEvent.Action == FrameAction_COMPONENT_ATTACHED ) || ( aEvent.Action == FrameAction_COMPONENT_REATTACHED )) + { + SAL_INFO( "fwk", "LayoutManager::frameAction (COMPONENT_ATTACHED|REATTACHED)" ); + + { + SolarMutexGuard aWriteLock; + m_bMustDoLayout = true; + } + + implts_reset( true ); + implts_doLayout( true, false ); + implts_doLayout( true, true ); + } + else if (( aEvent.Action == FrameAction_FRAME_UI_ACTIVATED ) || ( aEvent.Action == FrameAction_FRAME_UI_DEACTIVATING )) + { + SAL_INFO( "fwk", "LayoutManager::frameAction (FRAME_UI_ACTIVATED|DEACTIVATING)" ); + + implts_toggleFloatingUIElementsVisibility( aEvent.Action == FrameAction_FRAME_UI_ACTIVATED ); + } + else if ( aEvent.Action == FrameAction_COMPONENT_DETACHING ) + { + SAL_INFO( "fwk", "LayoutManager::frameAction (COMPONENT_DETACHING)" ); + + implts_reset( false ); + } +} + +void SAL_CALL LayoutManager::disposing( const lang::EventObject& rEvent ) +{ + bool bDisposeAndClear( false ); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + { + SolarMutexGuard aWriteLock; + + if (rEvent.Source == Reference<XInterface>(m_xFrame, UNO_QUERY)) + { + // Our frame gets disposed, release all our references that depends on a working frame reference. + + setDockingAreaAcceptor(Reference<ui::XDockingAreaAcceptor>()); + + // destroy all elements, it's possible that detaching is NOT called! + implts_destroyElements(); + impl_clearUpMenuBar(); + m_xMenuBar.clear(); + VclPtr<Menu> pMenuBar; + if (m_xInplaceMenuBar.is()) + { + pMenuBar = m_xInplaceMenuBar->GetMenuBar(); + m_xInplaceMenuBar->dispose(); + m_xInplaceMenuBar.clear(); + } + pMenuBar.disposeAndClear(); + m_xContainerWindow.clear(); + m_xContainerTopWindow.clear(); + + // forward disposing call to toolbar manager + if (m_xToolbarManager.is()) + m_xToolbarManager->disposing(rEvent); + + if (m_xModuleCfgMgr.is()) + { + try + { + Reference<XUIConfiguration> xModuleCfgMgr(m_xModuleCfgMgr, UNO_QUERY); + xModuleCfgMgr->removeConfigurationListener(Reference<XUIConfigurationListener>(this)); + } + catch (const Exception&) + { + } + } + + if (m_xDocCfgMgr.is()) + { + try + { + Reference<XUIConfiguration> xDocCfgMgr(m_xDocCfgMgr, UNO_QUERY); + xDocCfgMgr->removeConfigurationListener(Reference<XUIConfigurationListener>(this)); + } + catch (const Exception&) + { + } + } + + m_xDocCfgMgr.clear(); + m_xModuleCfgMgr.clear(); + m_xFrame.clear(); + m_pGlobalSettings.reset(); + + bDisposeAndClear = true; + } + else if (rEvent.Source == Reference<XInterface>(m_xContainerWindow, UNO_QUERY)) + { + // Our container window gets disposed. Remove all user interface elements. + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + if (pToolbarManager) + { + uno::Reference<awt::XWindowPeer> aEmptyWindowPeer; + pToolbarManager->setParentWindow(aEmptyWindowPeer); + } + impl_clearUpMenuBar(); + m_xMenuBar.clear(); + VclPtr<Menu> pMenuBar; + if (m_xInplaceMenuBar.is()) + { + pMenuBar = m_xInplaceMenuBar->GetMenuBar(); + m_xInplaceMenuBar->dispose(); + m_xInplaceMenuBar.clear(); + } + pMenuBar.disposeAndClear(); + m_xContainerWindow.clear(); + m_xContainerTopWindow.clear(); + } + else if (rEvent.Source == Reference<XInterface>(m_xDocCfgMgr, UNO_QUERY)) + m_xDocCfgMgr.clear(); + else if (rEvent.Source == Reference<XInterface>(m_xModuleCfgMgr, UNO_QUERY)) + m_xModuleCfgMgr.clear(); + } + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + // Send disposing to our listener when we have lost our frame. + if ( bDisposeAndClear ) + { + // Send message to all listener and forget her references. + uno::Reference< frame::XLayoutManager > xThis(this); + lang::EventObject aEvent( xThis ); + m_aListenerContainer.disposeAndClear( aEvent ); + } +} + +void SAL_CALL LayoutManager::elementInserted( const ui::ConfigurationEvent& Event ) +{ + SolarMutexClearableGuard aReadLock; + Reference< XFrame > xFrame( m_xFrame ); + rtl::Reference< ToolbarLayoutManager > xToolbarManager( m_xToolbarManager ); + aReadLock.clear(); + + if ( !xFrame.is() ) + return; + + OUString aElementType; + OUString aElementName; + bool bRefreshLayout(false); + + parseResourceURL( Event.ResourceURL, aElementType, aElementName ); + if ( aElementType.equalsIgnoreAsciiCase( UIRESOURCETYPE_TOOLBAR )) + { + if ( xToolbarManager.is() ) + { + xToolbarManager->elementInserted( Event ); + bRefreshLayout = xToolbarManager->isLayoutDirty(); + } + } + else if ( aElementType.equalsIgnoreAsciiCase( UIRESOURCETYPE_MENUBAR )) + { + Reference< XUIElement > xUIElement = implts_findElement( Event.ResourceURL ); + Reference< XUIElementSettings > xElementSettings( xUIElement, UNO_QUERY ); + if ( xElementSettings.is() ) + { + uno::Reference< XPropertySet > xPropSet( xElementSettings, uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + if ( Event.Source == uno::Reference< uno::XInterface >( m_xDocCfgMgr, uno::UNO_QUERY )) + xPropSet->setPropertyValue( "ConfigurationSource", Any( m_xDocCfgMgr )); + } + xElementSettings->updateSettings(); + } + } + + if ( bRefreshLayout ) + doLayout(); +} + +void SAL_CALL LayoutManager::elementRemoved( const ui::ConfigurationEvent& Event ) +{ + SolarMutexClearableGuard aReadLock; + Reference< frame::XFrame > xFrame( m_xFrame ); + rtl::Reference< ToolbarLayoutManager > xToolbarManager( m_xToolbarManager ); + Reference< awt::XWindow > xContainerWindow( m_xContainerWindow ); + Reference< ui::XUIElement > xMenuBar( m_xMenuBar ); + Reference< ui::XUIConfigurationManager > xModuleCfgMgr( m_xModuleCfgMgr ); + Reference< ui::XUIConfigurationManager > xDocCfgMgr( m_xDocCfgMgr ); + aReadLock.clear(); + + if ( !xFrame.is() ) + return; + + OUString aElementType; + OUString aElementName; + bool bRefreshLayout(false); + + parseResourceURL( Event.ResourceURL, aElementType, aElementName ); + if ( aElementType.equalsIgnoreAsciiCase( UIRESOURCETYPE_TOOLBAR )) + { + if ( xToolbarManager.is() ) + { + xToolbarManager->elementRemoved( Event ); + bRefreshLayout = xToolbarManager->isLayoutDirty(); + } + } + else + { + Reference< XUIElement > xUIElement = implts_findElement( Event.ResourceURL ); + Reference< XUIElementSettings > xElementSettings( xUIElement, UNO_QUERY ); + if ( xElementSettings.is() ) + { + bool bNoSettings( false ); + OUString aConfigSourcePropName( "ConfigurationSource" ); + Reference< XInterface > xElementCfgMgr; + Reference< XPropertySet > xPropSet( xElementSettings, UNO_QUERY ); + + if ( xPropSet.is() ) + xPropSet->getPropertyValue( aConfigSourcePropName ) >>= xElementCfgMgr; + + if ( !xElementCfgMgr.is() ) + return; + + // Check if the same UI configuration manager has changed => check further + if ( Event.Source == xElementCfgMgr ) + { + // Same UI configuration manager where our element has its settings + if ( Event.Source == Reference< XInterface >( xDocCfgMgr, UNO_QUERY )) + { + // document settings removed + if ( xModuleCfgMgr->hasSettings( Event.ResourceURL )) + { + xPropSet->setPropertyValue( aConfigSourcePropName, Any( m_xModuleCfgMgr )); + xElementSettings->updateSettings(); + return; + } + } + + bNoSettings = true; + } + + // No settings anymore, element must be destroyed + if ( xContainerWindow.is() && bNoSettings ) + { + if ( aElementType.equalsIgnoreAsciiCase("menubar") && + aElementName.equalsIgnoreAsciiCase("menubar") ) + { + SystemWindow* pSysWindow = getTopSystemWindow( xContainerWindow ); + if ( pSysWindow && !m_bInplaceMenuSet ) + pSysWindow->SetMenuBar( nullptr ); + + Reference< XComponent > xComp( xMenuBar, UNO_QUERY ); + if ( xComp.is() ) + xComp->dispose(); + + SolarMutexGuard g; + m_xMenuBar.clear(); + } + } + } + } + + if ( bRefreshLayout ) + doLayout(); +} + +void SAL_CALL LayoutManager::elementReplaced( const ui::ConfigurationEvent& Event ) +{ + SolarMutexClearableGuard aReadLock; + Reference< XFrame > xFrame( m_xFrame ); + rtl::Reference< ToolbarLayoutManager > xToolbarManager( m_xToolbarManager ); + aReadLock.clear(); + + if ( !xFrame.is() ) + return; + + OUString aElementType; + OUString aElementName; + bool bRefreshLayout(false); + + parseResourceURL( Event.ResourceURL, aElementType, aElementName ); + if ( aElementType.equalsIgnoreAsciiCase( UIRESOURCETYPE_TOOLBAR )) + { + if ( xToolbarManager.is() ) + { + xToolbarManager->elementReplaced( Event ); + bRefreshLayout = xToolbarManager->isLayoutDirty(); + } + } + else + { + Reference< XUIElement > xUIElement = implts_findElement( Event.ResourceURL ); + Reference< XUIElementSettings > xElementSettings( xUIElement, UNO_QUERY ); + if ( xElementSettings.is() ) + { + Reference< XInterface > xElementCfgMgr; + Reference< XPropertySet > xPropSet( xElementSettings, UNO_QUERY ); + + if ( xPropSet.is() ) + xPropSet->getPropertyValue( "ConfigurationSource" ) >>= xElementCfgMgr; + + if ( !xElementCfgMgr.is() ) + return; + + // Check if the same UI configuration manager has changed => update settings + if ( Event.Source == xElementCfgMgr ) + xElementSettings->updateSettings(); + } + } + + if ( bRefreshLayout ) + doLayout(); +} + +void SAL_CALL LayoutManager::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, + const uno::Any& aValue ) +{ + if ( nHandle != LAYOUTMANAGER_PROPHANDLE_REFRESHVISIBILITY ) + LayoutManager_PBase::setFastPropertyValue_NoBroadcast( nHandle, aValue ); + + switch( nHandle ) + { + case LAYOUTMANAGER_PROPHANDLE_MENUBARCLOSER: + implts_updateMenuBarClose(); + break; + + case LAYOUTMANAGER_PROPHANDLE_REFRESHVISIBILITY: + { + bool bValue(false); + if (( aValue >>= bValue ) && bValue ) + { + SolarMutexClearableGuard aReadLock; + ToolbarLayoutManager* pToolbarManager = m_xToolbarManager.get(); + bool bAutomaticToolbars( m_bAutomaticToolbars ); + aReadLock.clear(); + + if ( pToolbarManager ) + pToolbarManager->refreshToolbarsVisibility( bAutomaticToolbars ); + } + break; + } + + case LAYOUTMANAGER_PROPHANDLE_HIDECURRENTUI: + implts_setCurrentUIVisibility( !m_bHideCurrentUI ); + break; + default: break; + } +} + +namespace detail +{ + class InfoHelperBuilder + { + private: + std::unique_ptr<::cppu::OPropertyArrayHelper> m_pInfoHelper; + public: + explicit InfoHelperBuilder(const LayoutManager &rManager) + { + uno::Sequence< beans::Property > aProperties; + rManager.describeProperties(aProperties); + m_pInfoHelper.reset( new ::cppu::OPropertyArrayHelper(aProperties, true) ); + } + InfoHelperBuilder(const InfoHelperBuilder&) = delete; + InfoHelperBuilder& operator=(const InfoHelperBuilder&) = delete; + + ::cppu::OPropertyArrayHelper& getHelper() { return *m_pInfoHelper; } + }; +} + +::cppu::IPropertyArrayHelper& SAL_CALL LayoutManager::getInfoHelper() +{ + static detail::InfoHelperBuilder INFO(*this); + return INFO.getHelper(); +} + +uno::Reference< beans::XPropertySetInfo > SAL_CALL LayoutManager::getPropertySetInfo() +{ + static uno::Reference< beans::XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) ); + + return xInfo; +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_LayoutManager_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new framework::LayoutManager(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/layoutmanager/toolbarlayoutmanager.cxx b/framework/source/layoutmanager/toolbarlayoutmanager.cxx new file mode 100644 index 000000000..34b2b0899 --- /dev/null +++ b/framework/source/layoutmanager/toolbarlayoutmanager.cxx @@ -0,0 +1,4116 @@ +/* -*- 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 "toolbarlayoutmanager.hxx" +#include <uiconfiguration/windowstateproperties.hxx> +#include <uielement/addonstoolbarwrapper.hxx> +#include "helpers.hxx" +#include <services/layoutmanager.hxx> +#include <strings.hrc> +#include <classes/fwkresid.hxx> + +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/ui/UIElementType.hpp> +#include <com/sun/star/container/XNameReplace.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/ui/XUIElementSettings.hpp> +#include <com/sun/star/ui/XUIFunctionListener.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <o3tl/string_view.hxx> +#include <unotools/cmdoptions.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <toolkit/helper/convert.hxx> +#include <utility> +#include <vcl/i18nhelp.hxx> +#include <vcl/dockingarea.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> +#include <tools/gen.hxx> + + +using namespace ::com::sun::star; + +namespace framework +{ + +ToolbarLayoutManager::ToolbarLayoutManager( + uno::Reference< uno::XComponentContext > xContext, + uno::Reference< ui::XUIElementFactory > xUIElementFactory, + LayoutManager* pParentLayouter ): + m_xContext(std::move( xContext)), + m_xUIElementFactoryManager(std::move( xUIElementFactory )), + m_pParentLayouter( pParentLayouter ), + m_aDockingArea(0, 0, 0, 0), + m_aDockingAreaOffsets(0, 0, 0, 0), + m_eDockOperation( DOCKOP_ON_COLROW ), + m_ePreviewDetection( PREVIEWFRAME_UNKNOWN ), + m_bComponentAttached( false ), + m_bLayoutDirty( false ), + m_bGlobalSettings( false ), + m_bDockingInProgress( false ), + m_bLayoutInProgress( false ), + m_bToolbarCreation( false ) +{ +} + +ToolbarLayoutManager::~ToolbarLayoutManager() +{ + m_pGlobalSettings.reset(); + m_pAddonOptions.reset(); +} + +// XInterface + +void SAL_CALL ToolbarLayoutManager::acquire() noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL ToolbarLayoutManager::release() noexcept +{ + OWeakObject::release(); +} + +uno::Any SAL_CALL ToolbarLayoutManager::queryInterface( const uno::Type & rType ) +{ + uno::Any a = ::cppu::queryInterface( rType, + static_cast< awt::XDockableWindowListener* >(this), + static_cast< ui::XUIConfigurationListener* >(this), + static_cast< awt::XWindowListener* >(this)); + + if ( a.hasValue() ) + return a; + + return OWeakObject::queryInterface( rType ); +} + +void SAL_CALL ToolbarLayoutManager::disposing( const lang::EventObject& aEvent ) +{ + if ( aEvent.Source == m_xFrame ) + { + // Reset all internal references + reset(); + implts_destroyDockingAreaWindows(); + } +} + +awt::Rectangle ToolbarLayoutManager::getDockingArea() +{ + SolarMutexResettableGuard aWriteLock; + tools::Rectangle aNewDockingArea( m_aDockingArea ); + aWriteLock.clear(); + + if ( isLayoutDirty() ) + aNewDockingArea = implts_calcDockingArea(); + + aWriteLock.reset(); + m_aDockingArea = aNewDockingArea; + aWriteLock.clear(); + + return putRectangleValueToAWT(aNewDockingArea); +} + +void ToolbarLayoutManager::setDockingArea( const awt::Rectangle& rDockingArea ) +{ + SolarMutexGuard g; + m_aDockingArea = putAWTToRectangle( rDockingArea ); + m_bLayoutDirty = true; +} + +void ToolbarLayoutManager::implts_setDockingAreaWindowSizes( const awt::Rectangle& rBorderSpace ) +{ + SolarMutexClearableGuard aReadLock; + tools::Rectangle aDockOffsets = m_aDockingAreaOffsets; + uno::Reference< awt::XWindow2 > xContainerWindow( m_xContainerWindow ); + uno::Reference< awt::XWindow > xTopDockAreaWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_TOP)] ); + uno::Reference< awt::XWindow > xBottomDockAreaWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_BOTTOM)] ); + uno::Reference< awt::XWindow > xLeftDockAreaWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_LEFT)] ); + uno::Reference< awt::XWindow > xRightDockAreaWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_RIGHT)] ); + aReadLock.clear(); + + uno::Reference< awt::XDevice > xDevice( xContainerWindow, uno::UNO_QUERY ); + + // Convert relative size to output size. + awt::Rectangle aRectangle = xContainerWindow->getPosSize(); + awt::DeviceInfo aInfo = xDevice->getInfo(); + awt::Size aContainerClientSize( aRectangle.Width - aInfo.LeftInset - aInfo.RightInset , + aRectangle.Height - aInfo.TopInset - aInfo.BottomInset ); + tools::Long aStatusBarHeight = aDockOffsets.GetHeight(); + + sal_Int32 nLeftRightDockingAreaHeight( aContainerClientSize.Height ); + if ( rBorderSpace.Y >= 0 ) + { + // Top docking area window + xTopDockAreaWindow->setPosSize( 0, 0, aContainerClientSize.Width, rBorderSpace.Y, awt::PosSize::POSSIZE ); + xTopDockAreaWindow->setVisible( true ); + nLeftRightDockingAreaHeight -= rBorderSpace.Y; + } + + if ( rBorderSpace.Height >= 0 ) + { + // Bottom docking area window + sal_Int32 nBottomPos = std::max( sal_Int32( aContainerClientSize.Height - rBorderSpace.Height - aStatusBarHeight + 1 ), sal_Int32( 0 )); + sal_Int32 nHeight = ( nBottomPos == 0 ) ? 0 : rBorderSpace.Height; + + xBottomDockAreaWindow->setPosSize( 0, nBottomPos, aContainerClientSize.Width, nHeight, awt::PosSize::POSSIZE ); + xBottomDockAreaWindow->setVisible( true ); + nLeftRightDockingAreaHeight -= nHeight - 1; + } + + nLeftRightDockingAreaHeight -= aStatusBarHeight; + if ( rBorderSpace.X >= 0 || nLeftRightDockingAreaHeight > 0 ) + { + // Left docking area window + // We also have to change our right docking area window if the top or bottom area has changed. They have a higher priority! + sal_Int32 nHeight = std::max<sal_Int32>( 0, nLeftRightDockingAreaHeight ); + + xLeftDockAreaWindow->setPosSize( 0, rBorderSpace.Y, rBorderSpace.X, nHeight, awt::PosSize::POSSIZE ); + xLeftDockAreaWindow->setVisible( true ); + } + if ( rBorderSpace.Width >= 0 || nLeftRightDockingAreaHeight > 0 ) + { + // Right docking area window + // We also have to change our right docking area window if the top or bottom area has changed. They have a higher priority! + sal_Int32 nLeftPos = std::max<sal_Int32>( 0, aContainerClientSize.Width - rBorderSpace.Width ); + sal_Int32 nHeight = std::max<sal_Int32>( 0, nLeftRightDockingAreaHeight ); + sal_Int32 nWidth = ( nLeftPos == 0 ) ? 0 : rBorderSpace.Width; + + xRightDockAreaWindow->setPosSize( nLeftPos, rBorderSpace.Y, nWidth, nHeight, awt::PosSize::POSSIZE ); + xRightDockAreaWindow->setVisible( true ); + } +} + + +void ToolbarLayoutManager::doLayout(const ::Size& aContainerSize) +{ + SolarMutexResettableGuard aWriteLock; + bool bLayoutInProgress( m_bLayoutInProgress ); + m_bLayoutInProgress = true; + awt::Rectangle aDockingArea = putRectangleValueToAWT( m_aDockingArea ); + aWriteLock.clear(); + + if ( bLayoutInProgress ) + return; + + // Retrieve row/column dependent data from all docked user-interface elements + for ( sal_Int32 i = 0; i < DOCKINGAREAS_COUNT; i++ ) + { + bool bReverse( isReverseOrderDockingArea( i )); + std::vector< SingleRowColumnWindowData > aRowColumnsWindowData; + + implts_getDockingAreaElementInfos( static_cast<ui::DockingArea>(i), aRowColumnsWindowData ); + + sal_Int32 nOffset( 0 ); + const sal_uInt32 nCount = aRowColumnsWindowData.size(); + for ( sal_uInt32 j = 0; j < nCount; ++j ) + { + sal_uInt32 nIndex = bReverse ? nCount-j-1 : j; + implts_calcWindowPosSizeOnSingleRowColumn( i, nOffset, aRowColumnsWindowData[nIndex], aContainerSize ); + nOffset += aRowColumnsWindowData[j].nStaticSize; + } + } + + implts_setDockingAreaWindowSizes( aDockingArea ); + + aWriteLock.reset(); + m_bLayoutDirty = false; + m_bLayoutInProgress = false; + aWriteLock.clear(); +} + +bool ToolbarLayoutManager::implts_isParentWindowVisible() const +{ + SolarMutexGuard g; + bool bVisible( false ); + if ( m_xContainerWindow.is() ) + bVisible = m_xContainerWindow->isVisible(); + + return bVisible; +} + +tools::Rectangle ToolbarLayoutManager::implts_calcDockingArea() +{ + SolarMutexClearableGuard aReadLock; + UIElementVector aWindowVector( m_aUIElements ); + aReadLock.clear(); + + tools::Rectangle aBorderSpace; + sal_Int32 nCurrRowColumn( 0 ); + sal_Int32 nCurrPos( 0 ); + ui::DockingArea nCurrDockingArea( ui::DockingArea_DOCKINGAREA_TOP ); + std::vector< sal_Int32 > aRowColumnSizes[DOCKINGAREAS_COUNT]; + + // initialize rectangle with zero values! + aBorderSpace.setWidth(0); + aBorderSpace.setHeight(0); + + aRowColumnSizes[static_cast<int>(nCurrDockingArea)].clear(); + aRowColumnSizes[static_cast<int>(nCurrDockingArea)].push_back( 0 ); + + for (auto const& window : aWindowVector) + { + if ( window.m_xUIElement.is() ) + { + uno::Reference< awt::XWindow > xWindow( window.m_xUIElement->getRealInterface(), uno::UNO_QUERY ); + uno::Reference< awt::XDockableWindow > xDockWindow( xWindow, uno::UNO_QUERY ); + if ( xWindow.is() && xDockWindow.is() ) + { + SolarMutexGuard aGuard; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && !xDockWindow->isFloating() && window.m_bVisible && !window.m_bMasterHide ) + { + awt::Rectangle aPosSize = xWindow->getPosSize(); + if ( window.m_aDockedData.m_nDockedArea != nCurrDockingArea ) + { + nCurrDockingArea = window.m_aDockedData.m_nDockedArea; + nCurrRowColumn = 0; + nCurrPos = 0; + aRowColumnSizes[static_cast<int>(nCurrDockingArea)].clear(); + aRowColumnSizes[static_cast<int>(nCurrDockingArea)].push_back( 0 ); + } + + if ( window.m_aDockedData.m_nDockedArea == nCurrDockingArea ) + { + if ( isHorizontalDockingArea( window.m_aDockedData.m_nDockedArea )) + { + if ( window.m_aDockedData.m_aPos.Y > nCurrPos ) + { + ++nCurrRowColumn; + nCurrPos = window.m_aDockedData.m_aPos.Y; + aRowColumnSizes[static_cast<int>(nCurrDockingArea)].push_back( 0 ); + } + + if ( aPosSize.Height > aRowColumnSizes[static_cast<int>(nCurrDockingArea)][nCurrRowColumn] ) + aRowColumnSizes[static_cast<int>(nCurrDockingArea)][nCurrRowColumn] = aPosSize.Height; + } + else + { + if ( window.m_aDockedData.m_aPos.X > nCurrPos ) + { + ++nCurrRowColumn; + nCurrPos = window.m_aDockedData.m_aPos.X; + aRowColumnSizes[static_cast<int>(nCurrDockingArea)].push_back( 0 ); + } + + if ( aPosSize.Width > aRowColumnSizes[static_cast<int>(nCurrDockingArea)][nCurrRowColumn] ) + aRowColumnSizes[static_cast<int>(nCurrDockingArea)][nCurrRowColumn] = aPosSize.Width; + } + } + } + } + } + } + + // Sum up max heights from every row/column + if ( !aWindowVector.empty() ) + { + for ( sal_Int32 i = 0; i <= sal_Int32(ui::DockingArea_DOCKINGAREA_RIGHT); i++ ) + { + sal_Int32 nSize( 0 ); + const sal_uInt32 nCount = aRowColumnSizes[i].size(); + for ( sal_uInt32 j = 0; j < nCount; j++ ) + nSize += aRowColumnSizes[i][j]; + + if ( i == sal_Int32(ui::DockingArea_DOCKINGAREA_TOP) ) + aBorderSpace.SetTop( nSize ); + else if ( i == sal_Int32(ui::DockingArea_DOCKINGAREA_BOTTOM) ) + aBorderSpace.SetBottom( nSize ); + else if ( i == sal_Int32(ui::DockingArea_DOCKINGAREA_LEFT) ) + aBorderSpace.SetLeft( nSize ); + else + aBorderSpace.SetRight( nSize ); + } + } + + return aBorderSpace; +} + +void ToolbarLayoutManager::reset() +{ + { + SolarMutexGuard aWriteLock; + m_xModuleCfgMgr.clear(); + m_xDocCfgMgr.clear(); + m_ePreviewDetection = PREVIEWFRAME_UNKNOWN; + m_bComponentAttached = false; + } + + destroyToolbars(); + resetDockingArea(); +} + +void ToolbarLayoutManager::attach( + const uno::Reference< frame::XFrame >& xFrame, + const uno::Reference< ui::XUIConfigurationManager >& xModuleCfgMgr, + const uno::Reference< ui::XUIConfigurationManager >& xDocCfgMgr, + const uno::Reference< container::XNameAccess >& xPersistentWindowState ) +{ + // reset toolbar manager if we lose our current frame + if ( m_xFrame.is() && m_xFrame != xFrame ) + reset(); + + SolarMutexGuard g; + m_xFrame = xFrame; + m_xModuleCfgMgr = xModuleCfgMgr; + m_xDocCfgMgr = xDocCfgMgr; + m_xPersistentWindowState = xPersistentWindowState; + m_bComponentAttached = true; +} + +bool ToolbarLayoutManager::isPreviewFrame() +{ + SolarMutexGuard g; + if (m_ePreviewDetection == PREVIEWFRAME_UNKNOWN) + { + uno::Reference< frame::XFrame > xFrame( m_xFrame ); + + uno::Reference< frame::XModel > xModel( impl_getModelFromFrame( xFrame )); + + m_ePreviewDetection = (implts_isPreviewModel( xModel ) ? PREVIEWFRAME_YES : PREVIEWFRAME_NO); + } + return m_ePreviewDetection == PREVIEWFRAME_YES; +} + +void ToolbarLayoutManager::createStaticToolbars() +{ + resetDockingArea(); + implts_createCustomToolBars(); + implts_createAddonsToolBars(); + implts_createNonContextSensitiveToolBars(); + implts_sortUIElements(); +} + +bool ToolbarLayoutManager::requestToolbar( const OUString& rResourceURL ) +{ + if (isPreviewFrame()) + return false; // no toolbars for preview frame! + + bool bNotify( false ); + bool bMustCallCreate( false ); + uno::Reference< ui::XUIElement > xUIElement; + + UIElement aRequestedToolbar = impl_findToolbar( rResourceURL ); + if ( aRequestedToolbar.m_aName != rResourceURL ) + { + bMustCallCreate = true; + aRequestedToolbar.m_aName = rResourceURL; + aRequestedToolbar.m_aType = UIRESOURCETYPE_TOOLBAR; + aRequestedToolbar.m_xUIElement = xUIElement; + implts_readWindowStateData( rResourceURL, aRequestedToolbar ); + } + + xUIElement = aRequestedToolbar.m_xUIElement; + if ( !xUIElement.is() ) + bMustCallCreate = true; + + bool bCreateOrShowToolbar( aRequestedToolbar.m_bVisible && !aRequestedToolbar.m_bMasterHide ); + + if ( bCreateOrShowToolbar ) + bNotify = bMustCallCreate ? createToolbar( rResourceURL ) : showToolbar( rResourceURL ); + + return bNotify; +} + +bool ToolbarLayoutManager::createToolbar( const OUString& rResourceURL ) +{ + bool bNotify( false ); + + uno::Reference<frame::XFrame> xFrame; + uno::Reference<awt::XWindow2> xContainerWindow; + { + SolarMutexGuard aReadLock; + xFrame.set(m_xFrame); + xContainerWindow.set(m_xContainerWindow); + } + + if ( !xFrame.is() || !xContainerWindow.is() ) + return false; + + UIElement aToolbarElement = implts_findToolbar( rResourceURL ); + if ( !aToolbarElement.m_xUIElement.is() ) + { + uno::Reference< ui::XUIElement > xUIElement; + + uno::Sequence< beans::PropertyValue > aPropSeq{ + comphelper::makePropertyValue("Frame", xFrame), + comphelper::makePropertyValue("Persistent", true) + }; + uno::Reference<ui::XUIElementFactory> xUIElementFactory; + { + SolarMutexGuard aReadLock; + xUIElementFactory.set(m_xUIElementFactoryManager); + } + + implts_setToolbarCreation(); + try + { + if ( xUIElementFactory.is() ) + xUIElement = xUIElementFactory->createUIElement( rResourceURL, aPropSeq ); + } + catch (const container::NoSuchElementException&) + { + } + catch (const lang::IllegalArgumentException&) + { + } + implts_setToolbarCreation( false ); + + if ( xUIElement.is() ) + { + uno::Reference< awt::XWindow > xWindow( xUIElement->getRealInterface(), uno::UNO_QUERY ); + uno::Reference< awt::XDockableWindow > xDockWindow( xWindow, uno::UNO_QUERY ); + if ( xDockWindow.is() && xWindow.is() ) + { + try + { + xDockWindow->addDockableWindowListener( uno::Reference< awt::XDockableWindowListener >(this) ); + xWindow->addWindowListener( uno::Reference< awt::XWindowListener >(this) ); + xDockWindow->enableDocking( true ); + } + catch (const uno::Exception&) + { + } + } + + bool bVisible = false; + bool bFloating = false; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + { + SolarMutexClearableGuard aWriteLock; + + UIElement& rElement = impl_findToolbar(rResourceURL); + if (rElement.m_xUIElement.is()) + { + // somebody else must have created it while we released + // the SolarMutex - just dispose our new instance and + // do nothing. (We have to dispose either the new or the + // existing m_xUIElement.) + aWriteLock.clear(); + uno::Reference<lang::XComponent> const xC(xUIElement, uno::UNO_QUERY); + xC->dispose(); + return false; + } + if (!rElement.m_aName.isEmpty()) + { + // Reuse a local entry so we are able to use the latest + // UI changes for this document. + implts_setElementData(rElement, xDockWindow); + rElement.m_xUIElement = xUIElement; + bVisible = rElement.m_bVisible; + bFloating = rElement.m_bFloating; + } + else + { + // Create new UI element and try to read its state data + UIElement aNewToolbar(rResourceURL, UIRESOURCETYPE_TOOLBAR, xUIElement); + implts_readWindowStateData(rResourceURL, aNewToolbar); + implts_setElementData(aNewToolbar, xDockWindow); + implts_insertToolbar(aNewToolbar); + bVisible = aNewToolbar.m_bVisible; + bFloating = rElement.m_bFloating; + } + } + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + // set toolbar menu style according to customize command state + SvtCommandOptions aCmdOptions; + + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->GetType() == WindowType::TOOLBOX ) + { + ToolBox* pToolbar = static_cast<ToolBox *>(pWindow.get()); + ToolBoxMenuType nMenuType = pToolbar->GetMenuType(); + if ( aCmdOptions.Lookup( SvtCommandOptions::CMDOPTION_DISABLED, "ConfigureDialog" )) + pToolbar->SetMenuType( nMenuType & ~ToolBoxMenuType::Customize ); + else + pToolbar->SetMenuType( nMenuType | ToolBoxMenuType::Customize ); + } + bNotify = true; + + implts_sortUIElements(); + + if ( bVisible && !bFloating ) + implts_setLayoutDirty(); + } + } + + return bNotify; +} + +bool ToolbarLayoutManager::destroyToolbar( std::u16string_view rResourceURL ) +{ + uno::Reference< lang::XComponent > xComponent; + + bool bNotify( false ); + bool bMustBeSorted( false ); + bool bMustLayouted( false ); + bool bMustBeDestroyed( !o3tl::starts_with(rResourceURL, u"private:resource/toolbar/addon_") ); + + { + SolarMutexGuard aWriteLock; + for (auto & elem : m_aUIElements) + { + if (elem.m_aName == rResourceURL) + { + xComponent.set(elem.m_xUIElement, uno::UNO_QUERY); + if (bMustBeDestroyed) + elem.m_xUIElement.clear(); + else + elem.m_bVisible = false; + break; + } + } + } + + uno::Reference< ui::XUIElement > xUIElement( xComponent, uno::UNO_QUERY ); + if ( xUIElement.is() ) + { + uno::Reference< awt::XWindow > xWindow( xUIElement->getRealInterface(), uno::UNO_QUERY ); + uno::Reference< awt::XDockableWindow > xDockWindow( xWindow, uno::UNO_QUERY ); + + if ( bMustBeDestroyed ) + { + try + { + if ( xWindow.is() ) + xWindow->removeWindowListener( uno::Reference< awt::XWindowListener >(this) ); + } + catch (const uno::Exception&) + { + } + + try + { + if ( xDockWindow.is() ) + xDockWindow->removeDockableWindowListener( uno::Reference< awt::XDockableWindowListener >(this) ); + } + catch (const uno::Exception&) + { + } + } + else + { + if ( xWindow.is() ) + xWindow->setVisible( false ); + bNotify = true; + } + + if ( !xDockWindow->isFloating() ) + bMustLayouted = true; + bMustBeSorted = true; + } + + if ( bMustBeDestroyed ) + { + if ( xComponent.is() ) + xComponent->dispose(); + bNotify = true; + } + + if ( bMustLayouted ) + implts_setLayoutDirty(); + + if ( bMustBeSorted ) + implts_sortUIElements(); + + return bNotify; +} + +void ToolbarLayoutManager::destroyToolbars() +{ + UIElementVector aUIElementVector; + implts_getUIElementVectorCopy( aUIElementVector ); + + { + SolarMutexGuard aWriteLock; + m_aUIElements.clear(); + m_bLayoutDirty = true; + } + + for (auto const& elem : aUIElementVector) + { + uno::Reference< lang::XComponent > xComponent( elem.m_xUIElement, uno::UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + } +} + +bool ToolbarLayoutManager::showToolbar( std::u16string_view rResourceURL ) +{ + UIElement aUIElement = implts_findToolbar( rResourceURL ); + + SolarMutexGuard aGuard; + vcl::Window* pWindow = getWindowFromXUIElement( aUIElement.m_xUIElement ); + + // Addons appear to need to be populated at start, but we don't + // want to populate them with (scaled) images until later. + AddonsToolBarWrapper *pAddOns; + pAddOns = dynamic_cast<AddonsToolBarWrapper *>( aUIElement.m_xUIElement.get()); + if (pAddOns) + pAddOns->populateImages(); + + if ( pWindow ) + { + if ( !aUIElement.m_bFloating ) + implts_setLayoutDirty(); + else + pWindow->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); + + aUIElement.m_bVisible = true; + implts_writeWindowStateData( aUIElement ); + implts_setToolbar( aUIElement ); + implts_sortUIElements(); + return true; + } + + return false; +} + +bool ToolbarLayoutManager::hideToolbar( std::u16string_view rResourceURL ) +{ + UIElement aUIElement = implts_findToolbar( rResourceURL ); + + SolarMutexGuard aGuard; + vcl::Window* pWindow = getWindowFromXUIElement( aUIElement.m_xUIElement ); + if ( pWindow ) + { + pWindow->Show( false ); + if ( !aUIElement.m_bFloating ) + implts_setLayoutDirty(); + + aUIElement.m_bVisible = false; + implts_writeWindowStateData( aUIElement ); + implts_setToolbar( aUIElement ); + return true; + } + + return false; +} + +void ToolbarLayoutManager::refreshToolbarsVisibility( bool bAutomaticToolbars ) +{ + UIElementVector aUIElementVector; + + if ( !bAutomaticToolbars ) + return; + + implts_getUIElementVectorCopy( aUIElementVector ); + + UIElement aUIElement; + SolarMutexGuard aGuard; + for (auto const& elem : aUIElementVector) + { + if ( implts_readWindowStateData( elem.m_aName, aUIElement ) && + ( elem.m_bVisible != aUIElement.m_bVisible ) && !elem.m_bMasterHide ) + { + SolarMutexGuard g; + UIElement& rUIElement = impl_findToolbar( elem.m_aName ); + if ( rUIElement.m_aName == elem.m_aName ) + { + rUIElement.m_bVisible = aUIElement.m_bVisible; + implts_setLayoutDirty(); + } + } + } +} + +void ToolbarLayoutManager::setFloatingToolbarsVisibility( bool bVisible ) +{ + UIElementVector aUIElementVector; + implts_getUIElementVectorCopy( aUIElementVector ); + + SolarMutexGuard aGuard; + for (auto const& elem : aUIElementVector) + { + vcl::Window* pWindow = getWindowFromXUIElement( elem.m_xUIElement ); + if ( pWindow && elem.m_bFloating ) + { + if ( bVisible ) + { + if ( elem.m_bVisible && !elem.m_bMasterHide ) + pWindow->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); + } + else + pWindow->Show( false ); + } + } +} + +void ToolbarLayoutManager::setVisible( bool bVisible ) +{ + UIElementVector aUIElementVector; + implts_getUIElementVectorCopy( aUIElementVector ); + + SolarMutexGuard aGuard; + for (auto & elem : aUIElementVector) + { + if (!elem.m_bFloating) + { + elem.m_bMasterHide = !bVisible; + implts_setToolbar(elem); + implts_setLayoutDirty(); + } + + vcl::Window* pWindow = getWindowFromXUIElement( elem.m_xUIElement ); + if ( pWindow ) + { + bool bSetVisible( elem.m_bVisible && bVisible ); + if ( !bSetVisible ) + pWindow->Hide(); + else + { + if ( elem.m_bFloating ) + pWindow->Show(true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); + } + } + } + + if ( !bVisible ) + resetDockingArea(); +} + +bool ToolbarLayoutManager::dockToolbar( std::u16string_view rResourceURL, ui::DockingArea eDockingArea, const awt::Point& aPos ) +{ + UIElement aUIElement = implts_findToolbar( rResourceURL ); + + if ( !aUIElement.m_xUIElement.is() ) + return false; + + try + { + uno::Reference< awt::XWindow > xWindow( aUIElement.m_xUIElement->getRealInterface(), uno::UNO_QUERY ); + uno::Reference< awt::XDockableWindow > xDockWindow( xWindow, uno::UNO_QUERY ); + if ( xDockWindow.is() ) + { + if ( eDockingArea != ui::DockingArea_DOCKINGAREA_DEFAULT ) + aUIElement.m_aDockedData.m_nDockedArea = eDockingArea; + + if ( !isDefaultPos( aPos )) + aUIElement.m_aDockedData.m_aPos = aPos; + + if ( !xDockWindow->isFloating() ) + { + vcl::Window* pWindow( nullptr ); + ToolBox* pToolBox( nullptr ); + + { + SolarMutexGuard aGuard; + pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->GetType() == WindowType::TOOLBOX ) + { + pToolBox = static_cast<ToolBox *>(pWindow); + + // We have to set the alignment of the toolbox. It's possible that the toolbox is moved from a + // horizontal to a vertical docking area! + pToolBox->SetAlign( ImplConvertAlignment( aUIElement.m_aDockedData.m_nDockedArea )); + } + } + + if ( hasDefaultPosValue( aUIElement.m_aDockedData.m_aPos )) + { + // Docking on its default position without a preset position - + // we have to find a good place for it. + ::Size aSize; + + SolarMutexGuard aGuard; + { + if (pToolBox) + aSize = pToolBox->CalcWindowSizePixel( 1, ImplConvertAlignment( aUIElement.m_aDockedData.m_nDockedArea ) ); + else if (pWindow) + aSize = pWindow->GetSizePixel(); + } + + ::Point aPixelPos; + awt::Point aDockPos; + implts_findNextDockingPos(aUIElement.m_aDockedData.m_nDockedArea, aSize, aDockPos, aPixelPos ); + aUIElement.m_aDockedData.m_aPos = aDockPos; + } + } + + implts_setToolbar( aUIElement ); + + if ( xDockWindow->isFloating() ) + { + // ATTENTION: This will call toggleFloatingMode() via notifications which + // sets the floating member of the UIElement correctly! + xDockWindow->setFloatingMode( false ); + } + else + { + implts_writeWindowStateData( aUIElement ); + implts_sortUIElements(); + + if ( aUIElement.m_bVisible ) + implts_setLayoutDirty(); + } + return true; + } + } + catch (const lang::DisposedException&) + { + } + + return false; +} + +bool ToolbarLayoutManager::dockAllToolbars() +{ + std::vector< OUString > aToolBarNameVector; + + { + SolarMutexGuard aReadLock; + for (auto const& elem : m_aUIElements) + { + if (elem.m_aType == "toolbar" && elem.m_xUIElement.is() && elem.m_bFloating + && elem.m_bVisible) + aToolBarNameVector.push_back(elem.m_aName); + } + } + + bool bResult(true); + const sal_uInt32 nCount = aToolBarNameVector.size(); + for ( sal_uInt32 i = 0; i < nCount; ++i ) + { + awt::Point aPoint; + aPoint.X = aPoint.Y = SAL_MAX_INT32; + bResult &= dockToolbar( aToolBarNameVector[i], ui::DockingArea_DOCKINGAREA_DEFAULT, aPoint ); + } + + return bResult; +} + +void ToolbarLayoutManager::childWindowEvent( VclSimpleEvent const * pEvent ) +{ + // To enable toolbar controllers to change their image when a sub-toolbar function + // is activated, we need this mechanism. We have NO connection between these toolbars + // anymore! + auto pWindowEvent = dynamic_cast< const VclWindowEvent* >(pEvent); + if (!pWindowEvent) + return; + + if ( pEvent->GetId() == VclEventId::ToolboxSelect ) + { + OUString aToolbarName; + OUString aCommand; + ToolBox* pToolBox = getToolboxPtr( pWindowEvent->GetWindow() ); + + if ( pToolBox ) + { + aToolbarName = retrieveToolbarNameFromHelpURL( pToolBox ); + ToolBoxItemId nId = pToolBox->GetCurItemId(); + if ( nId > ToolBoxItemId(0) ) + aCommand = pToolBox->GetItemCommand( nId ); + } + + if ( !aToolbarName.isEmpty() && !aCommand.isEmpty() ) + { + SolarMutexClearableGuard aReadLock; + ::std::vector< uno::Reference< ui::XUIFunctionListener > > aListenerArray; + + for (auto const& elem : m_aUIElements) + { + if ( elem.m_xUIElement.is() ) + { + uno::Reference< ui::XUIFunctionListener > xListener( elem.m_xUIElement, uno::UNO_QUERY ); + if ( xListener.is() ) + aListenerArray.push_back( xListener ); + } + } + aReadLock.clear(); + + const sal_uInt32 nCount = aListenerArray.size(); + for ( sal_uInt32 i = 0; i < nCount; ++i ) + { + try + { + aListenerArray[i]->functionExecute( aToolbarName, aCommand ); + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception&) + { + } + } + } + } + else if ( pEvent->GetId() == VclEventId::ToolboxFormatChanged ) + { + if ( !implts_isToolbarCreationActive() ) + { + ToolBox* pToolBox = getToolboxPtr( pWindowEvent->GetWindow() ); + if ( pToolBox ) + { + OUString aToolbarName = retrieveToolbarNameFromHelpURL( pToolBox ); + if ( !aToolbarName.isEmpty() ) + { + OUString aToolbarUrl = "private:resource/toolbar/" + aToolbarName; + + UIElement aToolbar = implts_findToolbar( aToolbarUrl ); + if ( aToolbar.m_xUIElement.is() && !aToolbar.m_bFloating ) + { + implts_setLayoutDirty(); + m_pParentLayouter->requestLayout(); + } + } + } + } + } +} + +void ToolbarLayoutManager::resetDockingArea() +{ + SolarMutexClearableGuard aReadLock; + uno::Reference< awt::XWindow > xTopDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_TOP)] ); + uno::Reference< awt::XWindow > xLeftDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_LEFT)] ); + uno::Reference< awt::XWindow > xRightDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_RIGHT)] ); + uno::Reference< awt::XWindow > xBottomDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_BOTTOM)] ); + aReadLock.clear(); + + if ( xTopDockingWindow.is() ) + xTopDockingWindow->setPosSize( 0, 0, 0, 0, awt::PosSize::POSSIZE ); + if ( xLeftDockingWindow.is() ) + xLeftDockingWindow->setPosSize( 0, 0, 0, 0, awt::PosSize::POSSIZE ); + if ( xRightDockingWindow.is() ) + xRightDockingWindow->setPosSize( 0, 0, 0, 0, awt::PosSize::POSSIZE ); + if ( xBottomDockingWindow.is() ) + xBottomDockingWindow->setPosSize( 0, 0, 0, 0, awt::PosSize::POSSIZE ); +} + +void ToolbarLayoutManager::setParentWindow( + const uno::Reference< awt::XWindowPeer >& xParentWindow ) +{ + static const char DOCKINGAREASTRING[] = "dockingarea"; + + uno::Reference< awt::XWindow > xTopDockWindow( createToolkitWindow( m_xContext, xParentWindow, DOCKINGAREASTRING ), uno::UNO_QUERY ); + uno::Reference< awt::XWindow > xLeftDockWindow( createToolkitWindow( m_xContext, xParentWindow, DOCKINGAREASTRING ), uno::UNO_QUERY ); + uno::Reference< awt::XWindow > xRightDockWindow( createToolkitWindow( m_xContext, xParentWindow, DOCKINGAREASTRING ), uno::UNO_QUERY ); + uno::Reference< awt::XWindow > xBottomDockWindow( createToolkitWindow( m_xContext, xParentWindow, DOCKINGAREASTRING ), uno::UNO_QUERY ); + + { + SolarMutexGuard aWriteLock; + m_xContainerWindow.set(xParentWindow, uno::UNO_QUERY); + m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_TOP)] = xTopDockWindow; + m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_LEFT)] = xLeftDockWindow; + m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_RIGHT)] = xRightDockWindow; + m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_BOTTOM)] = xBottomDockWindow; + } + + if ( xParentWindow.is() ) + { + SolarMutexGuard aGuard; + VclPtr< ::DockingAreaWindow > pWindow = dynamic_cast< ::DockingAreaWindow* >(VCLUnoHelper::GetWindow( xTopDockWindow ) ); + if( pWindow ) + pWindow->SetAlign( WindowAlign::Top ); + pWindow = dynamic_cast< ::DockingAreaWindow* >(VCLUnoHelper::GetWindow( xBottomDockWindow ) ); + if( pWindow ) + pWindow->SetAlign( WindowAlign::Bottom ); + pWindow = dynamic_cast< ::DockingAreaWindow* >(VCLUnoHelper::GetWindow( xLeftDockWindow ) ); + if( pWindow ) + pWindow->SetAlign( WindowAlign::Left ); + pWindow = dynamic_cast< ::DockingAreaWindow* >(VCLUnoHelper::GetWindow( xRightDockWindow ) ); + if( pWindow ) + pWindow->SetAlign( WindowAlign::Right ); + implts_reparentToolbars(); + } + else + { + destroyToolbars(); + resetDockingArea(); + } +} + +void ToolbarLayoutManager::setDockingAreaOffsets( const ::tools::Rectangle& rOffsets ) +{ + SolarMutexGuard g; + m_aDockingAreaOffsets = rOffsets; + m_bLayoutDirty = true; +} + +OUString ToolbarLayoutManager::implts_generateGenericAddonToolbarTitle( sal_Int32 nNumber ) const +{ + OUString aAddonGenericTitle(FwkResId(STR_TOOLBAR_TITLE_ADDON)); + const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper(); + + OUString aNumStr = rI18nHelper.GetNum( nNumber, 0, false, false ); + aAddonGenericTitle = aAddonGenericTitle.replaceFirst( "%num%", aNumStr ); + + return aAddonGenericTitle; +} + +void ToolbarLayoutManager::implts_createAddonsToolBars() +{ + SolarMutexClearableGuard aWriteLock; + if ( !m_pAddonOptions ) + m_pAddonOptions.reset( new AddonsOptions ); + + uno::Reference< ui::XUIElementFactory > xUIElementFactory( m_xUIElementFactoryManager ); + uno::Reference< frame::XFrame > xFrame( m_xFrame ); + aWriteLock.clear(); + + if (isPreviewFrame()) + return; // no addon toolbars for preview frame! + + uno::Sequence< uno::Sequence< beans::PropertyValue > > aAddonToolBarData; + uno::Reference< ui::XUIElement > xUIElement; + + sal_uInt32 nCount = m_pAddonOptions->GetAddonsToolBarCount(); + + uno::Sequence< beans::PropertyValue > aPropSeq( 2 ); + auto pPropSeq = aPropSeq.getArray(); + pPropSeq[0].Name = "Frame"; + pPropSeq[0].Value <<= xFrame; + pPropSeq[1].Name = "ConfigurationData"; + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + OUString aAddonToolBarName( "private:resource/toolbar/addon_" + + m_pAddonOptions->GetAddonsToolbarResourceName(i) ); + aAddonToolBarData = m_pAddonOptions->GetAddonsToolBarPart( i ); + pPropSeq[1].Value <<= aAddonToolBarData; + + UIElement aElement = implts_findToolbar( aAddonToolBarName ); + + // #i79828 + // It's now possible that we are called more than once. Be sure to not create + // add-on toolbars more than once! + if ( aElement.m_xUIElement.is() ) + continue; + + try + { + xUIElement = xUIElementFactory->createUIElement( aAddonToolBarName, aPropSeq ); + if ( xUIElement.is() ) + { + uno::Reference< awt::XDockableWindow > xDockWindow( xUIElement->getRealInterface(), uno::UNO_QUERY ); + if ( xDockWindow.is() ) + { + try + { + xDockWindow->addDockableWindowListener( uno::Reference< awt::XDockableWindowListener >(this) ); + xDockWindow->enableDocking( true ); + uno::Reference< awt::XWindow > xWindow( xDockWindow, uno::UNO_QUERY ); + if ( xWindow.is() ) + xWindow->addWindowListener( uno::Reference< awt::XWindowListener >(this) ); + } + catch (const uno::Exception&) + { + } + } + + OUString aGenericAddonTitle = implts_generateGenericAddonToolbarTitle( i+1 ); + + if ( !aElement.m_aName.isEmpty() ) + { + // Reuse a local entry so we are able to use the latest + // UI changes for this document. + implts_setElementData( aElement, xDockWindow ); + aElement.m_xUIElement = xUIElement; + if ( aElement.m_aUIName.isEmpty() ) + { + aElement.m_aUIName = aGenericAddonTitle; + implts_writeWindowStateData( aElement ); + } + } + else + { + // Create new UI element and try to read its state data + UIElement aNewToolbar( aAddonToolBarName, "toolbar", xUIElement ); + aNewToolbar.m_bFloating = true; + implts_readWindowStateData( aAddonToolBarName, aNewToolbar ); + implts_setElementData( aNewToolbar, xDockWindow ); + if ( aNewToolbar.m_aUIName.isEmpty() ) + { + aNewToolbar.m_aUIName = aGenericAddonTitle; + implts_writeWindowStateData( aNewToolbar ); + } + implts_insertToolbar( aNewToolbar ); + } + + uno::Reference< awt::XWindow > xWindow( xDockWindow, uno::UNO_QUERY ); + if ( xWindow.is() ) + { + // Set generic title for add-on toolbar + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow->GetText().isEmpty() ) + pWindow->SetText( aGenericAddonTitle ); + if ( pWindow->GetType() == WindowType::TOOLBOX ) + { + ToolBox* pToolbar = static_cast<ToolBox *>(pWindow.get()); + pToolbar->SetMenuType(); + } + } + } + } + catch (const container::NoSuchElementException&) + { + } + catch (const lang::IllegalArgumentException&) + { + } + } +} + +void ToolbarLayoutManager::implts_createCustomToolBars() +{ + SolarMutexClearableGuard aReadLock; + if ( !m_bComponentAttached ) + return; + + uno::Reference< frame::XFrame > xFrame( m_xFrame ); + uno::Reference< ui::XUIConfigurationManager > xModuleCfgMgr = m_xModuleCfgMgr; + uno::Reference< ui::XUIConfigurationManager > xDocCfgMgr = m_xDocCfgMgr; + aReadLock.clear(); + + if ( !xFrame.is() ) + return; + + if (isPreviewFrame()) + return; // no custom toolbars for preview frame! + + uno::Sequence< uno::Sequence< beans::PropertyValue > > aTbxSeq; + if ( xDocCfgMgr.is() ) + { + aTbxSeq = xDocCfgMgr->getUIElementsInfo( ui::UIElementType::TOOLBAR ); + implts_createCustomToolBars( aTbxSeq ); // first create all document based toolbars + } + if ( xModuleCfgMgr.is() ) + { + aTbxSeq = xModuleCfgMgr->getUIElementsInfo( ui::UIElementType::TOOLBAR ); + implts_createCustomToolBars( aTbxSeq ); // second create module based toolbars + } +} + +void ToolbarLayoutManager::implts_createNonContextSensitiveToolBars() +{ + SolarMutexClearableGuard aReadLock; + + if ( !m_xPersistentWindowState.is() || !m_xFrame.is() || !m_bComponentAttached ) + return; + + uno::Reference< container::XNameAccess > xPersistentWindowState( m_xPersistentWindowState ); + aReadLock.clear(); + + if (isPreviewFrame()) + return; + + std::vector< OUString > aMakeVisibleToolbars; + + try + { + const uno::Sequence< OUString > aToolbarNames = xPersistentWindowState->getElementNames(); + + if ( aToolbarNames.hasElements() ) + { + OUString aElementType; + OUString aElementName; + + aMakeVisibleToolbars.reserve(aToolbarNames.getLength()); + + SolarMutexGuard g; + + for ( OUString const & aName : aToolbarNames ) + { + parseResourceURL( aName, aElementType, aElementName ); + + // Check that we only create: + // - Toolbars (the statusbar is also member of the persistent window state) + // - Not custom toolbars, there are created with their own method (implts_createCustomToolbars) + if ( aElementType.equalsIgnoreAsciiCase("toolbar") && + aElementName.indexOf( "custom_" ) == -1 ) + { + UIElement aNewToolbar = implts_findToolbar( aName ); + bool bFound = ( aNewToolbar.m_aName == aName ); + if ( !bFound ) + implts_readWindowStateData( aName, aNewToolbar ); + + if ( aNewToolbar.m_bVisible && !aNewToolbar.m_bContextSensitive ) + { + if ( !bFound ) + implts_insertToolbar( aNewToolbar ); + aMakeVisibleToolbars.push_back( aName ); + } + } + } + } + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception&) + { + } + + for (auto const& rURL : aMakeVisibleToolbars) + { + requestToolbar(rURL); + } +} + +void ToolbarLayoutManager::implts_createCustomToolBars( const uno::Sequence< uno::Sequence< beans::PropertyValue > >& aTbxSeqSeq ) +{ + for ( const uno::Sequence< beans::PropertyValue >& rTbxSeq : aTbxSeqSeq ) + { + OUString aTbxResName; + OUString aTbxTitle; + for ( const beans::PropertyValue& rProp : rTbxSeq ) + { + if ( rProp.Name == "ResourceURL" ) + rProp.Value >>= aTbxResName; + else if ( rProp.Name == "UIName" ) + rProp.Value >>= aTbxTitle; + } + + // Only create custom toolbars. Their name have to start with "custom_"! + if ( !aTbxResName.isEmpty() && ( aTbxResName.indexOf( "custom_" ) != -1 ) ) + implts_createCustomToolBar( aTbxResName, aTbxTitle ); + } +} + +void ToolbarLayoutManager::implts_createCustomToolBar( const OUString& aTbxResName, const OUString& aTitle ) +{ + if ( aTbxResName.isEmpty() ) + return; + + if ( !createToolbar( aTbxResName ) ) + SAL_WARN("fwk.uielement", "ToolbarLayoutManager cannot create custom toolbar"); + + uno::Reference< ui::XUIElement > xUIElement = getToolbar( aTbxResName ); + + if ( !aTitle.isEmpty() && xUIElement.is() ) + { + SolarMutexGuard aGuard; + + vcl::Window* pWindow = getWindowFromXUIElement( xUIElement ); + if ( pWindow ) + pWindow->SetText( aTitle ); + } +} + +void ToolbarLayoutManager::implts_reparentToolbars() +{ + SolarMutexClearableGuard aWriteLock; + UIElementVector aUIElementVector = m_aUIElements; + VclPtr<vcl::Window> pContainerWindow = VCLUnoHelper::GetWindow( m_xContainerWindow ); + VclPtr<vcl::Window> pTopDockWindow = VCLUnoHelper::GetWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_TOP)] ); + VclPtr<vcl::Window> pBottomDockWindow = VCLUnoHelper::GetWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_BOTTOM)] ); + VclPtr<vcl::Window> pLeftDockWindow = VCLUnoHelper::GetWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_LEFT)] ); + VclPtr<vcl::Window> pRightDockWindow = VCLUnoHelper::GetWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_RIGHT)] ); + aWriteLock.clear(); + + SolarMutexGuard aGuard; + if ( !pContainerWindow ) + return; + + for (auto const& elem : aUIElementVector) + { + uno::Reference< ui::XUIElement > xUIElement( elem.m_xUIElement ); + if ( xUIElement.is() ) + { + uno::Reference< awt::XWindow > xWindow; + try + { + // We have to retrieve the window reference with try/catch as it is + // possible that all elements have been disposed! + xWindow.set( xUIElement->getRealInterface(), uno::UNO_QUERY ); + } + catch (const uno::RuntimeException&) + { + throw; + } + catch (const uno::Exception&) + { + } + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow ) + { + // Reparent our child windows according to their current state. + if ( elem.m_bFloating ) + pWindow->SetParent( pContainerWindow ); + else + { + if ( elem.m_aDockedData.m_nDockedArea == ui::DockingArea_DOCKINGAREA_TOP ) + pWindow->SetParent( pTopDockWindow ); + else if ( elem.m_aDockedData.m_nDockedArea == ui::DockingArea_DOCKINGAREA_BOTTOM ) + pWindow->SetParent( pBottomDockWindow ); + else if ( elem.m_aDockedData.m_nDockedArea == ui::DockingArea_DOCKINGAREA_LEFT ) + pWindow->SetParent( pLeftDockWindow ); + else + pWindow->SetParent( pRightDockWindow ); + } + } + } + } +} + +void ToolbarLayoutManager::implts_setToolbarCreation( bool bStart ) +{ + SolarMutexGuard g; + m_bToolbarCreation = bStart; +} + +bool ToolbarLayoutManager::implts_isToolbarCreationActive() +{ + SolarMutexGuard g; + return m_bToolbarCreation; +} + +void ToolbarLayoutManager::implts_setElementData( UIElement& rElement, const uno::Reference< awt::XDockableWindow >& rDockWindow ) +{ + SolarMutexClearableGuard aReadLock; + bool bShowElement( rElement.m_bVisible && !rElement.m_bMasterHide && implts_isParentWindowVisible() ); + aReadLock.clear(); + + uno::Reference< awt::XWindow2 > xWindow( rDockWindow, uno::UNO_QUERY ); + + vcl::Window* pWindow( nullptr ); + ToolBox* pToolBox( nullptr ); + + if ( !(rDockWindow.is() && xWindow.is()) ) + return; + + { + SolarMutexGuard aGuard; + pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow ) + { + OUString aText = pWindow->GetText(); + if ( aText.isEmpty() ) + pWindow->SetText( rElement.m_aUIName ); + if ( rElement.m_bNoClose ) + pWindow->SetStyle( pWindow->GetStyle() & ~WB_CLOSEABLE ); + if ( pWindow->GetType() == WindowType::TOOLBOX ) + pToolBox = static_cast<ToolBox *>(pWindow); + } + if ( pToolBox ) + { + pToolBox->SetButtonType( rElement.m_nStyle ); + if ( rElement.m_bNoClose ) + pToolBox->SetFloatStyle( pToolBox->GetFloatStyle() & ~WB_CLOSEABLE ); + } + } + + if ( rElement.m_bFloating ) + { + if ( pWindow ) + { + SolarMutexGuard aGuard; + OUString aText = pWindow->GetText(); + if ( aText.isEmpty() ) + pWindow->SetText( rElement.m_aUIName ); + } + + awt::Point aPos(rElement.m_aFloatingData.m_aPos); + bool bWriteData( false ); + bool bUndefPos = hasDefaultPosValue( rElement.m_aFloatingData.m_aPos ); + bool bSetSize = ( rElement.m_aFloatingData.m_aSize.Width != 0 && + rElement.m_aFloatingData.m_aSize.Height != 0 ); + rDockWindow->setFloatingMode( true ); + if ( bUndefPos ) + { + aPos = implts_findNextCascadeFloatingPos(); + rElement.m_aFloatingData.m_aPos = aPos; // set new cascaded position + bWriteData = true; + } + + if( bSetSize ) + xWindow->setOutputSize(rElement.m_aFloatingData.m_aSize); + else + { + if( pToolBox ) + { + // set an optimal initial floating size + SolarMutexGuard aGuard; + ::Size aSize( pToolBox->CalcFloatingWindowSizePixel() ); + pToolBox->SetOutputSizePixel( aSize ); + } + } + + // #i60882# IMPORTANT: Set position after size as it is + // possible that we position some part of the toolbar + // outside of the desktop. A default constructed toolbar + // always has one line. Now VCL automatically + // position the toolbar back into the desktop. Therefore + // we resize the toolbar with the new (wrong) position. + // To fix this problem we have to set the size BEFORE the + // position. + xWindow->setPosSize( aPos.X, aPos.Y, 0, 0, awt::PosSize::POS ); + + if ( bWriteData ) + implts_writeWindowStateData( rElement ); + if ( bShowElement && pWindow ) + { + SolarMutexGuard aGuard; + pWindow->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); + } + } + else + { + bool bSetSize( false ); + ::Point aPixelPos; + ::Size aSize; + + if ( pToolBox ) + { + SolarMutexGuard aGuard; + pToolBox->SetAlign( ImplConvertAlignment(rElement.m_aDockedData.m_nDockedArea ) ); + pToolBox->SetLineCount( 1 ); + rDockWindow->setFloatingMode( false ); + if ( rElement.m_aDockedData.m_bLocked ) + rDockWindow->lock(); + aSize = pToolBox->CalcWindowSizePixel(); + bSetSize = true; + + if ( isDefaultPos( rElement.m_aDockedData.m_aPos )) + { + awt::Point aDockPos; + implts_findNextDockingPos( rElement.m_aDockedData.m_nDockedArea, aSize, aDockPos, aPixelPos ); + rElement.m_aDockedData.m_aPos = aDockPos; + } + } + + xWindow->setPosSize( aPixelPos.X(), aPixelPos.Y(), 0, 0, awt::PosSize::POS ); + if( bSetSize ) + xWindow->setOutputSize( AWTSize( aSize) ); + + if ( pWindow ) + { + SolarMutexGuard aGuard; + if ( !bShowElement ) + pWindow->Hide(); + } + } +} + +void ToolbarLayoutManager::implts_destroyDockingAreaWindows() +{ + SolarMutexClearableGuard aWriteLock; + uno::Reference< awt::XWindow > xTopDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_TOP)] ); + uno::Reference< awt::XWindow > xLeftDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_LEFT)] ); + uno::Reference< awt::XWindow > xRightDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_RIGHT)] ); + uno::Reference< awt::XWindow > xBottomDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_BOTTOM)] ); + m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_TOP)].clear(); + m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_LEFT)].clear(); + m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_RIGHT)].clear(); + m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_BOTTOM)].clear(); + aWriteLock.clear(); + + // destroy windows + xTopDockingWindow->dispose(); + xLeftDockingWindow->dispose(); + xRightDockingWindow->dispose(); + xBottomDockingWindow->dispose(); +} + +// persistence methods + +bool ToolbarLayoutManager::implts_readWindowStateData( const OUString& aName, UIElement& rElementData ) +{ + return LayoutManager::readWindowStateData( aName, rElementData, m_xPersistentWindowState, + m_pGlobalSettings, m_bGlobalSettings, m_xContext ); +} + +void ToolbarLayoutManager::implts_writeWindowStateData( const UIElement& rElementData ) +{ + SolarMutexClearableGuard aWriteLock; + uno::Reference< container::XNameAccess > xPersistentWindowState( m_xPersistentWindowState ); + aWriteLock.clear(); + + bool bPersistent( false ); + uno::Reference< beans::XPropertySet > xPropSet( rElementData.m_xUIElement, uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + // Check persistent flag of the user interface element + xPropSet->getPropertyValue("Persistent") >>= bPersistent; + } + catch (const beans::UnknownPropertyException&) + { + bPersistent = true; // Non-configurable elements should at least store their dimension/position + } + catch (const lang::WrappedTargetException&) + { + } + } + + if ( !(bPersistent && xPersistentWindowState.is()) ) + return; + + try + { + uno::Sequence<beans::PropertyValue> aWindowState{ + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_DOCKED, !rElementData.m_bFloating), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_VISIBLE, rElementData.m_bVisible), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_DOCKINGAREA, + rElementData.m_aDockedData.m_nDockedArea), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_DOCKPOS, + rElementData.m_aDockedData.m_aPos), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_POS, + rElementData.m_aFloatingData.m_aPos), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_SIZE, + rElementData.m_aFloatingData.m_aSize), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_UINAME, rElementData.m_aUIName), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_LOCKED, + rElementData.m_aDockedData.m_bLocked), + comphelper::makePropertyValue(WINDOWSTATE_PROPERTY_STYLE, + static_cast<sal_uInt16>(rElementData.m_nStyle)) + }; + + OUString aName = rElementData.m_aName; + if ( xPersistentWindowState->hasByName( aName )) + { + uno::Reference< container::XNameReplace > xReplace( xPersistentWindowState, uno::UNO_QUERY ); + xReplace->replaceByName( aName, uno::Any( aWindowState )); + } + else + { + uno::Reference< container::XNameContainer > xInsert( xPersistentWindowState, uno::UNO_QUERY ); + xInsert->insertByName( aName, uno::Any( aWindowState )); + } + } + catch (const uno::Exception&) + { + } +} + +/****************************************************************************** + LOOKUP PART FOR TOOLBARS +******************************************************************************/ + +UIElement& ToolbarLayoutManager::impl_findToolbar( std::u16string_view aName ) +{ + static UIElement aEmptyElement; + + SolarMutexGuard g; + for (auto & elem : m_aUIElements) + { + if ( elem.m_aName == aName ) + return elem; + } + + return aEmptyElement; +} + +UIElement ToolbarLayoutManager::implts_findToolbar( std::u16string_view aName ) +{ + SolarMutexGuard g; + return impl_findToolbar( aName ); +} + +UIElement ToolbarLayoutManager::implts_findToolbar( const uno::Reference< uno::XInterface >& xToolbar ) +{ + UIElement aToolbar; + + SolarMutexGuard g; + for (auto const& elem : m_aUIElements) + { + if ( elem.m_xUIElement.is() ) + { + uno::Reference< uno::XInterface > xIfac( elem.m_xUIElement->getRealInterface(), uno::UNO_QUERY ); + if ( xIfac == xToolbar ) + { + aToolbar = elem; + break; + } + } + } + + return aToolbar; +} + +uno::Reference< awt::XWindow > ToolbarLayoutManager::implts_getXWindow( std::u16string_view aName ) +{ + uno::Reference< awt::XWindow > xWindow; + + SolarMutexGuard g; + for (auto const& elem : m_aUIElements) + { + if ( elem.m_aName == aName && elem.m_xUIElement.is() ) + { + xWindow.set( elem.m_xUIElement->getRealInterface(), uno::UNO_QUERY ); + break; + } + } + + return xWindow; +} + +vcl::Window* ToolbarLayoutManager::implts_getWindow( std::u16string_view aName ) +{ + uno::Reference< awt::XWindow > xWindow = implts_getXWindow( aName ); + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + + return pWindow; +} + +bool ToolbarLayoutManager::implts_insertToolbar( const UIElement& rUIElement ) +{ + UIElement aTempData; + bool bFound( false ); + bool bResult( false ); + + aTempData = implts_findToolbar( rUIElement.m_aName ); + if ( aTempData.m_aName == rUIElement.m_aName ) + bFound = true; + + if ( !bFound ) + { + SolarMutexGuard g; + m_aUIElements.push_back( rUIElement ); + bResult = true; + } + + return bResult; +} + +void ToolbarLayoutManager::implts_setToolbar( const UIElement& rUIElement ) +{ + SolarMutexGuard g; + UIElement& rData = impl_findToolbar( rUIElement.m_aName ); + if ( rData.m_aName == rUIElement.m_aName ) + rData = rUIElement; + else + m_aUIElements.push_back( rUIElement ); +} + +/****************************************************************************** + LAYOUT CODE PART FOR TOOLBARS +******************************************************************************/ + +awt::Point ToolbarLayoutManager::implts_findNextCascadeFloatingPos() +{ + const sal_Int32 nHotZoneX = 50; + const sal_Int32 nHotZoneY = 50; + const sal_Int32 nCascadeIndentX = 15; + const sal_Int32 nCascadeIndentY = 15; + + SolarMutexClearableGuard aReadLock; + uno::Reference< awt::XWindow2 > xContainerWindow( m_xContainerWindow ); + uno::Reference< awt::XWindow > xTopDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_TOP)] ); + uno::Reference< awt::XWindow > xLeftDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_LEFT)] ); + aReadLock.clear(); + + awt::Point aStartPos( nCascadeIndentX, nCascadeIndentY ); + awt::Point aCurrPos( aStartPos ); + + if ( xContainerWindow.is() ) + { + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pContainerWindow = VCLUnoHelper::GetWindow( xContainerWindow ); + if ( pContainerWindow ) + aStartPos = AWTPoint(pContainerWindow->OutputToScreenPixel(VCLPoint(aStartPos))); + } + + // Determine size of top and left docking area + awt::Rectangle aTopRect( xTopDockingWindow->getPosSize() ); + awt::Rectangle aLeftRect( xLeftDockingWindow->getPosSize() ); + + aStartPos.X += aLeftRect.Width + nCascadeIndentX; + aStartPos.Y += aTopRect.Height + nCascadeIndentY; + aCurrPos = aStartPos; + + // Try to find a cascaded position for the new floating window + for (auto const& elem : m_aUIElements) + { + if ( elem.m_xUIElement.is() ) + { + uno::Reference< awt::XDockableWindow > xDockWindow( elem.m_xUIElement->getRealInterface(), uno::UNO_QUERY ); + uno::Reference< awt::XWindow > xWindow( xDockWindow, uno::UNO_QUERY ); + if ( xDockWindow.is() && xDockWindow->isFloating() ) + { + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->IsVisible() ) + { + awt::Rectangle aFloatRect = xWindow->getPosSize(); + if ((( aFloatRect.X - nHotZoneX ) <= aCurrPos.X ) && + ( aFloatRect.X >= aCurrPos.X ) && + (( aFloatRect.Y - nHotZoneY ) <= aCurrPos.Y ) && + ( aFloatRect.Y >= aCurrPos.Y )) + { + aCurrPos.X = aFloatRect.X + nCascadeIndentX; + aCurrPos.Y = aFloatRect.Y + nCascadeIndentY; + } + } + } + } + } + + return aCurrPos; +} + +void ToolbarLayoutManager::implts_sortUIElements() +{ + SolarMutexGuard g; + + std::stable_sort( m_aUIElements.begin(), m_aUIElements.end()); // first created element should first + + // We have to reset our temporary flags. + for (auto & elem : m_aUIElements) + elem.m_bUserActive = false; +} + +void ToolbarLayoutManager::implts_getUIElementVectorCopy( UIElementVector& rCopy ) +{ + SolarMutexGuard g; + rCopy = m_aUIElements; +} + +::Size ToolbarLayoutManager::implts_getTopBottomDockingAreaSizes() +{ + ::Size aSize; + uno::Reference< awt::XWindow > xTopDockingAreaWindow; + uno::Reference< awt::XWindow > xBottomDockingAreaWindow; + + { + SolarMutexGuard aReadLock; + xTopDockingAreaWindow = m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_TOP)]; + xBottomDockingAreaWindow = m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_BOTTOM)]; + } + + if ( xTopDockingAreaWindow.is() ) + aSize.setWidth( xTopDockingAreaWindow->getPosSize().Height ); + if ( xBottomDockingAreaWindow.is() ) + aSize.setHeight( xBottomDockingAreaWindow->getPosSize().Height ); + + return aSize; +} + +void ToolbarLayoutManager::implts_getDockingAreaElementInfos( ui::DockingArea eDockingArea, std::vector< SingleRowColumnWindowData >& rRowColumnsWindowData ) +{ + std::vector< UIElement > aWindowVector; + + if (( eDockingArea < ui::DockingArea_DOCKINGAREA_TOP ) || ( eDockingArea > ui::DockingArea_DOCKINGAREA_RIGHT )) + eDockingArea = ui::DockingArea_DOCKINGAREA_TOP; + + uno::Reference< awt::XWindow > xDockAreaWindow; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + { + SolarMutexGuard aReadLock; + aWindowVector.reserve(m_aUIElements.size()); + xDockAreaWindow = m_xDockAreaWindows[static_cast<int>(eDockingArea)]; + for (auto const& elem : m_aUIElements) + { + if (elem.m_aDockedData.m_nDockedArea == eDockingArea && elem.m_bVisible) + { + uno::Reference<ui::XUIElement> xUIElement(elem.m_xUIElement); + if (xUIElement.is()) + { + uno::Reference<awt::XWindow> xWindow(xUIElement->getRealInterface(), + uno::UNO_QUERY); + uno::Reference<awt::XDockableWindow> xDockWindow(xWindow, uno::UNO_QUERY); + if (xDockWindow.is()) + { + if (!elem.m_bFloating) + { + // docked windows + aWindowVector.push_back(elem); + } + else + { + // floating windows + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + DockingManager* pDockMgr = vcl::Window::GetDockingManager(); + if (pDockMgr != nullptr) + { + SystemWindow* pFloatingWindow = pDockMgr->GetFloatingWindow(pWindow); + if (pFloatingWindow) + { + // update the position data of the floating window + if (pFloatingWindow->UpdatePositionData()) + { + awt::Rectangle aTmpRect = xWindow->getPosSize(); + UIElement uiElem = elem; + uiElem.m_aFloatingData.m_aPos + = awt::Point(aTmpRect.X, aTmpRect.Y); + implts_setToolbar(uiElem); + implts_writeWindowStateData(uiElem); + } + } + } + } + } + } + } + } + } + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + rRowColumnsWindowData.clear(); + + // Collect data from windows that are on the same row/column + sal_Int32 j; + sal_Int32 nIndex( 0 ); + sal_Int32 nLastPos( 0 ); + sal_Int32 nCurrPos( -1 ); + sal_Int32 nLastRowColPixelPos( 0 ); + awt::Rectangle aDockAreaRect; + + if ( xDockAreaWindow.is() ) + aDockAreaRect = xDockAreaWindow->getPosSize(); + + if ( eDockingArea == ui::DockingArea_DOCKINGAREA_TOP ) + nLastRowColPixelPos = 0; + else if ( eDockingArea == ui::DockingArea_DOCKINGAREA_BOTTOM ) + nLastRowColPixelPos = aDockAreaRect.Height; + else if ( eDockingArea == ui::DockingArea_DOCKINGAREA_LEFT ) + nLastRowColPixelPos = 0; + else + nLastRowColPixelPos = aDockAreaRect.Width; + + const sal_uInt32 nCount = aWindowVector.size(); + for ( j = 0; j < sal_Int32( nCount); j++ ) + { + const UIElement& rElement = aWindowVector[j]; + uno::Reference< awt::XWindow > xWindow; + uno::Reference< ui::XUIElement > xUIElement( rElement.m_xUIElement ); + awt::Rectangle aPosSize; + + if ( !lcl_checkUIElement(xUIElement,aPosSize,xWindow) ) + continue; + if ( isHorizontalDockingArea( eDockingArea )) + { + if ( nCurrPos == -1 ) + { + nCurrPos = rElement.m_aDockedData.m_aPos.Y; + nLastPos = 0; + + SingleRowColumnWindowData aRowColumnWindowData; + aRowColumnWindowData.nRowColumn = nCurrPos; + rRowColumnsWindowData.push_back( aRowColumnWindowData ); + } + + sal_Int32 nSpace( 0 ); + if ( rElement.m_aDockedData.m_aPos.Y != nCurrPos ) + { + if ( eDockingArea == ui::DockingArea_DOCKINGAREA_TOP ) + nLastRowColPixelPos += rRowColumnsWindowData[nIndex].nStaticSize; + else + nLastRowColPixelPos -= rRowColumnsWindowData[nIndex].nStaticSize; + ++nIndex; + nLastPos = 0; + nCurrPos = rElement.m_aDockedData.m_aPos.Y; + SingleRowColumnWindowData aRowColumnWindowData; + aRowColumnWindowData.nRowColumn = nCurrPos; + rRowColumnsWindowData.push_back( aRowColumnWindowData ); + } + + // Calc space before an element and store it + nSpace = ( rElement.m_aDockedData.m_aPos.X - nLastPos ); + if ( rElement.m_aDockedData.m_aPos.X >= nLastPos ) + { + rRowColumnsWindowData[nIndex].nSpace += nSpace; + nLastPos = rElement.m_aDockedData.m_aPos.X + aPosSize.Width; + } + else + { + nSpace = 0; + nLastPos += aPosSize.Width; + } + rRowColumnsWindowData[nIndex].aRowColumnSpace.push_back( nSpace ); + + rRowColumnsWindowData[nIndex].aRowColumnWindows.push_back( xWindow ); + rRowColumnsWindowData[nIndex].aUIElementNames.push_back( rElement.m_aName ); + rRowColumnsWindowData[nIndex].aRowColumnWindowSizes.emplace_back( + rElement.m_aDockedData.m_aPos.X, + rElement.m_aDockedData.m_aPos.Y, + aPosSize.Width, + aPosSize.Height ); + if ( rRowColumnsWindowData[nIndex].nStaticSize < aPosSize.Height ) + rRowColumnsWindowData[nIndex].nStaticSize = aPosSize.Height; + if ( eDockingArea == ui::DockingArea_DOCKINGAREA_TOP ) + rRowColumnsWindowData[nIndex].aRowColumnRect = awt::Rectangle( 0, nLastRowColPixelPos, + aDockAreaRect.Width, aPosSize.Height ); + else + rRowColumnsWindowData[nIndex].aRowColumnRect = awt::Rectangle( 0, ( nLastRowColPixelPos - aPosSize.Height ), + aDockAreaRect.Width, aPosSize.Height ); + rRowColumnsWindowData[nIndex].nVarSize += aPosSize.Width + nSpace; + } + else + { + if ( nCurrPos == -1 ) + { + nCurrPos = rElement.m_aDockedData.m_aPos.X; + nLastPos = 0; + + SingleRowColumnWindowData aRowColumnWindowData; + aRowColumnWindowData.nRowColumn = nCurrPos; + rRowColumnsWindowData.push_back( aRowColumnWindowData ); + } + + sal_Int32 nSpace( 0 ); + if ( rElement.m_aDockedData.m_aPos.X != nCurrPos ) + { + if ( eDockingArea == ui::DockingArea_DOCKINGAREA_LEFT ) + nLastRowColPixelPos += rRowColumnsWindowData[nIndex].nStaticSize; + else + nLastRowColPixelPos -= rRowColumnsWindowData[nIndex].nStaticSize; + ++nIndex; + nLastPos = 0; + nCurrPos = rElement.m_aDockedData.m_aPos.X; + SingleRowColumnWindowData aRowColumnWindowData; + aRowColumnWindowData.nRowColumn = nCurrPos; + rRowColumnsWindowData.push_back( aRowColumnWindowData ); + } + + // Calc space before an element and store it + nSpace = ( rElement.m_aDockedData.m_aPos.Y - nLastPos ); + if ( rElement.m_aDockedData.m_aPos.Y > nLastPos ) + { + rRowColumnsWindowData[nIndex].nSpace += nSpace; + nLastPos = rElement.m_aDockedData.m_aPos.Y + aPosSize.Height; + } + else + { + nSpace = 0; + nLastPos += aPosSize.Height; + } + rRowColumnsWindowData[nIndex].aRowColumnSpace.push_back( nSpace ); + + rRowColumnsWindowData[nIndex].aRowColumnWindows.push_back( xWindow ); + rRowColumnsWindowData[nIndex].aUIElementNames.push_back( rElement.m_aName ); + rRowColumnsWindowData[nIndex].aRowColumnWindowSizes.emplace_back( + rElement.m_aDockedData.m_aPos.X, + rElement.m_aDockedData.m_aPos.Y, + aPosSize.Width, + aPosSize.Height ); + if ( rRowColumnsWindowData[nIndex].nStaticSize < aPosSize.Width ) + rRowColumnsWindowData[nIndex].nStaticSize = aPosSize.Width; + if ( eDockingArea == ui::DockingArea_DOCKINGAREA_LEFT ) + rRowColumnsWindowData[nIndex].aRowColumnRect = awt::Rectangle( nLastRowColPixelPos, 0, + aPosSize.Width, aDockAreaRect.Height ); + else + rRowColumnsWindowData[nIndex].aRowColumnRect = awt::Rectangle( ( nLastRowColPixelPos - aPosSize.Width ), 0, + aPosSize.Width, aDockAreaRect.Height ); + rRowColumnsWindowData[nIndex].nVarSize += aPosSize.Height + nSpace; + } + } +} + +void ToolbarLayoutManager::implts_getDockingAreaElementInfoOnSingleRowCol( ui::DockingArea eDockingArea, sal_Int32 nRowCol, SingleRowColumnWindowData& rRowColumnWindowData ) +{ + std::vector< UIElement > aWindowVector; + + if (( eDockingArea < ui::DockingArea_DOCKINGAREA_TOP ) || ( eDockingArea > ui::DockingArea_DOCKINGAREA_RIGHT )) + eDockingArea = ui::DockingArea_DOCKINGAREA_TOP; + + bool bHorzDockArea = isHorizontalDockingArea( eDockingArea ); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + { + SolarMutexGuard aReadLock; + for (auto const& elem : m_aUIElements) + { + if (elem.m_aDockedData.m_nDockedArea == eDockingArea) + { + bool bSameRowCol = bHorzDockArea ? (elem.m_aDockedData.m_aPos.Y == nRowCol) + : (elem.m_aDockedData.m_aPos.X == nRowCol); + uno::Reference<ui::XUIElement> xUIElement(elem.m_xUIElement); + + if (bSameRowCol && xUIElement.is()) + { + uno::Reference<awt::XWindow> xWindow(xUIElement->getRealInterface(), + uno::UNO_QUERY); + if (xWindow.is()) + { + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + uno::Reference<awt::XDockableWindow> xDockWindow(xWindow, uno::UNO_QUERY); + if (pWindow && elem.m_bVisible && xDockWindow.is() && !elem.m_bFloating) + aWindowVector.push_back(elem); // docked windows + } + } + } + } + } + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + // Initialize structure + rRowColumnWindowData.aUIElementNames.clear(); + rRowColumnWindowData.aRowColumnWindows.clear(); + rRowColumnWindowData.aRowColumnWindowSizes.clear(); + rRowColumnWindowData.aRowColumnSpace.clear(); + rRowColumnWindowData.nVarSize = 0; + rRowColumnWindowData.nStaticSize = 0; + rRowColumnWindowData.nSpace = 0; + rRowColumnWindowData.nRowColumn = nRowCol; + + // Collect data from windows that are on the same row/column + sal_Int32 j; + sal_Int32 nLastPos( 0 ); + + const sal_uInt32 nCount = aWindowVector.size(); + for ( j = 0; j < sal_Int32( nCount); j++ ) + { + const UIElement& rElement = aWindowVector[j]; + uno::Reference< awt::XWindow > xWindow; + uno::Reference< ui::XUIElement > xUIElement( rElement.m_xUIElement ); + awt::Rectangle aPosSize; + if ( !lcl_checkUIElement(xUIElement,aPosSize,xWindow) ) + continue; + + sal_Int32 nSpace; + if ( isHorizontalDockingArea( eDockingArea )) + { + nSpace = ( rElement.m_aDockedData.m_aPos.X - nLastPos ); + + // Calc space before an element and store it + if ( rElement.m_aDockedData.m_aPos.X > nLastPos ) + rRowColumnWindowData.nSpace += nSpace; + else + nSpace = 0; + + nLastPos = rElement.m_aDockedData.m_aPos.X + aPosSize.Width; + + rRowColumnWindowData.aRowColumnWindowSizes.emplace_back( + rElement.m_aDockedData.m_aPos.X, rElement.m_aDockedData.m_aPos.Y, + aPosSize.Width, aPosSize.Height ); + if ( rRowColumnWindowData.nStaticSize < aPosSize.Height ) + rRowColumnWindowData.nStaticSize = aPosSize.Height; + rRowColumnWindowData.nVarSize += aPosSize.Width; + } + else + { + // Calc space before an element and store it + nSpace = ( rElement.m_aDockedData.m_aPos.Y - nLastPos ); + if ( rElement.m_aDockedData.m_aPos.Y > nLastPos ) + rRowColumnWindowData.nSpace += nSpace; + else + nSpace = 0; + + nLastPos = rElement.m_aDockedData.m_aPos.Y + aPosSize.Height; + + rRowColumnWindowData.aRowColumnWindowSizes.emplace_back( + rElement.m_aDockedData.m_aPos.X, rElement.m_aDockedData.m_aPos.Y, + aPosSize.Width, aPosSize.Height ); + if ( rRowColumnWindowData.nStaticSize < aPosSize.Width ) + rRowColumnWindowData.nStaticSize = aPosSize.Width; + rRowColumnWindowData.nVarSize += aPosSize.Height; + } + + rRowColumnWindowData.aUIElementNames.push_back( rElement.m_aName ); + rRowColumnWindowData.aRowColumnWindows.push_back( xWindow ); + rRowColumnWindowData.aRowColumnSpace.push_back( nSpace ); + rRowColumnWindowData.nVarSize += nSpace; + } +} + +::tools::Rectangle ToolbarLayoutManager::implts_getWindowRectFromRowColumn( + ui::DockingArea DockingArea, + const SingleRowColumnWindowData& rRowColumnWindowData, + const ::Point& rMousePos, + std::u16string_view rExcludeElementName ) +{ + ::tools::Rectangle aWinRect; + + if (( DockingArea < ui::DockingArea_DOCKINGAREA_TOP ) || ( DockingArea > ui::DockingArea_DOCKINGAREA_RIGHT )) + DockingArea = ui::DockingArea_DOCKINGAREA_TOP; + + if ( rRowColumnWindowData.aRowColumnWindows.empty() ) + return aWinRect; + else + { + SolarMutexClearableGuard aReadLock; + VclPtr<vcl::Window> pContainerWindow( VCLUnoHelper::GetWindow( m_xContainerWindow )); + VclPtr<vcl::Window> pDockingAreaWindow( VCLUnoHelper::GetWindow( m_xDockAreaWindows[static_cast<int>(DockingArea)] )); + aReadLock.clear(); + + // Calc correct position of the column/row rectangle to be able to compare it with mouse pos/tracking rect + SolarMutexGuard aGuard; + + // Retrieve output size from container Window + if ( pDockingAreaWindow && pContainerWindow ) + { + const sal_uInt32 nCount = rRowColumnWindowData.aRowColumnWindows.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + awt::Rectangle aWindowRect = rRowColumnWindowData.aRowColumnWindows[i]->getPosSize(); + ::tools::Rectangle aRect( aWindowRect.X, aWindowRect.Y, aWindowRect.X+aWindowRect.Width, aWindowRect.Y+aWindowRect.Height ); + aRect.SetPos( pContainerWindow->ScreenToOutputPixel( pDockingAreaWindow->OutputToScreenPixel( aRect.TopLeft() ))); + if ( aRect.Contains( rMousePos )) + { + // Check if we have found the excluded element. If yes, we have to provide an empty rectangle. + // We prevent that a toolbar cannot be moved when the mouse pointer is inside its own rectangle! + if ( rExcludeElementName != rRowColumnWindowData.aUIElementNames[i] ) + return aRect; + else + break; + } + } + } + } + + return aWinRect; +} + +::tools::Rectangle ToolbarLayoutManager::implts_determineFrontDockingRect( + ui::DockingArea eDockingArea, + sal_Int32 nRowCol, + const ::tools::Rectangle& rDockedElementRect, + std::u16string_view rMovedElementName, + const ::tools::Rectangle& rMovedElementRect ) +{ + SingleRowColumnWindowData aRowColumnWindowData; + + bool bHorzDockArea( isHorizontalDockingArea( eDockingArea )); + implts_getDockingAreaElementInfoOnSingleRowCol( eDockingArea, nRowCol, aRowColumnWindowData ); + if ( aRowColumnWindowData.aRowColumnWindows.empty() ) + return rMovedElementRect; + else + { + sal_Int32 nSpace( 0 ); + ::tools::Rectangle aFrontDockingRect( rMovedElementRect ); + const sal_uInt32 nCount = aRowColumnWindowData.aRowColumnWindows.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + if ( bHorzDockArea ) + { + if ( aRowColumnWindowData.aRowColumnWindowSizes[i].X >= rDockedElementRect.Left() ) + { + nSpace += aRowColumnWindowData.aRowColumnSpace[i]; + break; + } + else if ( aRowColumnWindowData.aUIElementNames[i] == rMovedElementName ) + nSpace += aRowColumnWindowData.aRowColumnWindowSizes[i].Width + + aRowColumnWindowData.aRowColumnSpace[i]; + else + nSpace = 0; + } + else + { + if ( aRowColumnWindowData.aRowColumnWindowSizes[i].Y >= rDockedElementRect.Top() ) + { + nSpace += aRowColumnWindowData.aRowColumnSpace[i]; + break; + } + else if ( aRowColumnWindowData.aUIElementNames[i] == rMovedElementName ) + nSpace += aRowColumnWindowData.aRowColumnWindowSizes[i].Height + + aRowColumnWindowData.aRowColumnSpace[i]; + else + nSpace = 0; + } + } + + if ( nSpace > 0 ) + { + sal_Int32 nMove = std::min( nSpace, static_cast<sal_Int32>(aFrontDockingRect.getWidth()) ); + if ( bHorzDockArea ) + aFrontDockingRect.Move( -nMove, 0 ); + else + aFrontDockingRect.Move( 0, -nMove ); + } + + return aFrontDockingRect; + } +} + +void ToolbarLayoutManager::implts_findNextDockingPos( ui::DockingArea DockingArea, const ::Size& aUIElementSize, awt::Point& rVirtualPos, ::Point& rPixelPos ) +{ + SolarMutexClearableGuard aReadLock; + if (( DockingArea < ui::DockingArea_DOCKINGAREA_TOP ) || ( DockingArea > ui::DockingArea_DOCKINGAREA_RIGHT )) + DockingArea = ui::DockingArea_DOCKINGAREA_TOP; + uno::Reference< awt::XWindow > xDockingWindow( m_xDockAreaWindows[static_cast<int>(DockingArea)] ); + ::Size aDockingWinSize; + + // Retrieve output size from container Window + vcl::Window* pDockingWindow = VCLUnoHelper::GetWindow( xDockingWindow ); + if ( pDockingWindow ) + aDockingWinSize = pDockingWindow->GetOutputSizePixel(); + aReadLock.clear(); + + sal_Int32 nFreeRowColPixelPos( 0 ); + sal_Int32 nMaxSpace( 0 ); + sal_Int32 nNeededSpace( 0 ); + sal_Int32 nTopDockingAreaSize( 0 ); + + if ( isHorizontalDockingArea( DockingArea )) + { + nMaxSpace = aDockingWinSize.Width(); + nNeededSpace = aUIElementSize.Width(); + } + else + { + nMaxSpace = aDockingWinSize.Height(); + nNeededSpace = aUIElementSize.Height(); + nTopDockingAreaSize = implts_getTopBottomDockingAreaSizes().Width(); + } + + std::vector< SingleRowColumnWindowData > aRowColumnsWindowData; + + implts_getDockingAreaElementInfos( DockingArea, aRowColumnsWindowData ); + sal_Int32 nPixelPos( 0 ); + const sal_uInt32 nCount = aRowColumnsWindowData.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + SingleRowColumnWindowData& rRowColumnWindowData = aRowColumnsWindowData[i]; + + if (( DockingArea == ui::DockingArea_DOCKINGAREA_BOTTOM ) || + ( DockingArea == ui::DockingArea_DOCKINGAREA_RIGHT )) + nPixelPos += rRowColumnWindowData.nStaticSize; + + if ((( nMaxSpace - rRowColumnWindowData.nVarSize ) >= nNeededSpace ) || + ( rRowColumnWindowData.nSpace >= nNeededSpace )) + { + // Check current row where we can find the needed space + sal_Int32 nCurrPos( 0 ); + const sal_uInt32 nWindowSizesCount = rRowColumnWindowData.aRowColumnWindowSizes.size(); + for ( sal_uInt32 j = 0; j < nWindowSizesCount; j++ ) + { + awt::Rectangle rRect = rRowColumnWindowData.aRowColumnWindowSizes[j]; + sal_Int32& rSpace = rRowColumnWindowData.aRowColumnSpace[j]; + if ( isHorizontalDockingArea( DockingArea )) + { + if ( rSpace >= nNeededSpace ) + { + rVirtualPos = awt::Point( nCurrPos, rRowColumnWindowData.nRowColumn ); + if ( DockingArea == ui::DockingArea_DOCKINGAREA_TOP ) + rPixelPos = ::Point( nCurrPos, nPixelPos ); + else + rPixelPos = ::Point( nCurrPos, aDockingWinSize.Height() - nPixelPos ); + return; + } + nCurrPos = rRect.X + rRect.Width; + } + else + { + if ( rSpace >= nNeededSpace ) + { + rVirtualPos = awt::Point( rRowColumnWindowData.nRowColumn, nCurrPos ); + if ( DockingArea == ui::DockingArea_DOCKINGAREA_LEFT ) + rPixelPos = ::Point( nPixelPos, nTopDockingAreaSize + nCurrPos ); + else + rPixelPos = ::Point( aDockingWinSize.Width() - nPixelPos , nTopDockingAreaSize + nCurrPos ); + return; + } + nCurrPos = rRect.Y + rRect.Height; + } + } + + if (( nCurrPos + nNeededSpace ) <= nMaxSpace ) + { + if ( isHorizontalDockingArea( DockingArea )) + { + rVirtualPos = awt::Point( nCurrPos, rRowColumnWindowData.nRowColumn ); + if ( DockingArea == ui::DockingArea_DOCKINGAREA_TOP ) + rPixelPos = ::Point( nCurrPos, nPixelPos ); + else + rPixelPos = ::Point( nCurrPos, aDockingWinSize.Height() - nPixelPos ); + return; + } + else + { + rVirtualPos = awt::Point( rRowColumnWindowData.nRowColumn, nCurrPos ); + if ( DockingArea == ui::DockingArea_DOCKINGAREA_LEFT ) + rPixelPos = ::Point( nPixelPos, nTopDockingAreaSize + nCurrPos ); + else + rPixelPos = ::Point( aDockingWinSize.Width() - nPixelPos , nTopDockingAreaSize + nCurrPos ); + return; + } + } + } + + if (( DockingArea == ui::DockingArea_DOCKINGAREA_TOP ) || ( DockingArea == ui::DockingArea_DOCKINGAREA_LEFT )) + nPixelPos += rRowColumnWindowData.nStaticSize; + } + + sal_Int32 nNextFreeRowCol( 0 ); + sal_Int32 nRowColumnsCount = aRowColumnsWindowData.size(); + if ( nRowColumnsCount > 0 ) + nNextFreeRowCol = aRowColumnsWindowData[nRowColumnsCount-1].nRowColumn+1; + else + nNextFreeRowCol = 0; + + if ( nNextFreeRowCol == 0 ) + { + if ( DockingArea == ui::DockingArea_DOCKINGAREA_BOTTOM ) + nFreeRowColPixelPos = aDockingWinSize.Height() - aUIElementSize.Height(); + else if ( DockingArea == ui::DockingArea_DOCKINGAREA_RIGHT ) + nFreeRowColPixelPos = aDockingWinSize.Width() - aUIElementSize.Width(); + } + + if ( isHorizontalDockingArea( DockingArea )) + { + rVirtualPos = awt::Point( 0, nNextFreeRowCol ); + if ( DockingArea == ui::DockingArea_DOCKINGAREA_TOP ) + rPixelPos = ::Point( 0, nFreeRowColPixelPos ); + else + rPixelPos = ::Point( 0, aDockingWinSize.Height() - nFreeRowColPixelPos ); + } + else + { + rVirtualPos = awt::Point( nNextFreeRowCol, 0 ); + rPixelPos = ::Point( aDockingWinSize.Width() - nFreeRowColPixelPos, 0 ); + } +} + +void ToolbarLayoutManager::implts_calcWindowPosSizeOnSingleRowColumn( + sal_Int32 nDockingArea, + sal_Int32 nOffset, + SingleRowColumnWindowData& rRowColumnWindowData, + const ::Size& rContainerSize ) +{ + sal_Int32 nDiff(0); + sal_Int32 nRCSpace( rRowColumnWindowData.nSpace ); + sal_Int32 nContainerClientSize(0); + + if ( rRowColumnWindowData.aRowColumnWindows.empty() ) + return; + + if ( isHorizontalDockingArea( nDockingArea )) + { + nContainerClientSize = rContainerSize.Width(); + nDiff = nContainerClientSize - rRowColumnWindowData.nVarSize; + } + else + { + sal_Int32 nTopDockingAreaSize = implts_getTopBottomDockingAreaSizes().Width(); + sal_Int32 nBottomDockingAreaSize = implts_getTopBottomDockingAreaSizes().Height(); + nContainerClientSize = ( rContainerSize.Height() - nTopDockingAreaSize - nBottomDockingAreaSize ); + nDiff = nContainerClientSize - rRowColumnWindowData.nVarSize; + } + + const sal_uInt32 nCount = rRowColumnWindowData.aRowColumnWindowSizes.size(); + if (( nDiff < 0 ) && ( nRCSpace > 0 )) + { + // First we try to reduce the size of blank space before/behind docked windows + sal_Int32 i = nCount - 1; + while ( i >= 0 ) + { + sal_Int32 nSpace = rRowColumnWindowData.aRowColumnSpace[i]; + if ( nSpace >= -nDiff ) + { + if ( isHorizontalDockingArea( nDockingArea )) + { + // Try to move this and all user elements behind with the calculated difference + for ( sal_uInt32 j = i; j < nCount; j++ ) + rRowColumnWindowData.aRowColumnWindowSizes[j].X += nDiff; + } + else + { + // Try to move this and all user elements behind with the calculated difference + for ( sal_uInt32 j = i; j < nCount; j++ ) + rRowColumnWindowData.aRowColumnWindowSizes[j].Y += nDiff; + } + nDiff = 0; + + break; + } + else if ( nSpace > 0 ) + { + if ( isHorizontalDockingArea( nDockingArea )) + { + // Try to move this and all user elements behind with the calculated difference + for ( sal_uInt32 j = i; j < nCount; j++ ) + rRowColumnWindowData.aRowColumnWindowSizes[j].X -= nSpace; + } + else + { + // Try to move this and all user elements behind with the calculated difference + for ( sal_uInt32 j = i; j < nCount; j++ ) + rRowColumnWindowData.aRowColumnWindowSizes[j].Y -= nSpace; + } + nDiff += nSpace; + } + --i; + } + } + + // Check if we have to reduce further + if ( nDiff < 0 ) + { + // Now we have to reduce the size of certain docked windows + sal_Int32 i = sal_Int32( nCount - 1 ); + while ( i >= 0 ) + { + awt::Rectangle& rWinRect = rRowColumnWindowData.aRowColumnWindowSizes[i]; + ::Size aMinSize; + + SolarMutexGuard aGuard; + { + uno::Reference< awt::XWindow > xWindow = rRowColumnWindowData.aRowColumnWindows[i]; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->GetType() == WindowType::TOOLBOX ) + aMinSize = static_cast<ToolBox *>(pWindow.get())->CalcMinimumWindowSizePixel(); + } + + if ( !aMinSize.IsEmpty() ) + { + if ( isHorizontalDockingArea( nDockingArea )) + { + sal_Int32 nMaxReducation = rWinRect.Width - aMinSize.Width(); + if ( nMaxReducation >= -nDiff ) + { + rWinRect.Width = rWinRect.Width + nDiff; + nDiff = 0; + } + else + { + rWinRect.Width = aMinSize.Width(); + nDiff += nMaxReducation; + } + + // Try to move this and all user elements behind with the calculated difference + for ( sal_uInt32 j = i; j < nCount; j++ ) + rRowColumnWindowData.aRowColumnWindowSizes[j].X += nDiff; + } + else + { + sal_Int32 nMaxReducation = rWinRect.Height - aMinSize.Height(); + if ( nMaxReducation >= -nDiff ) + { + rWinRect.Height = rWinRect.Height + nDiff; + nDiff = 0; + } + else + { + rWinRect.Height = aMinSize.Height(); + nDiff += nMaxReducation; + } + + // Try to move this and all user elements behind with the calculated difference + for ( sal_uInt32 j = i; j < nCount; j++ ) + rRowColumnWindowData.aRowColumnWindowSizes[j].Y += nDiff; + } + } + + if ( nDiff >= 0 ) + break; + + --i; + } + } + + SolarMutexClearableGuard aReadLock; + VclPtr<vcl::Window> pDockAreaWindow = VCLUnoHelper::GetWindow( m_xDockAreaWindows[nDockingArea] ); + aReadLock.clear(); + + sal_Int32 nCurrPos( 0 ); + + SolarMutexGuard aGuard; + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + uno::Reference< awt::XWindow > xWindow = rRowColumnWindowData.aRowColumnWindows[i]; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + vcl::Window* pOldParentWindow = pWindow->GetParent(); + + if ( pDockAreaWindow != pOldParentWindow ) + pWindow->SetParent( pDockAreaWindow ); + + awt::Rectangle aWinRect = rRowColumnWindowData.aRowColumnWindowSizes[i]; + if ( isHorizontalDockingArea( nDockingArea )) + { + if ( aWinRect.X < nCurrPos ) + aWinRect.X = nCurrPos; + pWindow->SetPosSizePixel( ::Point( aWinRect.X, nOffset ), ::Size( aWinRect.Width, rRowColumnWindowData.nStaticSize )); + pWindow->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); + nCurrPos += ( aWinRect.X - nCurrPos ) + aWinRect.Width; + } + else + { + if ( aWinRect.Y < nCurrPos ) + aWinRect.Y = nCurrPos; + pWindow->SetPosSizePixel( ::Point( nOffset, aWinRect.Y ), ::Size( rRowColumnWindowData.nStaticSize, aWinRect.Height )); + pWindow->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); + nCurrPos += ( aWinRect.Y - nCurrPos ) + aWinRect.Height; + } + } +} + +void ToolbarLayoutManager::implts_setLayoutDirty() +{ + SolarMutexGuard g; + m_bLayoutDirty = true; +} + +void ToolbarLayoutManager::implts_setLayoutInProgress( bool bInProgress ) +{ + SolarMutexGuard g; + m_bLayoutInProgress = bInProgress; +} + +::tools::Rectangle ToolbarLayoutManager::implts_calcHotZoneRect( const ::tools::Rectangle& rRect, sal_Int32 nHotZoneOffset ) +{ + ::tools::Rectangle aRect( rRect ); + + aRect.AdjustLeft( -nHotZoneOffset ); + aRect.AdjustTop( -nHotZoneOffset ); + aRect.AdjustRight(nHotZoneOffset ); + aRect.AdjustBottom(nHotZoneOffset ); + + return aRect; +} + +void ToolbarLayoutManager::implts_calcDockingPosSize( + UIElement& rUIElement, + DockingOperation& rDockingOperation, + ::tools::Rectangle& rTrackingRect, + const Point& rMousePos ) +{ + SolarMutexResettableGuard aReadLock; + uno::Reference< awt::XWindow2 > xContainerWindow( m_xContainerWindow ); + ::Size aContainerWinSize; + vcl::Window* pContainerWindow( nullptr ); + ::tools::Rectangle aDockingAreaOffsets( m_aDockingAreaOffsets ); + aReadLock.clear(); + + if ( !rUIElement.m_xUIElement.is() ) + { + rTrackingRect = ::tools::Rectangle(); + return; + } + + { + // Retrieve output size from container Window + SolarMutexGuard aGuard; + pContainerWindow = VCLUnoHelper::GetWindow( xContainerWindow ); + aContainerWinSize = pContainerWindow->GetOutputSizePixel(); + } + + vcl::Window* pDockingAreaWindow( nullptr ); + uno::Reference< awt::XWindow > xWindow( rUIElement.m_xUIElement->getRealInterface(), uno::UNO_QUERY ); + uno::Reference< awt::XWindow > xDockingAreaWindow; + ::tools::Rectangle aTrackingRect( rTrackingRect ); + ui::DockingArea eDockedArea( rUIElement.m_aDockedData.m_nDockedArea ); + sal_Int32 nTopDockingAreaSize( implts_getTopBottomDockingAreaSizes().Width() ); + sal_Int32 nBottomDockingAreaSize( implts_getTopBottomDockingAreaSizes().Height() ); + bool bHorizontalDockArea(( eDockedArea == ui::DockingArea_DOCKINGAREA_TOP ) || + ( eDockedArea == ui::DockingArea_DOCKINGAREA_BOTTOM )); + sal_Int32 nMaxLeftRightDockAreaSize = aContainerWinSize.Height() - + nTopDockingAreaSize - + nBottomDockingAreaSize - + aDockingAreaOffsets.Top() - + aDockingAreaOffsets.Bottom(); + ::tools::Rectangle aDockingAreaRect; + + aReadLock.reset(); + xDockingAreaWindow = m_xDockAreaWindows[static_cast<int>(eDockedArea)]; + aReadLock.clear(); + + { + SolarMutexGuard aGuard; + pDockingAreaWindow = VCLUnoHelper::GetWindow( xDockingAreaWindow ); + VclPtr<vcl::Window> pDockWindow = VCLUnoHelper::GetWindow( xWindow ); + ToolBox* pToolBox( nullptr ); + if ( pDockWindow && pDockWindow->GetType() == WindowType::TOOLBOX ) + pToolBox = static_cast<ToolBox *>(pDockWindow.get()); + + aDockingAreaRect = ::tools::Rectangle( pDockingAreaWindow->GetPosPixel(), pDockingAreaWindow->GetSizePixel() ); + if ( pToolBox ) + { + // docked toolbars always have one line + ::Size aSize = pToolBox->CalcWindowSizePixel( 1, ImplConvertAlignment( eDockedArea ) ); + aTrackingRect.SetSize( ::Size( aSize.Width(), aSize.Height() )); + } + } + + // default docking operation, dock on the given row/column + bool bOpOutsideOfDockingArea( !aDockingAreaRect.Contains( rMousePos )); + + std::vector< SingleRowColumnWindowData > aRowColumnsWindowData; + + rDockingOperation = DOCKOP_ON_COLROW; + implts_getDockingAreaElementInfos( eDockedArea, aRowColumnsWindowData ); + + // determine current first row/column and last row/column + sal_Int32 nMaxRowCol( -1 ); + sal_Int32 nMinRowCol( SAL_MAX_INT32 ); + const sal_uInt32 nCount = aRowColumnsWindowData.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + if ( aRowColumnsWindowData[i].nRowColumn > nMaxRowCol ) + nMaxRowCol = aRowColumnsWindowData[i].nRowColumn; + if ( aRowColumnsWindowData[i].nRowColumn < nMinRowCol ) + nMinRowCol = aRowColumnsWindowData[i].nRowColumn; + } + + if ( !bOpOutsideOfDockingArea ) + { + // docking inside our docking area + sal_Int32 nIndex( -1 ); + sal_Int32 nRowCol( -1 ); + ::tools::Rectangle aWindowRect; + ::tools::Rectangle aRowColumnRect; + + const sal_uInt32 nWindowDataCount = aRowColumnsWindowData.size(); + for ( sal_uInt32 i = 0; i < nWindowDataCount; i++ ) + { + ::tools::Rectangle aRect( aRowColumnsWindowData[i].aRowColumnRect.X, + aRowColumnsWindowData[i].aRowColumnRect.Y, + aRowColumnsWindowData[i].aRowColumnRect.X + aRowColumnsWindowData[i].aRowColumnRect.Width, + aRowColumnsWindowData[i].aRowColumnRect.Y + aRowColumnsWindowData[i].aRowColumnRect.Height ); + + { + // Calc correct position of the column/row rectangle to be able to compare it with mouse pos/tracking rect + SolarMutexGuard aGuard; + aRect.SetPos( pContainerWindow->ScreenToOutputPixel( pDockingAreaWindow->OutputToScreenPixel( aRect.TopLeft() ))); + } + + bool bIsInsideRowCol( aRect.Contains( rMousePos ) ); + if ( bIsInsideRowCol ) + { + nIndex = i; + nRowCol = aRowColumnsWindowData[i].nRowColumn; + rDockingOperation = implts_determineDockingOperation( eDockedArea, aRect, rMousePos ); + aWindowRect = implts_getWindowRectFromRowColumn( eDockedArea, aRowColumnsWindowData[i], rMousePos, rUIElement.m_aName ); + aRowColumnRect = aRect; + break; + } + } + + OSL_ENSURE( ( nIndex >= 0 ) && ( nRowCol >= 0 ), "Impossible case - no row/column found but mouse pointer is inside our docking area" ); + if (( nIndex >= 0 ) && ( nRowCol >= 0 )) + { + if ( rDockingOperation == DOCKOP_ON_COLROW ) + { + if ( !aWindowRect.IsEmpty()) + { + // Tracking rect is on a row/column and mouse is over a docked toolbar. + // Determine if the tracking rect must be located before/after the docked toolbar. + + ::tools::Rectangle aUIElementRect( aWindowRect ); + sal_Int32 nMiddle( bHorizontalDockArea ? ( aWindowRect.Left() + aWindowRect.getWidth() / 2 ) : + ( aWindowRect.Top() + aWindowRect.getHeight() / 2 )); + bool bInsertBefore( bHorizontalDockArea ? ( rMousePos.X() < nMiddle ) : ( rMousePos.Y() < nMiddle )); + if ( bInsertBefore ) + { + if ( bHorizontalDockArea ) + { + sal_Int32 nSize = std::clamp( sal_Int32(aContainerWinSize.Width() - aWindowRect.Left()), + sal_Int32(0), sal_Int32(aTrackingRect.getWidth()) ); + if ( nSize == 0 ) + nSize = aWindowRect.getWidth(); + + aUIElementRect.SetSize( ::Size( nSize, aWindowRect.getHeight() )); + aWindowRect = implts_determineFrontDockingRect( eDockedArea, nRowCol, aWindowRect,rUIElement.m_aName, aUIElementRect ); + + // Set virtual position + rUIElement.m_aDockedData.m_aPos.X = aWindowRect.Left(); + rUIElement.m_aDockedData.m_aPos.Y = nRowCol; + } + else + { + sal_Int32 nSize = std::clamp( sal_Int32(nTopDockingAreaSize + nMaxLeftRightDockAreaSize - aWindowRect.Top()), + sal_Int32(0), sal_Int32(aTrackingRect.getHeight()) ); + if ( nSize == 0 ) + nSize = aWindowRect.getHeight(); + + aUIElementRect.SetSize( ::Size( aWindowRect.getWidth(), nSize )); + aWindowRect = implts_determineFrontDockingRect( eDockedArea, nRowCol, aWindowRect, rUIElement.m_aName, aUIElementRect ); + + // Set virtual position + sal_Int32 nPosY = pDockingAreaWindow->ScreenToOutputPixel( + pContainerWindow->OutputToScreenPixel( aWindowRect.TopLeft() )).Y(); + rUIElement.m_aDockedData.m_aPos.X = nRowCol; + rUIElement.m_aDockedData.m_aPos.Y = nPosY; + } + + rTrackingRect = aWindowRect; + return; + } + else + { + if ( bHorizontalDockArea ) + { + sal_Int32 nSize = ::std::clamp( sal_Int32(aContainerWinSize.Width() - aWindowRect.Right()), + sal_Int32(0), sal_Int32(aTrackingRect.getWidth()) ); + if ( nSize == 0 ) + { + aUIElementRect.SetPos( ::Point( aContainerWinSize.Width() - aTrackingRect.getWidth(), aWindowRect.Top() )); + aUIElementRect.SetSize( ::Size( aTrackingRect.getWidth(), aWindowRect.getHeight() )); + rUIElement.m_aDockedData.m_aPos.X = aUIElementRect.Left(); + + } + else + { + aUIElementRect.SetPos( ::Point( aWindowRect.Right(), aWindowRect.Top() )); + aUIElementRect.SetSize( ::Size( nSize, aWindowRect.getHeight() )); + rUIElement.m_aDockedData.m_aPos.X = aWindowRect.Right(); + } + + // Set virtual position + rUIElement.m_aDockedData.m_aPos.Y = nRowCol; + } + else + { + sal_Int32 nSize = std::clamp( sal_Int32(nTopDockingAreaSize + nMaxLeftRightDockAreaSize - aWindowRect.Bottom()), + sal_Int32(0), sal_Int32(aTrackingRect.getHeight()) ); + aUIElementRect.SetPos( ::Point( aWindowRect.Left(), aWindowRect.Bottom() )); + aUIElementRect.SetSize( ::Size( aWindowRect.getWidth(), nSize )); + + // Set virtual position + sal_Int32 nPosY( 0 ); + { + SolarMutexGuard aGuard; + nPosY = pDockingAreaWindow->ScreenToOutputPixel( + pContainerWindow->OutputToScreenPixel( aWindowRect.BottomRight() )).Y(); + } + rUIElement.m_aDockedData.m_aPos.X = nRowCol; + rUIElement.m_aDockedData.m_aPos.Y = nPosY; + } + + rTrackingRect = aUIElementRect; + return; + } + } + else + { + implts_setTrackingRect( eDockedArea, rMousePos, aTrackingRect ); + rTrackingRect = implts_calcTrackingAndElementRect( + eDockedArea, nRowCol, rUIElement, + aTrackingRect, aRowColumnRect, aContainerWinSize ); + return; + } + } + else + { + if ((( nRowCol == nMinRowCol ) && ( rDockingOperation == DOCKOP_BEFORE_COLROW )) || + (( nRowCol == nMaxRowCol ) && ( rDockingOperation == DOCKOP_AFTER_COLROW ))) + bOpOutsideOfDockingArea = true; + else + { + // handle docking before/after a row + implts_setTrackingRect( eDockedArea, rMousePos, aTrackingRect ); + rTrackingRect = implts_calcTrackingAndElementRect( + eDockedArea, nRowCol, rUIElement, + aTrackingRect, aRowColumnRect, aContainerWinSize ); + + sal_Int32 nOffsetX( 0 ); + sal_Int32 nOffsetY( 0 ); + if ( bHorizontalDockArea ) + nOffsetY = sal_Int32( floor( aRowColumnRect.getHeight() / 2.0 + 0.5 )); + else + nOffsetX = sal_Int32( floor( aRowColumnRect.getWidth() / 2.0 + 0.5 )); + + if ( rDockingOperation == DOCKOP_BEFORE_COLROW ) + { + if (( eDockedArea == ui::DockingArea_DOCKINGAREA_TOP ) || ( eDockedArea == ui::DockingArea_DOCKINGAREA_LEFT )) + { + // Docking before/after means move track rectangle half column/row. + // As left and top are ordered 0...n instead of right and bottom + // which uses n...0, we have to use negative values for top/left. + nOffsetX *= -1; + nOffsetY *= -1; + } + } + else + { + if (( eDockedArea == ui::DockingArea_DOCKINGAREA_BOTTOM ) || ( eDockedArea == ui::DockingArea_DOCKINGAREA_RIGHT )) + { + // Docking before/after means move track rectangle half column/row. + // As left and top are ordered 0...n instead of right and bottom + // which uses n...0, we have to use negative values for top/left. + nOffsetX *= -1; + nOffsetY *= -1; + } + nRowCol++; + } + + if ( bHorizontalDockArea ) + rUIElement.m_aDockedData.m_aPos.Y = nRowCol; + else + rUIElement.m_aDockedData.m_aPos.X = nRowCol; + + rTrackingRect.Move( nOffsetX, nOffsetY ); + rTrackingRect.SetSize( aTrackingRect.GetSize() ); + } + } + } + } + + // Docking outside of our docking window area => + // Users want to dock before/after first/last docked element or to an empty docking area + if ( !bOpOutsideOfDockingArea ) + return; + + // set correct size for docking + implts_setTrackingRect( eDockedArea, rMousePos, aTrackingRect ); + rTrackingRect = aTrackingRect; + + if ( bHorizontalDockArea ) + { + sal_Int32 nPosX( std::max( sal_Int32( rTrackingRect.Left()), sal_Int32( 0 ))); + if (( nPosX + rTrackingRect.getWidth()) > aContainerWinSize.Width() ) + nPosX = std::min( nPosX, + std::max( sal_Int32( aContainerWinSize.Width() - rTrackingRect.getWidth() ), + sal_Int32( 0 ))); + + sal_Int32 nSize = std::min( aContainerWinSize.Width(), rTrackingRect.getWidth() ); + sal_Int32 nDockHeight = std::max( static_cast<sal_Int32>(aDockingAreaRect.getHeight()), sal_Int32( 0 )); + if ( nDockHeight == 0 ) + { + sal_Int32 nPosY( std::max( aDockingAreaRect.Top(), aDockingAreaRect.Bottom() )); + if ( eDockedArea == ui::DockingArea_DOCKINGAREA_BOTTOM ) + nPosY -= rTrackingRect.getHeight(); + rTrackingRect.SetPos( Point( nPosX, nPosY )); + rUIElement.m_aDockedData.m_aPos.Y = 0; + } + else if ( rMousePos.Y() < ( aDockingAreaRect.Top() + ( nDockHeight / 2 ))) + { + rTrackingRect.SetPos( Point( nPosX, aDockingAreaRect.Top() - rTrackingRect.getHeight() )); + if ( eDockedArea == ui::DockingArea_DOCKINGAREA_TOP ) + rUIElement.m_aDockedData.m_aPos.Y = 0; + else + rUIElement.m_aDockedData.m_aPos.Y = ( nMaxRowCol >= 0 ) ? nMaxRowCol+1 : 0; + rDockingOperation = DOCKOP_BEFORE_COLROW; + } + else + { + rTrackingRect.SetPos( Point( nPosX, aDockingAreaRect.Bottom() )); + if ( eDockedArea == ui::DockingArea_DOCKINGAREA_TOP ) + rUIElement.m_aDockedData.m_aPos.Y = ( nMaxRowCol >= 0 ) ? nMaxRowCol+1 : 0; + else + rUIElement.m_aDockedData.m_aPos.Y = 0; + rDockingOperation = DOCKOP_AFTER_COLROW; + } + rTrackingRect.setWidth( nSize ); + + { + SolarMutexGuard aGuard; + nPosX = pDockingAreaWindow->ScreenToOutputPixel( + pContainerWindow->OutputToScreenPixel( rTrackingRect.TopLeft() )).X(); + } + rUIElement.m_aDockedData.m_aPos.X = nPosX; + } + else + { + sal_Int32 nMaxDockingAreaHeight = std::max<sal_Int32>( 0, nMaxLeftRightDockAreaSize ); + sal_Int32 nPosY( std::max<sal_Int32>( aTrackingRect.Top(), nTopDockingAreaSize )); + if (( nPosY + aTrackingRect.getHeight()) > ( nTopDockingAreaSize + nMaxDockingAreaHeight )) + nPosY = std::min( nPosY, + std::max<sal_Int32>( nTopDockingAreaSize + ( nMaxDockingAreaHeight - aTrackingRect.getHeight() ), + nTopDockingAreaSize )); + + sal_Int32 nSize = std::min( nMaxDockingAreaHeight, static_cast<sal_Int32>(aTrackingRect.getHeight()) ); + sal_Int32 nDockWidth = std::max( static_cast<sal_Int32>(aDockingAreaRect.getWidth()), sal_Int32( 0 )); + if ( nDockWidth == 0 ) + { + sal_Int32 nPosX( std::max( aDockingAreaRect.Left(), aDockingAreaRect.Right() )); + if ( eDockedArea == ui::DockingArea_DOCKINGAREA_RIGHT ) + nPosX -= rTrackingRect.getWidth(); + rTrackingRect.SetPos( Point( nPosX, nPosY )); + rUIElement.m_aDockedData.m_aPos.X = 0; + } + else if ( rMousePos.X() < ( aDockingAreaRect.Left() + ( nDockWidth / 2 ))) + { + rTrackingRect.SetPos( Point( aDockingAreaRect.Left() - rTrackingRect.getWidth(), nPosY )); + if ( eDockedArea == ui::DockingArea_DOCKINGAREA_LEFT ) + rUIElement.m_aDockedData.m_aPos.X = 0; + else + rUIElement.m_aDockedData.m_aPos.X = ( nMaxRowCol >= 0 ) ? nMaxRowCol+1 : 0; + rDockingOperation = DOCKOP_BEFORE_COLROW; + } + else + { + rTrackingRect.SetPos( Point( aDockingAreaRect.Right(), nPosY )); + if ( eDockedArea == ui::DockingArea_DOCKINGAREA_LEFT ) + rUIElement.m_aDockedData.m_aPos.X = ( nMaxRowCol >= 0 ) ? nMaxRowCol+1 : 0; + else + rUIElement.m_aDockedData.m_aPos.X = 0; + rDockingOperation = DOCKOP_AFTER_COLROW; + } + rTrackingRect.setHeight( nSize ); + + { + SolarMutexGuard aGuard; + nPosY = pDockingAreaWindow->ScreenToOutputPixel( + pContainerWindow->OutputToScreenPixel( rTrackingRect.TopLeft() )).Y(); + } + rUIElement.m_aDockedData.m_aPos.Y = nPosY; + } +} + +framework::ToolbarLayoutManager::DockingOperation ToolbarLayoutManager::implts_determineDockingOperation( + ui::DockingArea DockingArea, + const ::tools::Rectangle& rRowColRect, + const Point& rMousePos ) +{ + constexpr sal_Int32 nHorzVerticalRegionSize = 6; + constexpr sal_Int32 nHorzVerticalMoveRegion = 4; + + if ( rRowColRect.Contains( rMousePos )) + { + if ( isHorizontalDockingArea( DockingArea )) + { + sal_Int32 nRegion = rRowColRect.getHeight() / nHorzVerticalRegionSize; + sal_Int32 nPosY = rRowColRect.Top() + nRegion; + + if ( rMousePos.Y() < nPosY ) + return ( DockingArea == ui::DockingArea_DOCKINGAREA_TOP ) ? DOCKOP_BEFORE_COLROW : DOCKOP_AFTER_COLROW; + else if ( rMousePos.Y() < ( nPosY + nRegion*nHorzVerticalMoveRegion )) + return DOCKOP_ON_COLROW; + else + return ( DockingArea == ui::DockingArea_DOCKINGAREA_TOP ) ? DOCKOP_AFTER_COLROW : DOCKOP_BEFORE_COLROW; + } + else + { + sal_Int32 nRegion = rRowColRect.getWidth() / nHorzVerticalRegionSize; + sal_Int32 nPosX = rRowColRect.Left() + nRegion; + + if ( rMousePos.X() < nPosX ) + return ( DockingArea == ui::DockingArea_DOCKINGAREA_LEFT ) ? DOCKOP_BEFORE_COLROW : DOCKOP_AFTER_COLROW; + else if ( rMousePos.X() < ( nPosX + nRegion*nHorzVerticalMoveRegion )) + return DOCKOP_ON_COLROW; + else + return ( DockingArea == ui::DockingArea_DOCKINGAREA_LEFT ) ? DOCKOP_AFTER_COLROW : DOCKOP_BEFORE_COLROW; + } + } + else + return DOCKOP_ON_COLROW; +} + +::tools::Rectangle ToolbarLayoutManager::implts_calcTrackingAndElementRect( + ui::DockingArea eDockingArea, + sal_Int32 nRowCol, + UIElement& rUIElement, + const ::tools::Rectangle& rTrackingRect, + const ::tools::Rectangle& rRowColumnRect, + const ::Size& rContainerWinSize ) +{ + SolarMutexResettableGuard aReadGuard; + ::tools::Rectangle aDockingAreaOffsets( m_aDockingAreaOffsets ); + aReadGuard.clear(); + + bool bHorizontalDockArea( isHorizontalDockingArea( eDockingArea )); + + sal_Int32 nTopDockingAreaSize( implts_getTopBottomDockingAreaSizes().Width() ); + sal_Int32 nBottomDockingAreaSize( implts_getTopBottomDockingAreaSizes().Height() ); + + sal_Int32 nMaxLeftRightDockAreaSize = rContainerWinSize.Height() - + nTopDockingAreaSize - + nBottomDockingAreaSize - + aDockingAreaOffsets.Top() - + aDockingAreaOffsets.Bottom(); + + ::tools::Rectangle aTrackingRect( rTrackingRect ); + if ( bHorizontalDockArea ) + { + sal_Int32 nPosX( std::max( sal_Int32( rTrackingRect.Left()), sal_Int32( 0 ))); + if (( nPosX + rTrackingRect.getWidth()) > rContainerWinSize.Width() ) + nPosX = std::min( nPosX, + std::max( sal_Int32( rContainerWinSize.Width() - rTrackingRect.getWidth() ), + sal_Int32( 0 ))); + + sal_Int32 nSize = std::min( rContainerWinSize.Width(), rTrackingRect.getWidth() ); + + aTrackingRect.SetPos( ::Point( nPosX, rRowColumnRect.Top() )); + aTrackingRect.setWidth( nSize ); + aTrackingRect.setHeight( rRowColumnRect.getHeight() ); + + // Set virtual position + rUIElement.m_aDockedData.m_aPos.X = nPosX; + rUIElement.m_aDockedData.m_aPos.Y = nRowCol; + } + else + { + sal_Int32 nMaxDockingAreaHeight = std::max<sal_Int32>( 0, nMaxLeftRightDockAreaSize ); + + sal_Int32 nPosY( std::max<sal_Int32>( aTrackingRect.Top(), nTopDockingAreaSize )); + if (( nPosY + aTrackingRect.getHeight()) > ( nTopDockingAreaSize + nMaxDockingAreaHeight )) + nPosY = std::min( nPosY, + std::max<sal_Int32>( nTopDockingAreaSize + ( nMaxDockingAreaHeight - aTrackingRect.getHeight() ), + nTopDockingAreaSize )); + + sal_Int32 nSize = std::min( nMaxDockingAreaHeight, static_cast<sal_Int32>(aTrackingRect.getHeight()) ); + + aTrackingRect.SetPos( ::Point( rRowColumnRect.Left(), nPosY )); + aTrackingRect.setWidth( rRowColumnRect.getWidth() ); + aTrackingRect.setHeight( nSize ); + + aReadGuard.reset(); + uno::Reference< awt::XWindow > xDockingAreaWindow( m_xDockAreaWindows[static_cast<int>(eDockingArea)] ); + uno::Reference< awt::XWindow2 > xContainerWindow( m_xContainerWindow ); + aReadGuard.clear(); + + sal_Int32 nDockPosY( 0 ); + { + SolarMutexGuard aGuard; + vcl::Window* pDockingAreaWindow = VCLUnoHelper::GetWindow( xDockingAreaWindow ); + VclPtr<vcl::Window> pContainerWindow = VCLUnoHelper::GetWindow( xContainerWindow ); + nDockPosY = pDockingAreaWindow->ScreenToOutputPixel( pContainerWindow->OutputToScreenPixel( ::Point( 0, nPosY ))).Y(); + } + + // Set virtual position + rUIElement.m_aDockedData.m_aPos.X = nRowCol; + rUIElement.m_aDockedData.m_aPos.Y = nDockPosY; + } + + return aTrackingRect; +} + +void ToolbarLayoutManager::implts_setTrackingRect( ui::DockingArea eDockingArea, const ::Point& rMousePos, ::tools::Rectangle& rTrackingRect ) +{ + ::Point aPoint( rTrackingRect.TopLeft()); + if ( isHorizontalDockingArea( eDockingArea )) + aPoint.setX( rMousePos.X() ); + else + aPoint.setY( rMousePos.Y() ); + rTrackingRect.SetPos( aPoint ); +} + +void ToolbarLayoutManager::implts_renumberRowColumnData( + ui::DockingArea eDockingArea, + const UIElement& rUIElement ) +{ + SolarMutexClearableGuard aReadLock; + uno::Reference< container::XNameAccess > xPersistentWindowState( m_xPersistentWindowState ); + aReadLock.clear(); + + bool bHorzDockingArea( isHorizontalDockingArea( eDockingArea )); + sal_Int32 nRowCol( bHorzDockingArea ? rUIElement.m_aDockedData.m_aPos.Y : rUIElement.m_aDockedData.m_aPos.X ); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + { + SolarMutexGuard aWriteLock; + for (auto& elem : m_aUIElements) + { + if ((elem.m_aDockedData.m_nDockedArea == eDockingArea) + && (elem.m_aName != rUIElement.m_aName)) + { + // Don't change toolbars without a valid docking position! + if (isDefaultPos(elem.m_aDockedData.m_aPos)) + continue; + + sal_Int32 nWindowRowCol + = bHorzDockingArea ? elem.m_aDockedData.m_aPos.Y : elem.m_aDockedData.m_aPos.X; + if (nWindowRowCol >= nRowCol) + { + if (bHorzDockingArea) + elem.m_aDockedData.m_aPos.Y += 1; + else + elem.m_aDockedData.m_aPos.X += 1; + } + } + } + } + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + // We have to change the persistent window state part + if ( !xPersistentWindowState.is() ) + return; + + try + { + const uno::Sequence< OUString > aWindowElements = xPersistentWindowState->getElementNames(); + for ( OUString const & rWindowElementName : aWindowElements ) + { + if ( rUIElement.m_aName != rWindowElementName ) + { + try + { + uno::Sequence< beans::PropertyValue > aPropValueSeq; + awt::Point aDockedPos; + ui::DockingArea nDockedArea( ui::DockingArea_DOCKINGAREA_DEFAULT ); + + xPersistentWindowState->getByName( rWindowElementName ) >>= aPropValueSeq; + for ( beans::PropertyValue const & rProp : std::as_const(aPropValueSeq) ) + { + if ( rProp.Name == WINDOWSTATE_PROPERTY_DOCKINGAREA ) + rProp.Value >>= nDockedArea; + else if ( rProp.Name == WINDOWSTATE_PROPERTY_DOCKPOS ) + rProp.Value >>= aDockedPos; + } + + // Don't change toolbars without a valid docking position! + if ( isDefaultPos( aDockedPos )) + continue; + + sal_Int32 nWindowRowCol = bHorzDockingArea ? aDockedPos.Y : aDockedPos.X; + if (( nDockedArea == eDockingArea ) && ( nWindowRowCol >= nRowCol )) + { + if ( bHorzDockingArea ) + aDockedPos.Y += 1; + else + aDockedPos.X += 1; + + uno::Reference< container::XNameReplace > xReplace( xPersistentWindowState, uno::UNO_QUERY ); + xReplace->replaceByName( rWindowElementName, css::uno::Any( aPropValueSeq )); + } + } + catch (const uno::Exception&) + { + } + } + } + } + catch (const uno::Exception&) + { + } +} + +// XWindowListener + +void SAL_CALL ToolbarLayoutManager::windowResized( const awt::WindowEvent& aEvent ) +{ + SolarMutexClearableGuard aWriteLock; + bool bLocked( m_bDockingInProgress ); + bool bLayoutInProgress( m_bLayoutInProgress ); + aWriteLock.clear(); + + // Do not do anything if we are in the middle of a docking process. This would interfere all other + // operations. We will store the new position and size in the docking handlers. + // Do not do anything if we are in the middle of our layouting process. We will adapt the position + // and size of the user interface elements. + if ( bLocked || bLayoutInProgress ) + return; + + bool bNotify( false ); + uno::Reference< awt::XWindow > xWindow( aEvent.Source, uno::UNO_QUERY ); + + UIElement aUIElement = implts_findToolbar( aEvent.Source ); + if ( aUIElement.m_xUIElement.is() ) + { + if ( aUIElement.m_bFloating ) + { + uno::Reference< awt::XWindow2 > xWindow2( xWindow, uno::UNO_QUERY ); + + if( xWindow2.is() ) + { + awt::Rectangle aPos = xWindow2->getPosSize(); + awt::Size aSize = xWindow2->getOutputSize(); // always use output size for consistency + bool bVisible = xWindow2->isVisible(); + + // update element data + aUIElement.m_aFloatingData.m_aPos = awt::Point(aPos.X, aPos.Y); + aUIElement.m_aFloatingData.m_aSize = aSize; + aUIElement.m_bVisible = bVisible; + } + + implts_writeWindowStateData( aUIElement ); + } + else + { + implts_setLayoutDirty(); + bNotify = true; + } + } + + if ( bNotify ) + m_pParentLayouter->requestLayout(); +} + +void SAL_CALL ToolbarLayoutManager::windowMoved( const awt::WindowEvent& /*aEvent*/ ) +{ +} + +void SAL_CALL ToolbarLayoutManager::windowShown( const lang::EventObject& /*aEvent*/ ) +{ +} + +void SAL_CALL ToolbarLayoutManager::windowHidden( const lang::EventObject& /*aEvent*/ ) +{ +} + +// XDockableWindowListener + +void SAL_CALL ToolbarLayoutManager::startDocking( const awt::DockingEvent& e ) +{ + bool bWinFound( false ); + + SolarMutexClearableGuard aReadGuard; + uno::Reference< awt::XWindow2 > xWindow( e.Source, uno::UNO_QUERY ); + aReadGuard.clear(); + + UIElement aUIElement = implts_findToolbar( e.Source ); + + if ( aUIElement.m_xUIElement.is() && xWindow.is() ) + { + bWinFound = true; + uno::Reference< awt::XDockableWindow > xDockWindow( xWindow, uno::UNO_QUERY ); + if ( xDockWindow->isFloating() ) + { + awt::Rectangle aPos = xWindow->getPosSize(); + awt::Size aSize = xWindow->getOutputSize(); + + aUIElement.m_aFloatingData.m_aPos = awt::Point(aPos.X, aPos.Y); + aUIElement.m_aFloatingData.m_aSize = aSize; + + SolarMutexGuard aGuard; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->GetType() == WindowType::TOOLBOX ) + { + ToolBox* pToolBox = static_cast<ToolBox *>(pWindow.get()); + aUIElement.m_aFloatingData.m_nLines = pToolBox->GetFloatingLines(); + aUIElement.m_aFloatingData.m_bIsHorizontal = isToolboxHorizontalAligned( pToolBox ); + } + } + } + + SolarMutexGuard g; + m_bDockingInProgress = bWinFound; + m_aDockUIElement = aUIElement; + m_aDockUIElement.m_bUserActive = true; +} + +awt::DockingData SAL_CALL ToolbarLayoutManager::docking( const awt::DockingEvent& e ) +{ + constexpr sal_Int32 MAGNETIC_DISTANCE_UNDOCK = 25; + constexpr sal_Int32 MAGNETIC_DISTANCE_DOCK = 20; + + SolarMutexClearableGuard aReadLock; + awt::DockingData aDockingData; + uno::Reference< awt::XDockableWindow > xDockWindow( e.Source, uno::UNO_QUERY ); + uno::Reference< awt::XWindow > xWindow( e.Source, uno::UNO_QUERY ); + uno::Reference< awt::XWindow > xTopDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_TOP)] ); + uno::Reference< awt::XWindow > xLeftDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_LEFT)] ); + uno::Reference< awt::XWindow > xRightDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_RIGHT)] ); + uno::Reference< awt::XWindow > xBottomDockingWindow( m_xDockAreaWindows[int(ui::DockingArea_DOCKINGAREA_BOTTOM)] ); + uno::Reference< awt::XWindow2 > xContainerWindow( m_xContainerWindow ); + UIElement aUIDockingElement( m_aDockUIElement ); + + bool bDockingInProgress( m_bDockingInProgress ); + aReadLock.clear(); + + if ( bDockingInProgress ) + aDockingData.TrackingRectangle = e.TrackingRectangle; + + if ( bDockingInProgress && xDockWindow.is() && xWindow.is() ) + { + try + { + SolarMutexGuard aGuard; + + DockingOperation eDockingOperation( DOCKOP_ON_COLROW ); + ui::DockingArea eDockingArea( ui::DockingArea(-1) ); // none + sal_Int32 nMagneticZone( aUIDockingElement.m_bFloating ? MAGNETIC_DISTANCE_DOCK : MAGNETIC_DISTANCE_UNDOCK ); + ::tools::Rectangle aTrackingRect( e.TrackingRectangle.X, e.TrackingRectangle.Y, + ( e.TrackingRectangle.X + e.TrackingRectangle.Width ), + ( e.TrackingRectangle.Y + e.TrackingRectangle.Height )); + + awt::Rectangle aTmpRect = xTopDockingWindow->getPosSize(); + ::tools::Rectangle aTopDockRect( aTmpRect.X, aTmpRect.Y, aTmpRect.Width, aTmpRect.Height ); + ::tools::Rectangle aHotZoneTopDockRect( implts_calcHotZoneRect( aTopDockRect, nMagneticZone )); + + aTmpRect = xBottomDockingWindow->getPosSize(); + ::tools::Rectangle aBottomDockRect( aTmpRect.X, aTmpRect.Y, ( aTmpRect.X + aTmpRect.Width), ( aTmpRect.Y + aTmpRect.Height )); + ::tools::Rectangle aHotZoneBottomDockRect( implts_calcHotZoneRect( aBottomDockRect, nMagneticZone )); + + aTmpRect = xLeftDockingWindow->getPosSize(); + ::tools::Rectangle aLeftDockRect( aTmpRect.X, aTmpRect.Y, ( aTmpRect.X + aTmpRect.Width ), ( aTmpRect.Y + aTmpRect.Height )); + ::tools::Rectangle aHotZoneLeftDockRect( implts_calcHotZoneRect( aLeftDockRect, nMagneticZone )); + + aTmpRect = xRightDockingWindow->getPosSize(); + ::tools::Rectangle aRightDockRect( aTmpRect.X, aTmpRect.Y, ( aTmpRect.X + aTmpRect.Width ), ( aTmpRect.Y + aTmpRect.Height )); + ::tools::Rectangle aHotZoneRightDockRect( implts_calcHotZoneRect( aRightDockRect, nMagneticZone )); + + VclPtr<vcl::Window> pContainerWindow( VCLUnoHelper::GetWindow( xContainerWindow ) ); + ::Point aMousePos( pContainerWindow->ScreenToOutputPixel( ::Point( e.MousePos.X, e.MousePos.Y ))); + + if ( aHotZoneTopDockRect.Contains( aMousePos )) + eDockingArea = ui::DockingArea_DOCKINGAREA_TOP; + else if ( aHotZoneBottomDockRect.Contains( aMousePos )) + eDockingArea = ui::DockingArea_DOCKINGAREA_BOTTOM; + else if ( aHotZoneLeftDockRect.Contains( aMousePos )) + eDockingArea = ui::DockingArea_DOCKINGAREA_LEFT; + else if ( aHotZoneRightDockRect.Contains( aMousePos )) + eDockingArea = ui::DockingArea_DOCKINGAREA_RIGHT; + + // Higher priority for movements inside the real docking area + if ( aTopDockRect.Contains( aMousePos )) + eDockingArea = ui::DockingArea_DOCKINGAREA_TOP; + else if ( aBottomDockRect.Contains( aMousePos )) + eDockingArea = ui::DockingArea_DOCKINGAREA_BOTTOM; + else if ( aLeftDockRect.Contains( aMousePos )) + eDockingArea = ui::DockingArea_DOCKINGAREA_LEFT; + else if ( aRightDockRect.Contains( aMousePos )) + eDockingArea = ui::DockingArea_DOCKINGAREA_RIGHT; + + // Determine if we have a toolbar and set alignment according to the docking area! + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + ToolBox* pToolBox = nullptr; + if ( pWindow && pWindow->GetType() == WindowType::TOOLBOX ) + pToolBox = static_cast<ToolBox *>(pWindow.get()); + + if ( eDockingArea != ui::DockingArea(-1) ) + { + if ( eDockingArea == ui::DockingArea_DOCKINGAREA_TOP ) + { + aUIDockingElement.m_aDockedData.m_nDockedArea = ui::DockingArea_DOCKINGAREA_TOP; + aUIDockingElement.m_bFloating = false; + } + else if ( eDockingArea == ui::DockingArea_DOCKINGAREA_BOTTOM ) + { + aUIDockingElement.m_aDockedData.m_nDockedArea = ui::DockingArea_DOCKINGAREA_BOTTOM; + aUIDockingElement.m_bFloating = false; + } + else if ( eDockingArea == ui::DockingArea_DOCKINGAREA_LEFT ) + { + aUIDockingElement.m_aDockedData.m_nDockedArea = ui::DockingArea_DOCKINGAREA_LEFT; + aUIDockingElement.m_bFloating = false; + } + else if ( eDockingArea == ui::DockingArea_DOCKINGAREA_RIGHT ) + { + aUIDockingElement.m_aDockedData.m_nDockedArea = ui::DockingArea_DOCKINGAREA_RIGHT; + aUIDockingElement.m_bFloating = false; + } + + ::Point aOutputPos = pContainerWindow->ScreenToOutputPixel( aTrackingRect.TopLeft() ); + aTrackingRect.SetPos( aOutputPos ); + + ::tools::Rectangle aNewDockingRect( aTrackingRect ); + + implts_calcDockingPosSize( aUIDockingElement, eDockingOperation, aNewDockingRect, aMousePos ); + + ::Point aScreenPos = pContainerWindow->OutputToScreenPixel( aNewDockingRect.TopLeft() ); + aDockingData.TrackingRectangle = awt::Rectangle( aScreenPos.X(), aScreenPos.Y(), + aNewDockingRect.getWidth(), aNewDockingRect.getHeight() ); + } + else if (pToolBox) + { + bool bIsHorizontal = isToolboxHorizontalAligned( pToolBox ); + awt::Size aFloatSize = aUIDockingElement.m_aFloatingData.m_aSize; + if ( aFloatSize.Width > 0 && aFloatSize.Height > 0 ) + { + aUIDockingElement.m_aFloatingData.m_aPos = AWTPoint(pContainerWindow->ScreenToOutputPixel(VCLPoint(e.MousePos))); + aDockingData.TrackingRectangle.Height = aFloatSize.Height; + aDockingData.TrackingRectangle.Width = aFloatSize.Width; + } + else + { + aFloatSize = AWTSize(pToolBox->CalcWindowSizePixel()); + if ( !bIsHorizontal ) + { + // Floating toolbars are always horizontal aligned! We have to swap + // width/height if we have a vertical aligned toolbar. + sal_Int32 nTemp = aFloatSize.Height; + aFloatSize.Height = aFloatSize.Width; + aFloatSize.Width = nTemp; + } + + aDockingData.TrackingRectangle.Height = aFloatSize.Height; + aDockingData.TrackingRectangle.Width = aFloatSize.Width; + + // For the first time we don't have any data about the floating size of a toolbar. + // We calculate it and store it for later use. + aUIDockingElement.m_aFloatingData.m_aPos = AWTPoint(pContainerWindow->ScreenToOutputPixel(VCLPoint(e.MousePos))); + aUIDockingElement.m_aFloatingData.m_aSize = aFloatSize; + aUIDockingElement.m_aFloatingData.m_nLines = pToolBox->GetFloatingLines(); + aUIDockingElement.m_aFloatingData.m_bIsHorizontal = isToolboxHorizontalAligned( pToolBox ); + } + aDockingData.TrackingRectangle.X = e.MousePos.X; + aDockingData.TrackingRectangle.Y = e.MousePos.Y; + } + + aDockingData.bFloating = ( eDockingArea == ui::DockingArea(-1) ); + + // Write current data to the member docking progress data + SolarMutexGuard g; + m_aDockUIElement.m_bFloating = aDockingData.bFloating; + if ( !aDockingData.bFloating ) + { + m_aDockUIElement.m_aDockedData = aUIDockingElement.m_aDockedData; + + m_eDockOperation = eDockingOperation; + } + else + m_aDockUIElement.m_aFloatingData = aUIDockingElement.m_aFloatingData; + } + catch (const uno::Exception&) + { + } + } + + return aDockingData; +} + +void SAL_CALL ToolbarLayoutManager::endDocking( const awt::EndDockingEvent& e ) +{ + if (e.bCancelled) + return; + + bool bDockingInProgress( false ); + bool bStartDockFloated( false ); + bool bFloating( false ); + UIElement aUIDockingElement; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexResettableGuard aWriteLock; + bDockingInProgress = m_bDockingInProgress; + aUIDockingElement = m_aDockUIElement; + bFloating = aUIDockingElement.m_bFloating; + + UIElement& rUIElement = impl_findToolbar( aUIDockingElement.m_aName ); + if ( rUIElement.m_aName == aUIDockingElement.m_aName ) + { + if ( aUIDockingElement.m_bFloating ) + { + // Write last position into position data + uno::Reference< awt::XWindow > xWindow( aUIDockingElement.m_xUIElement->getRealInterface(), uno::UNO_QUERY ); + rUIElement.m_aFloatingData = aUIDockingElement.m_aFloatingData; + awt::Rectangle aTmpRect = xWindow->getPosSize(); + rUIElement.m_aFloatingData.m_aPos = awt::Point(aTmpRect.X, aTmpRect.Y); + // make changes also for our local data as we use it to make data persistent + aUIDockingElement.m_aFloatingData = rUIElement.m_aFloatingData; + } + else + { + rUIElement.m_aDockedData = aUIDockingElement.m_aDockedData; + rUIElement.m_aFloatingData.m_aSize = aUIDockingElement.m_aFloatingData.m_aSize; + + if ( m_eDockOperation != DOCKOP_ON_COLROW ) + { + // we have to renumber our row/column data to insert a new row/column + implts_renumberRowColumnData(aUIDockingElement.m_aDockedData.m_nDockedArea, aUIDockingElement ); + } + } + + bStartDockFloated = rUIElement.m_bFloating; + rUIElement.m_bFloating = m_aDockUIElement.m_bFloating; + rUIElement.m_bUserActive = true; + } + + // reset member for next docking operation + m_aDockUIElement.m_xUIElement.clear(); + m_eDockOperation = DOCKOP_ON_COLROW; + aWriteLock.clear(); + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + implts_writeWindowStateData( aUIDockingElement ); + + if ( bDockingInProgress ) + { + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( uno::Reference< awt::XWindow >( e.Source, uno::UNO_QUERY )); + ToolBox* pToolBox = nullptr; + if ( pWindow && pWindow->GetType() == WindowType::TOOLBOX ) + pToolBox = static_cast<ToolBox *>(pWindow.get()); + + if ( pToolBox ) + { + if( e.bFloating ) + { + if ( aUIDockingElement.m_aFloatingData.m_bIsHorizontal ) + pToolBox->SetAlign(); + else + pToolBox->SetAlign( WindowAlign::Left ); + } + else + { + ::Size aSize; + + pToolBox->SetAlign( ImplConvertAlignment( aUIDockingElement.m_aDockedData.m_nDockedArea) ); + + // Docked toolbars have always one line + aSize = pToolBox->CalcWindowSizePixel( 1 ); + + // Lock layouting updates as our listener would be called due to SetSizePixel + pToolBox->SetOutputSizePixel( aSize ); + } + } + } + + implts_sortUIElements(); + + aWriteLock.reset(); + m_bDockingInProgress = false; + m_bLayoutDirty = !bStartDockFloated || !bFloating; + bool bNotify = m_bLayoutDirty; + aWriteLock.clear(); + + if ( bNotify ) + m_pParentLayouter->requestLayout(); +} + +sal_Bool SAL_CALL ToolbarLayoutManager::prepareToggleFloatingMode( const lang::EventObject& e ) +{ + SolarMutexClearableGuard aReadLock; + bool bDockingInProgress = m_bDockingInProgress; + aReadLock.clear(); + + UIElement aUIDockingElement = implts_findToolbar( e.Source ); + bool bWinFound( !aUIDockingElement.m_aName.isEmpty() ); + uno::Reference< awt::XWindow > xWindow( e.Source, uno::UNO_QUERY ); + + if ( !bWinFound || !xWindow.is() ) + return true; + + if ( bDockingInProgress ) + return true; + + uno::Reference< awt::XDockableWindow > xDockWindow( xWindow, uno::UNO_QUERY ); + if ( !xDockWindow->isFloating() ) + return true; + + { + SolarMutexGuard aGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->GetType() == WindowType::TOOLBOX ) + { + ToolBox* pToolBox = static_cast< ToolBox *>( pWindow.get() ); + aUIDockingElement.m_aFloatingData.m_aPos = AWTPoint(pToolBox->GetPosPixel()); + aUIDockingElement.m_aFloatingData.m_aSize = AWTSize(pToolBox->GetOutputSizePixel()); + aUIDockingElement.m_aFloatingData.m_nLines = pToolBox->GetFloatingLines(); + aUIDockingElement.m_aFloatingData.m_bIsHorizontal = isToolboxHorizontalAligned( pToolBox ); + } + } + + UIElement aUIElement = implts_findToolbar( aUIDockingElement.m_aName ); + if ( aUIElement.m_aName == aUIDockingElement.m_aName ) + implts_setToolbar( aUIDockingElement ); + + return true; +} + +void SAL_CALL ToolbarLayoutManager::toggleFloatingMode( const lang::EventObject& e ) +{ + UIElement aUIDockingElement; + + SolarMutexResettableGuard aReadLock; + bool bDockingInProgress( m_bDockingInProgress ); + if ( bDockingInProgress ) + aUIDockingElement = m_aDockUIElement; + aReadLock.clear(); + + vcl::Window* pWindow( nullptr ); + ToolBox* pToolBox( nullptr ); + uno::Reference< awt::XWindow2 > xWindow; + + { + SolarMutexGuard aGuard; + xWindow.set( e.Source, uno::UNO_QUERY ); + pWindow = VCLUnoHelper::GetWindow( xWindow ); + + if ( pWindow && pWindow->GetType() == WindowType::TOOLBOX ) + pToolBox = static_cast<ToolBox *>(pWindow); + } + + if ( !bDockingInProgress ) + { + aUIDockingElement = implts_findToolbar( e.Source ); + bool bWinFound = !aUIDockingElement.m_aName.isEmpty(); + + if ( bWinFound && xWindow.is() ) + { + aUIDockingElement.m_bFloating = !aUIDockingElement.m_bFloating; + aUIDockingElement.m_bUserActive = true; + + implts_setLayoutInProgress(); + if ( aUIDockingElement.m_bFloating ) + { + SolarMutexGuard aGuard; + if ( pToolBox ) + { + pToolBox->SetLineCount( aUIDockingElement.m_aFloatingData.m_nLines ); + if ( aUIDockingElement.m_aFloatingData.m_bIsHorizontal ) + pToolBox->SetAlign(); + else + pToolBox->SetAlign( WindowAlign::Left ); + } + + bool bUndefPos = hasDefaultPosValue( aUIDockingElement.m_aFloatingData.m_aPos ); + bool bSetSize = !hasEmptySize( aUIDockingElement.m_aFloatingData.m_aSize ); + + if ( bUndefPos ) + aUIDockingElement.m_aFloatingData.m_aPos = implts_findNextCascadeFloatingPos(); + + if ( !bSetSize ) + { + if ( pToolBox ) + aUIDockingElement.m_aFloatingData.m_aSize = AWTSize(pToolBox->CalcFloatingWindowSizePixel()); + else if ( pWindow ) + aUIDockingElement.m_aFloatingData.m_aSize = AWTSize(pWindow->GetOutputSizePixel()); + } + + xWindow->setPosSize( aUIDockingElement.m_aFloatingData.m_aPos.X, + aUIDockingElement.m_aFloatingData.m_aPos.Y, + 0, 0, awt::PosSize::POS ); + xWindow->setOutputSize(aUIDockingElement.m_aFloatingData.m_aSize); + } + else + { + if ( isDefaultPos( aUIDockingElement.m_aDockedData.m_aPos )) + { + // Docking on its default position without a preset position - + // we have to find a good place for it. + ::Point aPixelPos; + awt::Point aDockPos; + ::Size aSize; + + { + SolarMutexGuard aGuard; + if ( pToolBox ) + aSize = pToolBox->CalcWindowSizePixel( 1, ImplConvertAlignment( aUIDockingElement.m_aDockedData.m_nDockedArea ) ); + else if ( pWindow ) + aSize = pWindow->GetSizePixel(); + } + + implts_findNextDockingPos(aUIDockingElement.m_aDockedData.m_nDockedArea, aSize, aDockPos, aPixelPos ); + aUIDockingElement.m_aDockedData.m_aPos = aDockPos; + } + + SolarMutexGuard aGuard; + if ( pToolBox ) + { + pToolBox->SetAlign( ImplConvertAlignment( aUIDockingElement.m_aDockedData.m_nDockedArea) ); + ::Size aSize = pToolBox->CalcWindowSizePixel( 1 ); + awt::Rectangle aRect = xWindow->getPosSize(); + xWindow->setPosSize( aRect.X, aRect.Y, 0, 0, awt::PosSize::POS ); + xWindow->setOutputSize( AWTSize( aSize ) ); + } + } + + implts_setLayoutInProgress( false ); + implts_setToolbar( aUIDockingElement ); + implts_writeWindowStateData( aUIDockingElement ); + implts_sortUIElements(); + implts_setLayoutDirty(); + + aReadLock.reset(); + LayoutManager* pParentLayouter( m_pParentLayouter ); + aReadLock.clear(); + + if ( pParentLayouter ) + pParentLayouter->requestLayout(); + } + } + else + { + SolarMutexGuard aGuard; + if ( pToolBox ) + { + if ( aUIDockingElement.m_bFloating ) + { + if ( aUIDockingElement.m_aFloatingData.m_bIsHorizontal ) + pToolBox->SetAlign(); + else + pToolBox->SetAlign( WindowAlign::Left ); + } + else + pToolBox->SetAlign( ImplConvertAlignment( aUIDockingElement.m_aDockedData.m_nDockedArea) ); + } + } +} + +void SAL_CALL ToolbarLayoutManager::closed( const lang::EventObject& e ) +{ + OUString aName; + UIElement aUIElement; + + { + SolarMutexGuard aWriteLock; + for (auto& elem : m_aUIElements) + { + uno::Reference<ui::XUIElement> xUIElement(elem.m_xUIElement); + if (xUIElement.is()) + { + uno::Reference<uno::XInterface> xIfac(xUIElement->getRealInterface(), + uno::UNO_QUERY); + if (xIfac == e.Source) + { + aName = elem.m_aName; + + // user closes a toolbar => + // context sensitive toolbar: only destroy toolbar and store state. + // non context sensitive toolbar: make it invisible, store state and destroy it. + if (!elem.m_bContextSensitive) + elem.m_bVisible = false; + + aUIElement = elem; + break; + } + } + } + } + + // destroy element + if ( aName.isEmpty() ) + return; + + implts_writeWindowStateData( aUIElement ); + destroyToolbar( aName ); + + SolarMutexClearableGuard aReadLock; + bool bLayoutDirty = m_bLayoutDirty; + LayoutManager* pParentLayouter( m_pParentLayouter ); + aReadLock.clear(); + + if ( bLayoutDirty && pParentLayouter ) + pParentLayouter->requestLayout(); +} + +void SAL_CALL ToolbarLayoutManager::endPopupMode( const awt::EndPopupModeEvent& /*e*/ ) +{ +} + +// XUIConfigurationListener + +void SAL_CALL ToolbarLayoutManager::elementInserted( const ui::ConfigurationEvent& rEvent ) +{ + UIElement aUIElement = implts_findToolbar( rEvent.ResourceURL ); + + uno::Reference< ui::XUIElementSettings > xElementSettings( aUIElement.m_xUIElement, uno::UNO_QUERY ); + if ( xElementSettings.is() ) + { + uno::Reference< beans::XPropertySet > xPropSet( xElementSettings, uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + if ( rEvent.Source == uno::Reference< uno::XInterface >( m_xDocCfgMgr, uno::UNO_QUERY )) + xPropSet->setPropertyValue( "ConfigurationSource", css::uno::Any( m_xDocCfgMgr )); + } + xElementSettings->updateSettings(); + } + else + { + OUString aElementType; + OUString aElementName; + parseResourceURL( rEvent.ResourceURL, aElementType, aElementName ); + if ( aElementName.indexOf( "custom_" ) != -1 ) + { + // custom toolbar must be directly created, shown and layouted! + createToolbar( rEvent.ResourceURL ); + uno::Reference< ui::XUIElement > xUIElement = getToolbar( rEvent.ResourceURL ); + if ( xUIElement.is() ) + { + OUString aUIName; + uno::Reference< ui::XUIConfigurationManager > xCfgMgr; + uno::Reference< beans::XPropertySet > xPropSet; + + try + { + xCfgMgr.set( rEvent.Source, uno::UNO_QUERY ); + xPropSet.set( xCfgMgr->getSettings( rEvent.ResourceURL, false ), uno::UNO_QUERY ); + + if ( xPropSet.is() ) + xPropSet->getPropertyValue("UIName") >>= aUIName; + } + catch (const container::NoSuchElementException&) + { + } + catch (const beans::UnknownPropertyException&) + { + } + catch (const lang::WrappedTargetException&) + { + } + + { + SolarMutexGuard aGuard; + vcl::Window* pWindow = getWindowFromXUIElement( xUIElement ); + if ( pWindow ) + pWindow->SetText( aUIName ); + } + + showToolbar( rEvent.ResourceURL ); + } + } + } +} + +void SAL_CALL ToolbarLayoutManager::elementRemoved( const ui::ConfigurationEvent& rEvent ) +{ + SolarMutexClearableGuard aReadLock; + uno::Reference< awt::XWindow > xContainerWindow = m_xContainerWindow; + uno::Reference< ui::XUIConfigurationManager > xModuleCfgMgr( m_xModuleCfgMgr ); + uno::Reference< ui::XUIConfigurationManager > xDocCfgMgr( m_xDocCfgMgr ); + aReadLock.clear(); + + UIElement aUIElement = implts_findToolbar( rEvent.ResourceURL ); + uno::Reference< ui::XUIElementSettings > xElementSettings( aUIElement.m_xUIElement, uno::UNO_QUERY ); + if ( !xElementSettings.is() ) + return; + + bool bNoSettings( false ); + OUString aConfigSourcePropName( "ConfigurationSource" ); + uno::Reference< uno::XInterface > xElementCfgMgr; + uno::Reference< beans::XPropertySet > xPropSet( xElementSettings, uno::UNO_QUERY ); + + if ( xPropSet.is() ) + xPropSet->getPropertyValue( aConfigSourcePropName ) >>= xElementCfgMgr; + + if ( !xElementCfgMgr.is() ) + return; + + // Check if the same UI configuration manager has changed => check further + if ( rEvent.Source == xElementCfgMgr ) + { + // Same UI configuration manager where our element has its settings + if ( rEvent.Source == uno::Reference< uno::XInterface >( xDocCfgMgr, uno::UNO_QUERY )) + { + // document settings removed + if ( xModuleCfgMgr->hasSettings( rEvent.ResourceURL )) + { + xPropSet->setPropertyValue( aConfigSourcePropName, css::uno::Any( xModuleCfgMgr )); + xElementSettings->updateSettings(); + return; + } + } + + bNoSettings = true; + } + + // No settings anymore, element must be destroyed + if ( xContainerWindow.is() && bNoSettings ) + destroyToolbar( rEvent.ResourceURL ); +} + +void SAL_CALL ToolbarLayoutManager::elementReplaced( const ui::ConfigurationEvent& rEvent ) +{ + UIElement aUIElement = implts_findToolbar( rEvent.ResourceURL ); + + uno::Reference< ui::XUIElementSettings > xElementSettings( aUIElement.m_xUIElement, uno::UNO_QUERY ); + if ( !xElementSettings.is() ) + return; + + uno::Reference< uno::XInterface > xElementCfgMgr; + uno::Reference< beans::XPropertySet > xPropSet( xElementSettings, uno::UNO_QUERY ); + + if ( xPropSet.is() ) + xPropSet->getPropertyValue( "ConfigurationSource" ) >>= xElementCfgMgr; + + if ( !xElementCfgMgr.is() ) + return; + + // Check if the same UI configuration manager has changed => update settings + if ( rEvent.Source != xElementCfgMgr ) + return; + + xElementSettings->updateSettings(); + + SolarMutexClearableGuard aWriteLock; + bool bNotify = !aUIElement.m_bFloating; + m_bLayoutDirty = bNotify; + LayoutManager* pParentLayouter( m_pParentLayouter ); + aWriteLock.clear(); + + if ( bNotify && pParentLayouter ) + pParentLayouter->requestLayout(); +} + +uno::Reference< ui::XUIElement > ToolbarLayoutManager::getToolbar( std::u16string_view aName ) +{ + return implts_findToolbar( aName ).m_xUIElement; +} + +uno::Sequence< uno::Reference< ui::XUIElement > > ToolbarLayoutManager::getToolbars() +{ + uno::Sequence< uno::Reference< ui::XUIElement > > aSeq; + + SolarMutexGuard g; + if ( !m_aUIElements.empty() ) + { + sal_uInt32 nCount(0); + for (auto const& elem : m_aUIElements) + { + if ( elem.m_xUIElement.is() ) + { + ++nCount; + aSeq.realloc( nCount ); + aSeq.getArray()[nCount-1] = elem.m_xUIElement; + } + } + } + + return aSeq; +} + +bool ToolbarLayoutManager::floatToolbar( std::u16string_view rResourceURL ) +{ + UIElement aUIElement = implts_findToolbar( rResourceURL ); + if ( !aUIElement.m_xUIElement.is() ) + return false; + + try + { + uno::Reference< awt::XDockableWindow > xDockWindow( aUIElement.m_xUIElement->getRealInterface(), uno::UNO_QUERY ); + if ( xDockWindow.is() && !xDockWindow->isFloating() ) + { + aUIElement.m_bFloating = true; + implts_writeWindowStateData( aUIElement ); + xDockWindow->setFloatingMode( true ); + + implts_setLayoutDirty(); + implts_setToolbar( aUIElement ); + return true; + } + } + catch (const lang::DisposedException&) + { + } + + return false; +} + +bool ToolbarLayoutManager::lockToolbar( std::u16string_view rResourceURL ) +{ + UIElement aUIElement = implts_findToolbar( rResourceURL ); + if ( !aUIElement.m_xUIElement.is() ) + return false; + + try + { + uno::Reference< awt::XDockableWindow > xDockWindow( aUIElement.m_xUIElement->getRealInterface(), uno::UNO_QUERY ); + if ( xDockWindow.is() && !xDockWindow->isFloating() && !xDockWindow->isLocked() ) + { + aUIElement.m_aDockedData.m_bLocked = true; + implts_writeWindowStateData( aUIElement ); + xDockWindow->lock(); + + implts_setLayoutDirty(); + implts_setToolbar( aUIElement ); + return true; + } + } + catch (const lang::DisposedException&) + { + } + + return false; +} + +bool ToolbarLayoutManager::unlockToolbar( std::u16string_view rResourceURL ) +{ + UIElement aUIElement = implts_findToolbar( rResourceURL ); + if ( !aUIElement.m_xUIElement.is() ) + return false; + + try + { + uno::Reference< awt::XDockableWindow > xDockWindow( aUIElement.m_xUIElement->getRealInterface(), uno::UNO_QUERY ); + if ( xDockWindow.is() && !xDockWindow->isFloating() && xDockWindow->isLocked() ) + { + aUIElement.m_aDockedData.m_bLocked = false; + implts_writeWindowStateData( aUIElement ); + xDockWindow->unlock(); + + implts_setLayoutDirty(); + implts_setToolbar( aUIElement ); + return true; + } + } + catch (const lang::DisposedException&) + { + } + + return false; +} + +bool ToolbarLayoutManager::isToolbarVisible( std::u16string_view rResourceURL ) +{ + uno::Reference< awt::XWindow2 > xWindow2( implts_getXWindow( rResourceURL ), uno::UNO_QUERY ); + return ( xWindow2.is() && xWindow2->isVisible() ); +} + +bool ToolbarLayoutManager::isToolbarFloating( std::u16string_view rResourceURL ) +{ + uno::Reference< awt::XDockableWindow > xDockWindow( implts_getXWindow( rResourceURL ), uno::UNO_QUERY ); + return ( xDockWindow.is() && xDockWindow->isFloating() ); +} + +bool ToolbarLayoutManager::isToolbarDocked( std::u16string_view rResourceURL ) +{ + return !isToolbarFloating( rResourceURL ); +} + +bool ToolbarLayoutManager::isToolbarLocked( std::u16string_view rResourceURL ) +{ + uno::Reference< awt::XDockableWindow > xDockWindow( implts_getXWindow( rResourceURL ), uno::UNO_QUERY ); + return ( xDockWindow.is() && xDockWindow->isLocked() ); +} + +awt::Size ToolbarLayoutManager::getToolbarSize( std::u16string_view rResourceURL ) +{ + vcl::Window* pWindow = implts_getWindow( rResourceURL ); + + SolarMutexGuard aGuard; + if ( pWindow ) + { + ::Size aSize = pWindow->GetSizePixel(); + awt::Size aWinSize; + aWinSize.Width = aSize.Width(); + aWinSize.Height = aSize.Height(); + return aWinSize; + } + + return awt::Size(); +} + +awt::Point ToolbarLayoutManager::getToolbarPos( std::u16string_view rResourceURL ) +{ + awt::Point aPos; + UIElement aUIElement = implts_findToolbar( rResourceURL ); + + uno::Reference< awt::XWindow > xWindow( implts_getXWindow( rResourceURL )); + if ( xWindow.is() ) + { + if ( aUIElement.m_bFloating ) + { + awt::Rectangle aRect = xWindow->getPosSize(); + aPos.X = aRect.X; + aPos.Y = aRect.Y; + } + else + aPos = aUIElement.m_aDockedData.m_aPos; + } + + return aPos; +} + +void ToolbarLayoutManager::setToolbarSize( std::u16string_view rResourceURL, const awt::Size& aSize ) +{ + uno::Reference< awt::XWindow2 > xWindow( implts_getXWindow( rResourceURL ), uno::UNO_QUERY ); + uno::Reference< awt::XDockableWindow > xDockWindow( xWindow, uno::UNO_QUERY ); + UIElement aUIElement = implts_findToolbar( rResourceURL ); + + if ( xWindow.is() && xDockWindow.is() && xDockWindow->isFloating() ) + { + xWindow->setOutputSize( aSize ); + aUIElement.m_aFloatingData.m_aSize = aSize; + implts_setToolbar( aUIElement ); + implts_writeWindowStateData( aUIElement ); + implts_sortUIElements(); + } +} + +void ToolbarLayoutManager::setToolbarPos( std::u16string_view rResourceURL, const awt::Point& aPos ) +{ + uno::Reference< awt::XWindow > xWindow( implts_getXWindow( rResourceURL )); + uno::Reference< awt::XDockableWindow > xDockWindow( xWindow, uno::UNO_QUERY ); + UIElement aUIElement = implts_findToolbar( rResourceURL ); + + if ( xWindow.is() && xDockWindow.is() && xDockWindow->isFloating() ) + { + xWindow->setPosSize( aPos.X, aPos.Y, 0, 0, awt::PosSize::POS ); + aUIElement.m_aFloatingData.m_aPos = aPos; + implts_setToolbar( aUIElement ); + implts_writeWindowStateData( aUIElement ); + implts_sortUIElements(); + } +} + +void ToolbarLayoutManager::setToolbarPosSize( std::u16string_view rResourceURL, const awt::Point& aPos, const awt::Size& aSize ) +{ + setToolbarPos( rResourceURL, aPos ); + setToolbarSize( rResourceURL, aSize ); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/layoutmanager/toolbarlayoutmanager.hxx b/framework/source/layoutmanager/toolbarlayoutmanager.hxx new file mode 100644 index 000000000..3e7b631c6 --- /dev/null +++ b/framework/source/layoutmanager/toolbarlayoutmanager.hxx @@ -0,0 +1,283 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> +#include <vector> + +#include <uiconfiguration/globalsettings.hxx> +#include <framework/addonsoptions.hxx> +#include <uielement/uielement.hxx> +#include <services/layoutmanager.hxx> + +#include <com/sun/star/ui/XUIConfigurationManager.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/ui/XUIElementFactory.hpp> +#include <com/sun/star/ui/DockingArea.hpp> +#include <com/sun/star/awt/XWindow2.hpp> +#include <com/sun/star/awt/XDockableWindow.hpp> +#include <com/sun/star/awt/XDockableWindowListener.hpp> + +#include <cppuhelper/implbase.hxx> + +namespace framework +{ + +class ToolbarLayoutManager : public ::cppu::WeakImplHelper< css::awt::XDockableWindowListener, + css::ui::XUIConfigurationListener, + css::awt::XWindowListener > +{ + public: + enum { DOCKINGAREAS_COUNT = 4 }; + + enum PreviewFrameDetection + { + PREVIEWFRAME_UNKNOWN, + PREVIEWFRAME_NO, + PREVIEWFRAME_YES + }; + + ToolbarLayoutManager( css::uno::Reference< css::uno::XComponentContext > xContext, + css::uno::Reference< css::ui::XUIElementFactory > xUIElementFactory, + LayoutManager* pParentLayouter ); + virtual ~ToolbarLayoutManager() override; + + void reset(); + void attach( const css::uno::Reference< css::frame::XFrame >& xFrame, + const css::uno::Reference< css::ui::XUIConfigurationManager >& xModuleCfgMgr, + const css::uno::Reference< css::ui::XUIConfigurationManager >& xDocCfgMgr, + const css::uno::Reference< css::container::XNameAccess >& xPersistentWindowState ); + + void setParentWindow( const css::uno::Reference< css::awt::XWindowPeer >& xParentWindow ); + void setDockingAreaOffsets(const ::tools::Rectangle& rOffsets); + + void resetDockingArea(); + + css::awt::Rectangle getDockingArea(); + void setDockingArea( const css::awt::Rectangle& rDockingArea ); + + bool isPreviewFrame(); + + // layouting + bool isLayoutDirty() const { return m_bLayoutDirty;} + void doLayout(const ::Size& aContainerSize); + + // creation/destruction + void createStaticToolbars(); + void destroyToolbars(); + + bool requestToolbar( const OUString& rResourceURL ); + bool createToolbar( const OUString& rResourceURL ); + bool destroyToolbar( std::u16string_view rResourceURL ); + + // visibility + bool showToolbar( std::u16string_view rResourceURL ); + bool hideToolbar( std::u16string_view rResourceURL ); + + void refreshToolbarsVisibility( bool bAutomaticToolbars ); + void setFloatingToolbarsVisibility( bool bVisible ); + void setVisible(bool bVisible); + + // docking and further functions + bool dockToolbar( std::u16string_view rResourceURL, css::ui::DockingArea eDockingArea, const css::awt::Point& aPos ); + bool dockAllToolbars(); + bool floatToolbar( std::u16string_view rResourceURL ); + bool lockToolbar( std::u16string_view rResourceURL ); + bool unlockToolbar( std::u16string_view rResourceURL ); + void setToolbarPos( std::u16string_view rResourceURL, const css::awt::Point& aPos ); + void setToolbarSize( std::u16string_view rResourceURL, const css::awt::Size& aSize ); + void setToolbarPosSize( std::u16string_view rResourceURL, const css::awt::Point& aPos, const css::awt::Size& aSize ); + bool isToolbarVisible( std::u16string_view rResourceURL ); + bool isToolbarFloating( std::u16string_view rResourceURL ); + bool isToolbarDocked( std::u16string_view rResourceURL ); + bool isToolbarLocked( std::u16string_view rResourceURL ); + css::awt::Point getToolbarPos( std::u16string_view rResourceURL ); + css::awt::Size getToolbarSize( std::u16string_view rResourceURL ); + css::uno::Reference< css::ui::XUIElement > getToolbar( std::u16string_view aName ); + css::uno::Sequence< css::uno::Reference< css::ui::XUIElement > > getToolbars(); + + // child window notifications + void childWindowEvent( VclSimpleEvent const * pEvent ); + + // XInterface + + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& aEvent ) override; + + // XWindowListener + virtual void SAL_CALL windowResized( const css::awt::WindowEvent& aEvent ) override; + virtual void SAL_CALL windowMoved( const css::awt::WindowEvent& aEvent ) override; + virtual void SAL_CALL windowShown( const css::lang::EventObject& aEvent ) override; + virtual void SAL_CALL windowHidden( const css::lang::EventObject& aEvent ) override; + + // XDockableWindowListener + virtual void SAL_CALL startDocking( const css::awt::DockingEvent& e ) override; + virtual css::awt::DockingData SAL_CALL docking( const css::awt::DockingEvent& e ) override; + virtual void SAL_CALL endDocking( const css::awt::EndDockingEvent& e ) override; + virtual sal_Bool SAL_CALL prepareToggleFloatingMode( const css::lang::EventObject& e ) override; + virtual void SAL_CALL toggleFloatingMode( const css::lang::EventObject& e ) override; + virtual void SAL_CALL closed( const css::lang::EventObject& e ) override; + virtual void SAL_CALL endPopupMode( const css::awt::EndPopupModeEvent& e ) override; + + // XUIConfigurationListener + virtual void SAL_CALL elementInserted( const css::ui::ConfigurationEvent& Event ) override; + virtual void SAL_CALL elementRemoved( const css::ui::ConfigurationEvent& Event ) override; + virtual void SAL_CALL elementReplaced( const css::ui::ConfigurationEvent& Event ) override; + + private: + enum DockingOperation + { + DOCKOP_BEFORE_COLROW, + DOCKOP_ON_COLROW, + DOCKOP_AFTER_COLROW + }; + + typedef std::vector< UIElement > UIElementVector; + struct SingleRowColumnWindowData + { + SingleRowColumnWindowData() + : nVarSize(0) + , nStaticSize(0) + , nSpace(0) + , nRowColumn(0) + {} + + std::vector< OUString > aUIElementNames; + std::vector< css::uno::Reference< css::awt::XWindow > > aRowColumnWindows; + std::vector< css::awt::Rectangle > aRowColumnWindowSizes; + std::vector< sal_Int32 > aRowColumnSpace; + css::awt::Rectangle aRowColumnRect; + sal_Int32 nVarSize; + sal_Int32 nStaticSize; + sal_Int32 nSpace; + sal_Int32 nRowColumn; + }; + + // internal helper methods + + bool implts_isParentWindowVisible() const; + ::tools::Rectangle implts_calcDockingArea(); + void implts_sortUIElements(); + void implts_reparentToolbars(); + OUString implts_generateGenericAddonToolbarTitle( sal_Int32 nNumber ) const; + void implts_setElementData( UIElement& rUIElement, const css::uno::Reference< css::awt::XDockableWindow >& rDockWindow ); + void implts_destroyDockingAreaWindows(); + + // layout methods + + void implts_setDockingAreaWindowSizes( const css::awt::Rectangle& rBorderSpace ); + css::awt::Point implts_findNextCascadeFloatingPos(); + void implts_renumberRowColumnData( css::ui::DockingArea eDockingArea, const UIElement& rUIElement ); + void implts_calcWindowPosSizeOnSingleRowColumn( sal_Int32 nDockingArea, + sal_Int32 nOffset, + SingleRowColumnWindowData& rRowColumnWindowData, + const ::Size& rContainerSize ); + void implts_setLayoutDirty(); + void implts_setLayoutInProgress( bool bInProgress = true ); + + // lookup/container methods + + UIElement implts_findToolbar( std::u16string_view aName ); + UIElement implts_findToolbar( const css::uno::Reference< css::uno::XInterface >& xToolbar ); + UIElement& impl_findToolbar( std::u16string_view aName ); + css::uno::Reference< css::awt::XWindow > implts_getXWindow( std::u16string_view aName ); + vcl::Window* implts_getWindow( std::u16string_view aName ); + bool implts_insertToolbar( const UIElement& rUIElement ); + void implts_setToolbar( const UIElement& rUIElement ); + ::Size implts_getTopBottomDockingAreaSizes(); + void implts_getUIElementVectorCopy( UIElementVector& rCopy ); + + // internal docking methods + + ::tools::Rectangle implts_calcHotZoneRect( const ::tools::Rectangle& rRect, sal_Int32 nHotZoneOffset ); + void implts_calcDockingPosSize( UIElement& aUIElement, DockingOperation& eDockOperation, ::tools::Rectangle& rTrackingRect, const Point& rMousePos ); + DockingOperation implts_determineDockingOperation( css::ui::DockingArea DockingArea, const ::tools::Rectangle& rRowColRect, const Point& rMousePos ); + ::tools::Rectangle implts_getWindowRectFromRowColumn( css::ui::DockingArea DockingArea, const SingleRowColumnWindowData& rRowColumnWindowData, const ::Point& rMousePos, std::u16string_view rExcludeElementName ); + ::tools::Rectangle implts_determineFrontDockingRect( css::ui::DockingArea eDockingArea, + sal_Int32 nRowCol, + const ::tools::Rectangle& rDockedElementRect, + std::u16string_view rMovedElementName, + const ::tools::Rectangle& rMovedElementRect ); + ::tools::Rectangle implts_calcTrackingAndElementRect( css::ui::DockingArea eDockingArea, + sal_Int32 nRowCol, + UIElement& rUIElement, + const ::tools::Rectangle& rTrackingRect, + const ::tools::Rectangle& rRowColumnRect, + const ::Size& rContainerWinSize ); + + void implts_getDockingAreaElementInfos( css::ui::DockingArea DockingArea, std::vector< SingleRowColumnWindowData >& rRowColumnsWindowData ); + void implts_getDockingAreaElementInfoOnSingleRowCol( css::ui::DockingArea, sal_Int32 nRowCol, SingleRowColumnWindowData& rRowColumnWindowData ); + void implts_findNextDockingPos( css::ui::DockingArea DockingArea, const ::Size& aUIElementSize, css::awt::Point& rVirtualPos, ::Point& rPixelPos ); + void implts_setTrackingRect( css::ui::DockingArea eDockingArea, const ::Point& rMousePos, ::tools::Rectangle& rTrackingRect ); + + // creation methods + + void implts_createAddonsToolBars(); + void implts_createCustomToolBars(); + void implts_createNonContextSensitiveToolBars(); + void implts_createCustomToolBars( const css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > >& aCustomTbxSeq ); + void implts_createCustomToolBar( const OUString& aTbxResName, const OUString& aTitle ); + void implts_setToolbarCreation( bool bStart = true ); + bool implts_isToolbarCreationActive(); + + // persistence methods + + bool implts_readWindowStateData( const OUString& aName, UIElement& rElementData ); + void implts_writeWindowStateData( const UIElement& rElementData ); + + // members + + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::frame::XFrame > m_xFrame; + css::uno::Reference< css::awt::XWindow2 > m_xContainerWindow; + css::uno::Reference< css::awt::XWindow > m_xDockAreaWindows[DOCKINGAREAS_COUNT]; + css::uno::Reference< css::ui::XUIElementFactory > m_xUIElementFactoryManager; + css::uno::Reference< css::ui::XUIConfigurationManager > m_xModuleCfgMgr; + css::uno::Reference< css::ui::XUIConfigurationManager > m_xDocCfgMgr; + css::uno::Reference< css::container::XNameAccess > m_xPersistentWindowState; + LayoutManager* m_pParentLayouter; + + UIElementVector m_aUIElements; + UIElement m_aDockUIElement; + tools::Rectangle m_aDockingArea; + tools::Rectangle m_aDockingAreaOffsets; + DockingOperation m_eDockOperation; + PreviewFrameDetection m_ePreviewDetection; + + std::unique_ptr<AddonsOptions> m_pAddonOptions; + std::unique_ptr<GlobalSettings> m_pGlobalSettings; + + bool m_bComponentAttached; + bool m_bLayoutDirty; + bool m_bGlobalSettings; + bool m_bDockingInProgress; + bool m_bLayoutInProgress; + bool m_bToolbarCreation; +}; + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/layoutmanager/uielement.cxx b/framework/source/layoutmanager/uielement.cxx new file mode 100644 index 000000000..582187515 --- /dev/null +++ b/framework/source/layoutmanager/uielement.cxx @@ -0,0 +1,99 @@ +/* -*- 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 <uielement/uielement.hxx> + +#include <com/sun/star/ui/DockingArea.hpp> + +using namespace ::com::sun::star; + +namespace framework +{ + + bool UIElement::operator< ( const ::framework::UIElement& aUIElement ) const +{ + if ( !m_xUIElement.is() && aUIElement.m_xUIElement.is() ) + return false; + else if ( m_xUIElement.is() && !aUIElement.m_xUIElement.is() ) + return true; + else if ( !m_bVisible && aUIElement.m_bVisible ) + return false; + else if ( m_bVisible && !aUIElement.m_bVisible ) + return true; + else if ( !m_bFloating && aUIElement.m_bFloating ) + return true; + else if ( m_bFloating && !aUIElement.m_bFloating ) + return false; + else + { + if ( m_bFloating ) + { + bool bEqual = ( m_aFloatingData.m_aPos.Y == aUIElement.m_aFloatingData.m_aPos.Y ); + if ( bEqual ) + return ( m_aFloatingData.m_aPos.X < aUIElement.m_aFloatingData.m_aPos.X ); + else + return ( m_aFloatingData.m_aPos.Y < aUIElement.m_aFloatingData.m_aPos.Y ); + } + else + { + if ( m_aDockedData.m_nDockedArea < aUIElement.m_aDockedData.m_nDockedArea ) + return true; + else if ( m_aDockedData.m_nDockedArea > aUIElement.m_aDockedData.m_nDockedArea ) + return false; + else + { + if ( m_aDockedData.m_nDockedArea == ui::DockingArea_DOCKINGAREA_TOP || + m_aDockedData.m_nDockedArea == ui::DockingArea_DOCKINGAREA_BOTTOM ) + { + if ( m_aDockedData.m_aPos.Y != aUIElement.m_aDockedData.m_aPos.Y ) + return ( m_aDockedData.m_aPos.Y < aUIElement.m_aDockedData.m_aPos.Y ); + else + { + bool bEqual = ( m_aDockedData.m_aPos.X == aUIElement.m_aDockedData.m_aPos.X ); + if ( bEqual ) + { + return m_bUserActive && !aUIElement.m_bUserActive; + } + else + return ( m_aDockedData.m_aPos.X <= aUIElement.m_aDockedData.m_aPos.X ); + } + } + else + { + if ( m_aDockedData.m_aPos.X != aUIElement.m_aDockedData.m_aPos.X ) + return ( m_aDockedData.m_aPos.X < aUIElement.m_aDockedData.m_aPos.X ); + else + { + bool bEqual = ( m_aDockedData.m_aPos.Y == aUIElement.m_aDockedData.m_aPos.Y ); + if ( bEqual ) + { + return m_bUserActive && !aUIElement.m_bUserActive; + } + else + return ( m_aDockedData.m_aPos.Y <= aUIElement.m_aDockedData.m_aPos.Y ); + } + } + } + } + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/loadenv/loadenv.cxx b/framework/source/loadenv/loadenv.cxx new file mode 100644 index 000000000..76116ad98 --- /dev/null +++ b/framework/source/loadenv/loadenv.cxx @@ -0,0 +1,1808 @@ +/* -*- 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 <loadenv/loadenv.hxx> + +#include <loadenv/loadenvexception.hxx> +#include <loadenv/targethelper.hxx> +#include <framework/framelistanalyzer.hxx> + +#include <interaction/quietinteraction.hxx> +#include <properties.h> +#include <protocols.h> +#include <services.h> +#include <targets.h> +#include <comphelper/interaction.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <comphelper/propertysequence.hxx> +#include <framework/interaction.hxx> +#include <comphelper/processfactory.hxx> +#include <officecfg/Office/Common.hxx> +#include <officecfg/Setup.hxx> + +#include <com/sun/star/awt/XWindow2.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/document/MacroExecMode.hpp> +#include <com/sun/star/document/XTypeDetection.hpp> +#include <com/sun/star/document/XActionLockable.hpp> +#include <com/sun/star/document/UpdateDocMode.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/OfficeFrameLoader.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/XFrameLoader.hpp> +#include <com/sun/star/frame/XSynchronousFrameLoader.hpp> +#include <com/sun/star/frame/XNotifyingDispatch.hpp> +#include <com/sun/star/frame/FrameLoaderFactory.hpp> +#include <com/sun/star/frame/ContentHandlerFactory.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/task/ErrorCodeRequest.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/task/XStatusIndicatorFactory.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/ucb/UniversalContentBroker.hpp> +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/util/XModifiable.hpp> + +#include <utility> +#include <vcl/window.hxx> +#include <vcl/wrkwin.hxx> +#include <vcl/syswin.hxx> + +#include <toolkit/helper/vclunohelper.hxx> +#include <unotools/moduleoptions.hxx> +#include <svtools/sfxecode.hxx> +#include <unotools/ucbhelper.hxx> +#include <comphelper/configurationhelper.hxx> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> +#include <vcl/errcode.hxx> +#include <vcl/svapp.hxx> +#include <cppuhelper/implbase.hxx> +#include <comphelper/profilezone.hxx> +#include <classes/taskcreator.hxx> +#include <tools/fileutil.hxx> + +constexpr OUStringLiteral PROP_TYPES = u"Types"; +constexpr OUStringLiteral PROP_NAME = u"Name"; + +namespace framework { + +using namespace com::sun::star; + +namespace { + +class LoadEnvListener : public ::cppu::WeakImplHelper< css::frame::XLoadEventListener , + css::frame::XDispatchResultListener > +{ + private: + osl::Mutex m_mutex; + bool m_bWaitingResult; + LoadEnv* m_pLoadEnv; + + public: + + explicit LoadEnvListener(LoadEnv* pLoadEnv) + : m_bWaitingResult(true) + , m_pLoadEnv(pLoadEnv) + { + } + + // frame.XLoadEventListener + virtual void SAL_CALL loadFinished(const css::uno::Reference< css::frame::XFrameLoader >& xLoader) override; + + virtual void SAL_CALL loadCancelled(const css::uno::Reference< css::frame::XFrameLoader >& xLoader) override; + + // frame.XDispatchResultListener + virtual void SAL_CALL dispatchFinished(const css::frame::DispatchResultEvent& aEvent) override; + + // lang.XEventListener + virtual void SAL_CALL disposing(const css::lang::EventObject& aEvent) override; +}; + +} + +LoadEnv::LoadEnv(css::uno::Reference< css::uno::XComponentContext > xContext) + : m_xContext(std::move(xContext)) + , m_nSearchFlags(0) + , m_eFeature(LoadEnvFeatures::NONE) + , m_eContentType(E_UNSUPPORTED_CONTENT) + , m_bCloseFrameOnError(false) + , m_bReactivateControllerOnError(false) + , m_bLoaded( false ) +{ +} + +LoadEnv::~LoadEnv() +{ +} + +css::uno::Reference< css::lang::XComponent > LoadEnv::loadComponentFromURL(const css::uno::Reference< css::frame::XComponentLoader >& xLoader, + const css::uno::Reference< css::uno::XComponentContext >& xContext , + const OUString& sURL , + const OUString& sTarget, + sal_Int32 nSearchFlags , + const css::uno::Sequence< css::beans::PropertyValue >& lArgs ) +{ + css::uno::Reference< css::lang::XComponent > xComponent; + comphelper::ProfileZone aZone("loadComponentFromURL"); + + try + { + LoadEnv aEnv(xContext); + + LoadEnvFeatures loadEnvFeatures = LoadEnvFeatures::WorkWithUI; + // tdf#118238 Only disable UI interaction when loading as hidden + if (comphelper::NamedValueCollection::get(lArgs, u"Hidden") == uno::Any(true) || Application::IsHeadlessModeEnabled()) + loadEnvFeatures = LoadEnvFeatures::NONE; + + aEnv.startLoading(sURL, + lArgs, + css::uno::Reference< css::frame::XFrame >(xLoader, css::uno::UNO_QUERY), + sTarget, + nSearchFlags, + loadEnvFeatures); + aEnv.waitWhileLoading(); // wait for ever! + + xComponent = aEnv.getTargetComponent(); + } + catch(const LoadEnvException& ex) + { + switch(ex.m_nID) + { + case LoadEnvException::ID_INVALID_MEDIADESCRIPTOR: + throw css::lang::IllegalArgumentException( + "Optional list of arguments seem to be corrupted.", xLoader, 4); + + case LoadEnvException::ID_UNSUPPORTED_CONTENT: + throw css::lang::IllegalArgumentException( + "Unsupported URL <" + sURL + ">: \"" + ex.m_sMessage + "\"", + xLoader, 1); + + default: + SAL_WARN( + "fwk.loadenv", + "caught LoadEnvException " << +ex.m_nID << " \"" + << ex.m_sMessage << "\"" + << (ex.m_exOriginal.has<css::uno::Exception>() + ? (", " + ex.m_exOriginal.getValueTypeName() + " \"" + + (ex.m_exOriginal.get<css::uno::Exception>(). + Message) + + "\"") + : OUString()) + << " while loading <" << sURL << ">"); + xComponent.clear(); + break; + } + } + + return xComponent; +} + +namespace { + +utl::MediaDescriptor addModelArgs(const uno::Sequence<beans::PropertyValue>& rDescriptor) +{ + utl::MediaDescriptor rResult(rDescriptor); + uno::Reference<frame::XModel> xModel(rResult.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_MODEL, uno::Reference<frame::XModel>())); + + if (xModel.is()) + { + utl::MediaDescriptor aModelArgs(xModel->getArgs()); + utl::MediaDescriptor::iterator pIt = aModelArgs.find( utl::MediaDescriptor::PROP_MACROEXECUTIONMODE); + if (pIt != aModelArgs.end()) + rResult[utl::MediaDescriptor::PROP_MACROEXECUTIONMODE] = pIt->second; + } + + return rResult; +} + +} + +void LoadEnv::startLoading(const OUString& sURL, const uno::Sequence<beans::PropertyValue>& lMediaDescriptor, + const uno::Reference<frame::XFrame>& xBaseFrame, const OUString& sTarget, + sal_Int32 nSearchFlags, LoadEnvFeatures eFeature) +{ + osl::MutexGuard g(m_mutex); + + // Handle still running processes! + if (m_xAsynchronousJob.is()) + throw LoadEnvException(LoadEnvException::ID_STILL_RUNNING); + + // take over all new parameters. + m_xTargetFrame.clear(); + m_xBaseFrame = xBaseFrame; + m_lMediaDescriptor = addModelArgs(lMediaDescriptor); + m_sTarget = sTarget; + m_nSearchFlags = nSearchFlags; + m_eFeature = eFeature; + m_eContentType = E_UNSUPPORTED_CONTENT; + m_bCloseFrameOnError = false; + m_bReactivateControllerOnError = false; + m_bLoaded = false; + + OUString aRealURL; + if (!officecfg::Office::Common::Load::DetectWebDAVRedirection::get() + || !tools::IsMappedWebDAVPath(sURL, &aRealURL)) + aRealURL = sURL; + + // try to find out, if it's really a content, which can be loaded or must be "handled" + // We use a default value for this in-parameter. Then we have to start a complex check method + // internally. But if this check was already done outside it can be suppressed to perform + // the load request. We take over the result then! + m_eContentType = LoadEnv::classifyContent(aRealURL, lMediaDescriptor); + if (m_eContentType == E_UNSUPPORTED_CONTENT) + throw LoadEnvException(LoadEnvException::ID_UNSUPPORTED_CONTENT, "from LoadEnv::startLoading"); + + // make URL part of the MediaDescriptor + // It doesn't matter if it is already an item of it. + // It must be the same value... so we can overwrite it :-) + m_lMediaDescriptor[utl::MediaDescriptor::PROP_URL] <<= aRealURL; + + // parse it - because some following code require that + m_aURL.Complete = aRealURL; + uno::Reference<util::XURLTransformer> xParser(util::URLTransformer::create(m_xContext)); + xParser->parseStrict(m_aURL); + + // BTW: Split URL and JumpMark ... + // Because such mark is an explicit value of the media descriptor! + if (!m_aURL.Mark.isEmpty()) + m_lMediaDescriptor[utl::MediaDescriptor::PROP_JUMPMARK] <<= m_aURL.Mark; + + // By the way: remove the old and deprecated value "FileName" from the descriptor! + utl::MediaDescriptor::iterator pIt = m_lMediaDescriptor.find(utl::MediaDescriptor::PROP_FILENAME); + if (pIt != m_lMediaDescriptor.end()) + m_lMediaDescriptor.erase(pIt); + + // patch the MediaDescriptor, so it fulfil the outside requirements + // Means especially items like e.g. UI InteractionHandler, Status Indicator, + // MacroExecutionMode, etc. + + /*TODO progress is bound to a frame ... How can we set it here? */ + + // UI mode + const bool bUIMode = + (m_eFeature & LoadEnvFeatures::WorkWithUI) && + !m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_HIDDEN, false) && + !m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_PREVIEW, false); + + initializeUIDefaults(m_xContext, m_lMediaDescriptor, bUIMode, &m_pQuietInteraction); + + start(); +} + +void LoadEnv::initializeUIDefaults( const css::uno::Reference< css::uno::XComponentContext >& i_rxContext, + utl::MediaDescriptor& io_lMediaDescriptor, const bool i_bUIMode, + rtl::Reference<QuietInteraction>* o_ppQuietInteraction ) +{ + css::uno::Reference< css::task::XInteractionHandler > xInteractionHandler; + sal_Int16 nMacroMode; + sal_Int16 nUpdateMode; + + if ( i_bUIMode ) + { + nMacroMode = css::document::MacroExecMode::USE_CONFIG; + nUpdateMode = css::document::UpdateDocMode::ACCORDING_TO_CONFIG; + try + { + xInteractionHandler.set( css::task::InteractionHandler::createWithParent( i_rxContext, nullptr ), css::uno::UNO_QUERY_THROW ); + } + catch(const css::uno::RuntimeException&) {throw;} + catch(const css::uno::Exception& ) { } + } + // hidden mode + else + { + nMacroMode = css::document::MacroExecMode::NEVER_EXECUTE; + nUpdateMode = css::document::UpdateDocMode::NO_UPDATE; + rtl::Reference<QuietInteraction> pQuietInteraction = new QuietInteraction(); + xInteractionHandler = pQuietInteraction.get(); + if ( o_ppQuietInteraction != nullptr ) + { + *o_ppQuietInteraction = pQuietInteraction; + } + } + + if ( xInteractionHandler.is() ) + { + if( io_lMediaDescriptor.find(utl::MediaDescriptor::PROP_INTERACTIONHANDLER) == io_lMediaDescriptor.end() ) + { + io_lMediaDescriptor[utl::MediaDescriptor::PROP_INTERACTIONHANDLER] <<= xInteractionHandler; + } + if( io_lMediaDescriptor.find(utl::MediaDescriptor::PROP_AUTHENTICATIONHANDLER) == io_lMediaDescriptor.end() ) + { + io_lMediaDescriptor[utl::MediaDescriptor::PROP_AUTHENTICATIONHANDLER] <<= xInteractionHandler; + } + } + + if (io_lMediaDescriptor.find(utl::MediaDescriptor::PROP_MACROEXECUTIONMODE) == io_lMediaDescriptor.end()) + io_lMediaDescriptor[utl::MediaDescriptor::PROP_MACROEXECUTIONMODE] <<= nMacroMode; + + if (io_lMediaDescriptor.find(utl::MediaDescriptor::PROP_UPDATEDOCMODE) == io_lMediaDescriptor.end()) + io_lMediaDescriptor[utl::MediaDescriptor::PROP_UPDATEDOCMODE] <<= nUpdateMode; +} + +void LoadEnv::start() +{ + // SAFE -> + { + osl::MutexGuard aReadLock(m_mutex); + + // Handle still running processes! + if (m_xAsynchronousJob.is()) + throw LoadEnvException(LoadEnvException::ID_STILL_RUNNING); + + // content can not be loaded or handled + // check "classifyContent()" failed before ... + if (m_eContentType == E_UNSUPPORTED_CONTENT) + throw LoadEnvException(LoadEnvException::ID_UNSUPPORTED_CONTENT, + "from LoadEnv::start"); + } + // <- SAFE + + // detect its type/filter etc. + // This information will be available by the + // used descriptor member afterwards and is needed + // for all following operations! + // Note: An exception will be thrown, in case operation was not successfully ... + if (m_eContentType != E_CAN_BE_SET)/* Attention: special feature to set existing component on a frame must ignore type detection! */ + impl_detectTypeAndFilter(); + + // start loading the content... + // Attention: Don't check m_eContentType deeper then UNSUPPORTED/SUPPORTED! + // Because it was made in the easiest way... may a flat detection was made only. + // And such simple detection can fail sometimes .-) + // Use another strategy here. Try it and let it run into the case "loading not possible". + bool bStarted = false; + if ( + (m_eFeature & LoadEnvFeatures::AllowContentHandler) && + (m_eContentType != E_CAN_BE_SET ) /* Attention: special feature to set existing component on a frame must ignore type detection! */ + ) + { + bStarted = impl_handleContent(); + } + + if (!bStarted) + bStarted = impl_loadContent(); + + // not started => general error + // We can't say - what was the reason for. + if (!bStarted) + throw LoadEnvException( + LoadEnvException::ID_GENERAL_ERROR, "not started"); +} + +/*----------------------------------------------- + TODO + First draft does not implement timeout using [ms]. + Current implementation counts yield calls only ... +-----------------------------------------------*/ +bool LoadEnv::waitWhileLoading(sal_uInt32 nTimeout) +{ + // Because it's not a good idea to block the main thread + // (and we can't be sure that we are currently not used inside the + // main thread!), we can't use conditions here really. We must yield + // in an intelligent manner :-) + + sal_Int32 nTime = nTimeout; + while(!Application::IsQuit()) + { + // SAFE -> ------------------------------ + { + osl::MutexGuard aReadLock1(m_mutex); + if (!m_xAsynchronousJob.is()) + break; + } + // <- SAFE ------------------------------ + + Application::Yield(); + + // forever! + if (nTimeout==0) + continue; + + // timed out? + --nTime; + if (nTime<1) + break; + } + + osl::MutexGuard g(m_mutex); + return !m_xAsynchronousJob.is(); +} + +css::uno::Reference< css::lang::XComponent > LoadEnv::getTargetComponent() const +{ + osl::MutexGuard g(m_mutex); + + if (!m_xTargetFrame.is()) + return css::uno::Reference< css::lang::XComponent >(); + + css::uno::Reference< css::frame::XController > xController = m_xTargetFrame->getController(); + if (!xController.is()) + return m_xTargetFrame->getComponentWindow(); + + css::uno::Reference< css::frame::XModel > xModel = xController->getModel(); + if (!xModel.is()) + return xController; + + return xModel; +} + +void SAL_CALL LoadEnvListener::loadFinished(const css::uno::Reference< css::frame::XFrameLoader >&) +{ + osl::MutexGuard g(m_mutex); + if (m_bWaitingResult) + m_pLoadEnv->impl_setResult(true); + m_bWaitingResult = false; +} + +void SAL_CALL LoadEnvListener::loadCancelled(const css::uno::Reference< css::frame::XFrameLoader >&) +{ + osl::MutexGuard g(m_mutex); + if (m_bWaitingResult) + m_pLoadEnv->impl_setResult(false); + m_bWaitingResult = false; +} + +void SAL_CALL LoadEnvListener::dispatchFinished(const css::frame::DispatchResultEvent& aEvent) +{ + osl::MutexGuard g(m_mutex); + + if (!m_bWaitingResult) + return; + + switch(aEvent.State) + { + case css::frame::DispatchResultState::FAILURE : + m_pLoadEnv->impl_setResult(false); + break; + + case css::frame::DispatchResultState::SUCCESS : + m_pLoadEnv->impl_setResult(false); + break; + + case css::frame::DispatchResultState::DONTKNOW : + m_pLoadEnv->impl_setResult(false); + break; + } + m_bWaitingResult = false; +} + +void SAL_CALL LoadEnvListener::disposing(const css::lang::EventObject&) +{ + osl::MutexGuard g(m_mutex); + if (m_bWaitingResult) + m_pLoadEnv->impl_setResult(false); + m_bWaitingResult = false; +} + +void LoadEnv::impl_setResult(bool bResult) +{ + osl::MutexGuard g(m_mutex); + + m_bLoaded = bResult; + + impl_reactForLoadingState(); + + // clearing of this reference will unblock waitWhileLoading()! + // So we must be sure, that loading process was really finished. + // => do it as last operation of this method ... + m_xAsynchronousJob.clear(); +} + +/*----------------------------------------------- + TODO: Is it a good idea to change Sequence<> + parameter to stl-adapter? +-----------------------------------------------*/ +LoadEnv::EContentType LoadEnv::classifyContent(const OUString& sURL , + const css::uno::Sequence< css::beans::PropertyValue >& lMediaDescriptor) +{ + + // (i) Filter some special well known URL protocols, + // which can not be handled or loaded in general. + // Of course an empty URL must be ignored here too. + // Note: These URL schemata are fix and well known ... + // But there can be some additional ones, which was not + // defined at implementation time of this class :-( + // So we have to make sure, that the following code + // can detect such protocol schemata too :-) + + if( + (sURL.isEmpty() ) || + (ProtocolCheck::isProtocol(sURL,EProtocol::Uno )) || + (ProtocolCheck::isProtocol(sURL,EProtocol::Slot )) || + (ProtocolCheck::isProtocol(sURL,EProtocol::Macro )) || + (ProtocolCheck::isProtocol(sURL,EProtocol::Service)) || + (ProtocolCheck::isProtocol(sURL,EProtocol::MailTo )) || + (ProtocolCheck::isProtocol(sURL,EProtocol::News )) + ) + { + return E_UNSUPPORTED_CONTENT; + } + + // (ii) Some special URLs indicates a given input stream, + // a full featured document model directly or + // specify a request for opening an empty document. + // Such contents are loadable in general. + // But we have to check, if the media descriptor contains + // all needed resources. If they are missing - the following + // load request will fail. + + /* Attention: The following code can't work on such special URLs! + It should not break the office... but it makes no sense + to start expensive object creations and complex search + algorithm if it's clear, that such URLs must be handled + in a special way .-) + */ + + // creation of new documents + if (ProtocolCheck::isProtocol(sURL,EProtocol::PrivateFactory)) + return E_CAN_BE_LOADED; + + // using of an existing input stream + utl::MediaDescriptor stlMediaDescriptor(lMediaDescriptor); + utl::MediaDescriptor::const_iterator pIt; + if (ProtocolCheck::isProtocol(sURL,EProtocol::PrivateStream)) + { + pIt = stlMediaDescriptor.find(utl::MediaDescriptor::PROP_INPUTSTREAM); + css::uno::Reference< css::io::XInputStream > xStream; + if (pIt != stlMediaDescriptor.end()) + pIt->second >>= xStream; + if (xStream.is()) + return E_CAN_BE_LOADED; + SAL_INFO("fwk.loadenv", "LoadEnv::classifyContent(): loading from stream with right URL but invalid stream detected"); + return E_UNSUPPORTED_CONTENT; + } + + // using of a full featured document + if (ProtocolCheck::isProtocol(sURL,EProtocol::PrivateObject)) + { + pIt = stlMediaDescriptor.find(utl::MediaDescriptor::PROP_MODEL); + css::uno::Reference< css::frame::XModel > xModel; + if (pIt != stlMediaDescriptor.end()) + pIt->second >>= xModel; + if (xModel.is()) + return E_CAN_BE_SET; + SAL_INFO("fwk.loadenv", "LoadEnv::classifyContent(): loading with object with right URL but invalid object detected"); + return E_UNSUPPORTED_CONTENT; + } + + // following operations can work on an internal type name only :-( + css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + css::uno::Reference< css::document::XTypeDetection > xDetect( + xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.document.TypeDetection", xContext), + css::uno::UNO_QUERY_THROW); + + OUString sType = xDetect->queryTypeByURL(sURL); + + css::uno::Reference< css::frame::XLoaderFactory > xLoaderFactory; + css::uno::Reference< css::container::XEnumeration > xSet; + + // (iii) If a FrameLoader service (or at least + // a Filter) can be found, which supports + // this URL - it must be a loadable content. + // Because both items are registered for types + // it's enough to check for frame loaders only. + // Most of our filters are handled by our global + // default loader. But there exist some specialized + // loader, which does not work on top of filters! + // So it's not enough to search on the filter configuration. + // Further it's not enough to search for types! + // Because there exist some types, which are referenced by + // other objects... but neither by filters nor frame loaders! + css::uno::Sequence< OUString > lTypesReg { sType }; + css::uno::Sequence< css::beans::NamedValue > lQuery + { + css::beans::NamedValue(PROP_TYPES, css::uno::Any(lTypesReg)) + }; + + xLoaderFactory = css::frame::FrameLoaderFactory::create(xContext); + xSet = xLoaderFactory->createSubSetEnumerationByProperties(lQuery); + // at least one registered frame loader is enough! + if (xSet->hasMoreElements()) + return E_CAN_BE_LOADED; + + // (iv) Some URL protocols are supported by special services. + // E.g. ContentHandler. + // Such contents can be handled ... but not loaded. + + xLoaderFactory = css::frame::ContentHandlerFactory::create(xContext); + xSet = xLoaderFactory->createSubSetEnumerationByProperties(lQuery); + // at least one registered content handler is enough! + if (xSet->hasMoreElements()) + return E_CAN_BE_HANDLED; + + // (v) Last but not least the UCB is used inside office to + // load contents. He has a special configuration to know + // which URL schemata can be used inside office. + css::uno::Reference< css::ucb::XUniversalContentBroker > xUCB(css::ucb::UniversalContentBroker::create(xContext)); + if (xUCB->queryContentProvider(sURL).is()) + return E_CAN_BE_LOADED; + + // (TODO) At this point, we have no idea .-) + // But it seems to be better, to break all + // further requests for this URL. Otherwise + // we can run into some trouble. + return E_UNSUPPORTED_CONTENT; +} + +namespace { + +bool queryOrcusTypeAndFilter(const uno::Sequence<beans::PropertyValue>& rDescriptor, OUString& rType, OUString& rFilter) +{ + OUString aURL; + sal_Int32 nSize = rDescriptor.getLength(); + for (sal_Int32 i = 0; i < nSize; ++i) + { + const beans::PropertyValue& rProp = rDescriptor[i]; + if (rProp.Name == "URL") + { + rProp.Value >>= aURL; + break; + } + } + + if (aURL.isEmpty() || o3tl::equalsIgnoreAsciiCase(aURL.subView(0,8), u"private:")) + return false; + + // TODO : Type must be set to be generic_Text (or any other type that + // exists) in order to find a usable loader. Exploit it as a temporary + // hack. + + // depending on the experimental mode + if (!officecfg::Office::Common::Misc::ExperimentalMode::get()) + { + return false; + } + + OUString aUseOrcus; + rtl::Bootstrap::get("LIBO_USE_ORCUS", aUseOrcus); + bool bUseOrcus = (aUseOrcus == "YES"); + + if (!bUseOrcus) + return false; + + if (aURL.endsWith(".xlsx")) + { + rType = "generic_Text"; + rFilter = "xlsx"; + return true; + } + else if (aURL.endsWith(".ods")) + { + rType = "generic_Text"; + rFilter = "ods"; + return true; + } + else if (aURL.endsWith(".csv")) + { + rType = "generic_Text"; + rFilter = "csv"; + return true; + } + + return false; +} + +} + +void LoadEnv::impl_detectTypeAndFilter() +{ + static const sal_Int32 FILTERFLAG_TEMPLATEPATH = 16; + + // SAFE -> + osl::ClearableMutexGuard aReadLock(m_mutex); + + // Attention: Because our stl media descriptor is a copy of a uno sequence + // we can't use as an in/out parameter here. Copy it before and don't forget to + // update structure afterwards again! + css::uno::Sequence< css::beans::PropertyValue > lDescriptor = m_lMediaDescriptor.getAsConstPropertyValueList(); + css::uno::Reference< css::uno::XComponentContext > xContext = m_xContext; + + aReadLock.clear(); + // <- SAFE + + OUString sType, sFilter; + + if (queryOrcusTypeAndFilter(lDescriptor, sType, sFilter) && !sType.isEmpty() && !sFilter.isEmpty()) + { + // SAFE -> + osl::MutexGuard aWriteLock(m_mutex); + + // Orcus type detected. Skip the normal type detection process. + m_lMediaDescriptor << lDescriptor; + m_lMediaDescriptor[utl::MediaDescriptor::PROP_TYPENAME] <<= sType; + m_lMediaDescriptor[utl::MediaDescriptor::PROP_FILTERNAME] <<= sFilter; + m_lMediaDescriptor[utl::MediaDescriptor::PROP_FILTERPROVIDER] <<= OUString("orcus"); + m_lMediaDescriptor[utl::MediaDescriptor::PROP_DOCUMENTSERVICE] <<= OUString("com.sun.star.sheet.SpreadsheetDocument"); + return; + // <- SAFE + } + + css::uno::Reference< css::document::XTypeDetection > xDetect( + xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.document.TypeDetection", xContext), + css::uno::UNO_QUERY_THROW); + sType = xDetect->queryTypeByDescriptor(lDescriptor, true); /*TODO should deep detection be able for enable/disable it from outside? */ + + // no valid content -> loading not possible + if (sType.isEmpty()) + throw LoadEnvException( + LoadEnvException::ID_UNSUPPORTED_CONTENT, "type detection failed"); + + // SAFE -> + osl::ResettableMutexGuard aWriteLock(m_mutex); + + // detection was successful => update the descriptor member of this class + m_lMediaDescriptor << lDescriptor; + m_lMediaDescriptor[utl::MediaDescriptor::PROP_TYPENAME] <<= sType; + // Is there an already detected (may be preselected) filter? + // see below ... + sFilter = m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_FILTERNAME, OUString()); + + aWriteLock.clear(); + // <- SAFE + + // We do have potentially correct type, but the detection process was aborted. + if (m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_ABORTED, false)) + throw LoadEnvException( + LoadEnvException::ID_UNSUPPORTED_CONTENT, "type detection aborted"); + + // But the type isn't enough. For loading sometimes we need more information. + // E.g. for our "_default" feature, where we recycle any frame which contains + // and "Untitled" document, we must know if the new document is based on a template! + // But this information is available as a filter property only. + // => We must try(!) to detect the right filter for this load request. + // On the other side ... if no filter is available .. ignore it. + // Then the type information must be enough. + if (sFilter.isEmpty()) + { + // no -> try to find a preferred filter for the detected type. + // Don't forget to update the media descriptor. + css::uno::Reference< css::container::XNameAccess > xTypeCont(xDetect, css::uno::UNO_QUERY_THROW); + try + { + ::comphelper::SequenceAsHashMap lTypeProps(xTypeCont->getByName(sType)); + sFilter = lTypeProps.getUnpackedValueOrDefault("PreferredFilter", OUString()); + if (!sFilter.isEmpty()) + { + // SAFE -> + aWriteLock.reset(); + m_lMediaDescriptor[utl::MediaDescriptor::PROP_FILTERNAME] <<= sFilter; + aWriteLock.clear(); + // <- SAFE + } + } + catch(const css::container::NoSuchElementException&) + {} + } + + // check if the filter (if one exists) points to a template format filter. + // Then we have to add the property "AsTemplate". + // We need this information to decide afterwards if we can use a "recycle frame" + // for target "_default" or has to create a new one every time. + // On the other side we have to suppress that, if this property already exists + // and should trigger a special handling. Then the outside call of this method here, + // has to know, what he is doing .-) + + bool bIsOwnTemplate = false; + if (!sFilter.isEmpty()) + { + css::uno::Reference< css::container::XNameAccess > xFilterCont(xContext->getServiceManager()->createInstanceWithContext(SERVICENAME_FILTERFACTORY, xContext), css::uno::UNO_QUERY_THROW); + try + { + ::comphelper::SequenceAsHashMap lFilterProps(xFilterCont->getByName(sFilter)); + sal_Int32 nFlags = lFilterProps.getUnpackedValueOrDefault("Flags", sal_Int32(0)); + bIsOwnTemplate = ((nFlags & FILTERFLAG_TEMPLATEPATH) == FILTERFLAG_TEMPLATEPATH); + } + catch(const css::container::NoSuchElementException&) + {} + } + if (bIsOwnTemplate) + { + // SAFE -> + aWriteLock.reset(); + // Don't overwrite external decisions! See comments before ... + utl::MediaDescriptor::const_iterator pAsTemplateItem = m_lMediaDescriptor.find(utl::MediaDescriptor::PROP_ASTEMPLATE); + if (pAsTemplateItem == m_lMediaDescriptor.end()) + m_lMediaDescriptor[utl::MediaDescriptor::PROP_ASTEMPLATE] <<= true; + aWriteLock.clear(); + // <- SAFE + } +} + +bool LoadEnv::impl_handleContent() +{ + // SAFE -> ----------------------------------- + osl::ClearableMutexGuard aReadLock(m_mutex); + + // the type must exist inside the descriptor ... otherwise this class is implemented wrong :-) + OUString sType = m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_TYPENAME, OUString()); + if (sType.isEmpty()) + throw LoadEnvException(LoadEnvException::ID_INVALID_MEDIADESCRIPTOR); + + // convert media descriptor and URL to right format for later interface call! + css::uno::Sequence< css::beans::PropertyValue > lDescriptor; + m_lMediaDescriptor >> lDescriptor; + css::util::URL aURL = m_aURL; + + // get necessary container to query for a handler object + css::uno::Reference< css::frame::XLoaderFactory > xLoaderFactory = css::frame::ContentHandlerFactory::create(m_xContext); + + aReadLock.clear(); + // <- SAFE ----------------------------------- + + // query + css::uno::Sequence< OUString > lTypeReg { sType }; + + css::uno::Sequence< css::beans::NamedValue > lQuery { { PROP_TYPES, css::uno::Any(lTypeReg) } }; + + css::uno::Reference< css::container::XEnumeration > xSet = xLoaderFactory->createSubSetEnumerationByProperties(lQuery); + while(xSet->hasMoreElements()) + { + ::comphelper::SequenceAsHashMap lProps (xSet->nextElement()); + OUString sHandler = lProps.getUnpackedValueOrDefault(PROP_NAME, OUString()); + + css::uno::Reference< css::frame::XNotifyingDispatch > xHandler; + try + { + xHandler.set(xLoaderFactory->createInstance(sHandler), css::uno::UNO_QUERY); + if (!xHandler.is()) + continue; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { continue; } + + // SAFE -> ----------------------------------- + osl::ClearableMutexGuard aWriteLock(m_mutex); + m_xAsynchronousJob = xHandler; + rtl::Reference<LoadEnvListener> xListener = new LoadEnvListener(this); + aWriteLock.clear(); + // <- SAFE ----------------------------------- + + xHandler->dispatchWithNotification(aURL, lDescriptor, xListener); + + return true; + } + + return false; +} + +bool LoadEnv::impl_furtherDocsAllowed() +{ + // SAFE -> + osl::ResettableMutexGuard aReadLock(m_mutex); + css::uno::Reference< css::uno::XComponentContext > xContext = m_xContext; + aReadLock.clear(); + // <- SAFE + + bool bAllowed = true; + + try + { + std::optional<sal_Int32> x(officecfg::Office::Common::Misc::MaxOpenDocuments::get()); + + // NIL means: count of allowed documents = infinite ! + // => return true + if ( !x) + bAllowed = true; + else + { + sal_Int32 nMaxOpenDocuments(*x); + + css::uno::Reference< css::frame::XFramesSupplier > xDesktop( + css::frame::Desktop::create(xContext), + css::uno::UNO_QUERY_THROW); + + FrameListAnalyzer aAnalyzer(xDesktop, + css::uno::Reference< css::frame::XFrame >(), + FrameAnalyzerFlags::Help | + FrameAnalyzerFlags::BackingComponent | + FrameAnalyzerFlags::Hidden); + + sal_Int32 nOpenDocuments = aAnalyzer.m_lOtherVisibleFrames.size(); + bAllowed = (nOpenDocuments < nMaxOpenDocuments); + } + } + catch(const css::uno::Exception&) + { bAllowed = true; } // !! internal errors are no reason to disturb the office from opening documents .-) + + if ( ! bAllowed ) + { + // SAFE -> + aReadLock.reset(); + css::uno::Reference< css::task::XInteractionHandler > xInteraction = m_lMediaDescriptor.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_INTERACTIONHANDLER, + css::uno::Reference< css::task::XInteractionHandler >()); + aReadLock.clear(); + // <- SAFE + + if (xInteraction.is()) + { + css::uno::Any aInteraction; + + rtl::Reference<comphelper::OInteractionAbort> pAbort = new comphelper::OInteractionAbort(); + rtl::Reference<comphelper::OInteractionApprove> pApprove = new comphelper::OInteractionApprove(); + + css::uno::Sequence< css::uno::Reference< css::task::XInteractionContinuation > > lContinuations{ + pAbort, pApprove + }; + + css::task::ErrorCodeRequest aErrorCode; + aErrorCode.ErrCode = sal_uInt32(ERRCODE_SFX_NOMOREDOCUMENTSALLOWED); + aInteraction <<= aErrorCode; + xInteraction->handle( InteractionRequest::CreateRequest(aInteraction, lContinuations) ); + } + } + + return bAllowed; +} + +bool LoadEnv::impl_filterHasInteractiveDialog() const +{ + //show the frame now so it can be the parent for any message dialogs shown during import + + //unless (tdf#114648) an Interactive case such as the new database wizard + if (m_aURL.Arguments == "Interactive") + return true; + + // unless (tdf#116277) it's the labels/business cards slave frame + if (m_aURL.Arguments.indexOf("slot=") != -1) + return true; + + OUString sFilter = m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_FILTERNAME, OUString()); + if (sFilter.isEmpty()) + return false; + + // unless (tdf#115683) the filter has a UIComponent + OUString sUIComponent; + css::uno::Reference<css::container::XNameAccess> xFilterCont(m_xContext->getServiceManager()->createInstanceWithContext(SERVICENAME_FILTERFACTORY, m_xContext), + css::uno::UNO_QUERY_THROW); + try + { + ::comphelper::SequenceAsHashMap lFilterProps(xFilterCont->getByName(sFilter)); + sUIComponent = lFilterProps.getUnpackedValueOrDefault("UIComponent", OUString()); + } + catch(const css::container::NoSuchElementException&) + { + } + + return !sUIComponent.isEmpty(); +} + +bool LoadEnv::impl_loadContent() +{ + // SAFE -> ----------------------------------- + osl::ClearableMutexGuard aWriteLock(m_mutex); + + // search or create right target frame + OUString sTarget = m_sTarget; + if (TargetHelper::matchSpecialTarget(sTarget, TargetHelper::ESpecialTarget::Default)) + { + m_xTargetFrame = impl_searchAlreadyLoaded(); + if (m_xTargetFrame.is()) + { + impl_setResult(true); + return true; + } + m_xTargetFrame = impl_searchRecycleTarget(); + } + + if (! m_xTargetFrame.is()) + { + if ( + (TargetHelper::matchSpecialTarget(sTarget, TargetHelper::ESpecialTarget::Blank )) || + (TargetHelper::matchSpecialTarget(sTarget, TargetHelper::ESpecialTarget::Default)) + ) + { + if (! impl_furtherDocsAllowed()) + return false; + TaskCreator aCreator(m_xContext); + m_xTargetFrame = aCreator.createTask(SPECIALTARGET_BLANK, m_lMediaDescriptor); + m_bCloseFrameOnError = m_xTargetFrame.is(); + } + else + { + sal_Int32 nSearchFlags = m_nSearchFlags & ~css::frame::FrameSearchFlag::CREATE; + m_xTargetFrame = m_xBaseFrame->findFrame(sTarget, nSearchFlags); + if (! m_xTargetFrame.is()) + { + if (! impl_furtherDocsAllowed()) + return false; + m_xTargetFrame = m_xBaseFrame->findFrame(SPECIALTARGET_BLANK, 0); + m_bCloseFrameOnError = m_xTargetFrame.is(); + } + } + } + + // If we couldn't find a valid frame or the frame has no container window + // we have to throw an exception. + if ( + ( ! m_xTargetFrame.is() ) || + ( ! m_xTargetFrame->getContainerWindow().is() ) + ) + throw LoadEnvException(LoadEnvException::ID_NO_TARGET_FOUND); + + css::uno::Reference< css::frame::XFrame > xTargetFrame = m_xTargetFrame; + + // Now we have a valid frame ... and type detection was already done. + // We should apply the module dependent window position and size to the + // frame window. + impl_applyPersistentWindowState(xTargetFrame->getContainerWindow()); + + // Don't forget to lock task for following load process. Otherwise it could die + // during this operation runs by terminating the office or closing this task via api. + // If we set this lock "close()" will return false and closing will be broken. + // Attention: Don't forget to reset this lock again after finishing operation. + // Otherwise task AND office couldn't die!!! + // This includes gracefully handling of Exceptions (Runtime!) too ... + // That's why we use a specialized guard, which will reset the lock + // if it will be run out of scope. + + // Note further: ignore if this internal guard already contains a resource. + // Might impl_searchRecycleTarget() set it before. But in case this impl-method wasn't used + // and the target frame was new created ... this lock here must be set! + css::uno::Reference< css::document::XActionLockable > xTargetLock(xTargetFrame, css::uno::UNO_QUERY); + m_aTargetLock.setResource(xTargetLock); + + // Add status indicator to descriptor. Loader can show a progress then. + // But don't do it, if loading should be hidden or preview is used...! + // So we prevent our code against wrong using. Why? + // It could be, that using of this progress could make trouble. e.g. He makes window visible... + // but shouldn't do that. But if no indicator is available... nobody has a chance to do that! + bool bHidden = m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_HIDDEN, false); + bool bMinimized = m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_MINIMIZED, false); + bool bPreview = m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_PREVIEW, false); + + if (!bHidden && !bMinimized && !bPreview) + { + css::uno::Reference<css::task::XStatusIndicator> xProgress = m_lMediaDescriptor.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_STATUSINDICATOR, css::uno::Reference<css::task::XStatusIndicator>()); + if (!xProgress.is()) + { + // Note: it's an optional interface! + css::uno::Reference< css::task::XStatusIndicatorFactory > xProgressFactory(xTargetFrame, css::uno::UNO_QUERY); + if (xProgressFactory.is()) + { + xProgress = xProgressFactory->createStatusIndicator(); + if (xProgress.is()) + m_lMediaDescriptor[utl::MediaDescriptor::PROP_STATUSINDICATOR] <<= xProgress; + } + } + + // Now that we have a target window into which we can load, reinit the interaction handler to have this + // window as its parent for modal dialogs and ensure the window is visible + css::uno::Reference< css::task::XInteractionHandler > xInteraction = m_lMediaDescriptor.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_INTERACTIONHANDLER, + css::uno::Reference< css::task::XInteractionHandler >()); + css::uno::Reference<css::lang::XInitialization> xHandler(xInteraction, css::uno::UNO_QUERY); + if (xHandler.is()) + { + css::uno::Reference<css::awt::XWindow> xWindow = xTargetFrame->getContainerWindow(); + uno::Sequence<uno::Any> aArguments(comphelper::InitAnyPropertySequence( + { + {"Parent", uno::Any(xWindow)} + })); + xHandler->initialize(aArguments); + //show the frame as early as possible to make it the parent of any message dialogs + if (!impl_filterHasInteractiveDialog()) + impl_makeFrameWindowVisible(xWindow, false); + } + } + + // convert media descriptor and URL to right format for later interface call! + css::uno::Sequence< css::beans::PropertyValue > lDescriptor; + m_lMediaDescriptor >> lDescriptor; + OUString sURL = m_aURL.Complete; + + // try to locate any interested frame loader + css::uno::Reference< css::uno::XInterface > xLoader = impl_searchLoader(); + css::uno::Reference< css::frame::XFrameLoader > xAsyncLoader(xLoader, css::uno::UNO_QUERY); + css::uno::Reference< css::frame::XSynchronousFrameLoader > xSyncLoader (xLoader, css::uno::UNO_QUERY); + + if (xAsyncLoader.is()) + { + m_xAsynchronousJob = xAsyncLoader; + rtl::Reference<LoadEnvListener> xListener = new LoadEnvListener(this); + aWriteLock.clear(); + // <- SAFE ----------------------------------- + + xAsyncLoader->load(xTargetFrame, sURL, lDescriptor, xListener); + + return true; + } + else if (xSyncLoader.is()) + { + uno::Reference<beans::XPropertySet> xTargetFrameProps(xTargetFrame, uno::UNO_QUERY); + if (xTargetFrameProps.is()) + { + // Set the URL on the frame itself, for the duration of the load, when it has no + // controller. + xTargetFrameProps->setPropertyValue("URL", uno::Any(sURL)); + } + bool bResult = xSyncLoader->load(lDescriptor, xTargetFrame); + // react for the result here, so the outside waiting + // code can ask for it later. + impl_setResult(bResult); + // But the return value indicates a valid started(!) operation. + // And that's true every time we reach this line :-) + return true; + } + + aWriteLock.clear(); + // <- SAFE + + return false; +} + +css::uno::Reference< css::uno::XInterface > LoadEnv::impl_searchLoader() +{ + // SAFE -> ----------------------------------- + osl::ClearableMutexGuard aReadLock(m_mutex); + + // special mode to set an existing component on this frame + // In such case the loader is fix. It must be the SFX based implementation, + // which can create a view on top of such xModel components :-) + if (m_eContentType == E_CAN_BE_SET) + { + try + { + return css::frame::OfficeFrameLoader::create(m_xContext); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + {} + throw LoadEnvException(LoadEnvException::ID_INVALID_ENVIRONMENT); + } + + // Otherwise... + // We need this type information to locate a registered frame loader + // Without such information we can't work! + OUString sType = m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_TYPENAME, OUString()); + if (sType.isEmpty()) + throw LoadEnvException(LoadEnvException::ID_INVALID_MEDIADESCRIPTOR); + + // try to locate any interested frame loader + css::uno::Reference< css::frame::XLoaderFactory > xLoaderFactory = css::frame::FrameLoaderFactory::create(m_xContext); + + aReadLock.clear(); + // <- SAFE ----------------------------------- + + css::uno::Sequence< OUString > lTypesReg { sType }; + + css::uno::Sequence< css::beans::NamedValue > lQuery { { PROP_TYPES, css::uno::Any(lTypesReg) } }; + + css::uno::Reference< css::container::XEnumeration > xSet = xLoaderFactory->createSubSetEnumerationByProperties(lQuery); + while(xSet->hasMoreElements()) + { + try + { + // try everyone ... + // Ignore any loader, which makes trouble :-) + ::comphelper::SequenceAsHashMap lLoaderProps(xSet->nextElement()); + OUString sLoader = lLoaderProps.getUnpackedValueOrDefault(PROP_NAME, OUString()); + css::uno::Reference< css::uno::XInterface > xLoader = xLoaderFactory->createInstance(sLoader); + if (xLoader.is()) + return xLoader; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { continue; } + } + + return css::uno::Reference< css::uno::XInterface >(); +} + +void LoadEnv::impl_jumpToMark(const css::uno::Reference< css::frame::XFrame >& xFrame, + const css::util::URL& aURL ) +{ + if (aURL.Mark.isEmpty()) + return; + + css::uno::Reference< css::frame::XDispatchProvider > xProvider(xFrame, css::uno::UNO_QUERY); + if (! xProvider.is()) + return; + + // SAFE -> + osl::ClearableMutexGuard aReadLock(m_mutex); + css::uno::Reference< css::uno::XComponentContext > xContext = m_xContext; + aReadLock.clear(); + // <- SAFE + + css::util::URL aCmd; + aCmd.Complete = ".uno:JumpToMark"; + + css::uno::Reference< css::util::XURLTransformer > xParser(css::util::URLTransformer::create(xContext)); + xParser->parseStrict(aCmd); + + css::uno::Reference< css::frame::XDispatch > xDispatcher = xProvider->queryDispatch(aCmd, SPECIALTARGET_SELF, 0); + if (! xDispatcher.is()) + return; + + ::comphelper::SequenceAsHashMap lArgs; + lArgs[OUString("Bookmark")] <<= aURL.Mark; + xDispatcher->dispatch(aCmd, lArgs.getAsConstPropertyValueList()); +} + +css::uno::Reference< css::frame::XFrame > LoadEnv::impl_searchAlreadyLoaded() +{ + osl::MutexGuard g(m_mutex); + + // such search is allowed for special requests only ... + // or better it's not allowed for some requests in general :-) + if ( + ( ! TargetHelper::matchSpecialTarget(m_sTarget, TargetHelper::ESpecialTarget::Default) ) || + m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_ASTEMPLATE , false) || +// (m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_HIDDEN() , false) == sal_True) || + m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_OPENNEWVIEW, false) + ) + { + return css::uno::Reference< css::frame::XFrame >(); + } + + // check URL + // May it's not useful to start expensive document search, if it + // can fail only .. because we load from a stream or model directly! + if ( + (ProtocolCheck::isProtocol(m_aURL.Complete, EProtocol::PrivateStream )) || + (ProtocolCheck::isProtocol(m_aURL.Complete, EProtocol::PrivateObject )) + /*TODO should be private:factory here tested too? */ + ) + { + return css::uno::Reference< css::frame::XFrame >(); + } + + // otherwise - iterate through the tasks of the desktop container + // to find out, which of them might contains the requested document + css::uno::Reference< css::frame::XDesktop2 > xSupplier = css::frame::Desktop::create( m_xContext ); + css::uno::Reference< css::container::XIndexAccess > xTaskList = xSupplier->getFrames(); + + if (!xTaskList.is()) + return css::uno::Reference< css::frame::XFrame >(); // task list can be empty! + + // Note: To detect if a document was already loaded before + // we check URLs here only. But might the existing and the required + // document has different versions! Then its URLs are the same... + sal_Int16 nNewVersion = m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_VERSION, sal_Int16(-1)); + + // will be used to save the first hidden frame referring the searched model + // Normally we are interested on visible frames... but if there is no such visible + // frame we refer to any hidden frame also (but as fallback only). + css::uno::Reference< css::frame::XFrame > xHiddenTask; + css::uno::Reference< css::frame::XFrame > xTask; + + sal_Int32 count = xTaskList->getCount(); + for (sal_Int32 i=0; i<count; ++i) + { + try + { + // locate model of task + // Note: Without a model there is no chance to decide if + // this task contains the searched document or not! + xTaskList->getByIndex(i) >>= xTask; + if (!xTask.is()) + continue; + + OUString sURL; + css::uno::Reference< css::frame::XController > xController = xTask->getController(); + if (!xController.is()) + { + // If we have no controller, then perhaps there is a load in progress. The frame + // itself has the URL in this case. + uno::Reference<beans::XPropertySet> xTaskProps(xTask, uno::UNO_QUERY); + if (xTaskProps.is()) + { + xTaskProps->getPropertyValue("URL") >>= sURL; + } + if (sURL.isEmpty()) + { + xTask.clear(); + continue; + } + } + + uno::Reference<frame::XModel> xModel; + if (sURL.isEmpty()) + { + xModel = xController->getModel(); + if (!xModel.is()) + { + xTask.clear(); + continue; + } + + // don't check the complete URL here. + // use its main part - ignore optional jumpmarks! + sURL = xModel->getURL(); + } + if (!::utl::UCBContentHelper::EqualURLs( m_aURL.Main, sURL )) + { + xTask.clear (); + continue; + } + + // get the original load arguments from the current document + // and decide if it's really the same then the one will be. + // It must be visible and must use the same file revision ... + // or must not have any file revision set (-1 == -1!) + utl::MediaDescriptor lOldDocDescriptor; + if (xModel.is()) + { + lOldDocDescriptor = xModel->getArgs(); + + if (lOldDocDescriptor.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_VERSION, sal_Int32(-1)) + != nNewVersion) + { + xTask.clear(); + continue; + } + } + + // Hidden frames are special. + // They will be used as "last chance" if there is no visible frame pointing to the same model. + // Safe the result but continue with current loop might be looking for other visible frames. + bool bIsHidden = lOldDocDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_HIDDEN, false); + if ( bIsHidden && ! xHiddenTask.is() ) + { + xHiddenTask = xTask; + xTask.clear (); + continue; + } + + // We found a visible task pointing to the right model ... + // Break search. + break; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { continue; } + } + + css::uno::Reference< css::frame::XFrame > xResult; + if (xTask.is()) + xResult = xTask; + else if (xHiddenTask.is()) + xResult = xHiddenTask; + + if (xResult.is()) + { + // Now we are sure, that this task includes the searched document. + // It's time to activate it. As special feature we try to jump internally + // if an optional jumpmark is given too. + if (!m_aURL.Mark.isEmpty()) + impl_jumpToMark(xResult, m_aURL); + + // bring it to front and make sure it's visible... + impl_makeFrameWindowVisible(xResult->getContainerWindow(), true); + } + + return xResult; +} + +bool LoadEnv::impl_isFrameAlreadyUsedForLoading(const css::uno::Reference< css::frame::XFrame >& xFrame) const +{ + css::uno::Reference< css::document::XActionLockable > xLock(xFrame, css::uno::UNO_QUERY); + + // ? no lock interface ? + // Maybe it's an external written frame implementation :-( + // Allowing using of it... but it can fail if it's not synchronized with our processes! + if (!xLock.is()) + return false; + + // Otherwise we have to look for any other existing lock. + return xLock->isActionLocked(); +} + +css::uno::Reference< css::frame::XFrame > LoadEnv::impl_searchRecycleTarget() +{ + // SAFE -> .................................. + osl::ClearableMutexGuard aReadLock(m_mutex); + + // The special backing mode frame will be recycled by definition! + // It doesn't matter if somewhere wants to create a new view + // or open a new untitled document... + // The only exception from that - hidden frames! + if (m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_HIDDEN, false)) + return css::uno::Reference< css::frame::XFrame >(); + + css::uno::Reference< css::frame::XFramesSupplier > xSupplier = css::frame::Desktop::create( m_xContext ); + FrameListAnalyzer aTasksAnalyzer(xSupplier, css::uno::Reference< css::frame::XFrame >(), FrameAnalyzerFlags::BackingComponent); + if (aTasksAnalyzer.m_xBackingComponent.is()) + { + if (!impl_isFrameAlreadyUsedForLoading(aTasksAnalyzer.m_xBackingComponent)) + { + // bring it to front... + impl_makeFrameWindowVisible(aTasksAnalyzer.m_xBackingComponent->getContainerWindow(), true); + m_bReactivateControllerOnError = true; + return aTasksAnalyzer.m_xBackingComponent; + } + } + + // These states indicates a wish for creation of a new view in general. + if ( + m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_ASTEMPLATE , false) || + m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_OPENNEWVIEW, false) + ) + { + return css::uno::Reference< css::frame::XFrame >(); + } + + // On the other side some special URLs will open a new frame every time (expecting + // they can use the backing-mode frame!) + if ( + (ProtocolCheck::isProtocol(m_aURL.Complete, EProtocol::PrivateFactory )) || + (ProtocolCheck::isProtocol(m_aURL.Complete, EProtocol::PrivateStream )) || + (ProtocolCheck::isProtocol(m_aURL.Complete, EProtocol::PrivateObject )) + ) + { + return css::uno::Reference< css::frame::XFrame >(); + } + + // No backing frame! No special URL => recycle active task - if possible. + // Means - if it does not already contains a modified document, or + // use another office module. + css::uno::Reference< css::frame::XFrame > xTask = xSupplier->getActiveFrame(); + + // not a real error - but might a focus problem! + if (!xTask.is()) + return css::uno::Reference< css::frame::XFrame >(); + + // not a real error - may it's a view only + css::uno::Reference< css::frame::XController > xController = xTask->getController(); + if (!xController.is()) + return css::uno::Reference< css::frame::XFrame >(); + + // not a real error - may it's a db component instead of a full featured office document + css::uno::Reference< css::frame::XModel > xModel = xController->getModel(); + if (!xModel.is()) + return css::uno::Reference< css::frame::XFrame >(); + + // get some more information ... + + // A valid set URL means: there is already a location for this document. + // => it was saved there or opened from there. Such Documents can not be used here. + // We search for empty document ... created by a private:factory/ URL! + if (xModel->getURL().getLength()>0) + return css::uno::Reference< css::frame::XFrame >(); + + // The old document must be unmodified ... + css::uno::Reference< css::util::XModifiable > xModified(xModel, css::uno::UNO_QUERY); + if (xModified->isModified()) + return css::uno::Reference< css::frame::XFrame >(); + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xTask->getContainerWindow()); + if (pWindow && pWindow->IsInModalMode()) + return css::uno::Reference< css::frame::XFrame >(); + + // find out the application type of this document + // We can recycle only documents, which uses the same application + // then the new one. + SvtModuleOptions::EFactory eOldApp = SvtModuleOptions::ClassifyFactoryByModel(xModel); + SvtModuleOptions::EFactory eNewApp = SvtModuleOptions::ClassifyFactoryByURL (m_aURL.Complete, m_lMediaDescriptor.getAsConstPropertyValueList()); + + aReadLock.clear(); + // <- SAFE .................................. + + if (eOldApp != eNewApp) + return css::uno::Reference< css::frame::XFrame >(); + + // OK this task seems to be usable for recycling + // But we should mark it as such - means set an action lock. + // Otherwise it would be used more than ones or will be destroyed + // by a close() or terminate() request. + // But if such lock already exist ... it means this task is used for + // any other operation already. Don't use it then. + if (impl_isFrameAlreadyUsedForLoading(xTask)) + return css::uno::Reference< css::frame::XFrame >(); + + // OK - there is a valid target frame. + // But may be it contains already a document. + // Then we have to ask it, if it allows recycling of this frame .-) + bool bReactivateOldControllerOnError = false; + css::uno::Reference< css::frame::XController > xOldDoc = xTask->getController(); + if (xOldDoc.is()) + { + utl::MediaDescriptor lOldDocDescriptor(xModel->getArgs()); + + // replaceable document + if (!lOldDocDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_REPLACEABLE, false)) + return css::uno::Reference< css::frame::XFrame >(); + + bReactivateOldControllerOnError = xOldDoc->suspend(true); + if (! bReactivateOldControllerOnError) + return css::uno::Reference< css::frame::XFrame >(); + } + + // SAFE -> .................................. + { + osl::MutexGuard aWriteLock(m_mutex); + + css::uno::Reference< css::document::XActionLockable > xLock(xTask, css::uno::UNO_QUERY); + if (!m_aTargetLock.setResource(xLock)) + return css::uno::Reference< css::frame::XFrame >(); + + m_bReactivateControllerOnError = bReactivateOldControllerOnError; + } + // <- SAFE .................................. + + // bring it to front ... + impl_makeFrameWindowVisible(xTask->getContainerWindow(), true); + + return xTask; +} + +void LoadEnv::impl_reactForLoadingState() +{ + /*TODO reset action locks */ + + // SAFE -> ---------------------------------- + osl::ClearableMutexGuard aReadLock(m_mutex); + + if (m_bLoaded) + { + // Bring the new loaded document to front (if allowed!). + // Note: We show new created frames here only. + // We don't hide already visible frames here ... + css::uno::Reference< css::awt::XWindow > xWindow = m_xTargetFrame->getContainerWindow(); + bool bHidden = m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_HIDDEN, false); + bool bMinimized = m_lMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_MINIMIZED, false); + + if (bMinimized) + { + SolarMutexGuard aSolarGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + // check for system window is necessary to guarantee correct pointer cast! + if (pWindow && pWindow->IsSystemWindow()) + static_cast<WorkWindow*>(pWindow.get())->Minimize(); + } + else if (!bHidden) + { + // show frame ... if it's not still visible ... + // But do nothing if it's already visible! + impl_makeFrameWindowVisible(xWindow, false); + } + + // Note: Only if an existing property "FrameName" is given by this media descriptor, + // it should be used. Otherwise we should do nothing. May be the outside code has already + // set a frame name on the target! + utl::MediaDescriptor::const_iterator pFrameName = m_lMediaDescriptor.find(utl::MediaDescriptor::PROP_FRAMENAME); + if (pFrameName != m_lMediaDescriptor.end()) + { + OUString sFrameName; + pFrameName->second >>= sFrameName; + // Check the name again. e.g. "_default" isn't allowed. + // On the other side "_beamer" is a valid name :-) + if (TargetHelper::isValidNameForFrame(sFrameName)) + m_xTargetFrame->setName(sFrameName); + } + } + else if (m_bReactivateControllerOnError) + { + // Try to reactivate the old document (if any exists!) + css::uno::Reference< css::frame::XController > xOldDoc = m_xTargetFrame->getController(); + // clear does not depend from reactivation state of a might existing old document! + // We must make sure, that a might following getTargetComponent() call does not return + // the old document! + m_xTargetFrame.clear(); + if (xOldDoc.is()) + { + bool bReactivated = xOldDoc->suspend(false); + if (!bReactivated) + throw LoadEnvException(LoadEnvException::ID_COULD_NOT_REACTIVATE_CONTROLLER); + m_bReactivateControllerOnError = false; + } + } + else if (m_bCloseFrameOnError) + { + // close empty frames + css::uno::Reference< css::util::XCloseable > xCloseable (m_xTargetFrame, css::uno::UNO_QUERY); + + try + { + if (xCloseable.is()) + xCloseable->close(true); + else if (m_xTargetFrame.is()) + m_xTargetFrame->dispose(); + } + catch(const css::util::CloseVetoException&) + {} + catch(const css::lang::DisposedException&) + {} + m_xTargetFrame.clear(); + } + + // This max force an implicit closing of our target frame ... + // e.g. in case close(sal_True) was called before and the frame + // kill itself if our external use-lock is released here! + // That's why we release this lock AFTER ALL OPERATIONS on this frame + // are finished. The frame itself must handle then + // this situation gracefully. + m_aTargetLock.freeResource(); + + // Last but not least :-) + // We have to clear the current media descriptor. + // Otherwise it hold a might existing stream open! + m_lMediaDescriptor.clear(); + + css::uno::Any aRequest; + bool bThrow = false; + if ( !m_bLoaded && m_pQuietInteraction.is() && m_pQuietInteraction->wasUsed() ) + { + aRequest = m_pQuietInteraction->getRequest(); + m_pQuietInteraction.clear(); + bThrow = true; + } + + aReadLock.clear(); + + if (bThrow) + { + if ( aRequest.isExtractableTo( ::cppu::UnoType< css::uno::Exception >::get() ) ) + throw LoadEnvException( + LoadEnvException::ID_GENERAL_ERROR, "interaction request", + aRequest); + } + + // <- SAFE ---------------------------------- +} + +void LoadEnv::impl_makeFrameWindowVisible(const css::uno::Reference< css::awt::XWindow >& xWindow , + bool bForceToFront) +{ + SolarMutexGuard aSolarGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + if ( !pWindow ) + return; + + bool const preview( m_lMediaDescriptor.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_PREVIEW, false) ); + + bool bForceFrontAndFocus(false); + if ( !preview ) + { + bForceFrontAndFocus = officecfg::Office::Common::View::NewDocumentHandling::ForceFocusAndToFront::get(); + } + + if( pWindow->IsVisible() && (bForceFrontAndFocus || bForceToFront) ) + pWindow->ToTop( ToTopFlags::RestoreWhenMin | ToTopFlags::ForegroundTask ); + else + pWindow->Show(true, (bForceFrontAndFocus || bForceToFront) ? ShowFlags::ForegroundTask : ShowFlags::NONE ); +} + +void LoadEnv::impl_applyPersistentWindowState(const css::uno::Reference< css::awt::XWindow >& xWindow) +{ + // no window -> action not possible + if (!xWindow.is()) + return; + + // window already visible -> do nothing! If we use a "recycle frame" for loading ... + // the current position and size must be used. + css::uno::Reference< css::awt::XWindow2 > xVisibleCheck(xWindow, css::uno::UNO_QUERY); + if ( + (xVisibleCheck.is() ) && + (xVisibleCheck->isVisible()) + ) + return; + + // SOLAR SAFE -> + { + SolarMutexGuard aSolarGuard1; + + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + if (!pWindow) + return; + + bool bSystemWindow = pWindow->IsSystemWindow(); + bool bWorkWindow = (pWindow->GetType() == WindowType::WORKWINDOW); + + if (!bSystemWindow && !bWorkWindow) + return; + + // don't overwrite this special state! + WorkWindow* pWorkWindow = static_cast<WorkWindow*>(pWindow.get()); + if (pWorkWindow->IsMinimized()) + return; + } + // <- SOLAR SAFE + + // SAFE -> + osl::ClearableMutexGuard aReadLock(m_mutex); + + // no filter -> no module -> no persistent window state + OUString sFilter = m_lMediaDescriptor.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_FILTERNAME, + OUString()); + if (sFilter.isEmpty()) + return; + + css::uno::Reference< css::uno::XComponentContext > xContext = m_xContext; + + aReadLock.clear(); + // <- SAFE + + try + { + // retrieve the module name from the filter configuration + css::uno::Reference< css::container::XNameAccess > xFilterCfg( + xContext->getServiceManager()->createInstanceWithContext(SERVICENAME_FILTERFACTORY, xContext), + css::uno::UNO_QUERY_THROW); + ::comphelper::SequenceAsHashMap lProps (xFilterCfg->getByName(sFilter)); + OUString sModule = lProps.getUnpackedValueOrDefault(FILTER_PROPNAME_ASCII_DOCUMENTSERVICE, OUString()); + + // get access to the configuration of this office module + css::uno::Reference< css::container::XNameAccess > xModuleCfg(officecfg::Setup::Office::Factories::get()); + + // read window state from the configuration + // and apply it on the window. + // Do nothing, if no configuration entry exists! + OUString sWindowState; + + // Don't look for persistent window attributes when used through LibreOfficeKit + if( !comphelper::LibreOfficeKit::isActive() ) + comphelper::ConfigurationHelper::readRelativeKey(xModuleCfg, sModule, "ooSetupFactoryWindowAttributes") >>= sWindowState; + + if (!sWindowState.isEmpty()) + { + // SOLAR SAFE -> + SolarMutexGuard aSolarGuard; + + // We have to retrieve the window pointer again. Because nobody can guarantee + // that the XWindow was not disposed in between .-) + // But if we get a valid pointer we can be sure, that it's the system window pointer + // we already checked and used before. Because nobody recycle the same uno reference for + // a new internal c++ implementation ... hopefully .-)) + VclPtr<vcl::Window> pWindowCheck = VCLUnoHelper::GetWindow(xWindow); + if (! pWindowCheck) + return; + + SystemWindow* pSystemWindow = static_cast<SystemWindow*>(pWindowCheck.get()); + pSystemWindow->SetWindowState(OUStringToOString(sWindowState,RTL_TEXTENCODING_UTF8)); + // <- SOLAR SAFE + } + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + {} +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/loadenv/targethelper.cxx b/framework/source/loadenv/targethelper.cxx new file mode 100644 index 000000000..7c06521da --- /dev/null +++ b/framework/source/loadenv/targethelper.cxx @@ -0,0 +1,64 @@ +/* -*- 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 <loadenv/targethelper.hxx> +#include <targets.h> + +namespace framework{ + +bool TargetHelper::matchSpecialTarget(std::u16string_view sCheckTarget , + ESpecialTarget eSpecialTarget) +{ + switch(eSpecialTarget) + { + case ESpecialTarget::Blank : + return sCheckTarget == SPECIALTARGET_BLANK; + + case ESpecialTarget::Default : + return sCheckTarget == SPECIALTARGET_DEFAULT; + + case ESpecialTarget::Beamer : + return sCheckTarget == SPECIALTARGET_BEAMER; + + case ESpecialTarget::HelpTask : + return sCheckTarget == SPECIALTARGET_HELPTASK; + default: + return false; + } +} + +bool TargetHelper::isValidNameForFrame(std::u16string_view sName) +{ + // some special targets are really special ones :-) + // E.g. the are really used to locate one frame inside the frame tree. + if ( + (sName.empty() ) || + (TargetHelper::matchSpecialTarget(sName, ESpecialTarget::HelpTask)) || + (TargetHelper::matchSpecialTarget(sName, ESpecialTarget::Beamer) ) + ) + return true; + + // all other names must be checked more general + // special targets starts with a "_". + return (sName.find('_') != 0); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/recording/dispatchrecorder.cxx b/framework/source/recording/dispatchrecorder.cxx new file mode 100644 index 000000000..2afa8926d --- /dev/null +++ b/framework/source/recording/dispatchrecorder.cxx @@ -0,0 +1,439 @@ +/* -*- 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 <recording/dispatchrecorder.hxx> +#include <com/sun/star/frame/DispatchStatement.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/script/CannotConvertException.hpp> +#include <com/sun/star/script/Converter.hpp> +#include <o3tl/any.hxx> +#include <osl/diagnose.h> +#include <vcl/svapp.hxx> +#include <typelib/typedescription.h> +#include <cppuhelper/supportsservice.hxx> + +using namespace ::com::sun::star::uno; + +namespace framework{ + +// used to mark a dispatch as comment (mostly it indicates an error) Changing of this define will impact all using of such comments... +constexpr OUStringLiteral REM_AS_COMMENT = u"rem "; + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL DispatchRecorder::getImplementationName() +{ + return "com.sun.star.comp.framework.DispatchRecorder"; +} + +sal_Bool SAL_CALL DispatchRecorder::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL DispatchRecorder::getSupportedServiceNames() +{ + return { "com.sun.star.frame.DispatchRecorder" }; +} + + + +static void flatten_struct_members( + ::std::vector< Any > * vec, void const * data, + typelib_CompoundTypeDescription * pTD ) +{ + if (pTD->pBaseTypeDescription) + { + flatten_struct_members( vec, data, pTD->pBaseTypeDescription ); + } + for ( sal_Int32 nPos = 0; nPos < pTD->nMembers; ++nPos ) + { + vec->push_back( + Any( static_cast<char const *>(data) + pTD->pMemberOffsets[ nPos ], pTD->ppTypeRefs[ nPos ] ) ); + } +} + +static Sequence< Any > make_seq_out_of_struct( + Any const & val ) +{ + Type const & type = val.getValueType(); + TypeClass eTypeClass = type.getTypeClass(); + if (TypeClass_STRUCT != eTypeClass && TypeClass_EXCEPTION != eTypeClass) + { + throw RuntimeException( + type.getTypeName() + "is no struct or exception!" ); + } + typelib_TypeDescription * pTD = nullptr; + TYPELIB_DANGER_GET( &pTD, type.getTypeLibType() ); + OSL_ASSERT( pTD ); + if (! pTD) + { + throw RuntimeException( + "cannot get type descr of type " + type.getTypeName() ); + } + + ::std::vector< Any > vec; + vec.reserve( reinterpret_cast<typelib_CompoundTypeDescription *>(pTD)->nMembers ); // good guess + flatten_struct_members( &vec, val.getValue(), reinterpret_cast<typelib_CompoundTypeDescription *>(pTD) ); + TYPELIB_DANGER_RELEASE( pTD ); + return Sequence< Any >( vec.data(), vec.size() ); +} + +DispatchRecorder::DispatchRecorder( const css::uno::Reference< css::uno::XComponentContext >& xContext ) + : m_nRecordingID(0) + , m_xConverter(css::script::Converter::create(xContext)) +{ +} + +DispatchRecorder::~DispatchRecorder() +{ +} + +// generate header +void SAL_CALL DispatchRecorder::startRecording( const css::uno::Reference< css::frame::XFrame >& ) +{ + /* SAFE{ */ + /* } */ +} + +void SAL_CALL DispatchRecorder::recordDispatch( const css::util::URL& aURL, + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) +{ + css::frame::DispatchStatement aStatement( aURL.Complete, OUString(), lArguments, 0, false ); + m_aStatements.push_back( aStatement ); +} + +void SAL_CALL DispatchRecorder::recordDispatchAsComment( const css::util::URL& aURL, + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) +{ + // last parameter must be set to true -> it's a comment + css::frame::DispatchStatement aStatement( aURL.Complete, OUString(), lArguments, 0, true ); + m_aStatements.push_back( aStatement ); +} + +void SAL_CALL DispatchRecorder::endRecording() +{ + SolarMutexGuard g; + m_aStatements.clear(); +} + +OUString SAL_CALL DispatchRecorder::getRecordedMacro() +{ + SolarMutexGuard g; + + if ( m_aStatements.empty() ) + return OUString(); + + OUStringBuffer aScriptBuffer; + aScriptBuffer.ensureCapacity(10000); + m_nRecordingID = 1; + + aScriptBuffer.append("rem ----------------------------------------------------------------------\n"); + aScriptBuffer.append("rem define variables\n"); + aScriptBuffer.append("dim document as object\n"); + aScriptBuffer.append("dim dispatcher as object\n"); + aScriptBuffer.append("rem ----------------------------------------------------------------------\n"); + aScriptBuffer.append("rem get access to the document\n"); + aScriptBuffer.append("document = ThisComponent.CurrentController.Frame\n"); + aScriptBuffer.append("dispatcher = createUnoService(\"com.sun.star.frame.DispatchHelper\")\n\n"); + + for (auto const& statement : m_aStatements) + implts_recordMacro( statement.aCommand, statement.aArgs, statement.bIsComment, aScriptBuffer ); + OUString sScript = aScriptBuffer.makeStringAndClear(); + return sScript; +} + +void DispatchRecorder::AppendToBuffer( const css::uno::Any& aValue, OUStringBuffer& aArgumentBuffer ) +{ + // if value == bool + if (aValue.getValueTypeClass() == css::uno::TypeClass_STRUCT ) + { + // structs are recorded as arrays, convert to "Sequence of any" + Sequence< Any > aSeq = make_seq_out_of_struct( aValue ); + aArgumentBuffer.append("Array("); + for ( sal_Int32 nAny=0; nAny<aSeq.getLength(); nAny++ ) + { + AppendToBuffer( aSeq[nAny], aArgumentBuffer ); + if ( nAny+1 < aSeq.getLength() ) + // not last argument + aArgumentBuffer.append(","); + } + + aArgumentBuffer.append(")"); + } + else if (aValue.getValueTypeClass() == css::uno::TypeClass_SEQUENCE ) + { + // convert to "Sequence of any" + css::uno::Sequence < css::uno::Any > aSeq; + css::uno::Any aNew; + try { aNew = m_xConverter->convertTo( aValue, cppu::UnoType<css::uno::Sequence < css::uno::Any >>::get() ); } + catch (const css::uno::Exception&) {} + + aNew >>= aSeq; + aArgumentBuffer.append("Array("); + for ( sal_Int32 nAny=0; nAny<aSeq.getLength(); nAny++ ) + { + AppendToBuffer( aSeq[nAny], aArgumentBuffer ); + if ( nAny+1 < aSeq.getLength() ) + // not last argument + aArgumentBuffer.append(","); + } + + aArgumentBuffer.append(")"); + } + else if (aValue.getValueTypeClass() == css::uno::TypeClass_STRING ) + { + // strings need \" + OUString sVal; + aValue >>= sVal; + + // encode non printable characters or '"' by using the CHR$ function + if ( !sVal.isEmpty() ) + { + const sal_Unicode* pChars = sVal.getStr(); + bool bInString = false; + for ( sal_Int32 nChar=0; nChar<sVal.getLength(); nChar ++ ) + { + if ( pChars[nChar] < 32 || pChars[nChar] == '"' ) + { + // problematic character detected + if ( bInString ) + { + // close current string + aArgumentBuffer.append("\""); + bInString = false; + } + + if ( nChar>0 ) + // if this is not the first character, parts of the string have already been added + aArgumentBuffer.append("+"); + + // add the character constant + aArgumentBuffer.append("CHR$("); + aArgumentBuffer.append( static_cast<sal_Int32>(pChars[nChar]) ); + aArgumentBuffer.append(")"); + } + else + { + if ( !bInString ) + { + if ( nChar>0 ) + // if this is not the first character, parts of the string have already been added + aArgumentBuffer.append("+"); + + // start a new string + aArgumentBuffer.append("\""); + bInString = true; + } + + aArgumentBuffer.append( pChars[nChar] ); + } + } + + // close string + if ( bInString ) + aArgumentBuffer.append("\""); + } + else + aArgumentBuffer.append("\"\""); + } + else if (auto nVal = o3tl::tryAccess<sal_Unicode>(aValue)) + { + // character variables are recorded as strings, back conversion must be handled in client code + aArgumentBuffer.append("\""); + if ( *nVal == '\"' ) + // encode \" to \"\" + aArgumentBuffer.append(*nVal); + aArgumentBuffer.append(*nVal); + aArgumentBuffer.append("\""); + } + else + { + css::uno::Any aNew; + try + { + aNew = m_xConverter->convertToSimpleType( aValue, css::uno::TypeClass_STRING ); + } + catch (const css::script::CannotConvertException&) {} + catch (const css::uno::Exception&) {} + OUString sVal; + aNew >>= sVal; + + if (aValue.getValueTypeClass() == css::uno::TypeClass_ENUM ) + { + OUString aName = aValue.getValueType().getTypeName(); + aArgumentBuffer.append( aName ); + aArgumentBuffer.append("."); + } + + aArgumentBuffer.append(sVal); + } +} + +void DispatchRecorder::implts_recordMacro( std::u16string_view aURL, + const css::uno::Sequence< css::beans::PropertyValue >& lArguments, + bool bAsComment, OUStringBuffer& aScriptBuffer ) +{ + OUStringBuffer aArgumentBuffer(1000); + // this value is used to name the arrays of aArgumentBuffer + OUString sArrayName = "args" + OUString::number(m_nRecordingID); + + aScriptBuffer.append("rem ----------------------------------------------------------------------\n"); + + sal_Int32 nLength = lArguments.getLength(); + sal_Int32 nValidArgs = 0; + for( sal_Int32 i=0; i<nLength; ++i ) + { + if(!lArguments[i].Value.hasValue()) + continue; + + OUStringBuffer sValBuffer(100); + try + { + AppendToBuffer(lArguments[i].Value, sValBuffer); + } + catch(const css::uno::Exception&) + { + sValBuffer.setLength(0); + } + if (sValBuffer.isEmpty()) + continue; + + { + // add arg().Name + if(bAsComment) + aArgumentBuffer.append(REM_AS_COMMENT); + aArgumentBuffer.append (sArrayName); + aArgumentBuffer.append("("); + aArgumentBuffer.append (nValidArgs); + aArgumentBuffer.append(").Name = \""); + aArgumentBuffer.append (lArguments[i].Name); + aArgumentBuffer.append("\"\n"); + + // add arg().Value + if(bAsComment) + aArgumentBuffer.append(REM_AS_COMMENT); + aArgumentBuffer.append (sArrayName); + aArgumentBuffer.append("("); + aArgumentBuffer.append (nValidArgs); + aArgumentBuffer.append(").Value = "); + aArgumentBuffer.append (sValBuffer.makeStringAndClear()); + aArgumentBuffer.append("\n"); + + ++nValidArgs; + } + } + + // if aArgumentBuffer exist - pack it into the aScriptBuffer + if(nValidArgs>0) + { + if(bAsComment) + aScriptBuffer.append(REM_AS_COMMENT); + aScriptBuffer.append("dim "); + aScriptBuffer.append (sArrayName); + aScriptBuffer.append("("); + aScriptBuffer.append (static_cast<sal_Int32>(nValidArgs-1)); // 0 based! + aScriptBuffer.append(") as new com.sun.star.beans.PropertyValue\n"); + aScriptBuffer.append (aArgumentBuffer.makeStringAndClear()); + aScriptBuffer.append("\n"); + } + + // add code for dispatches + if(bAsComment) + aScriptBuffer.append(REM_AS_COMMENT); + aScriptBuffer.append("dispatcher.executeDispatch(document, \""); + aScriptBuffer.append(aURL); + aScriptBuffer.append("\", \"\", 0, "); + if(nValidArgs<1) + aScriptBuffer.append("Array()"); + else + { + aScriptBuffer.append( sArrayName ); + aScriptBuffer.append("()"); + } + aScriptBuffer.append(")\n\n"); + + /* SAFE { */ + m_nRecordingID++; + /* } */ +} + +css::uno::Type SAL_CALL DispatchRecorder::getElementType() +{ + return cppu::UnoType<css::frame::DispatchStatement>::get(); +} + +sal_Bool SAL_CALL DispatchRecorder::hasElements() +{ + return (! m_aStatements.empty()); +} + +sal_Int32 SAL_CALL DispatchRecorder::getCount() +{ + return m_aStatements.size(); +} + +css::uno::Any SAL_CALL DispatchRecorder::getByIndex(sal_Int32 idx) +{ + if (idx >= static_cast<sal_Int32>(m_aStatements.size())) + throw css::lang::IndexOutOfBoundsException( "Dispatch recorder out of bounds" ); + + Any element(&m_aStatements[idx], + cppu::UnoType<css::frame::DispatchStatement>::get()); + + return element; +} + +void SAL_CALL DispatchRecorder::replaceByIndex(sal_Int32 idx, const css::uno::Any& element) +{ + if (element.getValueType() != + cppu::UnoType<css::frame::DispatchStatement>::get()) { + throw css::lang::IllegalArgumentException( + "Illegal argument in dispatch recorder", + Reference< XInterface >(), 2 ); + } + + if (idx >= static_cast<sal_Int32>(m_aStatements.size())) + throw css::lang::IndexOutOfBoundsException( + "Dispatch recorder out of bounds" ); + + auto pStatement = o3tl::doAccess<css::frame::DispatchStatement>(element); + + css::frame::DispatchStatement aStatement( + pStatement->aCommand, + pStatement->aTarget, + pStatement->aArgs, + pStatement->nFlags, + pStatement->bIsComment); + + m_aStatements[idx] = aStatement; +} + +} // namespace framework + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_DispatchRecorder_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::DispatchRecorder(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/recording/dispatchrecordersupplier.cxx b/framework/source/recording/dispatchrecordersupplier.cxx new file mode 100644 index 000000000..39707aca6 --- /dev/null +++ b/framework/source/recording/dispatchrecordersupplier.cxx @@ -0,0 +1,161 @@ +/* -*- 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 <recording/dispatchrecordersupplier.hxx> + +#include <com/sun/star/frame/XRecordableDispatch.hpp> + +#include <vcl/svapp.hxx> +#include <cppuhelper/supportsservice.hxx> + +namespace framework{ + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL DispatchRecorderSupplier::getImplementationName() +{ + return "com.sun.star.comp.framework.DispatchRecorderSupplier"; +} + +sal_Bool SAL_CALL DispatchRecorderSupplier::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL DispatchRecorderSupplier::getSupportedServiceNames() +{ + return { "com.sun.star.frame.DispatchRecorderSupplier" }; +} + + +DispatchRecorderSupplier::DispatchRecorderSupplier() +{ +} + +/** + @short standard destructor + @descr We are a helper and not a real service. So we don't provide + dispose() functionality. This supplier dies by ref count mechanism + and should release all internal used ones too. + */ +DispatchRecorderSupplier::~DispatchRecorderSupplier() +{ + m_xDispatchRecorder = nullptr; +} + +/** + @short set a new dispatch recorder on this supplier + @descr Because there can exist more than one recorder implementations + (to generate java/basic/... scripts from recorded data) it must + be possible to set it on a supplier. + + @see getDispatchRecorder() + + @param xRecorder + the new recorder to set it + <br><NULL/> isn't recommended, because recording without a + valid recorder can't work. But it's not checked here. So user + of this supplier can decide that without changing this + implementation. + + @change 09.04.2002 by Andreas Schluens + */ +void SAL_CALL DispatchRecorderSupplier::setDispatchRecorder( const css::uno::Reference< css::frame::XDispatchRecorder >& xRecorder ) +{ + SolarMutexGuard g; + m_xDispatchRecorder=xRecorder; +} + +/** + @short provides access to the dispatch recorder of this supplier + @descr Such recorder can be used outside to record dispatches. + But normally he is used internally only. Of course he must used + from outside to get the recorded data e.g. for saving it as a + script. + + @see setDispatchRecorder() + + @return the internal used dispatch recorder + <br>May it can be <NULL/> if no one was set before. + + @change 09.04.2002 by Andreas Schluens + */ +css::uno::Reference< css::frame::XDispatchRecorder > SAL_CALL DispatchRecorderSupplier::getDispatchRecorder() +{ + SolarMutexGuard g; + return m_xDispatchRecorder; +} + +/** + @short execute a dispatch request and record it + @descr If given dispatch object provides right recording interface it + will be used. If it's not supported it record the pure dispatch + parameters only. There is no code neither the possibility to + check if recording is enabled or not. + + @param aURL the command URL + @param lArguments optional arguments (see com.sun.star.document.MediaDescriptor for further information) + @param xDispatcher the original dispatch object which should be recorded + + @change 09.04.2002 by Andreas Schluens + */ +void SAL_CALL DispatchRecorderSupplier::dispatchAndRecord( const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments , + const css::uno::Reference< css::frame::XDispatch >& xDispatcher ) +{ + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::frame::XDispatchRecorder > xRecorder = m_xDispatchRecorder; + aReadLock.clear(); + + // clear unspecific situations + if (!xDispatcher.is()) + throw css::uno::RuntimeException("specification violation: dispatcher is NULL", static_cast< ::cppu::OWeakObject* >(this)); + + if (!xRecorder.is()) + throw css::uno::RuntimeException("specification violation: no valid dispatch recorder available", static_cast< ::cppu::OWeakObject* >(this)); + + // check, if given dispatch supports record functionality by itself ... + // or must be wrapped. + css::uno::Reference< css::frame::XRecordableDispatch > xRecordable( + xDispatcher, + css::uno::UNO_QUERY); + + if (xRecordable.is()) + xRecordable->dispatchAndRecord(aURL,lArguments,xRecorder); + else + { + // There is no reason to wait for information about success + // of this request. Because status information of a dispatch + // are not guaranteed. So we execute it and record used + // parameters only. + xDispatcher->dispatch(aURL,lArguments); + xRecorder->recordDispatch(aURL,lArguments); + } +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_DispatchRecorderSupplier_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::DispatchRecorderSupplier()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/ContextChangeEventMultiplexer.cxx b/framework/source/services/ContextChangeEventMultiplexer.cxx new file mode 100644 index 000000000..b2f6db457 --- /dev/null +++ b/framework/source/services/ContextChangeEventMultiplexer.cxx @@ -0,0 +1,366 @@ +/* -*- 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 <helper/mischelper.hxx> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/ui/XContextChangeEventMultiplexer.hpp> +#include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <comphelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/ref.hxx> +#include <osl/diagnose.h> + +#include <algorithm> +#include <map> +#include <vector> + +using namespace css; +using namespace css::uno; + +namespace { + +typedef comphelper::WeakComponentImplHelper < + css::ui::XContextChangeEventMultiplexer, + css::lang::XServiceInfo, + css::lang::XEventListener + > ContextChangeEventMultiplexerInterfaceBase; + +class ContextChangeEventMultiplexer + : public ContextChangeEventMultiplexerInterfaceBase +{ +public: + ContextChangeEventMultiplexer(); + ContextChangeEventMultiplexer(const ContextChangeEventMultiplexer&) = delete; + ContextChangeEventMultiplexer& operator=(const ContextChangeEventMultiplexer&) = delete; + + virtual void disposing(std::unique_lock<std::mutex>&) override; + + // XContextChangeEventMultiplexer + virtual void SAL_CALL addContextChangeEventListener ( + const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener, + const css::uno::Reference<css::uno::XInterface>& rxEventFocus) override; + virtual void SAL_CALL removeContextChangeEventListener ( + const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener, + const css::uno::Reference<css::uno::XInterface>& rxEventFocus) override; + virtual void SAL_CALL removeAllContextChangeEventListeners ( + const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener) override; + virtual void SAL_CALL broadcastContextChangeEvent ( + const css::ui::ContextChangeEventObject& rContextChangeEventObject, + const css::uno::Reference<css::uno::XInterface>& rxEventFocus) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService ( + const OUString& rsServiceName) override; + virtual css::uno::Sequence< OUString> SAL_CALL getSupportedServiceNames() override; + + // XEventListener + virtual void SAL_CALL disposing ( + const css::lang::EventObject& rEvent) override; + + typedef ::std::vector<css::uno::Reference<css::ui::XContextChangeEventListener> > ListenerContainer; + class FocusDescriptor + { + public: + ListenerContainer maListeners; + OUString msCurrentApplicationName; + OUString msCurrentContextName; + }; + typedef ::std::map<css::uno::Reference<css::uno::XInterface>, FocusDescriptor> ListenerMap; + ListenerMap maListeners; + + /** Notify all listeners in the container that is associated with + the given event focus. + + Typically called twice from broadcastEvent(), once for the + given event focus and once for NULL. + */ + void BroadcastEventToSingleContainer ( + const css::ui::ContextChangeEventObject& rEventObject, + const css::uno::Reference<css::uno::XInterface>& rxEventFocus); + FocusDescriptor* GetFocusDescriptor ( + const css::uno::Reference<css::uno::XInterface>& rxEventFocus, + const bool bCreateWhenMissing); +}; + +ContextChangeEventMultiplexer::ContextChangeEventMultiplexer() +{ +} + +void ContextChangeEventMultiplexer::disposing(std::unique_lock<std::mutex>& rGuard) +{ + ListenerMap aListeners; + aListeners.swap(maListeners); + + rGuard.unlock(); + + css::uno::Reference<css::uno::XInterface> xThis (static_cast<XWeak*>(this)); + css::lang::EventObject aEvent (xThis); + for (auto const& container : aListeners) + { + // Unregister from the focus object. + Reference<lang::XComponent> xComponent (container.first, UNO_QUERY); + if (xComponent.is()) + xComponent->removeEventListener(this); + + // Tell all listeners that we are being disposed. + const FocusDescriptor& rFocusDescriptor (container.second); + for (auto const& listener : rFocusDescriptor.maListeners) + { + listener->disposing(aEvent); + } + } +} + +// XContextChangeEventMultiplexer +void SAL_CALL ContextChangeEventMultiplexer::addContextChangeEventListener ( + const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener, + const css::uno::Reference<css::uno::XInterface>& rxEventFocus) +{ + if ( ! rxListener.is()) + throw css::lang::IllegalArgumentException( + "can not add an empty reference", + static_cast<XWeak*>(this), + 0); + + FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, true); + if (pFocusDescriptor != nullptr) + { + ListenerContainer& rContainer (pFocusDescriptor->maListeners); + if (::std::find(rContainer.begin(), rContainer.end(), rxListener) != rContainer.end()) + { + // The listener was added for the same event focus + // previously. That is an error. + throw css::lang::IllegalArgumentException("listener added twice", static_cast<XWeak*>(this), 0); + } + rContainer.push_back(rxListener); + } + + // Send out an initial event that informs the new listener about + // the current context. + if (!(rxEventFocus.is() && pFocusDescriptor!=nullptr)) + return; + + css::ui::ContextChangeEventObject aEvent ( + nullptr, + pFocusDescriptor->msCurrentApplicationName, + pFocusDescriptor->msCurrentContextName); + rxListener->notifyContextChangeEvent(aEvent); +} + +void SAL_CALL ContextChangeEventMultiplexer::removeContextChangeEventListener ( + const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener, + const css::uno::Reference<css::uno::XInterface>& rxEventFocus) +{ + if ( ! rxListener.is()) + throw css::lang::IllegalArgumentException( + "can not remove an empty reference", + static_cast<XWeak*>(this), 0); + + FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, false); + if (pFocusDescriptor == nullptr) + return; + + ListenerContainer& rContainer (pFocusDescriptor->maListeners); + const ListenerContainer::iterator iListener ( + ::std::find(rContainer.begin(), rContainer.end(), rxListener)); + if (iListener != rContainer.end()) + { + rContainer.erase(iListener); + + // We hold on to the focus descriptor even when the last listener has been removed. + // This allows us to keep track of the current context and send it to new listeners. + } + +} + +void SAL_CALL ContextChangeEventMultiplexer::removeAllContextChangeEventListeners ( + const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener) +{ + if ( ! rxListener.is()) + throw css::lang::IllegalArgumentException( + "can not remove an empty reference", + static_cast<XWeak*>(this), 0); + + for (auto& rContainer : maListeners) + { + const ListenerContainer::iterator iListener ( + ::std::find(rContainer.second.maListeners.begin(), rContainer.second.maListeners.end(), rxListener)); + if (iListener != rContainer.second.maListeners.end()) + { + rContainer.second.maListeners.erase(iListener); + + // We hold on to the focus descriptor even when the last listener has been removed. + // This allows us to keep track of the current context and send it to new listeners. + } + } +} + +void SAL_CALL ContextChangeEventMultiplexer::broadcastContextChangeEvent ( + const css::ui::ContextChangeEventObject& rEventObject, + const css::uno::Reference<css::uno::XInterface>& rxEventFocus) +{ + // Remember the current context. + if (rxEventFocus.is()) + { + FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, true); + if (pFocusDescriptor != nullptr) + { + pFocusDescriptor->msCurrentApplicationName = rEventObject.ApplicationName; + pFocusDescriptor->msCurrentContextName = rEventObject.ContextName; + } + } + + BroadcastEventToSingleContainer(rEventObject, rxEventFocus); + if (rxEventFocus.is()) + BroadcastEventToSingleContainer(rEventObject, nullptr); +} + +void ContextChangeEventMultiplexer::BroadcastEventToSingleContainer ( + const css::ui::ContextChangeEventObject& rEventObject, + const css::uno::Reference<css::uno::XInterface>& rxEventFocus) +{ + FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, false); + if (pFocusDescriptor != nullptr) + { + // Create a copy of the listener container to avoid problems + // when one of the called listeners calls add... or remove... + ListenerContainer aContainer (pFocusDescriptor->maListeners); + for (auto const& listener : aContainer) + { + listener->notifyContextChangeEvent(rEventObject); + } + } +} + +ContextChangeEventMultiplexer::FocusDescriptor* ContextChangeEventMultiplexer::GetFocusDescriptor ( + const css::uno::Reference<css::uno::XInterface>& rxEventFocus, + const bool bCreateWhenMissing) +{ + ListenerMap::iterator iDescriptor (maListeners.find(rxEventFocus)); + if (iDescriptor == maListeners.end() && bCreateWhenMissing) + { + // Listen for the focus being disposed. + Reference<lang::XComponent> xComponent (rxEventFocus, UNO_QUERY); + if (xComponent.is()) + xComponent->addEventListener(this); + + // Create a new listener container for the event focus. + iDescriptor = maListeners.emplace( + rxEventFocus, + FocusDescriptor()).first; + } + if (iDescriptor != maListeners.end()) + return &iDescriptor->second; + else + return nullptr; +} + +OUString SAL_CALL ContextChangeEventMultiplexer::getImplementationName() +{ + return "org.apache.openoffice.comp.framework.ContextChangeEventMultiplexer"; +} + +sal_Bool SAL_CALL ContextChangeEventMultiplexer::supportsService ( const OUString& rsServiceName) +{ + return cppu::supportsService(this, rsServiceName); +} + +css::uno::Sequence<OUString> SAL_CALL ContextChangeEventMultiplexer::getSupportedServiceNames() +{ + // it's a singleton, not a service + return css::uno::Sequence<OUString>(); +} + +void SAL_CALL ContextChangeEventMultiplexer::disposing ( const css::lang::EventObject& rEvent) +{ + ListenerMap::iterator iDescriptor (maListeners.find(rEvent.Source)); + + if (iDescriptor == maListeners.end()) + { + OSL_ASSERT(iDescriptor != maListeners.end()); + return; + } + + // Should we notify the remaining listeners? + + maListeners.erase(iDescriptor); +} + +} + +namespace framework { + +// right now we assume there's one matching listener +static uno::Reference<ui::XContextChangeEventListener> GetFirstListenerWith_ImplImpl( + css::uno::Reference<css::uno::XComponentContext> const & xComponentContext, + uno::Reference<uno::XInterface> const& xEventFocus, + std::function<bool (uno::Reference<ui::XContextChangeEventListener> const&)> const& rPredicate) +{ + assert(xEventFocus.is()); // in current usage it's a bug if the XController is null here + uno::Reference<ui::XContextChangeEventListener> xRet; + + rtl::Reference<ContextChangeEventMultiplexer> pMultiplexer = + dynamic_cast<ContextChangeEventMultiplexer *>(ui::ContextChangeEventMultiplexer::get(xComponentContext).get()); + assert(pMultiplexer); + + ContextChangeEventMultiplexer::FocusDescriptor const*const pFocusDescriptor( + pMultiplexer->GetFocusDescriptor(xEventFocus, false)); + if (!pFocusDescriptor) + return xRet; + + for (auto & xListener : pFocusDescriptor->maListeners) + { + if (rPredicate(xListener)) + { + assert(!xRet.is()); // generalize this if it is used for more than 1:1 mapping? + xRet = xListener; + } + } + return xRet; +} + +namespace { + +struct Hook +{ + Hook() { g_pGetMultiplexerListener = &GetFirstListenerWith_ImplImpl; } + ~Hook() { g_pGetMultiplexerListener = nullptr; } +}; + +Hook g_hook; + +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +org_apache_openoffice_comp_framework_ContextChangeEventMultiplexer_get_implementation( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new ContextChangeEventMultiplexer()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/autorecovery.cxx b/framework/source/services/autorecovery.cxx new file mode 100644 index 000000000..073f7094c --- /dev/null +++ b/framework/source/services/autorecovery.cxx @@ -0,0 +1,4243 @@ +/* -*- 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 <config_features.h> + +#include <loadenv/loadenv.hxx> + +#include <strings.hrc> +#include <classes/fwkresid.hxx> +#include <properties.h> +#include <targets.h> + +#include <helper/mischelper.hxx> + +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp> +#include <com/sun/star/frame/XLoadable.hpp> +#include <com/sun/star/frame/XModel3.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XTitle.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/util/XModifiable.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/frame/XDesktop.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/util/XChangesNotifier.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/document/XDocumentRecovery.hpp> +#include <com/sun/star/document/XExtendedFilterDetection.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/awt/XWindow2.hpp> +#include <com/sun/star/task/XStatusIndicatorFactory.hpp> +#include <com/sun/star/task/ErrorCodeIOException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/XTypeProvider.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/document/XDocumentEventListener.hpp> +#include <com/sun/star/document/XDocumentEventBroadcaster.hpp> +#include <com/sun/star/util/XChangesListener.hpp> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <com/sun/star/util/XModifyListener.hpp> + +#include <comphelper/configuration.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/propshlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <o3tl/string_view.hxx> +#include <unotools/mediadescriptor.hxx> +#include <comphelper/multiinterfacecontainer3.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <comphelper/sequence.hxx> +#include <utility> +#include <vcl/evntpost.hxx> +#include <vcl/svapp.hxx> +#include <vcl/timer.hxx> +#include <unotools/pathoptions.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/tempfile.hxx> +#include <ucbhelper/content.hxx> +#include <svtools/sfxecode.hxx> + +#include <vcl/weld.hxx> +#include <osl/file.hxx> +#include <sal/log.hxx> +#include <unotools/bootstrap.hxx> +#include <unotools/configmgr.hxx> +#include <svl/documentlockfile.hxx> +#include <tools/urlobj.hxx> +#include <officecfg/Office/Common.hxx> +#include <officecfg/Office/Recovery.hxx> +#include <officecfg/Setup.hxx> + +using namespace css::uno; +using namespace css::document; +using namespace css::frame; +using namespace css::lang; +using namespace framework; + +namespace { + +/** @short hold all needed information for an asynchronous dispatch alive. + + @descr Because some operations are forced to be executed asynchronously + (e.g. requested by our CrashSave/Recovery dialog) ... we must make sure + that this information won't be set as "normal" members of our AutoRecovery + instance. Otherwise they can disturb our normal AutoSave-timer handling. + e.g. it can be unclear then, which progress has to be used for storing documents... + */ +struct DispatchParams +{ +public: + DispatchParams(); + DispatchParams(const ::comphelper::SequenceAsHashMap& lArgs , + const css::uno::Reference< css::uno::XInterface >& xOwner); + + void forget(); + +public: + + /** @short can be set from outside and is provided to + our internal started operations. + + @descr Normally we use the normal status indicator + of the document windows to show a progress. + But in case we are used by any special UI, + it can provide its own status indicator object + to us - so we use it instead of the normal one. + */ + css::uno::Reference< css::task::XStatusIndicator > m_xProgress; + + /** TODO document me */ + OUString m_sSavePath; + + /** @short define the current cache entry, which should be used for current + backup or cleanUp operation ... which is may be done asynchronous */ + sal_Int32 m_nWorkingEntryID; + + /** @short used for async operations, to prevent us from dying. + + @descr If our dispatch() method was forced to start the + internal operation asynchronous... we send an event + to start and return immediately. But we must be sure that + our instance live if the event callback reach us. + So we hold a uno reference to ourself. + */ + css::uno::Reference< css::uno::XInterface > m_xHoldRefForAsyncOpAlive; +}; + +/** These values are used as flags and represent the current state of a document. + Every state of the life time of a document has to be recognized here. + + @attention Do not change (means reorganize) already used numbers. + There exists some code inside SVX, which uses the same numbers, + to analyze such document states. + Not the best design ... but may be it will be changed later .-) +*/ +enum class DocState: sal_Int32 +{ + /* TEMP STATES */ + + /// default state, if a document was new created or loaded + Unknown = 0, + /// modified against the original file + Modified = 1, + /// an active document can be postponed to be saved later. + Postponed = 2, + /// was already handled during one AutoSave/Recovery session. + Handled = 4, + /** an action was started (saving/loading) ... Can be interesting later if the process may be was interrupted by an exception. */ + TrySave = 8, + TryLoadBackup = 16, + TryLoadOriginal = 32, + + /* FINAL STATES */ + + /// the Auto/Emergency saved document is not usable any longer + Damaged = 64, + /// the Auto/Emergency saved document is not really up-to-date (some changes can be missing) + Incomplete = 128, + /// the Auto/Emergency saved document was processed successfully + Succeeded = 512 +}; + +} + +template<> struct o3tl::typed_flags<DocState>: o3tl::is_typed_flags<DocState, 0x2FF> {}; + +namespace { + +// TODO document me ... flag field +// Emergency_Save and Recovery overwrites Auto_Save! +enum class Job +{ + NoJob = 0, + AutoSave = 1, + EmergencySave = 2, + Recovery = 4, + EntryBackup = 8, + EntryCleanup = 16, + PrepareEmergencySave = 32, + SessionSave = 64, + SessionRestore = 128, + DisableAutorecovery = 256, + SetAutosaveState = 512, + SessionQuietQuit = 1024, + UserAutoSave = 2048 +}; + +} + +template<> struct o3tl::typed_flags<Job>: o3tl::is_typed_flags<Job, 0xFFF> {}; + +namespace { + +/** + implements the functionality of AutoSave and AutoRecovery + of documents - including features of an EmergencySave in + case a GPF occurs. + */ +typedef ::cppu::WeakComponentImplHelper< + css::lang::XServiceInfo, + css::frame::XDispatch, + css::document::XDocumentEventListener, // => css.lang.XEventListener + css::util::XChangesListener, // => css.lang.XEventListener + css::util::XModifyListener > // => css.lang.XEventListener + AutoRecovery_BASE; + +class AutoRecovery : private cppu::BaseMutex + , public AutoRecovery_BASE + , public ::cppu::OPropertySetHelper // => XPropertySet, XFastPropertySet, XMultiPropertySet +{ +public: + /** @short indicates the results of a FAILURE_SAFE operation + + @descr We must know, which reason was the real one in case + we couldn't copy a "failure document" to a user specified path. + We must know, if we can forget our cache entry or not. + */ + enum EFailureSafeResult + { + E_COPIED, + E_ORIGINAL_FILE_MISSING, + E_WRONG_TARGET_PATH + }; + + // TODO document me + enum ETimerType + { + /** the timer should not be used next time */ + E_DONT_START_TIMER, + /** timer (was/must be) started with normal AutoSaveTimeIntervall */ + E_NORMAL_AUTOSAVE_INTERVALL, + /** timer must be started with special short time interval, + to poll for an user idle period */ + E_POLL_FOR_USER_IDLE, + /** timer must be started with a very(!) short time interval, + to poll for the end of an user action, which does not allow saving documents in general */ + E_POLL_TILL_AUTOSAVE_IS_ALLOWED, + /** don't start the timer - but calls the same action then before immediately again! */ + E_CALL_ME_BACK + }; + + /** @short combine different information about one office document. */ + struct TDocumentInfo + { + public: + + TDocumentInfo() + : DocumentState (DocState::Unknown) + , UsedForSaving (false) + , ListenForModify (false) + , IgnoreClosing (false) + , ID (-1 ) + {} + + /** @short points to the document. */ + css::uno::Reference< css::frame::XModel > Document; + + /** @short knows, if the document is really modified since the last autosave, + or was postponed, because it was an active one etcpp... + + @descr Because we have no CHANGE TRACKING mechanism, based on office document, + we implements it by ourself. We listen for MODIFIED events + of each document and update this state flag here. + + Further we postpone saving of active documents, e.g. if the user + works currently on it. We wait for an idle period then... + */ + DocState DocumentState; + + /** Because our applications not ready for concurrent save requests at the same time, + we have suppress our own AutoSave for the moment, a document will be already saved + by others. + */ + bool UsedForSaving; + + /** For every user action, which modifies a document (e.g. key input) we get + a notification as XModifyListener. That seems to be a "performance issue" .-) + So we decided to listen for such modify events only for the time in which the document + was stored as temp. file and was not modified again by the user. + */ + bool ListenForModify; + + /** For SessionSave we must close all open documents by ourself. + But because we are listen for documents events, we get some ... + and deregister these documents from our configuration. + That's why we mark these documents as "Closed by ourself" so we can + ignore these "OnUnload" or disposing() events .-) + */ + bool IgnoreClosing; + + /** TODO: document me */ + OUString OrgURL; + OUString FactoryURL; + OUString TemplateURL; + + OUString OldTempURL; + OUString NewTempURL; + + OUString AppModule; // e.g. com.sun.star.text.TextDocument - used to identify app module + OUString FactoryService; // the service to create a document of the module + OUString RealFilter; // real filter, which was used at loading time + OUString DefaultFilter; // supports saving of the default format without losing data + OUString Extension; // file extension of the default filter + OUString Title; // can be used as "DisplayName" on every recovery UI! + css::uno::Sequence< OUString > + ViewNames; // names of the view which were active at emergency-save time + + sal_Int32 ID; + }; + + /** @short used to know every currently open document. */ + typedef ::std::vector< TDocumentInfo > TDocumentList; + +// member + +private: + + /** @short the global uno service manager. + @descr Must be used to create own needed services. + */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + /** @short points to the underlying recovery configuration. + @descr This instance does not cache - it calls directly the + configuration API! + */ + css::uno::Reference< css::container::XNameAccess > m_xRecoveryCFG; + + /** @short proxy weak binding to forward Events to ourself without + an ownership cycle + */ + css::uno::Reference< css::util::XChangesListener > m_xRecoveryCFGListener; + + /** @short points to the used configuration package or.openoffice.Setup + @descr This instance does not cache - it calls directly the + configuration API! + */ + css::uno::Reference< css::container::XNameAccess > m_xModuleCFG; + + /** @short holds the global event broadcaster alive, + where we listen for new created documents. + */ + css::uno::Reference< css::frame::XGlobalEventBroadcaster > m_xNewDocBroadcaster; + + /** @short proxy weak binding to forward Events to ourself without + an ownership cycle + */ + css::uno::Reference< css::document::XDocumentEventListener > m_xNewDocBroadcasterListener; + + /** @short because we stop/restart listening sometimes, it's a good idea to know + if we already registered as listener .-) + */ + bool m_bListenForDocEvents; + bool m_bListenForConfigChanges; + + /** @short specify the time interval between two save actions. + @descr tools::Time is measured in [min]. + */ + sal_Int32 m_nAutoSaveTimeIntervall; + + /** @short for an asynchronous operation we must know, if there is + at least one running job (may be asynchronous!). + */ + Job m_eJob; + + /** @short the timer, which is used to be informed about the next + saving time ... + @remark must lock SolarMutex to use + */ + Timer m_aTimer; + + /** @short make our dispatch asynchronous ... if required to do so! */ + std::unique_ptr<vcl::EventPoster> m_xAsyncDispatcher; + + /** @see DispatchParams + */ + DispatchParams m_aDispatchParams; + + /** @short indicates, which time period is currently used by the + internal timer. + */ + ETimerType m_eTimerType; + + /** @short this cache is used to hold all information about + recovery/emergency save documents alive. + */ + TDocumentList m_lDocCache; + + // TODO document me + sal_Int32 m_nIdPool; + + /** @short contains all status listener registered at this instance. + */ + comphelper::OMultiTypeInterfaceContainerHelperVar3<css::frame::XStatusListener, OUString> m_lListener; + + /** @descr This member is used to prevent us against re-entrance problems. + A mutex can't help to prevent us from concurrent using of members + inside the same thread. But e.g. our internally used stl structures + are not threadsafe ... and furthermore they can't be used at the same time + for iteration and add/remove requests! + So we have to detect such states and ... show a warning. + May be there will be a better solution next time ... (copying the cache temp. + bevor using). + + And further it's not possible to use a simple boolean value here. + Because if more than one operation iterates over the same stl container ... + (only to modify it's elements but don't add new or removing existing ones!) + it should be possible doing so. But we must guarantee that the last operation reset + this lock ... not the first one ! So we use a "ref count" mechanism for that." + */ + sal_Int32 m_nDocCacheLock; + + /** @descr These members are used to check the minimum disc space, which must exists + to start the corresponding operation. + */ + sal_Int32 m_nMinSpaceDocSave; + sal_Int32 m_nMinSpaceConfigSave; + +// interface + +public: + + explicit AutoRecovery(css::uno::Reference< css::uno::XComponentContext > xContext); + virtual ~AutoRecovery( ) override; + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.AutoRecovery"; + } + + 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.frame.AutoRecovery"}; + } + + // XInterface + virtual void SAL_CALL acquire() noexcept override + { OWeakObject::acquire(); } + virtual void SAL_CALL release() noexcept override + { OWeakObject::release(); } + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& type) override; + + /// Initialization function after having acquire()'d. + void initListeners(); + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + + // css.frame.XDispatch + virtual void SAL_CALL dispatch(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments) override; + + virtual void SAL_CALL addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener, + const css::util::URL& aURL ) override; + + virtual void SAL_CALL removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener, + const css::util::URL& aURL ) override; + + // css.document.XDocumentEventListener + /** @short informs about created/opened documents. + + @descr Every new opened/created document will be saved internally + so it can be checked if it's modified. This modified state + is used later to decide, if it must be saved or not. + + @param aEvent + points to the new created/opened document. + */ + virtual void SAL_CALL documentEventOccured(const css::document::DocumentEvent& aEvent) override; + + // css.util.XChangesListener + virtual void SAL_CALL changesOccurred(const css::util::ChangesEvent& aEvent) override; + + // css.util.XModifyListener + virtual void SAL_CALL modified(const css::lang::EventObject& aEvent) override; + + // css.lang.XEventListener + virtual void SAL_CALL disposing(const css::lang::EventObject& aEvent) override; + +protected: + + // OPropertySetHelper + + virtual sal_Bool SAL_CALL convertFastPropertyValue( css::uno::Any& aConvertedValue, + css::uno::Any& aOldValue , + sal_Int32 nHandle , + const css::uno::Any& aValue ) override; + + virtual void SAL_CALL setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, + const css::uno::Any& aValue ) override; + using cppu::OPropertySetHelper::getFastPropertyValue; + virtual void SAL_CALL getFastPropertyValue(css::uno::Any& aValue , + sal_Int32 nHandle) const override; + + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + +private: + virtual void SAL_CALL disposing() final override; + + /** @short open the underlying configuration. + + @descr This method must be called every time + a configuration call is needed. Because + method works together with the member + m_xCFG, open it on demand and cache it + afterwards. + + @throw [com.sun.star.uno.RuntimeException] + if config could not be opened successfully! + + @threadsafe + */ + void implts_openConfig(); + + /** @short read the underlying configuration. + + @descr After that we know the initial state - means: + - if AutoSave was enabled by the user + - which time interval has to be used + - which recovery entries may already exists + + @throw [com.sun.star.uno.RuntimeException] + if config could not be opened or read successfully! + + @threadsafe + */ + void implts_readConfig(); + + /** @short read the underlying configuration... + + @descr ... but only keys related to the AutoSave mechanism. + Means: State and Timer interval. + E.g. the recovery list is not addressed here. + + @throw [com.sun.star.uno.RuntimeException] + if config could not be opened or read successfully! + + @threadsafe + */ + void implts_readAutoSaveConfig(); + + // TODO document me + void implts_flushConfigItem(const AutoRecovery::TDocumentInfo& rInfo , + bool bRemoveIt = false); + + // TODO document me + void implts_startListening(); + void implts_startModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo); + + // TODO document me + void implts_stopListening(); + void implts_stopModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo); + + /** @short stops and may be(!) restarts the timer. + + @descr A running timer is stopped every time here. + But starting depends from the different internal + timer variables (e.g. AutoSaveEnabled, AutoSaveTimeIntervall, + TimerType etcpp.) + + @throw [com.sun.star.uno.RuntimeException] + if timer could not be stopped or started! + + @threadsafe + */ + void implts_updateTimer(); + + /** @short stop the timer. + + @descr Double calls will be ignored - means we do + nothing here, if the timer is already disabled. + + @throw [com.sun.star.uno.RuntimeException] + if timer could not be stopped! + + @threadsafe + */ + void implts_stopTimer(); + + /** @short callback of our internal timer. + */ + DECL_LINK(implts_timerExpired, Timer*, void); + + /** @short makes our dispatch() method asynchronous! + */ + DECL_LINK(implts_asyncDispatch, LinkParamNone*, void); + + /** @short implements the dispatch real. */ + void implts_dispatch(const DispatchParams& aParams); + + /** @short validate new detected document and add it into the internal + document list. + + @descr This method should be called only if it's clear that a new + document was opened/created during office runtime. + This method checks if it's a top level document (means not an embedded one). + Only such top level documents can be recognized by this auto save mechanism. + + @param xDocument + the new document, which should be checked and registered. + + @threadsafe + */ + void implts_registerDocument(const css::uno::Reference< css::frame::XModel3 >& xDocument); + + /** @short remove the specified document from our internal document list. + + @param xDocument + the new document, which should be deregistered. + + @param bStopListening + sal_False: must be used in case this method is called within disposing() of the document, + where it makes no sense to deregister our listener. The container dies... + sal_True : must be used in case this method is used on "deregistration" of this document, where + we must deregister our listener .-) + + @threadsafe + */ + void implts_deregisterDocument(const css::uno::Reference< css::frame::XModel >& xDocument , + bool bStopListening = true); + + // TODO document me + void implts_markDocumentModifiedAgainstLastBackup(const css::uno::Reference< css::frame::XModel >& xDocument); + + // TODO document me + void implts_updateModifiedState(const css::uno::Reference< css::frame::XModel >& xDocument); + + // TODO document me + void implts_updateDocumentUsedForSavingState(const css::uno::Reference< css::frame::XModel >& xDocument , + bool bSaveInProgress); + + // TODO document me + void implts_markDocumentAsSaved(const css::uno::Reference< css::frame::XModel >& xDocument); + + /** @short search a document inside given list. + + @param rList + reference to a vector, which can contain such + document. + + @param xDocument + the document, which should be located inside the + given list. + + @return [TDocumentList::iterator] + which points to the located document. + If document does not exists - it's set to + rList.end()! + */ + static TDocumentList::iterator impl_searchDocument( AutoRecovery::TDocumentList& rList , + const css::uno::Reference< css::frame::XModel >& xDocument); + + /** TODO document me */ + void implts_changeAllDocVisibility(bool bVisible); + void implts_prepareSessionShutdown(); + + /** @short save all current opened documents to a specific + backup directory. + + @descr Only really changed documents will be saved here. + + Further this method returns a suggestion, if and how it should + be called again. May be some documents was not saved yet + and must wait for an user idle period ... + + @param bAllowUserIdleLoop + Because this method is used for different uses cases, it must + know, which actions are allowed or not. + Job::AutoSave => + If a document is the most active one, saving it + will be postponed if there exists other unsaved + documents. This feature was implemented, because + we don't wish to disturb the user on it's work. + ... bAllowUserIdleLoop should be set to sal_True + Job::EmergencySave / Job::SessionSave => + Here we must finish our work ASAP! It's not allowed + to postpone any document. + ... bAllowUserIdleLoop must(!) be set to sal_False + + @param pParams + sometimes this method is required inside an external dispatch request. + The it contains some special environment variables, which overwrites + our normal environment. + AutoSave => pParams == 0 + SessionSave/CrashSave => pParams != 0 + + @return A suggestion, how the timer (if it's not already disabled!) + should be restarted to fulfill the requirements. + + @threadsafe + */ + AutoRecovery::ETimerType implts_saveDocs( bool bAllowUserIdleLoop, + bool bRemoveLockFiles, + const DispatchParams* pParams = nullptr); + + /** @short save one of the current documents to a specific + backup directory. + + @descr It: + - defines a new(!) unique temp file name + - save the new temp file + - remove the old temp file + - patch the given info struct + - and return errors. + + It does not: + - patch the configuration. + + Note further: it patches the info struct + more than ones. E.g. the new temp URL is set + before the file is saved. And the old URL is removed + only if removing of the old file was successful. + If this method returns without an exception - everything + was OK. Otherwise the info struct can be analyzed to + get more information, e.g. when the problem occurs. + + @param sBackupPath + the base path for saving such temp files. + + @param rInfo + points to an information structure, where + e.g. the document, its modified state, the count + of autosave-retries etcpp. exists. + It's used also to return the new temp file name + and some other state values! + + @threadsafe + */ + void implts_saveOneDoc(const OUString& sBackupPath , + AutoRecovery::TDocumentInfo& rInfo , + const css::uno::Reference< css::task::XStatusIndicator >& xExternalProgress); + + /** @short recovery all documents, which was saved during + a crash before. + + @return A suggestion, how this method must be called back! + + @threadsafe + */ + AutoRecovery::ETimerType implts_openDocs(const DispatchParams& aParams); + + // TODO document me + void implts_openOneDoc(const OUString& sURL , + utl::MediaDescriptor& lDescriptor, + AutoRecovery::TDocumentInfo& rInfo ); + + // TODO document me + void implts_generateNewTempURL(const OUString& sBackupPath , + utl::MediaDescriptor& rMediaDescriptor, + AutoRecovery::TDocumentInfo& rInfo ); + + /** @short notifies all interested listener about the current state + of the currently running operation. + + @descr We support different set's of functions. Job::AutoSave, Job::EmergencySave, + Job::Recovery ... etcpp. + Listener can register itself for any type of supported + functionality ... but not for document URL's in special. + + @param eJob + is used to know, which set of listener we must notify. + + @param aEvent + describe the event more in detail. + + @threadsafe + */ + void implts_informListener( Job eJob , + const css::frame::FeatureStateEvent& aEvent); + + /** short create a feature event struct, which can be send + to any interested listener. + + @param eJob + describe the current running operation + Job::AutoSave, Job::EmergencySave, Job::Recovery + + @param sEventType + describe the type of this event + START, STOP, UPDATE + + @param pInfo + if sOperation is an update, this parameter must be different from NULL + and is used to send information regarding the current handled document. + + @return [css::frame::FeatureStateEvent] + the event structure for sending. + */ + static css::frame::FeatureStateEvent implst_createFeatureStateEvent( Job eJob , + const OUString& sEventType, + AutoRecovery::TDocumentInfo const * pInfo ); + + class ListenerInformer + { + private: + AutoRecovery &m_rRecovery; + Job m_eJob; + bool m_bStopped; + public: + ListenerInformer(AutoRecovery &rRecovery, Job eJob) + : m_rRecovery(rRecovery), m_eJob(eJob), m_bStopped(false) + { + } + void start(); + void stop(); + ~ListenerInformer() + { + stop(); + } + }; + + // TODO document me + void implts_resetHandleStates(); + + // TODO document me + void implts_specifyDefaultFilterAndExtension(AutoRecovery::TDocumentInfo& rInfo); + + // TODO document me + void implts_specifyAppModuleAndFactory(AutoRecovery::TDocumentInfo& rInfo); + + /** retrieves the names of all active views of the given document + @param rInfo + the document info, whose <code>Document</code> member must not be <NULL/>. + */ + void implts_collectActiveViewNames( AutoRecovery::TDocumentInfo& rInfo ); + + /** updates the configuration so that for all documents, their current view/names are stored + */ + void implts_persistAllActiveViewNames(); + + // TODO document me + void implts_prepareEmergencySave(); + + // TODO document me + void implts_doEmergencySave(const DispatchParams& aParams); + + // TODO document me + void implts_doRecovery(const DispatchParams& aParams); + + // TODO document me + void implts_doSessionSave(const DispatchParams& aParams); + + // TODO document me + void implts_doSessionQuietQuit(); + + // TODO document me + void implts_doSessionRestore(const DispatchParams& aParams); + + // TODO document me + void implts_backupWorkingEntry(const DispatchParams& aParams); + + // TODO document me + void implts_cleanUpWorkingEntry(const DispatchParams& aParams); + + /** try to make sure that all changed config items (not our used + config access only) will be flushed back to disc. + + E.g. our svtools::ConfigItems() has to be flushed explicitly .-( + + Note: This method can't fail. Flushing of config entries is an + optional feature. Errors can be ignored. + */ + void impl_flushALLConfigChanges(); + + // TODO document me + AutoRecovery::EFailureSafeResult implts_copyFile(const OUString& sSource , + const OUString& sTargetPath, + const OUString& sTargetName); + + /** @short converts m_eJob into a job description, which + can be used to inform an outside listener + about the current running operation + + @param eJob + describe the current running operation + Job::AutoSave, Job::EmergencySave, Job::Recovery + + @return [string] + a suitable job description of form: + vnd.sun.star.autorecovery:/do... + */ + static OUString implst_getJobDescription(Job eJob); + + /** @short map the given URL to an internal int representation. + + @param aURL + the url, which describe the next starting or may be already running + operation. + + @return [long] + the internal int representation + see enum class Job + */ + static Job implst_classifyJob(const css::util::URL& aURL); + + /// TODO + void implts_verifyCacheAgainstDesktopDocumentList(); + + /// TODO document me + bool impl_enoughDiscSpace(sal_Int32 nRequiredSpace); + + /// TODO document me + static void impl_showFullDiscError(); + + /** @short try to create/use a progress and set it inside the + environment. + + @descr The problem behind: There exists different use case of this method. + a) An external progress is provided by our CrashSave or Recovery dialog. + b) We must create our own progress e.g. for an AutoSave + c) Sometimes our application filters don't use the progress + provided by the MediaDescriptor. They use the Frame every time to create + its own progress. So we implemented a HACK for these and now we set + an InterceptedProgress there for the time WE use this frame for loading/storing documents .-) + + @param xNewFrame + must be set only in case WE create a new frame (e.g. for loading documents + on session restore or recovery). Then search for a frame using rInfo.Document must + be suppressed and xFrame must be preferred instead .-) + + @param rInfo + used e.g. to find the frame corresponding to a document. + This frame must be used to create a new progress e.g. for an AutoSave. + + @param rArgs + is used to set the new created progress as parameter on these set. + */ + void impl_establishProgress(const AutoRecovery::TDocumentInfo& rInfo , + utl::MediaDescriptor& rArgs , + const css::uno::Reference< css::frame::XFrame >& xNewFrame); + + void impl_forgetProgress(const AutoRecovery::TDocumentInfo& rInfo , + utl::MediaDescriptor& rArgs , + const css::uno::Reference< css::frame::XFrame >& xNewFrame); + + /** try to remove the specified file from disc. + + Every URL supported by our UCB component can be used here. + Further it doesn't matter if the file really exists or not. + Because removing a non existent file will have the same + result at the end... a non existing file .-) + + On the other side removing of files from disc is an optional + feature. If we are not able doing so... it's not a real problem. + Ok - users disc place will be smaller then... but we should produce + a crash during crash save because we can't delete a temporary file only! + + @param sURL + the url of the file, which should be removed. + */ + void st_impl_removeFile(const OUString& sURL); + + /** try to remove ".lock" file from disc if office will be terminated + not using the official way .-) + + This method has to be handled "optional". So every error inside + has to be ignored ! This method CAN NOT FAIL ... it can forget something only .-) + */ + void st_impl_removeLockFile(); +}; + +// recovery.xcu +constexpr OUStringLiteral CFG_PACKAGE_RECOVERY = u"org.openoffice.Office.Recovery/"; + +const char CFG_ENTRY_AUTOSAVE_ENABLED[] = "AutoSave/Enabled"; +const char CFG_ENTRY_AUTOSAVE_TIMEINTERVALL[] = "AutoSave/TimeIntervall"; //sic! + +constexpr OUStringLiteral CFG_ENTRY_REALDEFAULTFILTER = u"ooSetupFactoryActualFilter"; + +constexpr OUStringLiteral CFG_ENTRY_PROP_TEMPURL = u"TempURL"; +constexpr OUStringLiteral CFG_ENTRY_PROP_ORIGINALURL = u"OriginalURL"; +constexpr OUStringLiteral CFG_ENTRY_PROP_TEMPLATEURL = u"TemplateURL"; +constexpr OUStringLiteral CFG_ENTRY_PROP_FACTORYURL = u"FactoryURL"; +constexpr OUStringLiteral CFG_ENTRY_PROP_MODULE = u"Module"; +constexpr OUStringLiteral CFG_ENTRY_PROP_DOCUMENTSTATE = u"DocumentState"; +constexpr OUStringLiteral CFG_ENTRY_PROP_FILTER = u"Filter"; +constexpr OUStringLiteral CFG_ENTRY_PROP_TITLE = u"Title"; +constexpr OUStringLiteral CFG_ENTRY_PROP_ID = u"ID"; +constexpr OUStringLiteral CFG_ENTRY_PROP_VIEWNAMES = u"ViewNames"; + +constexpr OUStringLiteral FILTER_PROP_TYPE = u"Type"; +constexpr OUStringLiteral TYPE_PROP_EXTENSIONS = u"Extensions"; + +// setup.xcu +constexpr OUStringLiteral CFG_ENTRY_PROP_EMPTYDOCUMENTURL = u"ooSetupFactoryEmptyDocumentURL"; +constexpr OUStringLiteral CFG_ENTRY_PROP_FACTORYSERVICE = u"ooSetupFactoryDocumentService"; + +const char EVENT_ON_NEW[] = "OnNew"; +const char EVENT_ON_LOAD[] = "OnLoad"; +const char EVENT_ON_UNLOAD[] = "OnUnload"; +const char EVENT_ON_MODIFYCHANGED[] = "OnModifyChanged"; +const char EVENT_ON_SAVE[] = "OnSave"; +const char EVENT_ON_SAVEAS[] = "OnSaveAs"; +const char EVENT_ON_SAVETO[] = "OnCopyTo"; +const char EVENT_ON_SAVEDONE[] = "OnSaveDone"; +const char EVENT_ON_SAVEASDONE[] = "OnSaveAsDone"; +const char EVENT_ON_SAVETODONE[] = "OnCopyToDone"; +const char EVENT_ON_SAVEFAILED[] = "OnSaveFailed"; +const char EVENT_ON_SAVEASFAILED[] = "OnSaveAsFailed"; +const char EVENT_ON_SAVETOFAILED[] = "OnCopyToFailed"; + +constexpr OUStringLiteral RECOVERY_ITEM_BASE_IDENTIFIER = u"recovery_item_"; + +const char CMD_PROTOCOL[] = "vnd.sun.star.autorecovery:"; + +const char CMD_DO_AUTO_SAVE[] = "/doAutoSave"; // force AutoSave ignoring the AutoSave timer +const char CMD_DO_PREPARE_EMERGENCY_SAVE[] = "/doPrepareEmergencySave"; // prepare the office for the following EmergencySave step (hide windows etcpp.) +const char CMD_DO_EMERGENCY_SAVE[] = "/doEmergencySave"; // do EmergencySave on crash +const char CMD_DO_RECOVERY[] = "/doAutoRecovery"; // recover all crashed documents +const char CMD_DO_ENTRY_BACKUP[] = "/doEntryBackup"; // try to store a temp or original file to a user defined location +const char CMD_DO_ENTRY_CLEANUP[] = "/doEntryCleanUp"; // remove the specified entry from the recovery cache +const char CMD_DO_SESSION_SAVE[] = "/doSessionSave"; // save all open documents if e.g. a window manager closes an user session +const char CMD_DO_SESSION_QUIET_QUIT[] = "/doSessionQuietQuit"; // let the current session be quietly closed ( the saving should be done using doSessionSave previously ) if e.g. a window manager closes an user session +const char CMD_DO_SESSION_RESTORE[] = "/doSessionRestore"; // restore a saved user session from disc +const char CMD_DO_DISABLE_RECOVERY[] = "/disableRecovery"; // disable recovery and auto save (!) temp. for this office session +const char CMD_DO_SET_AUTOSAVE_STATE[] = "/setAutoSaveState"; // disable/enable auto save (not crash save) for this office session + +constexpr OUStringLiteral REFERRER_USER = u"private:user"; + +constexpr OUStringLiteral PROP_DISPATCH_ASYNCHRON = u"DispatchAsynchron"; +constexpr OUStringLiteral PROP_PROGRESS = u"StatusIndicator"; +constexpr OUStringLiteral PROP_SAVEPATH = u"SavePath"; +constexpr OUStringLiteral PROP_ENTRY_ID = u"EntryID"; +constexpr OUStringLiteral PROP_AUTOSAVE_STATE = u"AutoSaveState"; + +constexpr OUStringLiteral OPERATION_START = u"start"; +constexpr OUStringLiteral OPERATION_STOP = u"stop"; +constexpr OUStringLiteral OPERATION_UPDATE = u"update"; + +const sal_Int32 MIN_DISCSPACE_DOCSAVE = 5; // [MB] +const sal_Int32 MIN_DISCSPACE_CONFIGSAVE = 1; // [MB] +const sal_Int32 RETRY_STORE_ON_FULL_DISC_FOREVER = 300; // not forever ... but often enough .-) +const sal_Int32 RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL = 3; // in case FULL DISC does not seem the real problem +const sal_Int32 GIVE_UP_RETRY = 1; // in case FULL DISC does not seem the real problem + +#define SAVE_IN_PROGRESS true +#define SAVE_FINISHED false + +#define LOCK_FOR_CACHE_ADD_REMOVE true +#define LOCK_FOR_CACHE_USE false + +#define MIN_TIME_FOR_USER_IDLE 10000 // 10s user idle + +// enable the following defines in case you wish to simulate a full disc for debug purposes .-) + +// this define throws every time a document is stored or a configuration change +// should be flushed an exception ... so the special error handler for this scenario is triggered +// #define TRIGGER_FULL_DISC_CHECK + +// force "return sal_False" for the method impl_enoughDiscSpace(). +// #define SIMULATE_FULL_DISC + +class CacheLockGuard +{ + private: + + // holds the outside caller alive, so it's shared resources + // are valid every time + css::uno::Reference< css::uno::XInterface > m_xOwner; + + // mutex shared with outside caller! + osl::Mutex& m_rSharedMutex; + + // this variable knows the state of the "cache lock" + sal_Int32& m_rCacheLock; + + // to prevent increasing/decreasing of m_rCacheLock more than once + // we must know if THIS guard has an actual lock set there! + bool m_bLockedByThisGuard; + + public: + + CacheLockGuard(AutoRecovery* pOwner , + osl::Mutex& rMutex , + sal_Int32& rCacheLock , + bool bLockForAddRemoveVectorItems); + ~CacheLockGuard(); + + void lock(bool bLockForAddRemoveVectorItems); + void unlock(); +}; + +CacheLockGuard::CacheLockGuard(AutoRecovery* pOwner , + osl::Mutex& rMutex , + sal_Int32& rCacheLock , + bool bLockForAddRemoveVectorItems) + : m_xOwner (static_cast< css::frame::XDispatch* >(pOwner)) + , m_rSharedMutex (rMutex ) + , m_rCacheLock (rCacheLock ) + , m_bLockedByThisGuard(false ) +{ + lock(bLockForAddRemoveVectorItems); +} + +CacheLockGuard::~CacheLockGuard() +{ + unlock(); + m_xOwner.clear(); +} + +void CacheLockGuard::lock(bool bLockForAddRemoveVectorItems) +{ + /* SAFE */ + osl::MutexGuard g(m_rSharedMutex); + + if (m_bLockedByThisGuard) + return; + + // This cache lock is needed only to prevent us from removing/adding + // items from/into the recovery cache ... during it's used at another code place + // for iterating .-) + + // Modifying of item properties is allowed and sometimes needed! + // So we should detect only the dangerous state of concurrent add/remove + // requests and throw an exception then ... which can of course break the whole + // operation. On the other side a crash reasoned by an invalid stl iterator + // will have the same effect .-) + + if ( (m_rCacheLock > 0) && bLockForAddRemoveVectorItems ) + { + OSL_FAIL("Re-entrance problem detected. Using of an stl structure in combination with iteration, adding, removing of elements etcpp."); + throw css::uno::RuntimeException( + "Re-entrance problem detected. Using of an stl structure in combination with iteration, adding, removing of elements etcpp.", + m_xOwner); + } + + ++m_rCacheLock; + m_bLockedByThisGuard = true; + /* SAFE */ +} + +void CacheLockGuard::unlock() +{ + /* SAFE */ + osl::MutexGuard g(m_rSharedMutex); + + if ( ! m_bLockedByThisGuard) + return; + + --m_rCacheLock; + m_bLockedByThisGuard = false; + + if (m_rCacheLock < 0) + { + OSL_FAIL("Wrong using of member m_nDocCacheLock detected. A ref counted value shouldn't reach values <0 .-)"); + throw css::uno::RuntimeException( + "Wrong using of member m_nDocCacheLock detected. A ref counted value shouldn't reach values <0 .-)", + m_xOwner); + } + /* SAFE */ +} + +DispatchParams::DispatchParams() + : m_nWorkingEntryID(-1) +{ +}; + +DispatchParams::DispatchParams(const ::comphelper::SequenceAsHashMap& lArgs , + const css::uno::Reference< css::uno::XInterface >& xOwner) +{ + m_nWorkingEntryID = lArgs.getUnpackedValueOrDefault(PROP_ENTRY_ID, sal_Int32(-1) ); + m_xProgress = lArgs.getUnpackedValueOrDefault(PROP_PROGRESS, css::uno::Reference< css::task::XStatusIndicator >()); + m_sSavePath = lArgs.getUnpackedValueOrDefault(PROP_SAVEPATH, OUString() ); + m_xHoldRefForAsyncOpAlive = xOwner; +}; + +void DispatchParams::forget() +{ + m_sSavePath.clear(); + m_nWorkingEntryID = -1; + m_xProgress.clear(); + m_xHoldRefForAsyncOpAlive.clear(); +}; + +AutoRecovery::AutoRecovery(css::uno::Reference< css::uno::XComponentContext > xContext) + : AutoRecovery_BASE (m_aMutex) + , ::cppu::OPropertySetHelper(cppu::WeakComponentImplHelperBase::rBHelper) + , m_xContext (std::move(xContext )) + , m_bListenForDocEvents (false ) + , m_bListenForConfigChanges (false ) + , m_nAutoSaveTimeIntervall (0 ) + , m_eJob (Job::NoJob) + , m_aTimer( "framework::AutoRecovery m_aTimer" ) + , m_xAsyncDispatcher (new vcl::EventPoster( LINK( this, AutoRecovery, implts_asyncDispatch ) )) + , m_eTimerType (E_DONT_START_TIMER ) + , m_nIdPool (0 ) + , m_lListener (cppu::WeakComponentImplHelperBase::rBHelper.rMutex) + , m_nDocCacheLock (0 ) + , m_nMinSpaceDocSave (MIN_DISCSPACE_DOCSAVE ) + , m_nMinSpaceConfigSave (MIN_DISCSPACE_CONFIGSAVE ) +{ +} + +void AutoRecovery::initListeners() +{ + // read configuration to know if autosave/recovery is on/off etcpp... + implts_readConfig(); + + implts_startListening(); + + // establish callback for our internal used timer. + // Note: Its only active, if the timer will be started ... + SolarMutexGuard g; + m_aTimer.SetInvokeHandler(LINK(this, AutoRecovery, implts_timerExpired)); +} + +AutoRecovery::~AutoRecovery() +{ + assert(!m_aTimer.IsActive()); +} + +void AutoRecovery::disposing() +{ + implts_stopTimer(); + SolarMutexGuard g; + m_xAsyncDispatcher.reset(); +} + +Any SAL_CALL AutoRecovery::queryInterface( const css::uno::Type& _rType ) +{ + Any aRet = AutoRecovery_BASE::queryInterface( _rType ); + if ( !aRet.hasValue() ) + aRet = OPropertySetHelper::queryInterface( _rType ); + return aRet; +} + +Sequence< css::uno::Type > SAL_CALL AutoRecovery::getTypes( ) +{ + return comphelper::concatSequences( + AutoRecovery_BASE::getTypes(), + ::cppu::OPropertySetHelper::getTypes() + ); +} + +void SAL_CALL AutoRecovery::dispatch(const css::util::URL& aURL , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments) +{ + SAL_INFO("fwk.autorecovery", "AutoRecovery::dispatch() starts ..." << aURL.Complete); + + // valid request ? + Job eNewJob = AutoRecovery::implst_classifyJob(aURL); + if (eNewJob == Job::NoJob) + return; + + bool bAsync; + DispatchParams aParams; + /* SAFE */ { + osl::ClearableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + // still running operation ... ignoring Job::AutoSave. + // All other requests has higher prio! + if ( + ( m_eJob != Job::NoJob ) && + ((m_eJob & Job::AutoSave ) != Job::AutoSave) + ) + { + SAL_INFO("fwk.autorecovery", "AutoRecovery::dispatch(): There is already an asynchronous dispatch() running. New request will be ignored!"); + return; + } + + ::comphelper::SequenceAsHashMap lArgs(lArguments); + + // check if somewhere wish to disable recovery temp. for this office session + // This can be done immediately... must not been done asynchronous. + if ((eNewJob & Job::DisableAutorecovery) == Job::DisableAutorecovery) + { + // it's important to set a flag internally, so AutoRecovery will be suppressed - even if it's requested. + m_eJob |= eNewJob; + implts_stopTimer(); + implts_stopListening(); + return; + } + + // disable/enable AutoSave for this office session only + // independent from the configuration entry. + if ((eNewJob & Job::SetAutosaveState) == Job::SetAutosaveState) + { + bool bOn = lArgs.getUnpackedValueOrDefault(PROP_AUTOSAVE_STATE, true); + if (bOn) + { + // don't enable AutoSave hardly ! + // reload configuration to know the current state. + implts_readAutoSaveConfig(); + g.clear(); + implts_updateTimer(); + // can it happen that might be the listener was stopped? .-) + // make sure it runs always... even if AutoSave itself was disabled temporarily. + implts_startListening(); + } + else + { + implts_stopTimer(); + m_eJob &= ~Job::AutoSave; + m_eTimerType = AutoRecovery::E_DONT_START_TIMER; + } + return; + } + + m_eJob |= eNewJob; + + bAsync = lArgs.getUnpackedValueOrDefault(PROP_DISPATCH_ASYNCHRON, false); + aParams = DispatchParams(lArgs, static_cast< css::frame::XDispatch* >(this)); + + // Hold this instance alive till the asynchronous operation will be finished. + if (bAsync) + m_aDispatchParams = aParams; + + } /* SAFE */ + + if (bAsync) + m_xAsyncDispatcher->Post(); + else + implts_dispatch(aParams); +} + +void AutoRecovery::ListenerInformer::start() +{ + m_rRecovery.implts_informListener(m_eJob, + AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_START, nullptr)); +} + +void AutoRecovery::ListenerInformer::stop() +{ + if (m_bStopped) + return; + m_rRecovery.implts_informListener(m_eJob, + AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_STOP, nullptr)); + m_bStopped = true; +} + +void AutoRecovery::implts_dispatch(const DispatchParams& aParams) +{ + Job eJob; + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + eJob = m_eJob; + } /* SAFE */ + + // in case a new dispatch overwrites a may ba active AutoSave session + // we must restore this session later. see below ... + bool bWasAutoSaveActive = ((eJob & Job::AutoSave) == Job::AutoSave); + bool bWasUserAutoSaveActive = + ((eJob & Job::UserAutoSave) == Job::UserAutoSave); + + // On the other side it makes no sense to reactivate the AutoSave operation + // if the new dispatch indicates a final decision... + // E.g. an EmergencySave/SessionSave indicates the end of life of the current office session. + // It makes no sense to reactivate an AutoSave then. + // But a Recovery or SessionRestore should reactivate a may be already active AutoSave. + bool bAllowAutoSaveReactivation = true; + + implts_stopTimer(); + implts_stopListening(); + + ListenerInformer aListenerInformer(*this, eJob); + aListenerInformer.start(); + + try + { + // Auto save is called from our internal timer ... not via dispatch() API ! + // else + if ( + ((eJob & Job::PrepareEmergencySave) == Job::PrepareEmergencySave) && + ((eJob & Job::DisableAutorecovery ) != Job::DisableAutorecovery ) + ) + { + SAL_INFO("fwk.autorecovery", "... prepare emergency save ..."); + bAllowAutoSaveReactivation = false; + implts_prepareEmergencySave(); + } + else + if ( + ((eJob & Job::EmergencySave ) == Job::EmergencySave ) && + ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery) + ) + { + SAL_INFO("fwk.autorecovery", "... do emergency save ..."); + bAllowAutoSaveReactivation = false; + implts_doEmergencySave(aParams); + } + else + if ( + ((eJob & Job::Recovery ) == Job::Recovery ) && + ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery) + ) + { + SAL_INFO("fwk.autorecovery", "... do recovery ..."); + implts_doRecovery(aParams); + } + else + if ( + ((eJob & Job::SessionSave ) == Job::SessionSave ) && + ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery) + ) + { + SAL_INFO("fwk.autorecovery", "... do session save ..."); + bAllowAutoSaveReactivation = false; + implts_doSessionSave(aParams); + } + else + if ( + ((eJob & Job::SessionQuietQuit ) == Job::SessionQuietQuit ) && + ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery) + ) + { + SAL_INFO("fwk.autorecovery", "... do session quiet quit ..."); + bAllowAutoSaveReactivation = false; + implts_doSessionQuietQuit(); + } + else + if ( + ((eJob & Job::SessionRestore ) == Job::SessionRestore ) && + ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery) + ) + { + SAL_INFO("fwk.autorecovery", "... do session restore ..."); + implts_doSessionRestore(aParams); + } + else + if ( + ((eJob & Job::EntryBackup ) == Job::EntryBackup ) && + ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery) + ) + implts_backupWorkingEntry(aParams); + else + if ( + ((eJob & Job::EntryCleanup ) == Job::EntryCleanup ) && + ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery) + ) + implts_cleanUpWorkingEntry(aParams); + } + catch(const css::uno::RuntimeException&) + { + throw; + } + catch(const css::uno::Exception&) + { + // TODO better error handling + } + + aListenerInformer.stop(); + + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_eJob = Job::NoJob; + if ( bAllowAutoSaveReactivation && bWasAutoSaveActive ) + { + m_eJob |= Job::AutoSave; + + if (bWasUserAutoSaveActive) + { + m_eJob |= Job::UserAutoSave; + } + } + + } /* SAFE */ + + // depends on bAllowAutoSaveReactivation implicitly by looking on m_eJob=Job::AutoSave! see before ... + implts_updateTimer(); + + if (bAllowAutoSaveReactivation) + implts_startListening(); +} + +void SAL_CALL AutoRecovery::addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener, + const css::util::URL& aURL ) +{ + if (!xListener.is()) + throw css::uno::RuntimeException("Invalid listener reference.", static_cast< css::frame::XDispatch* >(this)); + // container is threadsafe by using a shared mutex! + m_lListener.addInterface(aURL.Complete, xListener); + + // REENTRANT !? -> -------------------------------- + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + /* SAFE */ { + osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + for (auto const& elem : m_lDocCache) + { + css::frame::FeatureStateEvent aEvent = AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_UPDATE, &elem); + + // } /* SAFE */ + g.clear(); + xListener->statusChanged(aEvent); + g.reset(); + // /* SAFE */ { + } + + } /* SAFE */ +} + +void SAL_CALL AutoRecovery::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener, + const css::util::URL& aURL ) +{ + if (!xListener.is()) + throw css::uno::RuntimeException("Invalid listener reference.", static_cast< css::frame::XDispatch* >(this)); + // container is threadsafe by using a shared mutex! + m_lListener.removeInterface(aURL.Complete, xListener); +} + +void SAL_CALL AutoRecovery::documentEventOccured(const css::document::DocumentEvent& aEvent) +{ + css::uno::Reference< css::frame::XModel3 > xDocument(aEvent.Source, css::uno::UNO_QUERY); + + // new document => put it into the internal list + if ( + (aEvent.EventName == EVENT_ON_NEW) || + (aEvent.EventName == EVENT_ON_LOAD) + ) + { + implts_registerDocument(xDocument); + } + // document modified => set its modify state new (means modified against the original file!) + else if ( aEvent.EventName == EVENT_ON_MODIFYCHANGED ) + { + implts_updateModifiedState(xDocument); + } + /* at least one document starts saving process => + Our application code is not ready for multiple save requests + at the same time. So we have to suppress our AutoSave feature + for the moment, till this other save requests will be finished. + */ + else if ( + (aEvent.EventName == EVENT_ON_SAVE) || + (aEvent.EventName == EVENT_ON_SAVEAS) || + (aEvent.EventName == EVENT_ON_SAVETO) + ) + { + implts_updateDocumentUsedForSavingState(xDocument, SAVE_IN_PROGRESS); + } + // document saved => remove tmp. files - but hold config entries alive! + else if ( + (aEvent.EventName == EVENT_ON_SAVEDONE) || + (aEvent.EventName == EVENT_ON_SAVEASDONE) + ) + { + SolarMutexGuard g; + implts_markDocumentAsSaved(xDocument); + implts_updateDocumentUsedForSavingState(xDocument, SAVE_FINISHED); + } + /* document saved as copy => mark it as "non used by concurrent save operation". + so we can try to create a backup copy if next time AutoSave is started too. + Don't remove temp. files or change the modified state of the document! + It was not really saved to the original file... + */ + else if ( aEvent.EventName == EVENT_ON_SAVETODONE ) + { + implts_updateDocumentUsedForSavingState(xDocument, SAVE_FINISHED); + } + // If saving of a document failed by an error ... we have to save this document + // by ourself next time AutoSave or EmergencySave is triggered. + // But we can reset the state "used for other save requests". Otherwise + // these documents will never be saved! + else if ( + (aEvent.EventName == EVENT_ON_SAVEFAILED) || + (aEvent.EventName == EVENT_ON_SAVEASFAILED) || + (aEvent.EventName == EVENT_ON_SAVETOFAILED) + ) + { + implts_updateDocumentUsedForSavingState(xDocument, SAVE_FINISHED); + } + // document closed => remove temp. files and configuration entries + else if ( aEvent.EventName == EVENT_ON_UNLOAD ) + { + implts_deregisterDocument(xDocument); // sal_True => stop listening for disposing() ! + } +} + +void SAL_CALL AutoRecovery::changesOccurred(const css::util::ChangesEvent& aEvent) +{ + const css::uno::Sequence< css::util::ElementChange > lChanges (aEvent.Changes); + const css::util::ElementChange* pChanges = lChanges.getConstArray(); + + sal_Int32 c = lChanges.getLength(); + sal_Int32 i = 0; + + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + // Changes of the configuration must be ignored if AutoSave/Recovery was disabled for this + // office session. That can happen if e.g. the command line arguments "--norestore" or "--headless" + // was set. + if ((m_eJob & Job::DisableAutorecovery) == Job::DisableAutorecovery) + return; + + for (i=0; i<c; ++i) + { + OUString sPath; + pChanges[i].Accessor >>= sPath; + + if ( sPath == CFG_ENTRY_AUTOSAVE_ENABLED ) + { + bool bEnabled = false; + if (pChanges[i].Element >>= bEnabled) + { + if (bEnabled) + { + m_eJob |= Job::AutoSave; + m_eTimerType = AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL; + } + else + { + m_eJob &= ~Job::AutoSave; + m_eTimerType = AutoRecovery::E_DONT_START_TIMER; + } + } + } + else + if ( sPath == CFG_ENTRY_AUTOSAVE_TIMEINTERVALL ) + pChanges[i].Element >>= m_nAutoSaveTimeIntervall; + } + + } /* SAFE */ + + // Note: This call stops the timer and starts it again. + // But it checks the different timer states internally and + // may be suppress the restart! + implts_updateTimer(); +} + +void SAL_CALL AutoRecovery::modified(const css::lang::EventObject& aEvent) +{ + css::uno::Reference< css::frame::XModel > xDocument(aEvent.Source, css::uno::UNO_QUERY); + if (! xDocument.is()) + return; + + implts_markDocumentModifiedAgainstLastBackup(xDocument); +} + +void SAL_CALL AutoRecovery::disposing(const css::lang::EventObject& aEvent) +{ + /* SAFE */ + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + if (aEvent.Source == m_xNewDocBroadcaster) + { + m_xNewDocBroadcaster.clear(); + return; + } + + if (aEvent.Source == m_xRecoveryCFG) + { + m_xRecoveryCFG.clear(); + return; + } + + // dispose from one of our cached documents ? + // Normally they should send a OnUnload message ... + // But some stacktraces shows another possible use case .-) + css::uno::Reference< css::frame::XModel > xDocument(aEvent.Source, css::uno::UNO_QUERY); + if (xDocument.is()) + { + implts_deregisterDocument(xDocument, false); // sal_False => don't call removeEventListener() .. because it's not needed here + return; + } + + /* SAFE */ +} + +void AutoRecovery::implts_openConfig() +{ + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + if (m_xRecoveryCFG.is()) + return; + } /* SAFE */ + + css::uno::Reference<css::lang::XMultiServiceFactory> xConfigProvider( + css::configuration::theDefaultProvider::get(m_xContext)); + + std::vector<css::uno::Any> lParams; + css::beans::PropertyValue aParam; + + // set root path + aParam.Name = "nodepath"; + aParam.Value <<= OUString(CFG_PACKAGE_RECOVERY); + lParams.push_back(css::uno::Any(aParam)); + + // throws a RuntimeException if an error occurs! + css::uno::Reference<css::container::XNameAccess> xCFG( + xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", + comphelper::containerToSequence(lParams)), + css::uno::UNO_QUERY); + + sal_Int32 nMinSpaceDocSave = MIN_DISCSPACE_DOCSAVE; + sal_Int32 nMinSpaceConfigSave = MIN_DISCSPACE_CONFIGSAVE; + + try + { + nMinSpaceDocSave = officecfg::Office::Recovery::AutoSave::MinSpaceDocSave::get(); + nMinSpaceConfigSave = officecfg::Office::Recovery::AutoSave::MinSpaceConfigSave::get(); + } + catch(const css::uno::Exception&) + { + // These config keys are not sooooo important, that + // we are interested on errors here really .-) + nMinSpaceDocSave = MIN_DISCSPACE_DOCSAVE; + nMinSpaceConfigSave = MIN_DISCSPACE_CONFIGSAVE; + } + + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_xRecoveryCFG = xCFG; + m_nMinSpaceDocSave = nMinSpaceDocSave; + m_nMinSpaceConfigSave = nMinSpaceConfigSave; + } /* SAFE */ +} + +void AutoRecovery::implts_readAutoSaveConfig() +{ + implts_openConfig(); + + // AutoSave [bool] + bool bEnabled(officecfg::Office::Common::Save::Document::AutoSave::get()); + + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + if (bEnabled) + { + bool bUserEnabled(officecfg::Office::Recovery::AutoSave::UserAutoSaveEnabled::get()); + + m_eJob |= Job::AutoSave; + m_eTimerType = AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL; + + if (bUserEnabled) + { + m_eJob |= Job::UserAutoSave; + } + else + { + m_eJob &= ~Job::UserAutoSave; + } + } + else + { + m_eJob &= ~Job::AutoSave; + m_eTimerType = AutoRecovery::E_DONT_START_TIMER; + } + } /* SAFE */ + + // AutoSaveTimeIntervall [int] in min + sal_Int32 nTimeIntervall( + officecfg::Office::Common::Save::Document::AutoSaveTimeIntervall::get()); + + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_nAutoSaveTimeIntervall = nTimeIntervall; + } /* SAFE */ +} + +void AutoRecovery::implts_readConfig() +{ + implts_readAutoSaveConfig(); + + // REENTRANT -> -------------------------------- + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_ADD_REMOVE); + + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + // reset current cache load cache + m_lDocCache.clear(); + m_nIdPool = 0; + } /* SAFE */ + + aCacheLock.unlock(); + // <- REENTRANT -------------------------------- + + css::uno::Reference<css::container::XNameAccess> xRecoveryList( + officecfg::Office::Recovery::RecoveryList::get()); + const OUString sRECOVERY_ITEM_BASE_IDENTIFIER(RECOVERY_ITEM_BASE_IDENTIFIER); + const css::uno::Sequence< OUString > lItems = xRecoveryList->getElementNames(); + const OUString* pItems = lItems.getConstArray(); + sal_Int32 c = lItems.getLength(); + sal_Int32 i = 0; + + // REENTRANT -> -------------------------- + aCacheLock.lock(LOCK_FOR_CACHE_ADD_REMOVE); + + for (i=0; i<c; ++i) + { + css::uno::Reference< css::beans::XPropertySet > xItem; + xRecoveryList->getByName(pItems[i]) >>= xItem; + if (!xItem.is()) + continue; + + AutoRecovery::TDocumentInfo aInfo; + aInfo.NewTempURL.clear(); + aInfo.Document.clear(); + xItem->getPropertyValue(CFG_ENTRY_PROP_ORIGINALURL) >>= aInfo.OrgURL; + xItem->getPropertyValue(CFG_ENTRY_PROP_TEMPURL) >>= aInfo.OldTempURL; + xItem->getPropertyValue(CFG_ENTRY_PROP_TEMPLATEURL) >>= aInfo.TemplateURL; + xItem->getPropertyValue(CFG_ENTRY_PROP_FILTER) >>= aInfo.RealFilter; + sal_Int32 tmp = 0; + xItem->getPropertyValue(CFG_ENTRY_PROP_DOCUMENTSTATE) >>= tmp; + aInfo.DocumentState = DocState(tmp); + xItem->getPropertyValue(CFG_ENTRY_PROP_MODULE) >>= aInfo.AppModule; + xItem->getPropertyValue(CFG_ENTRY_PROP_TITLE) >>= aInfo.Title; + xItem->getPropertyValue(CFG_ENTRY_PROP_VIEWNAMES) >>= aInfo.ViewNames; + implts_specifyAppModuleAndFactory(aInfo); + implts_specifyDefaultFilterAndExtension(aInfo); + + if (pItems[i].startsWith(sRECOVERY_ITEM_BASE_IDENTIFIER)) + { + std::u16string_view sID = pItems[i].subView(sRECOVERY_ITEM_BASE_IDENTIFIER.getLength()); + aInfo.ID = o3tl::toInt32(sID); + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + if (aInfo.ID > m_nIdPool) + { + m_nIdPool = aInfo.ID+1; + SAL_WARN_IF(m_nIdPool<0, "fwk.autorecovery", "AutoRecovery::implts_readConfig(): Overflow of IDPool detected!"); + } + } /* SAFE */ + } + else + SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_readConfig(): Who changed numbering of recovery items? Cache will be inconsistent then! I do not know, what will happen next time .-)"); + + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_lDocCache.push_back(aInfo); + } /* SAFE */ + } + + aCacheLock.unlock(); + // <- REENTRANT -------------------------- + + implts_updateTimer(); +} + +void AutoRecovery::implts_specifyDefaultFilterAndExtension(AutoRecovery::TDocumentInfo& rInfo) +{ + if (rInfo.AppModule.isEmpty()) + { + throw css::uno::RuntimeException( + "Can not find out the default filter and its extension, if no application module is known!", + static_cast< css::frame::XDispatch* >(this)); + } + + css::uno::Reference< css::container::XNameAccess> xCFG; + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + xCFG = m_xModuleCFG; + } /* SAFE */ + + try + { + if (! xCFG.is()) + { + implts_openConfig(); + // open module config on demand and cache the update access + xCFG.set(officecfg::Setup::Office::Factories::get(), + css::uno::UNO_SET_THROW); + + /* SAFE */ { + osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_xModuleCFG = xCFG; + } /* SAFE */ + } + + css::uno::Reference< css::container::XNameAccess > xModuleProps( + xCFG->getByName(rInfo.AppModule), + css::uno::UNO_QUERY_THROW); + + xModuleProps->getByName(CFG_ENTRY_REALDEFAULTFILTER) >>= rInfo.DefaultFilter; + + css::uno::Reference< css::container::XNameAccess > xFilterCFG( + m_xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.document.FilterFactory", m_xContext), css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::container::XNameAccess > xTypeCFG( + m_xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.document.TypeDetection", m_xContext), css::uno::UNO_QUERY_THROW); + + ::comphelper::SequenceAsHashMap lFilterProps (xFilterCFG->getByName(rInfo.DefaultFilter)); + OUString sTypeRegistration = lFilterProps.getUnpackedValueOrDefault(FILTER_PROP_TYPE, OUString()); + ::comphelper::SequenceAsHashMap lTypeProps (xTypeCFG->getByName(sTypeRegistration)); + css::uno::Sequence< OUString > lExtensions = lTypeProps.getUnpackedValueOrDefault(TYPE_PROP_EXTENSIONS, css::uno::Sequence< OUString >()); + if (lExtensions.hasElements()) + { + rInfo.Extension = "." + lExtensions[0]; + } + else + rInfo.Extension = ".unknown"; + } + catch(const css::uno::Exception&) + { + rInfo.DefaultFilter.clear(); + rInfo.Extension.clear(); + } +} + +void AutoRecovery::implts_specifyAppModuleAndFactory(AutoRecovery::TDocumentInfo& rInfo) +{ + ENSURE_OR_THROW2( + !rInfo.AppModule.isEmpty() || rInfo.Document.is(), + "Can not find out the application module nor its factory URL, if no application module (or a suitable) document is known!", + *this ); + + css::uno::Reference< css::frame::XModuleManager2 > xManager = ModuleManager::create(m_xContext); + + if (rInfo.AppModule.isEmpty()) + rInfo.AppModule = xManager->identify(rInfo.Document); + + ::comphelper::SequenceAsHashMap lModuleDescription(xManager->getByName(rInfo.AppModule)); + lModuleDescription[OUString(CFG_ENTRY_PROP_EMPTYDOCUMENTURL)] >>= rInfo.FactoryURL; + lModuleDescription[OUString(CFG_ENTRY_PROP_FACTORYSERVICE)] >>= rInfo.FactoryService; +} + +void AutoRecovery::implts_collectActiveViewNames( AutoRecovery::TDocumentInfo& i_rInfo ) +{ + ENSURE_OR_THROW2( i_rInfo.Document.is(), "need at document, at the very least", *this ); + + i_rInfo.ViewNames.realloc(0); + + // obtain list of controllers of this document + ::std::vector< OUString > aViewNames; + const Reference< XModel2 > xModel( i_rInfo.Document, UNO_QUERY ); + if ( xModel.is() ) + { + const Reference< css::container::XEnumeration > xEnumControllers( xModel->getControllers() ); + while ( xEnumControllers->hasMoreElements() ) + { + const Reference< XController2 > xController( xEnumControllers->nextElement(), UNO_QUERY ); + OUString sViewName; + if ( xController.is() ) + sViewName = xController->getViewControllerName(); + OSL_ENSURE( !sViewName.isEmpty(), "AutoRecovery::implts_collectActiveViewNames: (no XController2 ->) no view name -> no recovery of this view!" ); + + if ( !sViewName.isEmpty() ) + aViewNames.push_back( sViewName ); + } + } + + i_rInfo.ViewNames.realloc( aViewNames.size() ); + ::std::copy( aViewNames.begin(), aViewNames.end(), i_rInfo.ViewNames.getArray() ); +} + +void AutoRecovery::implts_persistAllActiveViewNames() +{ + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + // This list will be filled with every document + for (auto & elem : m_lDocCache) + { + implts_collectActiveViewNames(elem); + implts_flushConfigItem(elem); + } +} + +void AutoRecovery::implts_flushConfigItem(const AutoRecovery::TDocumentInfo& rInfo, bool bRemoveIt) +{ + std::shared_ptr<comphelper::ConfigurationChanges> batch( + comphelper::ConfigurationChanges::create()); + + try + { + implts_openConfig(); + + css::uno::Reference<css::container::XNameAccess> xCheck( + officecfg::Office::Recovery::RecoveryList::get(batch)); + + css::uno::Reference< css::container::XNameContainer > xModify(xCheck, css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::lang::XSingleServiceFactory > xCreate(xCheck, css::uno::UNO_QUERY_THROW); + + OUString sID = RECOVERY_ITEM_BASE_IDENTIFIER + OUString::number(rInfo.ID); + + // remove + if (bRemoveIt) + { + // Catch NoSuchElementException. + // It's not a good idea inside multithreaded environments to call hasElement - removeElement. + // DO IT! + try + { + xModify->removeByName(sID); + } + catch(const css::container::NoSuchElementException&) + { + return; + } + } + else + { + // new/modify + css::uno::Reference< css::beans::XPropertySet > xSet; + bool bNew = !xCheck->hasByName(sID); + if (bNew) + xSet.set(xCreate->createInstance(), css::uno::UNO_QUERY_THROW); + else + xCheck->getByName(sID) >>= xSet; + + xSet->setPropertyValue(CFG_ENTRY_PROP_ORIGINALURL, css::uno::Any(rInfo.OrgURL )); + xSet->setPropertyValue(CFG_ENTRY_PROP_TEMPURL, css::uno::Any(rInfo.OldTempURL )); + xSet->setPropertyValue(CFG_ENTRY_PROP_TEMPLATEURL, css::uno::Any(rInfo.TemplateURL )); + xSet->setPropertyValue(CFG_ENTRY_PROP_FILTER, css::uno::Any(rInfo.RealFilter)); + xSet->setPropertyValue(CFG_ENTRY_PROP_DOCUMENTSTATE, css::uno::Any(sal_Int32(rInfo.DocumentState))); + xSet->setPropertyValue(CFG_ENTRY_PROP_MODULE, css::uno::Any(rInfo.AppModule)); + xSet->setPropertyValue(CFG_ENTRY_PROP_TITLE, css::uno::Any(rInfo.Title)); + xSet->setPropertyValue(CFG_ENTRY_PROP_VIEWNAMES, css::uno::Any(rInfo.ViewNames)); + + if (bNew) + xModify->insertByName(sID, css::uno::Any(xSet)); + } + } + catch(const css::uno::RuntimeException&) + { + throw; + } + catch(const css::uno::Exception&) + { + // ??? can it happen that a full disc let these set of operations fail too ??? + } + + sal_Int32 nRetry = RETRY_STORE_ON_FULL_DISC_FOREVER; + do + { + try + { + batch->commit(); + +#ifdef TRIGGER_FULL_DISC_CHECK + throw css::uno::Exception("trigger full disk check"); +#else // TRIGGER_FULL_DISC_CHECK + nRetry = 0; +#endif // TRIGGER_FULL_DISC_CHECK + } + catch(const css::uno::Exception&) + { + // a) FULL DISC seems to be the problem behind => show error and retry it forever (e.g. retry=300) + // b) unknown problem (may be locking problem) => reset RETRY value to more useful value(!) (e.g. retry=3) + // c) unknown problem (may be locking problem) + 1..2 repeating operations => throw the original exception to force generation of a stacktrace ! + + sal_Int32 nMinSpaceConfigSave; + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + nMinSpaceConfigSave = m_nMinSpaceConfigSave; + } /* SAFE */ + + if (! impl_enoughDiscSpace(nMinSpaceConfigSave)) + AutoRecovery::impl_showFullDiscError(); + else if (nRetry > RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL) + nRetry = RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL; + else if (nRetry <= GIVE_UP_RETRY) + throw; // force stacktrace to know if there exist might other reasons, why an AutoSave can fail !!! + + --nRetry; + } + } + while(nRetry>0); +} + +void AutoRecovery::implts_startListening() +{ + css::uno::Reference< css::util::XChangesNotifier > xCFG; + css::uno::Reference< css::frame::XGlobalEventBroadcaster > xBroadcaster; + bool bListenForDocEvents; + bool bListenForConfigChanges; + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + xCFG.set (m_xRecoveryCFG, css::uno::UNO_QUERY); + xBroadcaster = m_xNewDocBroadcaster; + bListenForDocEvents = m_bListenForDocEvents; + bListenForConfigChanges = m_bListenForConfigChanges; + } /* SAFE */ + + if ( + ( xCFG.is() ) && + (! bListenForConfigChanges) + ) + { + css::uno::Reference<css::util::XChangesListener> const xListener( + new WeakChangesListener(this)); + xCFG->addChangesListener(xListener); + /* SAFE */ { + osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_xRecoveryCFGListener = xListener; + m_bListenForConfigChanges = true; + } /* SAFE */ + } + + if (!xBroadcaster.is()) + { + xBroadcaster = css::frame::theGlobalEventBroadcaster::get(m_xContext); + /* SAFE */ { + osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_xNewDocBroadcaster = xBroadcaster; + } /* SAFE */ + } + + if ( + ( xBroadcaster.is() ) && + (! bListenForDocEvents) + ) + { + css::uno::Reference<css::document::XDocumentEventListener> const + xListener(new WeakDocumentEventListener(this)); + xBroadcaster->addDocumentEventListener(xListener); + /* SAFE */ { + osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_xNewDocBroadcasterListener = xListener; + m_bListenForDocEvents = true; + } /* SAFE */ + } +} + +void AutoRecovery::implts_stopListening() +{ + css::uno::Reference< css::util::XChangesNotifier > xCFG; + css::uno::Reference< css::document::XDocumentEventBroadcaster > xGlobalEventBroadcaster; + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + // Attention: Don't reset our internal members here too. + // May be we must work with our configuration, but don't wish to be informed + // about changes any longer. Needed e.g. during Job::EmergencySave! + xCFG.set (m_xRecoveryCFG , css::uno::UNO_QUERY); + xGlobalEventBroadcaster = m_xNewDocBroadcaster; + } /* SAFE */ + + if (xGlobalEventBroadcaster.is() && m_bListenForDocEvents) + { + xGlobalEventBroadcaster->removeDocumentEventListener(m_xNewDocBroadcasterListener); + m_bListenForDocEvents = false; + } + + if (xCFG.is() && m_bListenForConfigChanges) + { + xCFG->removeChangesListener(m_xRecoveryCFGListener); + m_bListenForConfigChanges = false; + } +} + +void AutoRecovery::implts_startModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo) +{ + if (rInfo.ListenForModify) + return; + + css::uno::Reference< css::util::XModifyBroadcaster > xBroadcaster(rInfo.Document, css::uno::UNO_QUERY); + if (xBroadcaster.is()) + { + css::uno::Reference< css::util::XModifyListener > xThis(this); + xBroadcaster->addModifyListener(xThis); + rInfo.ListenForModify = true; + } +} + +void AutoRecovery::implts_stopModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo) +{ + if (! rInfo.ListenForModify) + return; + + css::uno::Reference< css::util::XModifyBroadcaster > xBroadcaster(rInfo.Document, css::uno::UNO_QUERY); + if (xBroadcaster.is()) + { + css::uno::Reference< css::util::XModifyListener > xThis(this); + xBroadcaster->removeModifyListener(xThis); + rInfo.ListenForModify = false; + } +} + +void AutoRecovery::implts_updateTimer() +{ + implts_stopTimer(); + + sal_Int32 nMilliSeconds = 0; + + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + if ( + (m_eJob == Job::NoJob ) || // TODO may be superfluous - E_DONT_START_TIMER should be used only + (m_eTimerType == AutoRecovery::E_DONT_START_TIMER) + ) + return; + + if (m_eTimerType == AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL) + { + nMilliSeconds = (m_nAutoSaveTimeIntervall*60000); // [min] => 60.000 ms + } + else if (m_eTimerType == AutoRecovery::E_POLL_FOR_USER_IDLE) + { + nMilliSeconds = MIN_TIME_FOR_USER_IDLE; + } + else if (m_eTimerType == AutoRecovery::E_POLL_TILL_AUTOSAVE_IS_ALLOWED) + nMilliSeconds = 300; // there is a minimum time frame, where the user can lose some key input data! + + + } /* SAFE */ + + SolarMutexGuard g; + m_aTimer.SetTimeout(nMilliSeconds); + m_aTimer.Start(); +} + +void AutoRecovery::implts_stopTimer() +{ + SolarMutexGuard g; + + if (!m_aTimer.IsActive()) + return; + m_aTimer.Stop(); +} + +IMPL_LINK_NOARG(AutoRecovery, implts_timerExpired, Timer *, void) +{ + try + { + // This method is called by using a pointer to us. + // But we must be aware that we can be destroyed hardly + // if our uno reference will be gone! + // => Hold this object alive till this method finish its work. + css::uno::Reference< css::uno::XInterface > xSelfHold(static_cast< css::lang::XTypeProvider* >(this)); + + // Needed! Otherwise every reschedule request allow a new triggered timer event :-( + implts_stopTimer(); + + // The timer must be ignored if AutoSave/Recovery was disabled for this + // office session. That can happen if e.g. the command line arguments "--norestore" or "--headless" + // was set. But normally the timer was disabled if recovery was disabled ... + // But so we are more "safe" .-) + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + if ((m_eJob & Job::DisableAutorecovery) == Job::DisableAutorecovery) + return; + } /* SAFE */ + + // check some "states", where it's not allowed (better: not a good idea) to + // start an AutoSave. (e.g. if the user makes drag & drop ...) + // Then we poll till this "disallowed" state is gone. + bool bAutoSaveNotAllowed = Application::IsUICaptured(); + if (bAutoSaveNotAllowed) + { + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_eTimerType = AutoRecovery::E_POLL_TILL_AUTOSAVE_IS_ALLOWED; + } /* SAFE */ + implts_updateTimer(); + return; + } + + // analyze timer type. + // If we poll for an user idle period, may be we must + // do nothing here and start the timer again. + /* SAFE */ { + osl::ClearableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + if (m_eTimerType == AutoRecovery::E_POLL_FOR_USER_IDLE) + { + bool bUserIdle = Application::GetLastInputInterval() > MIN_TIME_FOR_USER_IDLE; + if (!bUserIdle) + { + g.clear(); + implts_updateTimer(); + return; + } + } + + } /* SAFE */ + + implts_informListener(Job::AutoSave, + AutoRecovery::implst_createFeatureStateEvent(Job::AutoSave, OPERATION_START, nullptr)); + + // force save of all currently open documents + // The called method returns an info, if and how this + // timer must be restarted. + AutoRecovery::ETimerType eSuggestedTimer = implts_saveDocs(true/*bAllowUserIdleLoop*/, false); + + // If timer is not used for "short callbacks" (means polling + // for special states) ... reset the handle state of all + // cache items. Such handle state indicates, that a document + // was already saved during the THIS(!) AutoSave session. + // Of course NEXT AutoSave session must be started without + // any "handle" state ... + if ( + (eSuggestedTimer == AutoRecovery::E_DONT_START_TIMER ) || + (eSuggestedTimer == AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL) + ) + { + implts_resetHandleStates(); + } + + implts_informListener(Job::AutoSave, + AutoRecovery::implst_createFeatureStateEvent(Job::AutoSave, OPERATION_STOP, nullptr)); + + // restart timer - because it was disabled before ... + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_eTimerType = eSuggestedTimer; + } /* SAFE */ + + implts_updateTimer(); + } + catch(const css::uno::Exception&) + { + } +} + +IMPL_LINK_NOARG(AutoRecovery, implts_asyncDispatch, LinkParamNone*, void) +{ + DispatchParams aParams; + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + aParams = m_aDispatchParams; + css::uno::Reference< css::uno::XInterface > xHoldRefForMethodAlive = aParams.m_xHoldRefForAsyncOpAlive; + m_aDispatchParams.forget(); // clears all members ... including the ref-hold object .-) + } /* SAFE */ + + try + { + implts_dispatch(aParams); + } + catch (...) + { + } +} + +void AutoRecovery::implts_registerDocument(const css::uno::Reference< css::frame::XModel3 > & xDocument) +{ + // ignore corrupted events, where no document is given ... Runtime Error ?! + if (!xDocument.is()) + return; + + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + // notification for already existing document ! + // Can happen if events came in asynchronous on recovery time. + // Then our cache was filled from the configuration ... but now we get some + // asynchronous events from the global event broadcaster. We must be sure that + // we don't add the same document more than once. + AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument); + if (pIt != m_lDocCache.end()) + { + // Normally nothing must be done for this "late" notification. + // But may be the modified state was changed inbetween. + // Check it... + implts_updateModifiedState(xDocument); + return; + } + + aCacheLock.unlock(); + + utl::MediaDescriptor lDescriptor(xDocument->getArgs2( { utl::MediaDescriptor::PROP_FILTERNAME, utl::MediaDescriptor::PROP_NOAUTOSAVE } )); + + // check if this document must be ignored for recovery ! + // Some use cases don't wish support for AutoSave/Recovery ... as e.g. OLE-Server / ActiveX Control etcpp. + bool bNoAutoSave = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_NOAUTOSAVE, false); + if (bNoAutoSave) + return; + + // Check if doc is well known on the desktop. Otherwise ignore it! + // Other frames mostly are used from external programs - e.g. the bean ... + css::uno::Reference< css::frame::XController > xController = xDocument->getCurrentController(); + if (!xController.is()) + return; + + css::uno::Reference< css::frame::XFrame > xFrame = xController->getFrame(); + if (!xFrame.is()) + return; + css::uno::Reference< css::frame::XDesktop > xDesktop (xFrame->getCreator(), css::uno::UNO_QUERY); + if (!xDesktop.is()) + return; + + // if the document doesn't support the XDocumentRecovery interface, we're not interested in it. + Reference< XDocumentRecovery > xDocRecovery( xDocument, UNO_QUERY ); + if ( !xDocRecovery.is() ) + return; + + // get all needed information of this document + // We need it to update our cache or to locate already existing elements there! + AutoRecovery::TDocumentInfo aNew; + aNew.Document = xDocument; + + // TODO replace getLocation() with getURL() ... it's a workaround currently only! + css::uno::Reference< css::frame::XStorable > xDoc(aNew.Document, css::uno::UNO_QUERY_THROW); + aNew.OrgURL = xDoc->getLocation(); + + css::uno::Reference< css::frame::XTitle > xTitle(aNew.Document, css::uno::UNO_QUERY_THROW); + aNew.Title = xTitle->getTitle (); + + // classify the used application module, which is used by this document. + implts_specifyAppModuleAndFactory(aNew); + + // Hack! Check for "illegal office documents"... as e.g. the Basic IDE + // It's not really a full featured office document. It doesn't provide a URL, any filter, a factory URL etcpp. + // TODO file bug to Basic IDE developers. They must remove the office document API from its service. + if ( + (aNew.OrgURL.isEmpty()) && + (aNew.FactoryURL.isEmpty()) + ) + { + OSL_FAIL( "AutoRecovery::implts_registerDocument: this should not happen anymore!" ); + // nowadays, the Basic IDE should already die on the "supports XDocumentRecovery" check. And no other known + // document type fits in here ... + return; + } + + // By the way - get some information about the default format for saving! + // and save an information about the real used filter by this document. + // We save this document with DefaultFilter ... and load it with the RealFilter. + implts_specifyDefaultFilterAndExtension(aNew); + aNew.RealFilter = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_FILTERNAME, OUString()); + + // Further we must know, if this document base on a template. + // Then we must load it in a different way. + css::uno::Reference< css::document::XDocumentPropertiesSupplier > xSupplier(aNew.Document, css::uno::UNO_QUERY); + if (xSupplier.is()) // optional interface! + { + css::uno::Reference< css::document::XDocumentProperties > xDocProps(xSupplier->getDocumentProperties(), css::uno::UNO_SET_THROW); + aNew.TemplateURL = xDocProps->getTemplateURL(); + } + + css::uno::Reference< css::util::XModifiable > xModifyCheck(xDocument, css::uno::UNO_QUERY_THROW); + if (xModifyCheck->isModified()) + { + aNew.DocumentState |= DocState::Modified; + } + + aCacheLock.lock(LOCK_FOR_CACHE_ADD_REMOVE); + + AutoRecovery::TDocumentInfo aInfo; + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + // create a new cache entry ... this document is not known. + ++m_nIdPool; + aNew.ID = m_nIdPool; + SAL_WARN_IF(m_nIdPool<0, "fwk.autorecovery", "AutoRecovery::implts_registerDocument(): Overflow of ID pool detected."); + m_lDocCache.push_back(aNew); + + AutoRecovery::TDocumentList::iterator pIt1 = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument); + aInfo = *pIt1; + + } /* SAFE */ + + implts_flushConfigItem(aInfo); + implts_startModifyListeningOnDoc(aInfo); + + aCacheLock.unlock(); +} + +void AutoRecovery::implts_deregisterDocument(const css::uno::Reference< css::frame::XModel >& xDocument , + bool bStopListening) +{ + AutoRecovery::TDocumentInfo aInfo; + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + // Attention: Don't leave SAFE section, if you work with pIt! + // Because it points directly into the m_lDocCache list ... + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument); + if (pIt == m_lDocCache.end()) + return; // unknown document => not a runtime error! Because we register only a few documents. see registration ... + + aInfo = *pIt; + + aCacheLock.unlock(); + + // Sometimes we close documents by ourself. + // And these documents can't be deregistered. + // Otherwise we lose our configuration data... but need it ! + // see SessionSave ! + if (aInfo.IgnoreClosing) + return; + + CacheLockGuard aCacheLock2(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_ADD_REMOVE); + pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument); + if (pIt != m_lDocCache.end()) + m_lDocCache.erase(pIt); + pIt = m_lDocCache.end(); // otherwise it's not specified what pIt means! + aCacheLock2.unlock(); + + } /* SAFE */ + + /* This method is called within disposing() of the document too. But there it's not a good idea to + deregister us as listener. Further it makes no sense - because the broadcaster dies. + So we suppress deregistration in such case... + */ + if (bStopListening) + implts_stopModifyListeningOnDoc(aInfo); + + AutoRecovery::st_impl_removeFile(aInfo.OldTempURL); + AutoRecovery::st_impl_removeFile(aInfo.NewTempURL); + implts_flushConfigItem(aInfo, true); // sal_True => remove it from config +} + +void AutoRecovery::implts_markDocumentModifiedAgainstLastBackup(const css::uno::Reference< css::frame::XModel >& xDocument) +{ + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument); + if (pIt != m_lDocCache.end()) + { + /* Now we know, that this document was modified again and must be saved next time. + But we don't need this information for every e.g. key input of the user. + So we stop listening here. + But if the document was saved as temp. file we start listening for this event again. + */ + implts_stopModifyListeningOnDoc(*pIt); + } + + } /* SAFE */ +} + +void AutoRecovery::implts_updateModifiedState(const css::uno::Reference< css::frame::XModel >& xDocument) +{ + // use true as fallback to get every document on EmergencySave/AutoRecovery! + bool bModified = true; + css::uno::Reference< css::util::XModifiable > xModify(xDocument, css::uno::UNO_QUERY); + if (xModify.is()) + bModified = xModify->isModified(); + + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument); + if (pIt != m_lDocCache.end()) + { + AutoRecovery::TDocumentInfo& rInfo = *pIt; + + if (bModified) + { + rInfo.DocumentState |= DocState::Modified; + } + else + { + rInfo.DocumentState &= ~DocState::Modified; + } + } + + } /* SAFE */ +} + +void AutoRecovery::implts_updateDocumentUsedForSavingState(const css::uno::Reference< css::frame::XModel >& xDocument , + bool bSaveInProgress) +{ + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument); + if (pIt == m_lDocCache.end()) + return; + AutoRecovery::TDocumentInfo& rInfo = *pIt; + rInfo.UsedForSaving = bSaveInProgress; + + } /* SAFE */ +} + +void AutoRecovery::implts_markDocumentAsSaved(const css::uno::Reference< css::frame::XModel >& xDocument) +{ + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + AutoRecovery::TDocumentInfo aInfo; + OUString sRemoveURL1; + OUString sRemoveURL2; + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument); + if (pIt == m_lDocCache.end()) + return; + aInfo = *pIt; + + /* Since the document has been saved, update its entry in the document + * cache. We essentially reset the state of the document from an + * autorecovery perspective, updating things like the filename (which + * would change in the case of a 'Save as' operation) and the associated + * backup file URL. */ + + aInfo.DocumentState = DocState::Unknown; + // TODO replace getLocation() with getURL() ... it's a workaround currently only! + css::uno::Reference< css::frame::XStorable > xDoc(aInfo.Document, css::uno::UNO_QUERY); + aInfo.OrgURL = xDoc->getLocation(); + + /* Save off the backup file URLs and then clear them. NOTE - it is + * important that we clear them - otherwise, we could enter a state + * where pIt->OldTempURL == pIt->NewTempURL and our backup algorithm + * in implts_saveOneDoc will write to that URL and then delete the file + * at that URL (bug #96607) */ + sRemoveURL1 = aInfo.OldTempURL; + sRemoveURL2 = aInfo.NewTempURL; + aInfo.OldTempURL.clear(); + aInfo.NewTempURL.clear(); + + utl::MediaDescriptor lDescriptor(aInfo.Document->getArgs()); + aInfo.RealFilter = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_FILTERNAME, OUString()); + + css::uno::Reference< css::frame::XTitle > xDocTitle(xDocument, css::uno::UNO_QUERY); + if (xDocTitle.is ()) + aInfo.Title = xDocTitle->getTitle (); + else + { + aInfo.Title = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_TITLE, OUString()); + if (aInfo.Title.isEmpty()) + aInfo.Title = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_DOCUMENTTITLE, OUString()); + } + + aInfo.UsedForSaving = false; + + *pIt = aInfo; + + } /* SAFE */ + + implts_flushConfigItem(aInfo); + + aCacheLock.unlock(); + + AutoRecovery::st_impl_removeFile(sRemoveURL1); + AutoRecovery::st_impl_removeFile(sRemoveURL2); +} + +AutoRecovery::TDocumentList::iterator AutoRecovery::impl_searchDocument( AutoRecovery::TDocumentList& rList , + const css::uno::Reference< css::frame::XModel >& xDocument) +{ + return std::find_if(rList.begin(), rList.end(), + [&xDocument](const AutoRecovery::TDocumentInfo& rInfo) { return rInfo.Document == xDocument; }); +} + +void lcl_changeVisibility( const css::uno::Reference< css::frame::XFramesSupplier >& i_rFrames, bool i_bVisible ) +{ + css::uno::Reference< css::container::XIndexAccess > xFramesContainer = i_rFrames->getFrames(); + const sal_Int32 count = xFramesContainer->getCount(); + + Any aElement; + for ( sal_Int32 i=0; i < count; ++i ) + { + aElement = xFramesContainer->getByIndex(i); + // check for sub frames + css::uno::Reference< css::frame::XFramesSupplier > xFramesSupp( aElement, css::uno::UNO_QUERY ); + if ( xFramesSupp.is() ) + lcl_changeVisibility( xFramesSupp, i_bVisible ); + + css::uno::Reference< css::frame::XFrame > xFrame( aElement, css::uno::UNO_QUERY ); + if ( !xFrame.is() ) + continue; + + css::uno::Reference< css::awt::XWindow > xWindow( xFrame->getContainerWindow(), UNO_SET_THROW ); + xWindow->setVisible( i_bVisible ); + } +} + +void AutoRecovery::implts_changeAllDocVisibility(bool bVisible) +{ + css::uno::Reference< css::frame::XFramesSupplier > xDesktop = css::frame::Desktop::create(m_xContext); + lcl_changeVisibility( xDesktop, bVisible ); +} + +/* Currently the document is not closed in case of crash, + so the lock file must be removed explicitly +*/ +void lc_removeLockFile(AutoRecovery::TDocumentInfo const & rInfo) +{ +#if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT || HAVE_FEATURE_MACOSX_SANDBOX + (void) rInfo; +#else + if ( !rInfo.Document.is() ) + return; + + try + { + css::uno::Reference< css::frame::XStorable > xStore(rInfo.Document, css::uno::UNO_QUERY_THROW); + OUString aURL = xStore->getLocation(); + if ( !aURL.isEmpty() ) + { + ::svt::DocumentLockFile aLockFile( aURL ); + aLockFile.RemoveFile(); + } + } + catch( const css::uno::Exception& ) + { + } +#endif +} + +void AutoRecovery::implts_prepareSessionShutdown() +{ + SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_prepareSessionShutdown() starts ..."); + + // a) reset modified documents (of course the must be saved before this method is called!) + // b) close it without showing any UI! + + /* SAFE */ { + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + for (auto & info : m_lDocCache) + { + // WORKAROUND... Since the documents are not closed the lock file must be removed explicitly + // it is not done on documents saving since shutdown can be cancelled + lc_removeLockFile( info ); + + // Prevent us from deregistration of these documents. + // Because we close these documents by ourself (see XClosable below) ... + // it's fact, that we reach our deregistration method. There we + // must not(!) update our configuration ... Otherwise all + // session data are lost !!! + info.IgnoreClosing = true; + + // reset modified flag of these documents (ignoring the notification about it!) + // Otherwise a message box is shown on closing these models. + implts_stopModifyListeningOnDoc(info); + + // if the session save is still running the documents should not be thrown away, + // actually that would be a bad sign, that means that the SessionManager tries + // to kill the session before the saving is ready + if ((m_eJob & Job::SessionSave) != Job::SessionSave) + { + css::uno::Reference< css::util::XModifiable > xModify(info.Document, css::uno::UNO_QUERY); + if (xModify.is()) + xModify->setModified(false); + + // close the model. + css::uno::Reference< css::util::XCloseable > xClose(info.Document, css::uno::UNO_QUERY); + if (xClose.is()) + { + try + { + xClose->close(false); + } + catch(const css::uno::Exception&) + { + // At least it's only a try to close these documents before anybody else it does. + // So it seems to be possible to ignore any error here .-) + } + + info.Document.clear(); + } + } + } + + aCacheLock.unlock(); + } /* SAFE */ +} + +/* TODO WORKAROUND: + + #i64599# + + Normally the MediaDescriptor argument NoAutoSave indicates, + that a document must be ignored for AutoSave and Recovery. + But sometimes XModel->getArgs() does not contained this information + if implts_registerDocument() was called. + So we have to check a second time, if this property is set... + Best place doing so is to check it immediately before saving + and suppressing saving the document then. + Of course removing the corresponding cache entry is not an option. + Because it would disturb iteration over the cache! + So we ignore such documents only... + Hopefully next time they are not inserted in our cache. +*/ +bool lc_checkIfSaveForbiddenByArguments(AutoRecovery::TDocumentInfo const & rInfo) +{ + if (! rInfo.Document.is()) + return true; + + utl::MediaDescriptor lDescriptor(rInfo.Document->getArgs()); + bool bNoAutoSave = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_NOAUTOSAVE, false); + + return bNoAutoSave; +} + +AutoRecovery::ETimerType AutoRecovery::implts_saveDocs( bool bAllowUserIdleLoop, + bool bRemoveLockFiles, + const DispatchParams* pParams ) +{ + css::uno::Reference< css::task::XStatusIndicator > xExternalProgress; + if (pParams) + xExternalProgress = pParams->m_xProgress; + + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(m_xContext); + OUString sBackupPath(SvtPathOptions().GetBackupPath()); + + css::uno::Reference< css::frame::XController > xActiveController; + css::uno::Reference< css::frame::XModel > xActiveModel; + css::uno::Reference< css::frame::XFrame > xActiveFrame = xDesktop->getActiveFrame(); + if (xActiveFrame.is()) + xActiveController = xActiveFrame->getController(); + if (xActiveController.is()) + xActiveModel = xActiveController->getModel(); + + // Set the default timer action for our call. + // Default = NORMAL_AUTOSAVE + // We return a suggestion for an active timer only. + // It will be ignored if the timer was disabled by the user ... + // Further this state can be set to USER_IDLE only later in this method. + // It's not allowed to reset such state then. Because we must know, if + // there exists POSTPONED documents. see below ... + AutoRecovery::ETimerType eTimer = AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL; + + Job eJob = m_eJob; + + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + /* SAFE */ { + osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + // This list will be filled with every document + // which should be saved as last one. E.g. if it was used + // already for a UI save operation => crashed ... and + // now we try to save it again ... which can fail again ( of course .-) ). + ::std::vector< AutoRecovery::TDocumentList::iterator > lDangerousDocs; + + AutoRecovery::TDocumentList::iterator pIt; + for ( pIt = m_lDocCache.begin(); + pIt != m_lDocCache.end(); + ++pIt ) + { + AutoRecovery::TDocumentInfo aInfo = *pIt; + + // WORKAROUND... Since the documents are not closed the lock file must be removed explicitly + if ( bRemoveLockFiles ) + lc_removeLockFile( aInfo ); + + // WORKAROUND ... see comment of this method + if (lc_checkIfSaveForbiddenByArguments(aInfo)) + continue; + + // already auto saved during this session :-) + // This state must be reset for all documents + // if timer is started with normal AutoSaveTimerIntervall! + if ((aInfo.DocumentState & DocState::Handled) == DocState::Handled) + continue; + + // Not modified documents are not saved. + // We safe an information about the URL only! + Reference< XDocumentRecovery > xDocRecover( aInfo.Document, UNO_QUERY_THROW ); + if ( !xDocRecover->wasModifiedSinceLastSave() ) + { + aInfo.DocumentState |= DocState::Handled; + continue; + } + + // check if this document is still used by a concurrent save operation + // e.g. if the user tried to save via UI. + // Handle it in the following way: + // i) For an AutoSave ... ignore this document! It will be saved and next time we will (hopefully) + // get a notification about the state of this operation. + // And if a document was saved by the user we can remove our temp. file. But that will be done inside + // our callback for SaveDone notification. + // ii) For a CrashSave ... add it to the list of dangerous documents and + // save it after all other documents was saved successfully. That decrease + // the chance for a crash inside a crash. + // On the other side it's not necessary for documents, which are not modified. + // They can be handled normally - means we patch the corresponding configuration entry only. + // iii) For a SessionSave ... ignore it! There is no time to wait for this save operation. + // Because the WindowManager will kill the process if it doesn't react immediately. + // On the other side we can't risk a concurrent save request ... because we know + // that it will produce a crash. + + // Attention: Because eJob is used as a flag field, you have to check for the worst case first. + // E.g. a CrashSave can overwrite an AutoSave. So you have to check for a CrashSave before an AutoSave! + if (aInfo.UsedForSaving) + { + if ((eJob & Job::EmergencySave) == Job::EmergencySave) + { + lDangerousDocs.push_back(pIt); + continue; + } + else + if ((eJob & Job::SessionSave) == Job::SessionSave) + { + continue; + } + else + if ((eJob & Job::AutoSave) == Job::AutoSave) + { + eTimer = AutoRecovery::E_POLL_TILL_AUTOSAVE_IS_ALLOWED; + aInfo.DocumentState |= DocState::Postponed; + continue; + } + } + + // a) Document was not postponed - and is active now. => postpone it (restart timer, restart loop) + // b) Document was not postponed - and is not active now. => save it + // c) Document was postponed - and is not active now. => save it + // d) Document was postponed - and is active now. => save it (because user idle was checked already) + bool bActive = (xActiveModel == aInfo.Document); + bool bWasPostponed = ((aInfo.DocumentState & DocState::Postponed) == DocState::Postponed); + + if ( + ! bWasPostponed && + bActive + ) + { + aInfo.DocumentState |= DocState::Postponed; + *pIt = aInfo; + // postponed documents will be saved if this method is called again! + // That can be done by an outside started timer => E_POLL_FOR_USER_IDLE (if normal AutoSave is active) + // or it must be done directly without starting any timer => E_CALL_ME_BACK (if Emergency- or SessionSave is active and must be finished ASAP!) + eTimer = AutoRecovery::E_POLL_FOR_USER_IDLE; + if (!bAllowUserIdleLoop) + eTimer = AutoRecovery::E_CALL_ME_BACK; + continue; + } + + // b, c, d) + // } /* SAFE */ + g.clear(); + // changing of aInfo and flushing it is done inside implts_saveOneDoc! + implts_saveOneDoc(sBackupPath, aInfo, xExternalProgress); + implts_informListener(eJob, AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &aInfo)); + g.reset(); + // /* SAFE */ { + + *pIt = aInfo; + } + + // Did we have some "dangerous candidates" ? + // Try to save it ... but may be it will fail ! + for (auto const& dangerousDoc : lDangerousDocs) + { + pIt = dangerousDoc; + AutoRecovery::TDocumentInfo aInfo = *pIt; + + // } /* SAFE */ + g.clear(); + // changing of aInfo and flushing it is done inside implts_saveOneDoc! + implts_saveOneDoc(sBackupPath, aInfo, xExternalProgress); + implts_informListener(eJob, AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &aInfo)); + g.reset(); + // /* SAFE */ { + + *pIt = aInfo; + } + + } /* SAFE */ + + return eTimer; +} + +void AutoRecovery::implts_saveOneDoc(const OUString& sBackupPath , + AutoRecovery::TDocumentInfo& rInfo , + const css::uno::Reference< css::task::XStatusIndicator >& xExternalProgress) +{ + // no document? => can occur if we loaded our configuration with files, + // which couldn't be recovered successfully. In such case we have all needed information + // excepting the real document instance! + + // TODO: search right place, where such "dead files" can be removed from the configuration! + if (!rInfo.Document.is()) + return; + + utl::MediaDescriptor lOldArgs(rInfo.Document->getArgs()); + implts_generateNewTempURL(sBackupPath, lOldArgs, rInfo); + + // if the document was loaded with a password, it should be + // stored with password + utl::MediaDescriptor lNewArgs; + css::uno::Sequence< css::beans::NamedValue > aEncryptionData = + lOldArgs.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_ENCRYPTIONDATA, + css::uno::Sequence< css::beans::NamedValue >()); + if (aEncryptionData.hasElements()) + lNewArgs[utl::MediaDescriptor::PROP_ENCRYPTIONDATA] <<= aEncryptionData; + + // Further it must be saved using the default file format of that application. + // Otherwise we will some data lost. + if (!rInfo.DefaultFilter.isEmpty()) + lNewArgs[utl::MediaDescriptor::PROP_FILTERNAME] <<= rInfo.DefaultFilter; + + // prepare frame/document/mediadescriptor in a way, that it uses OUR progress .-) + if (xExternalProgress.is()) + lNewArgs[utl::MediaDescriptor::PROP_STATUSINDICATOR] <<= xExternalProgress; + impl_establishProgress(rInfo, lNewArgs, css::uno::Reference< css::frame::XFrame >()); + + // #i66598# use special handling of property "DocumentBaseURL" (it must be an empty string!) + // for make hyperlinks working + lNewArgs[utl::MediaDescriptor::PROP_DOCUMENTBASEURL] <<= OUString(); + + lNewArgs[utl::MediaDescriptor::PROP_AUTOSAVEEVENT] <<= true; + + // try to save this document as a new temp file every time. + // Mark AutoSave state as "INCOMPLETE" if it failed. + // Because the last temp file is too old and does not include all changes. + Reference< XDocumentRecovery > xDocRecover(rInfo.Document, css::uno::UNO_QUERY_THROW); + + // safe the state about "trying to save" + // ... we need it for recovery if e.g. a crash occurs inside next line! + rInfo.DocumentState |= DocState::TrySave; + implts_flushConfigItem(rInfo); + + // If userautosave is enabled, first try to save the original file. + // Note that we must do it *before* calling storeToRecoveryFile, so in case of failure here + // we won't remain with the modified flag set to true, even though the autorecovery save succeeded. + try + { + // We must check here for an empty URL to avoid a "This operation is not supported on this operating system." + // message during autosave. + if ((m_eJob & Job::UserAutoSave) == Job::UserAutoSave && !rInfo.OrgURL.isEmpty()) + { + Reference< XStorable > xDocSave(rInfo.Document, css::uno::UNO_QUERY_THROW); + xDocSave->store(); + } + } + catch(const css::uno::Exception&) + { + } + + sal_Int32 nRetry = RETRY_STORE_ON_FULL_DISC_FOREVER; + bool bError = false; + do + { + try + { + xDocRecover->storeToRecoveryFile( rInfo.NewTempURL, lNewArgs.getAsConstPropertyValueList() ); + +#ifdef TRIGGER_FULL_DISC_CHECK + throw css::uno::Exception("trigger full disk check"); +#else // TRIGGER_FULL_DISC_CHECK + + bError = false; + nRetry = 0; +#endif // TRIGGER_FULL_DISC_CHECK + } + catch(const css::uno::Exception& rException) + { + bError = true; + + // skip saving XLSX with protected sheets, if their passwords haven't supported yet + if ( rException.Message.startsWith("SfxBaseModel::impl_store") ) + { + const css::task::ErrorCodeIOException& pErrorCodeIOException = + static_cast<const css::task::ErrorCodeIOException&>(rException); + if ( static_cast<ErrCode>(pErrorCodeIOException.ErrCode) == ERRCODE_SFX_WRONGPASSWORD ) + { + // stop and remove the bad temporary file, instead of filling the disk with them + bError = false; + break; + } + } + + // a) FULL DISC seems to be the problem behind => show error and retry it forever (e.g. retry=300) + // b) unknown problem (may be locking problem) => reset RETRY value to more useful value(!) (e.g. retry=3) + // c) unknown problem (may be locking problem) + 1..2 repeating operations => throw the original exception to force generation of a stacktrace ! + + sal_Int32 nMinSpaceDocSave; + /* SAFE */ { + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + nMinSpaceDocSave = m_nMinSpaceDocSave; + } /* SAFE */ + + if (! impl_enoughDiscSpace(nMinSpaceDocSave)) + AutoRecovery::impl_showFullDiscError(); + else if (nRetry > RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL) + nRetry = RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL; + else if (nRetry <= GIVE_UP_RETRY) + throw; // force stacktrace to know if there exist might other reasons, why an AutoSave can fail !!! + + --nRetry; + } + } + while(nRetry>0); + + if (! bError) + { + // safe the state about success + // ... you know the reason: to know it on recovery time if next line crash .-) + rInfo.DocumentState &= ~DocState::TrySave; + rInfo.DocumentState |= DocState::Handled; + rInfo.DocumentState |= DocState::Succeeded; + } + else + { + // safe the state about error ... + rInfo.NewTempURL.clear(); + rInfo.DocumentState &= ~DocState::TrySave; + rInfo.DocumentState |= DocState::Handled; + rInfo.DocumentState |= DocState::Incomplete; + } + + // make sure the progress is not referred any longer + impl_forgetProgress(rInfo, lNewArgs, css::uno::Reference< css::frame::XFrame >()); + + // try to remove the old temp file. + // Ignore any error here. We have a new temp file, which is up to date. + // The only thing is: we fill the disk with temp files, if we can't remove old ones :-) + OUString sRemoveFile = rInfo.OldTempURL; + rInfo.OldTempURL = rInfo.NewTempURL; + rInfo.NewTempURL.clear(); + + implts_flushConfigItem(rInfo); + + // We must know if the user modifies the document again ... + implts_startModifyListeningOnDoc(rInfo); + + AutoRecovery::st_impl_removeFile(sRemoveFile); +} + +AutoRecovery::ETimerType AutoRecovery::implts_openDocs(const DispatchParams& aParams) +{ + AutoRecovery::ETimerType eTimer = AutoRecovery::E_DONT_START_TIMER; + + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + /* SAFE */ { + osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + Job eJob = m_eJob; + for (auto & info : m_lDocCache) + { + // Such documents are already loaded by the last loop. + // Don't check DocState::Succeeded here! It may be the final state of an AutoSave + // operation before!!! + if ((info.DocumentState & DocState::Handled) == DocState::Handled) + continue; + + // a1,b1,c1,d2,e2,f2) + if ((info.DocumentState & DocState::Damaged) == DocState::Damaged) + { + // don't forget to inform listener! May be this document was + // damaged on last saving time ... + // Then our listener need this notification. + // If it was damaged during last "try to open" ... + // it will be notified more than once. SH.. HAPPENS ... + // } /* SAFE */ + g.clear(); + implts_informListener(eJob, + AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info)); + g.reset(); + // /* SAFE */ { + continue; + } + + utl::MediaDescriptor lDescriptor; + + // it's a UI feature - so the "USER" itself must be set as referrer + lDescriptor[utl::MediaDescriptor::PROP_REFERRER] <<= OUString(REFERRER_USER); + lDescriptor[utl::MediaDescriptor::PROP_SALVAGEDFILE] <<= OUString(); + + // recovered documents are loaded hidden, and shown all at once, later + lDescriptor[utl::MediaDescriptor::PROP_HIDDEN] <<= true; + + if (aParams.m_xProgress.is()) + lDescriptor[utl::MediaDescriptor::PROP_STATUSINDICATOR] <<= aParams.m_xProgress; + + bool bBackupWasTried = ( + ((info.DocumentState & DocState::TryLoadBackup ) == DocState::TryLoadBackup) || // temp. state! + ((info.DocumentState & DocState::Incomplete ) == DocState::Incomplete ) // transport DocState::TryLoadBackup from last loop to this new one! + ); + bool bOriginalWasTried = ((info.DocumentState & DocState::TryLoadOriginal) == DocState::TryLoadOriginal); + + if (bBackupWasTried) + { + if (!bOriginalWasTried) + { + info.DocumentState |= DocState::Incomplete; + // try original URL ... ! don't continue with next item here ... + } + else + { + info.DocumentState |= DocState::Damaged; + continue; + } + } + + OUString sLoadOriginalURL; + OUString sLoadBackupURL; + + if (!bBackupWasTried) + sLoadBackupURL = info.OldTempURL; + + if (!info.OrgURL.isEmpty()) + { + sLoadOriginalURL = info.OrgURL; + } + else if (!info.TemplateURL.isEmpty()) + { + sLoadOriginalURL = info.TemplateURL; + lDescriptor[utl::MediaDescriptor::PROP_ASTEMPLATE] <<= true; + lDescriptor[utl::MediaDescriptor::PROP_TEMPLATENAME] <<= info.TemplateURL; + } + else if (!info.FactoryURL.isEmpty()) + { + sLoadOriginalURL = info.FactoryURL; + lDescriptor[utl::MediaDescriptor::PROP_ASTEMPLATE] <<= true; + } + + // A "Salvaged" item must exists every time. The core can make something special then for recovery. + // Of course it should be the real file name of the original file, in case we load the temp. backup here. + OUString sURL; + if (!sLoadBackupURL.isEmpty()) + { + sURL = sLoadBackupURL; + info.DocumentState |= DocState::TryLoadBackup; + lDescriptor[utl::MediaDescriptor::PROP_SALVAGEDFILE] <<= sLoadOriginalURL; + } + else if (!sLoadOriginalURL.isEmpty()) + { + sURL = sLoadOriginalURL; + info.DocumentState |= DocState::TryLoadOriginal; + } + else + continue; // TODO ERROR! + + LoadEnv::initializeUIDefaults( m_xContext, lDescriptor, true, nullptr ); + + // } /* SAFE */ + g.clear(); + + implts_flushConfigItem(info); + implts_informListener(eJob, + AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info)); + + try + { + implts_openOneDoc(sURL, lDescriptor, info); + } + catch(const css::uno::Exception&) + { + info.DocumentState &= ~DocState::TryLoadBackup; + info.DocumentState &= ~DocState::TryLoadOriginal; + if (!sLoadBackupURL.isEmpty()) + { + info.DocumentState |= DocState::Incomplete; + eTimer = AutoRecovery::E_CALL_ME_BACK; + } + else + { + info.DocumentState |= DocState::Handled; + info.DocumentState |= DocState::Damaged; + } + + implts_flushConfigItem(info, true); + implts_informListener(eJob, + AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info)); + + // /* SAFE */ { + // Needed for next loop! + g.reset(); + continue; + } + + if (!info.RealFilter.isEmpty()) + { + utl::MediaDescriptor lPatchDescriptor(info.Document->getArgs()); + lPatchDescriptor[utl::MediaDescriptor::PROP_FILTERNAME] <<= info.RealFilter; + info.Document->attachResource(info.Document->getURL(), lPatchDescriptor.getAsConstPropertyValueList()); + // do *not* use sURL here. In case this points to the recovery file, it has already been passed + // to recoverFromFile. Also, passing it here is logically wrong, as attachResource is intended + // to take the logical file URL. + } + + css::uno::Reference< css::util::XModifiable > xModify(info.Document, css::uno::UNO_QUERY); + if ( xModify.is() ) + { + bool bModified = ((info.DocumentState & DocState::Modified) == DocState::Modified); + xModify->setModified(bModified); + } + + info.DocumentState &= ~DocState::TryLoadBackup; + info.DocumentState &= ~DocState::TryLoadOriginal; + info.DocumentState |= DocState::Handled; + info.DocumentState |= DocState::Succeeded; + + implts_flushConfigItem(info); + implts_informListener(eJob, + AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info)); + + /* Normally we listen as XModifyListener on a document to know if a document was changed + since our last AutoSave. And we deregister us in case we know this state. + But directly after one document as recovered ... we must start listening. + Otherwise the first "modify" doesn't reach us. Because we ourself called setModified() + on the document via API. And currently we don't listen for any events (not at theGlobalEventBroadcaster + nor at any document!). + */ + implts_startModifyListeningOnDoc(info); + + // /* SAFE */ { + // Needed for next loop. Don't unlock it again! + g.reset(); + } + + } /* SAFE */ + + return eTimer; +} + +void AutoRecovery::implts_openOneDoc(const OUString& sURL , + utl::MediaDescriptor& lDescriptor, + AutoRecovery::TDocumentInfo& rInfo ) +{ + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(m_xContext); + + ::std::vector< Reference< XComponent > > aCleanup; + try + { + // create a new document of the desired type + Reference< XModel2 > xModel(m_xContext->getServiceManager()->createInstanceWithContext( + rInfo.FactoryService, m_xContext), UNO_QUERY_THROW); + aCleanup.emplace_back(xModel.get() ); + + // put the filter name into the descriptor - we're not going to involve any type detection, so + // the document might be lost without the FilterName property + if ( (rInfo.DocumentState & DocState::TryLoadOriginal) == DocState::TryLoadOriginal) + lDescriptor[ utl::MediaDescriptor::PROP_FILTERNAME ] <<= rInfo.RealFilter; + else + lDescriptor[ utl::MediaDescriptor::PROP_FILTERNAME ] <<= rInfo.DefaultFilter; + + if ( sURL == rInfo.FactoryURL ) + { + // if the document was a new, unmodified document, then there's nothing to recover, just to init + ENSURE_OR_THROW( ( rInfo.DocumentState & DocState::Modified ) == DocState(0), + "unexpected document state" ); + Reference< XLoadable > xModelLoad( xModel, UNO_QUERY_THROW ); + xModelLoad->initNew(); + + // TODO: remove load-process specific arguments from the descriptor, e.g. the status indicator + xModel->attachResource( sURL, lDescriptor.getAsConstPropertyValueList() ); + } + else + { + OUString sFilterName; + lDescriptor[utl::MediaDescriptor::PROP_FILTERNAME] >>= sFilterName; + if (!sFilterName.isEmpty() + && ( sFilterName == "Calc MS Excel 2007 XML" + || sFilterName == "Impress MS PowerPoint 2007 XML" + || sFilterName == "MS Word 2007 XML")) + // TODO: Probably need to check other affected formats + templates? + { + // tdf#129096: in case of recovery of password protected OOXML document it is done not + // the same way as ordinal loading. Inside XDocumentRecovery::recoverFromFile + // there is a call to XFilter::filter which has constant media descriptor and thus + // all encryption data used in document is lost. To avoid this try to walkaround + // with explicit call to FormatDetector. It will try to load document, prompt for password + // and store this info in media descriptor we will use for recoverFromFile call. + Reference< css::document::XExtendedFilterDetection > xDetection( + m_xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.comp.oox.FormatDetector", m_xContext), + UNO_QUERY_THROW); + lDescriptor[utl::MediaDescriptor::PROP_URL] <<= sURL; + Sequence< css::beans::PropertyValue > aDescriptorSeq = lDescriptor.getAsConstPropertyValueList(); + OUString sType = xDetection->detect(aDescriptorSeq); + + OUString sNewFilterName; + lDescriptor[utl::MediaDescriptor::PROP_FILTERNAME] >>= sNewFilterName; + if (!sType.isEmpty() && sNewFilterName == sFilterName) + { + // Filter detection was okay, update media descriptor with one received from FilterDetect + lDescriptor = aDescriptorSeq; + } + } + + // let it recover itself + Reference< XDocumentRecovery > xDocRecover( xModel, UNO_QUERY_THROW ); + xDocRecover->recoverFromFile( + sURL, + lDescriptor.getUnpackedValueOrDefault( utl::MediaDescriptor::PROP_SALVAGEDFILE, OUString() ), + lDescriptor.getAsConstPropertyValueList() + ); + + // No attachResource needed here. By definition (of XDocumentRecovery), the implementation is responsible + // for completely initializing the model, which includes attachResource (or equivalent), if required. + } + + // re-create all the views + ::std::vector< OUString > aViewsToRestore( std::cbegin(rInfo.ViewNames), std::cend(rInfo.ViewNames) ); + // if we don't have views for whatever reason, then create a default-view, at least + if ( aViewsToRestore.empty() ) + aViewsToRestore.emplace_back( ); + + for (auto const& viewToRestore : aViewsToRestore) + { + // create a frame + Reference< XFrame > xTargetFrame = xDesktop->findFrame( SPECIALTARGET_BLANK, 0 ); + aCleanup.emplace_back(xTargetFrame.get() ); + + // create a view to the document + Reference< XController2 > xController; + if ( viewToRestore.getLength() ) + { + xController.set( xModel->createViewController( viewToRestore, Sequence< css::beans::PropertyValue >(), xTargetFrame ), UNO_SET_THROW ); + } + else + { + xController.set( xModel->createDefaultViewController( xTargetFrame ), UNO_SET_THROW ); + } + + // introduce model/view/controller to each other + xController->attachModel( xModel ); + xModel->connectController( xController ); + xTargetFrame->setComponent( xController->getComponentWindow(), xController ); + xController->attachFrame( xTargetFrame ); + xModel->setCurrentController( xController ); + } + + rInfo.Document = xModel.get(); + } + catch(const css::uno::RuntimeException&) + { + throw; + } + catch(const css::uno::Exception&) + { + Any aCaughtException( ::cppu::getCaughtException() ); + + // clean up + for (auto const& component : aCleanup) + { + css::uno::Reference< css::util::XCloseable > xClose(component, css::uno::UNO_QUERY); + if ( xClose.is() ) + xClose->close( true ); + else + component->dispose(); + } + + // re-throw + throw css::lang::WrappedTargetException( + "Recovery of \"" + sURL + "\" failed.", + static_cast< css::frame::XDispatch* >(this), + aCaughtException + ); + } +} + +void AutoRecovery::implts_generateNewTempURL(const OUString& sBackupPath , + utl::MediaDescriptor& /*rMediaDescriptor*/, + AutoRecovery::TDocumentInfo& rInfo ) +{ + // specify URL for saving (which points to a temp file inside backup directory) + // and define a unique name, so we can locate it later. + // This unique name must solve an optimization problem too! + // In case we are asked to save unmodified documents too - and one of them + // is an empty one (because it was new created using e.g. a URL private:factory/...) + // we should not save it really. Then we put the information about such "empty document" + // into the configuration and don't create any recovery file on disk. + // We use the title of the document to make it unique. + OUStringBuffer sUniqueName; + if (!rInfo.OrgURL.isEmpty()) + { + css::uno::Reference< css::util::XURLTransformer > xParser(css::util::URLTransformer::create(m_xContext)); + css::util::URL aURL; + aURL.Complete = rInfo.OrgURL; + xParser->parseStrict(aURL); + sUniqueName.append(aURL.Name); + } + else if (!rInfo.FactoryURL.isEmpty()) + sUniqueName.append("untitled"); + sUniqueName.append("_"); + + // TODO: Must we strip some illegal signes - if we use the title? + + OUString sName(sUniqueName.makeStringAndClear()); + OUString sExtension(rInfo.Extension); + OUString sPath(sBackupPath); + ::utl::TempFile aTempFile(sName, true, &sExtension, &sPath, true); + + rInfo.NewTempURL = aTempFile.GetURL(); +} + +void AutoRecovery::implts_informListener( Job eJob , + const css::frame::FeatureStateEvent& aEvent) +{ + // Helper shares mutex with us -> threadsafe! + ::comphelper::OInterfaceContainerHelper3<css::frame::XStatusListener>* pListenerForURL = nullptr; + OUString sJob = AutoRecovery::implst_getJobDescription(eJob); + + // inform listener, which are registered for any URLs(!) + pListenerForURL = m_lListener.getContainer(sJob); + if(pListenerForURL == nullptr) + return; + + ::comphelper::OInterfaceIteratorHelper3 pIt(*pListenerForURL); + while(pIt.hasMoreElements()) + { + try + { + pIt.next()->statusChanged(aEvent); + } + catch(const css::uno::RuntimeException&) + { + pIt.remove(); + } + } +} + +OUString AutoRecovery::implst_getJobDescription(Job eJob) +{ + // describe the current running operation + OUStringBuffer sFeature(256); + sFeature.append(CMD_PROTOCOL); + + // Attention: Because "eJob" is used as a flag field the order of checking these + // flags is important. We must prefer job with higher priorities! + // E.g. EmergencySave has an higher prio then AutoSave ... + // On the other side there exist a well defined order between two different jobs. + // e.g. PrepareEmergencySave must be done before EmergencySave is started of course. + + if ((eJob & Job::PrepareEmergencySave) == Job::PrepareEmergencySave) + sFeature.append(CMD_DO_PREPARE_EMERGENCY_SAVE); + else if ((eJob & Job::EmergencySave) == Job::EmergencySave) + sFeature.append(CMD_DO_EMERGENCY_SAVE); + else if ((eJob & Job::Recovery) == Job::Recovery) + sFeature.append(CMD_DO_RECOVERY); + else if ((eJob & Job::SessionSave) == Job::SessionSave) + sFeature.append(CMD_DO_SESSION_SAVE); + else if ((eJob & Job::SessionQuietQuit) == Job::SessionQuietQuit) + sFeature.append(CMD_DO_SESSION_QUIET_QUIT); + else if ((eJob & Job::SessionRestore) == Job::SessionRestore) + sFeature.append(CMD_DO_SESSION_RESTORE); + else if ((eJob & Job::EntryBackup) == Job::EntryBackup) + sFeature.append(CMD_DO_ENTRY_BACKUP); + else if ((eJob & Job::EntryCleanup) == Job::EntryCleanup) + sFeature.append(CMD_DO_ENTRY_CLEANUP); + else if ((eJob & Job::AutoSave) == Job::AutoSave) + sFeature.append(CMD_DO_AUTO_SAVE); + else if ( eJob != Job::NoJob ) + SAL_INFO("fwk.autorecovery", "AutoRecovery::implst_getJobDescription(): Invalid job identifier detected."); + + return sFeature.makeStringAndClear(); +} + +Job AutoRecovery::implst_classifyJob(const css::util::URL& aURL) +{ + if ( aURL.Protocol == CMD_PROTOCOL ) + { + if ( aURL.Path == CMD_DO_PREPARE_EMERGENCY_SAVE ) + return Job::PrepareEmergencySave; + else if ( aURL.Path == CMD_DO_EMERGENCY_SAVE ) + return Job::EmergencySave; + else if ( aURL.Path == CMD_DO_RECOVERY ) + return Job::Recovery; + else if ( aURL.Path == CMD_DO_ENTRY_BACKUP ) + return Job::EntryBackup; + else if ( aURL.Path == CMD_DO_ENTRY_CLEANUP ) + return Job::EntryCleanup; + else if ( aURL.Path == CMD_DO_SESSION_SAVE ) + return Job::SessionSave; + else if ( aURL.Path == CMD_DO_SESSION_QUIET_QUIT ) + return Job::SessionQuietQuit; + else if ( aURL.Path == CMD_DO_SESSION_RESTORE ) + return Job::SessionRestore; + else if ( aURL.Path == CMD_DO_DISABLE_RECOVERY ) + return Job::DisableAutorecovery; + else if ( aURL.Path == CMD_DO_SET_AUTOSAVE_STATE ) + return Job::SetAutosaveState; + } + + SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_classifyJob(): Invalid URL (protocol)."); + return Job::NoJob; +} + +css::frame::FeatureStateEvent AutoRecovery::implst_createFeatureStateEvent( Job eJob , + const OUString& sEventType, + AutoRecovery::TDocumentInfo const * pInfo ) +{ + css::frame::FeatureStateEvent aEvent; + aEvent.FeatureURL.Complete = AutoRecovery::implst_getJobDescription(eJob); + aEvent.FeatureDescriptor = sEventType; + + if (pInfo && sEventType == OPERATION_UPDATE) + { + // pack rInfo for transport via UNO + ::comphelper::NamedValueCollection aInfo; + aInfo.put( OUString(CFG_ENTRY_PROP_ID), pInfo->ID ); + aInfo.put( OUString(CFG_ENTRY_PROP_ORIGINALURL), pInfo->OrgURL ); + aInfo.put( OUString(CFG_ENTRY_PROP_FACTORYURL), pInfo->FactoryURL ); + aInfo.put( OUString(CFG_ENTRY_PROP_TEMPLATEURL), pInfo->TemplateURL ); + aInfo.put( OUString(CFG_ENTRY_PROP_TEMPURL), pInfo->OldTempURL.isEmpty() ? pInfo->NewTempURL : pInfo->OldTempURL ); + aInfo.put( OUString(CFG_ENTRY_PROP_MODULE), pInfo->AppModule); + aInfo.put( OUString(CFG_ENTRY_PROP_TITLE), pInfo->Title); + aInfo.put( OUString(CFG_ENTRY_PROP_VIEWNAMES), pInfo->ViewNames); + aInfo.put( OUString(CFG_ENTRY_PROP_DOCUMENTSTATE), sal_Int32(pInfo->DocumentState)); + + aEvent.State <<= aInfo.getPropertyValues(); + } + + return aEvent; +} + +void AutoRecovery::implts_resetHandleStates() +{ + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + /* SAFE */ { + osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + for (auto & info : m_lDocCache) + { + info.DocumentState &= ~DocState::Handled; + info.DocumentState &= ~DocState::Postponed; + + // } /* SAFE */ + g.clear(); + implts_flushConfigItem(info); + g.reset(); + // /* SAFE */ { + } + } /* SAFE */ +} + +void AutoRecovery::implts_prepareEmergencySave() +{ + // Be sure to know all open documents really .-) + implts_verifyCacheAgainstDesktopDocumentList(); + + // hide all docs, so the user can't disturb our emergency save .-) + implts_changeAllDocVisibility(false); +} + +void AutoRecovery::implts_doEmergencySave(const DispatchParams& aParams) +{ + // Write a hint "we crashed" into the configuration, so + // the error report tool is started too in case no recovery + // documents exists and was saved. + + std::shared_ptr<comphelper::ConfigurationChanges> batch( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Recovery::RecoveryInfo::Crashed::set(true, batch); + batch->commit(); + + // for all docs, store their current view/names in the configuration + implts_persistAllActiveViewNames(); + + // The called method for saving documents runs + // during normal AutoSave more than once. Because + // it postpone active documents and save it later. + // That is normally done by recalling it from a timer. + // Here we must do it immediately! + // Of course this method returns the right state - + // because it knows, that we are running in EMERGENCY SAVE mode .-) + + bool const bAllowUserIdleLoop = false; // not allowed to change that .-) + AutoRecovery::ETimerType eSuggestedTimer = AutoRecovery::E_DONT_START_TIMER; + do + { + eSuggestedTimer = implts_saveDocs(bAllowUserIdleLoop, true, &aParams); + } + while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK); + + // reset the handle state of all + // cache items. Such handle state indicates, that a document + // was already saved during the THIS(!) EmergencySave session. + // Of course following recovery session must be started without + // any "handle" state ... + implts_resetHandleStates(); + + // flush config cached back to disc. + impl_flushALLConfigChanges(); + + // try to make sure next time office will be started user won't be + // notified about any other might be running office instance + // remove ".lock" file from disc ! + AutoRecovery::st_impl_removeLockFile(); +} + +void AutoRecovery::implts_doRecovery(const DispatchParams& aParams) +{ + AutoRecovery::ETimerType eSuggestedTimer = AutoRecovery::E_DONT_START_TIMER; + do + { + eSuggestedTimer = implts_openDocs(aParams); + } + while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK); + + // reset the handle state of all + // cache items. Such handle state indicates, that a document + // was already saved during the THIS(!) Recovery session. + // Of course a may be following EmergencySave session must be started without + // any "handle" state... + implts_resetHandleStates(); + + // Reset the configuration hint "we were crashed"! + std::shared_ptr<comphelper::ConfigurationChanges> batch( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Recovery::RecoveryInfo::Crashed::set(false, batch); + batch->commit(); +} + +void AutoRecovery::implts_doSessionSave(const DispatchParams& aParams) +{ + SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_doSessionSave()"); + + // Be sure to know all open documents really .-) + implts_verifyCacheAgainstDesktopDocumentList(); + + // for all docs, store their current view/names in the configuration + implts_persistAllActiveViewNames(); + + // The called method for saving documents runs + // during normal AutoSave more than once. Because + // it postpone active documents and save it later. + // That is normally done by recalling it from a timer. + // Here we must do it immediately! + // Of course this method returns the right state - + // because it knows, that we are running in SESSION SAVE mode .-) + + bool const bAllowUserIdleLoop = false; // not allowed to change that .-) + AutoRecovery::ETimerType eSuggestedTimer = AutoRecovery::E_DONT_START_TIMER; + do + { + // do not remove lock files of the documents, it will be done on session quit + eSuggestedTimer = implts_saveDocs(bAllowUserIdleLoop, false, &aParams); + } + while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK); + + // reset the handle state of all + // cache items. Such handle state indicates, that a document + // was already saved during the THIS(!) save session. + // Of course following restore session must be started without + // any "handle" state ... + implts_resetHandleStates(); + + // flush config cached back to disc. + impl_flushALLConfigChanges(); +} + +void AutoRecovery::implts_doSessionQuietQuit() +{ + SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_doSessionQuietQuit()"); + + // try to make sure next time office will be started user won't be + // notified about any other might be running office instance + // remove ".lock" file from disc! + // it is done as a first action for session save since Gnome sessions + // do not provide enough time for shutdown, and the dialog looks to be + // confusing for the user + AutoRecovery::st_impl_removeLockFile(); + + // reset all modified documents, so the don't show any UI on closing ... + // and close all documents, so we can shutdown the OS! + implts_prepareSessionShutdown(); + + // Write a hint for "stored session data" into the configuration, so + // the on next startup we know what's happen last time + std::shared_ptr<comphelper::ConfigurationChanges> batch( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Recovery::RecoveryInfo::SessionData::set(true, batch); + batch->commit(); + + // flush config cached back to disc. + impl_flushALLConfigChanges(); +} + +void AutoRecovery::implts_doSessionRestore(const DispatchParams& aParams) +{ + SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_doSessionRestore() ..."); + + AutoRecovery::ETimerType eSuggestedTimer = AutoRecovery::E_DONT_START_TIMER; + do + { + eSuggestedTimer = implts_openDocs(aParams); + } + while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK); + + // reset the handle state of all + // cache items. Such handle state indicates, that a document + // was already saved during the THIS(!) Restore session. + // Of course a may be following save session must be started without + // any "handle" state ... + implts_resetHandleStates(); + + // make all opened documents visible + implts_changeAllDocVisibility(true); + + // Reset the configuration hint for "session save"! + SAL_INFO("fwk.autorecovery", "... reset config key 'SessionData'"); + std::shared_ptr<comphelper::ConfigurationChanges> batch( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Recovery::RecoveryInfo::SessionData::set(false, batch); + batch->commit(); + + SAL_INFO("fwk.autorecovery", "... AutoRecovery::implts_doSessionRestore()"); +} + +void AutoRecovery::implts_backupWorkingEntry(const DispatchParams& aParams) +{ + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE); + + for (auto const& info : m_lDocCache) + { + if (info.ID != aParams.m_nWorkingEntryID) + continue; + + OUString sSourceURL; + // Prefer temp file. It contains the changes against the original document! + if (!info.OldTempURL.isEmpty()) + sSourceURL = info.OldTempURL; + else if (!info.NewTempURL.isEmpty()) + sSourceURL = info.NewTempURL; + else if (!info.OrgURL.isEmpty()) + sSourceURL = info.OrgURL; + else + continue; // nothing real to save! An unmodified but new created document. + + INetURLObject aParser(sSourceURL); + // AutoRecovery::EFailureSafeResult eResult = + implts_copyFile(sSourceURL, aParams.m_sSavePath, aParser.getName()); + + // TODO: Check eResult and react for errors (InteractionHandler!?) + // Currently we ignore it ... + // DON'T UPDATE THE CACHE OR REMOVE ANY TEMP. FILES FROM DISK. + // That has to be forced from outside explicitly. + // See implts_cleanUpWorkingEntry() for further details. + } +} + +void AutoRecovery::implts_cleanUpWorkingEntry(const DispatchParams& aParams) +{ + CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_ADD_REMOVE); + + AutoRecovery::TDocumentList::iterator pIt = std::find_if(m_lDocCache.begin(), m_lDocCache.end(), + [&aParams](const AutoRecovery::TDocumentInfo& rInfo) { return rInfo.ID == aParams.m_nWorkingEntryID; }); + if (pIt != m_lDocCache.end()) + { + AutoRecovery::TDocumentInfo& rInfo = *pIt; + AutoRecovery::st_impl_removeFile(rInfo.OldTempURL); + AutoRecovery::st_impl_removeFile(rInfo.NewTempURL); + implts_flushConfigItem(rInfo, true); // sal_True => remove it from xml config! + + m_lDocCache.erase(pIt); + } +} + +AutoRecovery::EFailureSafeResult AutoRecovery::implts_copyFile(const OUString& sSource , + const OUString& sTargetPath, + const OUString& sTargetName) +{ + // create content for the parent folder and call transfer on that content with the source content + // and the destination file name as parameters + + css::uno::Reference< css::ucb::XCommandEnvironment > xEnvironment; + + ::ucbhelper::Content aSourceContent; + ::ucbhelper::Content aTargetContent; + + try + { + aTargetContent = ::ucbhelper::Content(sTargetPath, xEnvironment, m_xContext); + } + catch(const css::uno::Exception&) + { + return AutoRecovery::E_WRONG_TARGET_PATH; + } + + sal_Int32 nNameClash; + nNameClash = css::ucb::NameClash::RENAME; + + try + { + bool bSuccess = ::ucbhelper::Content::create(sSource, xEnvironment, m_xContext, aSourceContent); + if (!bSuccess) + return AutoRecovery::E_ORIGINAL_FILE_MISSING; + aTargetContent.transferContent(aSourceContent, ::ucbhelper::InsertOperation::Copy, sTargetName, nNameClash); + } + catch(const css::uno::Exception&) + { + return AutoRecovery::E_ORIGINAL_FILE_MISSING; + } + + return AutoRecovery::E_COPIED; +} + +sal_Bool SAL_CALL AutoRecovery::convertFastPropertyValue( css::uno::Any& /*aConvertedValue*/, + css::uno::Any& /*aOldValue*/ , + sal_Int32 /*nHandle*/ , + const css::uno::Any& /*aValue*/ ) +{ + // not needed currently + return false; +} + +void SAL_CALL AutoRecovery::setFastPropertyValue_NoBroadcast( sal_Int32 /*nHandle*/, + const css::uno::Any& /*aValue*/ ) +{ + // not needed currently +} + +void SAL_CALL AutoRecovery::getFastPropertyValue(css::uno::Any& aValue , + sal_Int32 nHandle) const +{ + switch(nHandle) + { + case AUTORECOVERY_PROPHANDLE_EXISTS_RECOVERYDATA : + { + bool bSessionData = officecfg::Office::Recovery::RecoveryInfo::SessionData::get(); + bool bRecoveryData = !m_lDocCache.empty(); + + // exists session data ... => then we can't say, that these + // data are valid for recovery. So we have to return sal_False then! + if (bSessionData) + bRecoveryData = false; + + aValue <<= bRecoveryData; + } + break; + + case AUTORECOVERY_PROPHANDLE_CRASHED : + aValue <<= officecfg::Office::Recovery::RecoveryInfo::Crashed::get(); + break; + + case AUTORECOVERY_PROPHANDLE_EXISTS_SESSIONDATA : + aValue <<= officecfg::Office::Recovery::RecoveryInfo::SessionData::get(); + break; + } +} + +css::uno::Sequence< css::beans::Property > impl_getStaticPropertyDescriptor() +{ + return + { + css::beans::Property( AUTORECOVERY_PROPNAME_CRASHED , AUTORECOVERY_PROPHANDLE_CRASHED , cppu::UnoType<bool>::get() , css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ), + css::beans::Property( AUTORECOVERY_PROPNAME_EXISTS_RECOVERYDATA, AUTORECOVERY_PROPHANDLE_EXISTS_RECOVERYDATA, cppu::UnoType<bool>::get() , css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ), + css::beans::Property( AUTORECOVERY_PROPNAME_EXISTS_SESSIONDATA , AUTORECOVERY_PROPHANDLE_EXISTS_SESSIONDATA , cppu::UnoType<bool>::get() , css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ), + }; +} + +::cppu::IPropertyArrayHelper& SAL_CALL AutoRecovery::getInfoHelper() +{ + static ::cppu::OPropertyArrayHelper ourInfoHelper(impl_getStaticPropertyDescriptor(), true); + + return ourInfoHelper; +} + +css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL AutoRecovery::getPropertySetInfo() +{ + static css::uno::Reference< css::beans::XPropertySetInfo > xInfo( + ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper())); + + return xInfo; +} + +void AutoRecovery::implts_verifyCacheAgainstDesktopDocumentList() +{ + SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_verifyCacheAgainstDesktopDocumentList() ..."); + try + { + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(m_xContext); + + css::uno::Reference< css::container::XIndexAccess > xContainer( + xDesktop->getFrames(), + css::uno::UNO_QUERY_THROW); + + sal_Int32 i = 0; + sal_Int32 c = xContainer->getCount(); + + for (i=0; i<c; ++i) + { + css::uno::Reference< css::frame::XFrame > xFrame; + try + { + xContainer->getByIndex(i) >>= xFrame; + if (!xFrame.is()) + continue; + } + // can happen in multithreaded environments, that frames was removed from the container during this loop runs! + // Ignore it. + catch(const css::lang::IndexOutOfBoundsException&) + { + continue; + } + + // We are interested on visible documents only. + // Note: It's n optional interface .-( + css::uno::Reference< css::awt::XWindow2 > xVisibleCheck( + xFrame->getContainerWindow(), + css::uno::UNO_QUERY); + if ( + (!xVisibleCheck.is() ) || + (!xVisibleCheck->isVisible()) + ) + { + continue; + } + + // extract the model from the frame. + // Ignore "view only" frames, which does not have a model. + css::uno::Reference< css::frame::XController > xController; + css::uno::Reference< css::frame::XModel3 > xModel; + + xController = xFrame->getController(); + if (xController.is()) + xModel.set( xController->getModel(), UNO_QUERY_THROW ); + if (!xModel.is()) + continue; + + // insert model into cache ... + // If the model is already well known inside cache + // it's information set will be updated by asking the + // model again for its new states. + implts_registerDocument(xModel); + } + } + catch(const css::uno::RuntimeException&) + { + throw; + } + catch(const css::uno::Exception&) + { + } + + SAL_INFO("fwk.autorecovery", "... AutoRecovery::implts_verifyCacheAgainstDesktopDocumentList()"); +} + +bool AutoRecovery::impl_enoughDiscSpace(sal_Int32 nRequiredSpace) +{ +#ifdef SIMULATE_FULL_DISC + return sal_False; +#else // SIMULATE_FULL_DISC + // In case an error occurs and we are not able to retrieve the needed information + // it's better to "disable" the feature ShowErrorOnFullDisc ! + // Otherwise we start a confusing process of error handling ... + + sal_uInt64 nFreeSpace = SAL_MAX_UINT64; + + OUString sBackupPath(SvtPathOptions().GetBackupPath()); + ::osl::VolumeInfo aInfo (osl_VolumeInfo_Mask_FreeSpace); + ::osl::FileBase::RC aRC = ::osl::Directory::getVolumeInfo(sBackupPath, aInfo); + + if ( + (aInfo.isValid(osl_VolumeInfo_Mask_FreeSpace)) && + (aRC == ::osl::FileBase::E_None ) + ) + { + nFreeSpace = aInfo.getFreeSpace(); + } + + sal_uInt64 nFreeMB = nFreeSpace/1048576; + return (nFreeMB >= o3tl::make_unsigned(nRequiredSpace)); +#endif // SIMULATE_FULL_DISC +} + +void AutoRecovery::impl_showFullDiscError() +{ + OUString sBtn(FwkResId(STR_FULL_DISC_RETRY_BUTTON)); + OUString sMsg(FwkResId(STR_FULL_DISC_MSG)); + + OUString sBackupURL(SvtPathOptions().GetBackupPath()); + INetURLObject aConverter(sBackupURL); + sal_Unicode aDelimiter; + OUString sBackupPath = aConverter.getFSysPath(FSysStyle::Detect, &aDelimiter); + if (sBackupPath.getLength() < 1) + sBackupPath = sBackupURL; + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Error, VclButtonsType::NONE, + sMsg.replaceAll("%PATH", sBackupPath))); + xBox->add_button(sBtn, RET_OK); + xBox->run(); +} + +void AutoRecovery::impl_establishProgress(const AutoRecovery::TDocumentInfo& rInfo , + utl::MediaDescriptor& rArgs , + const css::uno::Reference< css::frame::XFrame >& xNewFrame) +{ + // external well known frame must be preferred (because it was created by ourself + // for loading documents into this frame)! + // But if no frame exists... we can try to locate it using any frame bound to the provided + // document. Of course we must live without any frame in case the document does not exists at this + // point. But this state should not occur. In such case xNewFrame should be valid ... hopefully .-) + css::uno::Reference< css::frame::XFrame > xFrame = xNewFrame; + if ( + (!xFrame.is() ) && + (rInfo.Document.is()) + ) + { + css::uno::Reference< css::frame::XController > xController = rInfo.Document->getCurrentController(); + if (xController.is()) + xFrame = xController->getFrame(); + } + + // Any outside progress must be used ... + // Only if there is no progress, we can create our own one. + css::uno::Reference< css::task::XStatusIndicator > xInternalProgress; + css::uno::Reference< css::task::XStatusIndicator > xExternalProgress = rArgs.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_STATUSINDICATOR, + css::uno::Reference< css::task::XStatusIndicator >() ); + + // Normally a progress is set from outside (e.g. by the CrashSave/Recovery dialog, which uses our dispatch API). + // But for a normal auto save we don't have such "external progress"... because this function is triggered by our own timer then. + // In such case we must create our own progress ! + if ( + (! xExternalProgress.is()) && + (xFrame.is() ) + ) + { + css::uno::Reference< css::task::XStatusIndicatorFactory > xProgressFactory(xFrame, css::uno::UNO_QUERY); + if (xProgressFactory.is()) + xInternalProgress = xProgressFactory->createStatusIndicator(); + } + + // HACK + // An external provided progress (most given by the CrashSave/Recovery dialog) + // must be preferred. But we know that some application filters query its own progress instance + // at the frame method Frame::createStatusIndicator(). + // So we use a two step mechanism: + // 1) we set the progress inside the MediaDescriptor, which will be provided to the filter + // 2) and we set a special Frame property, which overwrites the normal behaviour of Frame::createStatusIndicator .-) + // But we suppress 2) in case we uses an internal progress. Because then it doesn't matter + // if our applications make it wrong. In such case the internal progress resists at the same frame + // and there is no need to forward progress activities to e.g. an outside dialog .-) + if ( + (xExternalProgress.is()) && + (xFrame.is() ) + ) + { + css::uno::Reference< css::beans::XPropertySet > xFrameProps(xFrame, css::uno::UNO_QUERY); + if (xFrameProps.is()) + xFrameProps->setPropertyValue(FRAME_PROPNAME_ASCII_INDICATORINTERCEPTION, css::uno::Any(xExternalProgress)); + } + + // But inside the MediaDescriptor we must set our own create progress ... + // in case there is not already another progress set. + rArgs.createItemIfMissing(utl::MediaDescriptor::PROP_STATUSINDICATOR, xInternalProgress); +} + +void AutoRecovery::impl_forgetProgress(const AutoRecovery::TDocumentInfo& rInfo , + utl::MediaDescriptor& rArgs , + const css::uno::Reference< css::frame::XFrame >& xNewFrame) +{ + // external well known frame must be preferred (because it was created by ourself + // for loading documents into this frame)! + // But if no frame exists... we can try to locate it using any frame bound to the provided + // document. Of course we must live without any frame in case the document does not exists at this + // point. But this state should not occur. In such case xNewFrame should be valid ... hopefully .-) + css::uno::Reference< css::frame::XFrame > xFrame = xNewFrame; + if ( + (!xFrame.is() ) && + (rInfo.Document.is()) + ) + { + css::uno::Reference< css::frame::XController > xController = rInfo.Document->getCurrentController(); + if (xController.is()) + xFrame = xController->getFrame(); + } + + // stop progress interception on corresponding frame. + css::uno::Reference< css::beans::XPropertySet > xFrameProps(xFrame, css::uno::UNO_QUERY); + if (xFrameProps.is()) + xFrameProps->setPropertyValue(FRAME_PROPNAME_ASCII_INDICATORINTERCEPTION, css::uno::Any(css::uno::Reference< css::task::XStatusIndicator >())); + + // forget progress inside list of arguments. + utl::MediaDescriptor::iterator pArg = rArgs.find(utl::MediaDescriptor::PROP_STATUSINDICATOR); + if (pArg != rArgs.end()) + { + rArgs.erase(pArg); + pArg = rArgs.end(); + } +} + +void AutoRecovery::impl_flushALLConfigChanges() +{ + try + { + // SOLAR SAFE -> + SolarMutexGuard aGuard; + ::utl::ConfigManager::storeConfigItems(); + } + catch(const css::uno::Exception&) + { + } +} + +void AutoRecovery::st_impl_removeFile(const OUString& sURL) +{ + if ( sURL.isEmpty()) + return; + + try + { + ::ucbhelper::Content aContent(sURL, css::uno::Reference< css::ucb::XCommandEnvironment >(), m_xContext); + aContent.executeCommand("delete", css::uno::Any(true)); + } + catch(const css::uno::Exception&) + { + } +} + +void AutoRecovery::st_impl_removeLockFile() +{ + try + { + OUString sUserURL; + ::utl::Bootstrap::locateUserInstallation( sUserURL ); + + OUString sLockURL = sUserURL + "/.lock"; + AutoRecovery::st_impl_removeFile(sLockURL); + } + catch(const css::uno::Exception&) + { + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_AutoRecovery_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + rtl::Reference<AutoRecovery> xAutoRecovery = new AutoRecovery(context); + // 2nd phase initialization needed + xAutoRecovery->initListeners(); + + return cppu::acquire(xAutoRecovery.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/desktop.cxx b/framework/source/services/desktop.cxx new file mode 100644 index 000000000..cbc3ce6eb --- /dev/null +++ b/framework/source/services/desktop.cxx @@ -0,0 +1,1775 @@ +/* -*- 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 <framework/desktop.hxx> + +#include <loadenv/loadenv.hxx> + +#include <helper/ocomponentaccess.hxx> +#include <helper/oframes.hxx> +#include <dispatch/dispatchprovider.hxx> + +#include <dispatch/interceptionhelper.hxx> +#include <classes/taskcreator.hxx> +#include <threadhelp/transactionguard.hxx> +#include <properties.h> +#include <targets.h> + +#include <strings.hrc> +#include <classes/fwkresid.hxx> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/frame/TerminationVetoException.hpp> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/task/XInteractionApprove.hpp> +#include <com/sun/star/document/XInteractionFilterSelect.hpp> +#include <com/sun/star/task/ErrorCodeRequest.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/frame/XTerminateListener2.hpp> + +#include <comphelper/numberedcollection.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/lok.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <desktop/crashreport.hxx> +#include <vcl/scheduler.hxx> +#include <sal/log.hxx> +#include <vcl/errcode.hxx> +#include <vcl/threadex.hxx> +#include <unotools/configmgr.hxx> + +namespace framework{ + +namespace { + +enum PropHandle { + ActiveFrame, DispatchRecorderSupplier, IsPlugged, SuspendQuickstartVeto, + Title }; + +} + +OUString SAL_CALL Desktop::getImplementationName() +{ + return "com.sun.star.comp.framework.Desktop"; +} + +sal_Bool SAL_CALL Desktop::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence<OUString> SAL_CALL Desktop::getSupportedServiceNames() +{ + return { "com.sun.star.frame.Desktop" }; +} + +void Desktop::constructorInit() +{ + // Initialize a new XFrames-helper-object to handle XIndexAccess and XElementAccess. + // We hold member as reference ... not as pointer too! + // Attention: We share our frame container with this helper. Container is threadsafe himself ... So I think we can do that. + // But look on dispose() for right order of deinitialization. + m_xFramesHelper = new OFrames( this, &m_aChildTaskContainer ); + + // Initialize a new dispatchhelper-object to handle dispatches. + // We use these helper as slave for our interceptor helper ... not directly! + // But he is event listener on THIS instance! + rtl::Reference<DispatchProvider> xDispatchProvider = new DispatchProvider( m_xContext, this ); + + // Initialize a new interception helper object to handle dispatches and implement an interceptor mechanism. + // Set created dispatch provider as slowest slave of it. + // Hold interception helper by reference only - not by pointer! + // So it's easier to destroy it. + m_xDispatchHelper = new InterceptionHelper( this, xDispatchProvider ); + + OUString sUntitledPrefix = FwkResId(STR_UNTITLED_DOCUMENT) + " "; + + rtl::Reference<::comphelper::NumberedCollection> pNumbers = new ::comphelper::NumberedCollection (); + m_xTitleNumberGenerator = pNumbers; + pNumbers->setOwner ( static_cast< ::cppu::OWeakObject* >(this) ); + pNumbers->setUntitledPrefix ( sUntitledPrefix ); + + // Safe impossible cases + // We can't work without this helper! + SAL_WARN_IF( !m_xFramesHelper.is(), "fwk.desktop", "Desktop::Desktop(): Frames helper is not valid. XFrames, XIndexAccess and XElementAccess are not supported!"); + SAL_WARN_IF( !m_xDispatchHelper.is(), "fwk.desktop", "Desktop::Desktop(): Dispatch helper is not valid. XDispatch will not work correctly!" ); + + // Enable object for real working! + // Otherwise all calls will be rejected ... + m_aTransactionManager.setWorkingMode( E_WORK ); +} + +/*-************************************************************************************************************ + @short standard constructor to create instance by factory + @descr This constructor initialize a new instance of this class by valid factory, + and will be set valid values on his member and baseclasses. + + @attention a) Don't use your own reference during a UNO-Service-ctor! There is no guarantee, that you + will get over this. (e.g. using of your reference as parameter to initialize some member) + Do such things in DEFINE_INIT_SERVICE() method, which is called automatically after your ctor!!! + b) Baseclass OBroadcastHelper is a typedef in namespace cppu! + The microsoft compiler has some problems to handle it right BY using namespace explicitly ::cppu::OBroadcastHelper. + If we write it without a namespace or expand the typedef to OBroadcastHelperVar<...> -> it will be OK!? + I don't know why! (other compiler not tested .. but it works!) + + @seealso method DEFINE_INIT_SERVICE() + + @param "xFactory" is the multi service manager, which create this instance. + The value must be different from NULL! + @onerror We throw an ASSERT in debug version or do nothing in release version. +*//*-*************************************************************************************************************/ +Desktop::Desktop( css::uno::Reference< css::uno::XComponentContext > xContext ) + : Desktop_BASE ( m_aMutex ) + , cppu::OPropertySetHelper( cppu::WeakComponentImplHelperBase::rBHelper ) + // Init member + , m_bIsTerminated(false) + , m_bIsShutdown(false) // see dispose() for further information! + , m_bSession ( false ) + , m_xContext (std::move( xContext )) + , m_aListenerContainer ( m_aMutex ) + , m_eLoadState ( E_NOTSET ) + , m_bSuspendQuickstartVeto( false ) +{ +} + +/*-************************************************************************************************************ + @short standard destructor + @descr This one do NOTHING! Use dispose() instead of this. + + @seealso method dispose() +*//*-*************************************************************************************************************/ +Desktop::~Desktop() +{ + SAL_WARN_IF(!m_bIsShutdown, "fwk.desktop", "Desktop not terminated before being destructed"); + SAL_WARN_IF( m_aTransactionManager.getWorkingMode()!=E_CLOSE, "fwk.desktop", "Desktop::~Desktop(): Who forgot to dispose this service?" ); +} + +css::uno::Any SAL_CALL Desktop::queryInterface( const css::uno::Type& _rType ) +{ + css::uno::Any aRet = Desktop_BASE::queryInterface( _rType ); + if ( !aRet.hasValue() ) + aRet = OPropertySetHelper::queryInterface( _rType ); + return aRet; +} + +css::uno::Sequence< css::uno::Type > SAL_CALL Desktop::getTypes( ) +{ + return comphelper::concatSequences( + Desktop_BASE::getTypes(), + ::cppu::OPropertySetHelper::getTypes() + ); +} + +sal_Bool SAL_CALL Desktop::terminate() +{ + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + SolarMutexResettableGuard aGuard; + + if (m_bIsTerminated) + return true; + + css::uno::Reference< css::frame::XTerminateListener > xPipeTerminator = m_xPipeTerminator; + css::uno::Reference< css::frame::XTerminateListener > xQuickLauncher = m_xQuickLauncher; + css::uno::Reference< css::frame::XTerminateListener > xSWThreadManager = m_xSWThreadManager; + css::uno::Reference< css::frame::XTerminateListener > xSfxTerminator = m_xSfxTerminator; + + css::lang::EventObject aEvent ( static_cast< ::cppu::OWeakObject* >(this) ); + bool bAskQuickStart = !m_bSuspendQuickstartVeto; + const bool bRestartableMainLoop = Application::IsEventTestingModeEnabled() || + comphelper::LibreOfficeKit::isActive(); + aGuard.clear(); + + // Allow using of any UI ... because Desktop.terminate() was designed as UI functionality in the past. + + // Ask normal terminate listener. They could veto terminating the process. + Desktop::TTerminateListenerList lCalledTerminationListener; + if (!impl_sendQueryTerminationEvent(lCalledTerminationListener)) + { + impl_sendCancelTerminationEvent(lCalledTerminationListener); + return false; + } + + // try to close all open frames + if (!impl_closeFrames(!bRestartableMainLoop)) + { + impl_sendCancelTerminationEvent(lCalledTerminationListener); + return false; + } + + // Normal listener had no problem ... + // all frames was closed ... + // now it's time to ask our specialized listener. + // They are handled these way because they wish to hinder the office on termination + // but they wish also closing of all frames. + + // Note further: + // We shouldn't ask quicklauncher in case it was allowed from outside only. + // This is special trick to "ignore existing quick starter" for debug purposes. + + // Attention: + // Order of called listener is important! + // Some of them are harmless,-) + // but some can be dangerous. E.g. it would be dangerous if we close our pipe + // and don't terminate in real because another listener throws a veto exception .-) + + try + { + if( bAskQuickStart && xQuickLauncher.is() ) + { + xQuickLauncher->queryTermination( aEvent ); + lCalledTerminationListener.push_back( xQuickLauncher ); + } + + if ( xSWThreadManager.is() ) + { + xSWThreadManager->queryTermination( aEvent ); + lCalledTerminationListener.push_back( xSWThreadManager ); + } + + if ( xPipeTerminator.is() ) + { + xPipeTerminator->queryTermination( aEvent ); + lCalledTerminationListener.push_back( xPipeTerminator ); + } + + if ( xSfxTerminator.is() ) + { + xSfxTerminator->queryTermination( aEvent ); + lCalledTerminationListener.push_back( xSfxTerminator ); + } + } + catch(const css::frame::TerminationVetoException&) + { + impl_sendCancelTerminationEvent(lCalledTerminationListener); + return false; + } + + aGuard.reset(); + if (m_bIsTerminated) + return true; + m_bIsTerminated = true; + + if (!bRestartableMainLoop) + { + CrashReporter::addKeyValue("ShutDown", OUString::boolean(true), CrashReporter::Write); + + // The clipboard listener needs to be the first. It can create copies of the + // existing document which needs basically all the available infrastructure. + impl_sendTerminateToClipboard(); + { + SolarMutexReleaser aReleaser; + impl_sendNotifyTerminationEvent(); + } + Scheduler::ProcessEventsToIdle(); + + if( bAskQuickStart && xQuickLauncher.is() ) + xQuickLauncher->notifyTermination( aEvent ); + + if ( xSWThreadManager.is() ) + xSWThreadManager->notifyTermination( aEvent ); + + if ( xPipeTerminator.is() ) + xPipeTerminator->notifyTermination( aEvent ); + + // further termination is postponed to shutdown, if LO already runs the main loop + if (!Application::IsInExecute()) + shutdown(); + } + else + m_bIsShutdown = true; + +#ifndef IOS // or ANDROID? + aGuard.clear(); + // In the iOS app, posting the ImplQuitMsg user event will be too late, it will not be handled during the + // lifetime of the current document, but handled for the next document opened, which thus will break horribly. + Application::Quit(); +#endif + + return true; +} + +void Desktop::shutdown() +{ + TransactionGuard aTransaction(m_aTransactionManager, E_HARDEXCEPTIONS); + SolarMutexGuard aGuard; + + if (m_bIsShutdown) + return; + m_bIsShutdown = true; + + css::uno::Reference<css::frame::XTerminateListener> xSfxTerminator = m_xSfxTerminator; + css::lang::EventObject aEvent(static_cast<::cppu::OWeakObject* >(this)); + + // we need a copy here as the notifyTermination call might cause a removeTerminateListener call + std::vector< css::uno::Reference<css::frame::XTerminateListener> > xComponentDllListeners; + xComponentDllListeners.swap(m_xComponentDllListeners); + for (auto& xListener : xComponentDllListeners) + xListener->notifyTermination(aEvent); + xComponentDllListeners.clear(); + + // Must be really the last listener to be called. + // Because it shuts down the whole process asynchronous! + if (xSfxTerminator.is()) + xSfxTerminator->notifyTermination(aEvent); +} + +namespace +{ + class QuickstartSuppressor + { + Desktop* const m_pDesktop; + css::uno::Reference< css::frame::XTerminateListener > m_xQuickLauncher; + public: + QuickstartSuppressor(Desktop* const pDesktop, css::uno::Reference< css::frame::XTerminateListener > xQuickLauncher) + : m_pDesktop(pDesktop) + , m_xQuickLauncher(std::move(xQuickLauncher)) + { + SAL_INFO("fwk.desktop", "temporary removing Quickstarter"); + if(m_xQuickLauncher.is()) + m_pDesktop->removeTerminateListener(m_xQuickLauncher); + } + ~QuickstartSuppressor() + { + SAL_INFO("fwk.desktop", "readding Quickstarter"); + if(m_xQuickLauncher.is()) + m_pDesktop->addTerminateListener(m_xQuickLauncher); + } + }; +} + +bool Desktop::terminateQuickstarterToo() +{ + QuickstartSuppressor aQuickstartSuppressor(this, m_xQuickLauncher); + m_bSession = true; + return terminate(); +} + +void SAL_CALL Desktop::addTerminateListener( const css::uno::Reference< css::frame::XTerminateListener >& xListener ) +{ + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + css::uno::Reference< css::lang::XServiceInfo > xInfo( xListener, css::uno::UNO_QUERY ); + if ( xInfo.is() ) + { + OUString sImplementationName = xInfo->getImplementationName(); + + SolarMutexGuard g; + + if( sImplementationName == "com.sun.star.comp.sfx2.SfxTerminateListener" ) + { + m_xSfxTerminator = xListener; + return; + } + if( sImplementationName == "com.sun.star.comp.RequestHandlerController" ) + { + m_xPipeTerminator = xListener; + return; + } + if( sImplementationName == "com.sun.star.comp.desktop.QuickstartWrapper" ) + { + m_xQuickLauncher = xListener; + return; + } + if( sImplementationName == "com.sun.star.util.comp.FinalThreadManager" ) + { + m_xSWThreadManager = xListener; + return; + } + else if ( sImplementationName == "com.sun.star.comp.ComponentDLLListener" ) + { + m_xComponentDllListeners.push_back(xListener); + return; + } + } + + // No lock required... container is threadsafe by itself. + m_aListenerContainer.addInterface( cppu::UnoType<css::frame::XTerminateListener>::get(), xListener ); +} + +void SAL_CALL Desktop::removeTerminateListener( const css::uno::Reference< css::frame::XTerminateListener >& xListener ) +{ + TransactionGuard aTransaction( m_aTransactionManager, E_SOFTEXCEPTIONS ); + + css::uno::Reference< css::lang::XServiceInfo > xInfo( xListener, css::uno::UNO_QUERY ); + if ( xInfo.is() ) + { + OUString sImplementationName = xInfo->getImplementationName(); + + SolarMutexGuard g; + + if( sImplementationName == "com.sun.star.comp.sfx2.SfxTerminateListener" ) + { + m_xSfxTerminator.clear(); + return; + } + + if( sImplementationName == "com.sun.star.comp.RequestHandlerController" ) + { + m_xPipeTerminator.clear(); + return; + } + + if( sImplementationName == "com.sun.star.comp.desktop.QuickstartWrapper" ) + { + m_xQuickLauncher.clear(); + return; + } + + if( sImplementationName == "com.sun.star.util.comp.FinalThreadManager" ) + { + m_xSWThreadManager.clear(); + return; + } + else if (sImplementationName == "com.sun.star.comp.ComponentDLLListener") + { + m_xComponentDllListeners.erase( + std::remove(m_xComponentDllListeners.begin(), m_xComponentDllListeners.end(), xListener), + m_xComponentDllListeners.end()); + return; + } + } + + // No lock required ... container is threadsafe by itself. + m_aListenerContainer.removeInterface( cppu::UnoType<css::frame::XTerminateListener>::get(), xListener ); +} + +/*-************************************************************************************************************ + @interface XDesktop + @short get access to create enumerations of all current components + @descr You will be the owner of the returned object and must delete it if you don't use it again. + + @seealso class TasksAccess + @seealso class TasksEnumeration + @return A reference to an XEnumerationAccess-object. + + @onerror We return a null-reference. + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::container::XEnumerationAccess > SAL_CALL Desktop::getComponents() +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + // We use a helper class OComponentAccess to have access on all child components. + // Create it on demand and return it as a reference. + return new OComponentAccess( this ); +} + +/*-************************************************************************************************************ + @interface XDesktop + @short return the current active component + @descr The most current component is the window, model or the controller of the current active frame. + + @seealso method getCurrentFrame() + @seealso method impl_getFrameComponent() + @return A reference to the component. + + @onerror We return a null-reference. + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::lang::XComponent > SAL_CALL Desktop::getCurrentComponent() +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + // Set return value if method failed. + css::uno::Reference< css::lang::XComponent > xComponent; + + // Get reference to current frame ... + // ... get component of this frame ... (It can be the window, the model or the controller.) + // ... and return the result. + css::uno::Reference< css::frame::XFrame > xCurrentFrame = getCurrentFrame(); + if( xCurrentFrame.is() ) + { + xComponent = impl_getFrameComponent( xCurrentFrame ); + } + return xComponent; +} + +/*-************************************************************************************************************ + @interface XDesktop + @short return the current active frame in hierarchy + @descr There can be more than one different active paths in our frame hierarchy. But only one of them + could be the most active frame (normal he has the focus). + Don't mix it with getActiveFrame()! That will return our current active frame, which must be + a direct child of us and should be a part(!) of an active path. + + @seealso method getActiveFrame() + @return A valid reference, if there is an active frame. + A null reference , otherwise. + + @onerror We return a null reference. + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::getCurrentFrame() +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + // Start search with our direct active frame (if it exist!). + // Search on his children for other active frames too. + // Stop if no one could be found and return last of found ones. + css::uno::Reference< css::frame::XFramesSupplier > xLast( getActiveFrame(), css::uno::UNO_QUERY ); + if( xLast.is() ) + { + css::uno::Reference< css::frame::XFramesSupplier > xNext( xLast->getActiveFrame(), css::uno::UNO_QUERY ); + while( xNext.is() ) + { + xLast = xNext; + xNext.set( xNext->getActiveFrame(), css::uno::UNO_QUERY ); + } + } + return xLast; +} + +/*-************************************************************************************************************ + @interface XComponentLoader + @short try to load given URL into a task + @descr You can give us some information about the content, which you will load into a frame. + We search or create this target for you, make a type detection of given URL and try to load it. + As result of this operation we return the new created component or nothing, if loading failed. + @param "sURL" , URL, which represent the content + @param "sTargetFrameName" , name of target frame or special value like "_self", "_blank" ... + @param "nSearchFlags" , optional arguments for frame search, if target isn't a special one + @param "lArguments" , optional arguments for loading + @return A valid component reference, if loading was successful. + A null reference otherwise. + + @onerror We return a null reference. + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::lang::XComponent > SAL_CALL Desktop::loadComponentFromURL( const OUString& sURL , + const OUString& sTargetFrameName, + sal_Int32 nSearchFlags , + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + SAL_INFO( "fwk.desktop", "loadComponentFromURL" ); + + css::uno::Reference< css::frame::XComponentLoader > xThis(this); + + utl::MediaDescriptor aDescriptor(lArguments); + bool bOnMainThread = aDescriptor.getUnpackedValueOrDefault("OnMainThread", false); + + if (bOnMainThread) + { + // Make sure that we own the solar mutex, otherwise later + // vcl::SolarThreadExecutor::execute() will release the solar mutex, even if it's owned by + // another thread, leading to an std::abort() at the end. + SolarMutexGuard g; + + return vcl::solarthread::syncExecute(std::bind(&LoadEnv::loadComponentFromURL, xThis, + m_xContext, sURL, sTargetFrameName, + nSearchFlags, lArguments)); + } + else + { + return LoadEnv::loadComponentFromURL(xThis, m_xContext, sURL, sTargetFrameName, + nSearchFlags, lArguments); + } +} + +/*-************************************************************************************************************ + @interface XTasksSupplier + @short get access to create enumerations of our taskchildren + @descr Direct children of desktop are tasks every time. + Call these method to could create enumerations of it. + +But; Don't forget - you will be the owner of returned object and must release it! + We use a helper class to implement the access interface. They hold a weakreference to us. + It can be, that the desktop is dead - but not your tasksaccess-object! Then they will do nothing! + You can't create enumerations then. + + @attention Normally we don't need any lock here. We don't work on internal member! + + @seealso class TasksAccess + @return A reference to an accessobject, which can create enumerations of our childtasks. + + @onerror A null reference is returned. + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::container::XEnumerationAccess > SAL_CALL Desktop::getTasks() +{ + SAL_INFO("fwk.desktop", "Desktop::getTasks(): Use of obsolete interface XTaskSupplier"); + return nullptr; +} + +/*-************************************************************************************************************ + @interface XTasksSupplier + @short return current active task of our direct children + @descr Desktop children are tasks only ! If we have an active path from desktop + as top to any frame on bottom, we must have an active direct child. His reference is returned here. + + @attention a) Do not confuse it with getCurrentFrame()! The current frame don't must one of our direct children. + It can be every frame in subtree and must have the focus (Is the last one of an active path!). + b) We don't need any lock here. Our container is threadsafe himself and live, if we live! + + @seealso method getCurrentFrame() + @return A reference to our current active taskchild. + + @onerror A null reference is returned. + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::frame::XTask > SAL_CALL Desktop::getActiveTask() +{ + SAL_INFO("fwk.desktop", "Desktop::getActiveTask(): Use of obsolete interface XTaskSupplier"); + return nullptr; +} + +/*-************************************************************************************************************ + @interface XDispatchProvider + @short search a dispatcher for given URL + @descr We use a helper implementation (class DispatchProvider) to do so. + So we don't must implement this algorithm twice! + + @attention We don't need any lock here. Our helper is threadsafe himself and live, if we live! + + @seealso class DispatchProvider + + @param "aURL" , URL to dispatch + @param "sTargetFrameName" , name of target frame, who should dispatch these URL + @param "nSearchFlags" , flags to regulate the search + @param "lQueries" , list of queryDispatch() calls! + @return A reference or list of founded dispatch objects for these URL. + + @onerror A null reference is returned. + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::frame::XDispatch > SAL_CALL Desktop::queryDispatch( const css::util::URL& aURL , + const OUString& sTargetFrameName , + sal_Int32 nSearchFlags ) +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + // Remove uno and cmd protocol part as we want to support both of them. We store only the command part + // in our hash map. All other protocols are stored with the protocol part. + OUString aCommand( aURL.Main ); + if ( aURL.Protocol.equalsIgnoreAsciiCase(".uno:") ) + aCommand = aURL.Path; + + if (!m_xCommandOptions && !utl::ConfigManager::IsFuzzing()) + m_xCommandOptions.reset(new SvtCommandOptions); + + // Make std::unordered_map lookup if the current URL is in the disabled list + if (m_xCommandOptions && m_xCommandOptions->Lookup(SvtCommandOptions::CMDOPTION_DISABLED, aCommand)) + return css::uno::Reference< css::frame::XDispatch >(); + else + { + // We use a helper to support these interface and an interceptor mechanism. + // Our helper is threadsafe by himself! + return m_xDispatchHelper->queryDispatch( aURL, sTargetFrameName, nSearchFlags ); + } +} + +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL Desktop::queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lQueries ) +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + return m_xDispatchHelper->queryDispatches( lQueries ); +} + +/*-************************************************************************************************************ + @interface XDispatchProviderInterception + @short supports registration/deregistration of interception objects, which + are interested on special dispatches. + + @descr It's really provided by an internal helper, which is used inside the dispatch API too. + @param xInterceptor + the interceptor object, which wishes to be (de)registered. + + @threadsafe yes +*//*-*************************************************************************************************************/ +void SAL_CALL Desktop::registerDispatchProviderInterceptor( const css::uno::Reference< css::frame::XDispatchProviderInterceptor >& xInterceptor) +{ + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + css::uno::Reference< css::frame::XDispatchProviderInterception > xInterceptionHelper( m_xDispatchHelper, css::uno::UNO_QUERY ); + xInterceptionHelper->registerDispatchProviderInterceptor( xInterceptor ); +} + +void SAL_CALL Desktop::releaseDispatchProviderInterceptor ( const css::uno::Reference< css::frame::XDispatchProviderInterceptor >& xInterceptor) +{ + TransactionGuard aTransaction( m_aTransactionManager, E_SOFTEXCEPTIONS ); + + css::uno::Reference< css::frame::XDispatchProviderInterception > xInterceptionHelper( m_xDispatchHelper, css::uno::UNO_QUERY ); + xInterceptionHelper->releaseDispatchProviderInterceptor( xInterceptor ); +} + +/*-************************************************************************************************************ + @interface XFramesSupplier + @short return access to append or remove children on desktop + @descr We don't implement these interface directly. We use a helper class to do this. + If you wish to add or delete children to/from the container, call these method to get + a reference to the helper. + + @attention Helper is threadsafe himself. So we don't need any lock here. + + @seealso class OFrames + @return A reference to the helper. + + @onerror A null reference is returned. + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::frame::XFrames > SAL_CALL Desktop::getFrames() +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + return m_xFramesHelper; +} + +/*-************************************************************************************************************ + @interface XFramesSupplier + @short set/get the current active child frame + @descr It must be a task. Direct children of desktop are tasks only! No frames are accepted. + We don't save this information directly in this class. We use our container-helper + to do that. + + @attention Helper is threadsafe himself. So we don't need any lock here. + + @seealso class OFrameContainer + + @param "xFrame", new active frame (must be valid!) + @return A reference to our current active childtask, if anyone exist. + + @onerror A null reference is returned. + @threadsafe yes +*//*-*************************************************************************************************************/ +void SAL_CALL Desktop::setActiveFrame( const css::uno::Reference< css::frame::XFrame >& xFrame ) +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + // Get old active frame first. + // If nothing will change - do nothing! + // Otherwise set new active frame ... + // and deactivate last frame. + // It's necessary for our FrameActionEvent listener on a frame! + css::uno::Reference< css::frame::XFrame > xLastActiveChild = m_aChildTaskContainer.getActive(); + if( xLastActiveChild != xFrame ) + { + m_aChildTaskContainer.setActive( xFrame ); + if( xLastActiveChild.is() ) + { + xLastActiveChild->deactivate(); + } + } +} + +css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::getActiveFrame() +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + return m_aChildTaskContainer.getActive(); +} + +/* + @interface XFrame + @short non implemented methods! + @descr Some method make no sense for our desktop! He has no window or parent or ... + So we should implement it empty and warn programmer, if he use it! +*/ +void SAL_CALL Desktop::initialize( const css::uno::Reference< css::awt::XWindow >& ) +{ +} + +css::uno::Reference< css::awt::XWindow > SAL_CALL Desktop::getContainerWindow() +{ + return css::uno::Reference< css::awt::XWindow >(); +} + +void SAL_CALL Desktop::setCreator( const css::uno::Reference< css::frame::XFramesSupplier >& /*xCreator*/ ) +{ +} + +css::uno::Reference< css::frame::XFramesSupplier > SAL_CALL Desktop::getCreator() +{ + return css::uno::Reference< css::frame::XFramesSupplier >(); +} + +OUString SAL_CALL Desktop::getName() +{ + SolarMutexGuard g; + return m_sName; +} + +void SAL_CALL Desktop::setName( const OUString& sName ) +{ + SolarMutexGuard g; + m_sName = sName; +} + +sal_Bool SAL_CALL Desktop::isTop() +{ + return true; +} + +void SAL_CALL Desktop::activate() +{ + // Desktop is active always... but sometimes our frames try to activate + // the complete path from bottom to top... And our desktop is the topest frame :-( + // So - please don't show any assertions here. Do nothing! +} + +void SAL_CALL Desktop::deactivate() +{ + // Desktop is active always... but sometimes our frames try to deactivate + // the complete path from bottom to top... And our desktop is the topest frame :-( + // So - please don't show any assertions here. Do nothing! +} + +sal_Bool SAL_CALL Desktop::isActive() +{ + return true; +} + +sal_Bool SAL_CALL Desktop::setComponent( const css::uno::Reference< css::awt::XWindow >& /*xComponentWindow*/ , + const css::uno::Reference< css::frame::XController >& /*xController*/ ) +{ + return false; +} + +css::uno::Reference< css::awt::XWindow > SAL_CALL Desktop::getComponentWindow() +{ + return css::uno::Reference< css::awt::XWindow >(); +} + +css::uno::Reference< css::frame::XController > SAL_CALL Desktop::getController() +{ + return css::uno::Reference< css::frame::XController >(); +} + +void SAL_CALL Desktop::contextChanged() +{ +} + +void SAL_CALL Desktop::addFrameActionListener( const css::uno::Reference< css::frame::XFrameActionListener >& ) +{ +} + +// css::frame::XFrame +void SAL_CALL Desktop::removeFrameActionListener( const css::uno::Reference< css::frame::XFrameActionListener >& ) +{ +} + +/*-************************************************************************************************************ + @interface XFrame + @short try to find a frame with special parameters + @descr This method searches for a frame with the specified name. + Frames may contain other frames (e.g. a frameset) and may + be contained in other frames. This hierarchy is searched by + this method. + First some special names are taken into account, i.e. "", + "_self", "_top", "_parent" etc. The FrameSearchFlags are ignored + when comparing these names with aTargetFrameName, further steps are + controlled by the FrameSearchFlags. If allowed, the name of the frame + itself is compared with the desired one, then ( again if allowed ) + the method findFrame is called for all children of the frame. + If no Frame with the given name is found until the top frames container, + a new top Frame is created, if this is allowed by a special + FrameSearchFlag. The new Frame also gets the desired name. + We use a helper to get right search direction and react in a right manner. + + @seealso class TargetFinder + + @param "sTargetFrameName" , name of searched frame + @param "nSearchFlags" , flags to regulate search + @return A reference to an existing frame in hierarchy, if it exist. + + @onerror A null reference is returned. + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::findFrame( const OUString& sTargetFrameName , + sal_Int32 nSearchFlags ) +{ + css::uno::Reference< css::frame::XFrame > xTarget; + + // 0) Ignore wrong parameter! + // We don't support search for following special targets. + // If we reject these requests, we must not check for such names + // in following code again and again. If we do not, so wrong + // search results can occur! + + if ( + (sTargetFrameName==SPECIALTARGET_DEFAULT ) || // valid for dispatches - not for findFrame()! + (sTargetFrameName==SPECIALTARGET_PARENT ) || // we have no parent by definition + (sTargetFrameName==SPECIALTARGET_BEAMER ) // beamer frames are allowed as child of tasks only - + // and they exist more than ones. We have no idea which our sub tasks is the right one + ) + { + return nullptr; + } + + // I) check for special defined targets first which must be handled exclusive. + // force using of "if() else if() ..." + + // I.I) "_blank" + // create a new task as child of this desktop instance + // Note: Used helper TaskCreator use us automatically ... + + if ( sTargetFrameName==SPECIALTARGET_BLANK ) + { + TaskCreator aCreator( m_xContext ); + xTarget = aCreator.createTask(sTargetFrameName, utl::MediaDescriptor()); + } + + // I.II) "_top" + // We are top by definition + + else if ( sTargetFrameName==SPECIALTARGET_TOP ) + { + xTarget = this; + } + + // I.III) "_self", "" + // This mean this "frame" in every case. + + else if ( + ( sTargetFrameName==SPECIALTARGET_SELF ) || + ( sTargetFrameName.isEmpty() ) + ) + { + xTarget = this; + } + + else + { + + // II) otherwise use optional given search flags + // force using of combinations of such flags. means no "else" part of use if() statements. + // But we ust break further searches if target was already found. + // Order of using flags is fix: SELF - CHILDREN - SIBLINGS - PARENT + // TASK and CREATE are handled special. + // But note: Such flags are not valid for the desktop - especially SIBLINGS or PARENT. + + // II.I) SELF + // Check for right name. If it's the searched one return ourself - otherwise + // ignore this flag. + + if ( + (nSearchFlags & css::frame::FrameSearchFlag::SELF) && + (m_sName == sTargetFrameName) + ) + { + xTarget = this; + } + + // II.II) TASKS + // This is a special flag. Normally it regulate search inside tasks and forbid access to parent trees. + // But the desktop exists outside such task trees. They are our sub trees. So the desktop implement + // a special feature: We use it to start search on our direct children only. That means we suppress + // search on ALL child frames. May that can be useful to get access on opened document tasks + // only without filter out all non really required sub frames ... + // Used helper method on our container doesn't create any frame - it's a search only. + + if ( + ( ! xTarget.is() ) && + (nSearchFlags & css::frame::FrameSearchFlag::TASKS) + ) + { + xTarget = m_aChildTaskContainer.searchOnDirectChildrens(sTargetFrameName); + } + + // II.III) CHILDREN + // Search on all children for the given target name. + // An empty name value can't occur here - because it must be already handled as "_self" + // before. Used helper function of container doesn't create any frame. + // It makes a deep search only. + + if ( + ( ! xTarget.is() ) && + (nSearchFlags & css::frame::FrameSearchFlag::CHILDREN) + ) + { + xTarget = m_aChildTaskContainer.searchOnAllChildrens(sTargetFrameName); + } + + // II.IV) CREATE + // If we haven't found any valid target frame by using normal flags - but user allowed us to create + // a new one ... we should do that. Used TaskCreator use us automatically as parent! + + if ( + ( ! xTarget.is() ) && + (nSearchFlags & css::frame::FrameSearchFlag::CREATE) + ) + { + TaskCreator aCreator( m_xContext ); + xTarget = aCreator.createTask(sTargetFrameName, utl::MediaDescriptor()); + } + } + + return xTarget; +} + +void SAL_CALL Desktop::disposing() +{ + // Safe impossible cases + // It's a programming error if dispose is called before terminate! + + // But if you just ignore the assertion (which happens in unit + // tests for instance in sc/qa/unit) nothing bad happens. + assert(m_bIsShutdown && "Desktop disposed before terminating it"); + + { + SolarMutexGuard aWriteLock; + + { + TransactionGuard aTransaction(m_aTransactionManager, E_HARDEXCEPTIONS); + } + + // Disable this instance for further work. + // This will wait for all current running transactions ... + // and reject all new incoming requests! + m_aTransactionManager.setWorkingMode(E_BEFORECLOSE); + } + + // Following lines of code can be called outside a synchronized block ... + // Because our transaction manager will block all new requests to this object. + // So nobody can use us any longer. + // Exception: Only removing of listener will work ... and this code can't be dangerous. + + // First we have to kill all listener connections. + // They might rely on our member and can hinder us on releasing them. + css::uno::Reference< css::uno::XInterface > xThis ( static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY ); + css::lang::EventObject aEvent( xThis ); + m_aListenerContainer.disposeAndClear( aEvent ); + + // Clear our child task container and forget all task references hardly. + // Normally all open document was already closed by our terminate() function before ... + // New opened frames will have a problem now .-) + m_aChildTaskContainer.clear(); + + // Dispose our helper too. + css::uno::Reference< css::lang::XEventListener > xFramesHelper( m_xFramesHelper, css::uno::UNO_QUERY ); + if( xFramesHelper.is() ) + xFramesHelper->disposing( aEvent ); + + // At least clean up other member references. + m_xDispatchHelper.clear(); + m_xFramesHelper.clear(); + m_xContext.clear(); + + m_xPipeTerminator.clear(); + m_xQuickLauncher.clear(); + m_xSWThreadManager.clear(); + + // we need a copy because the disposing might call the removeEventListener method + std::vector< css::uno::Reference<css::frame::XTerminateListener> > xComponentDllListeners; + xComponentDllListeners.swap(m_xComponentDllListeners); + for (auto& xListener: xComponentDllListeners) + { + xListener->disposing(aEvent); + } + xComponentDllListeners.clear(); + m_xSfxTerminator.clear(); + m_xCommandOptions.reset(); + + // From this point nothing will work further on this object ... + // excepting our dtor() .-) + m_aTransactionManager.setWorkingMode( E_CLOSE ); +} + +/* + @interface XComponent + @short add/remove listener for dispose events + @descr Add an event listener to this object, if you wish to get information + about our dying! + You must release this listener reference during your own disposing() method. + + @attention Our container is threadsafe himself. So we don't need any lock here. + @param "xListener", reference to valid listener. We don't accept invalid values! + @threadsafe yes +*/ +void SAL_CALL Desktop::addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Safe impossible cases + // Method not defined for all incoming parameter. + SAL_WARN_IF( !xListener.is(), "fwk.desktop", "Desktop::addEventListener(): Invalid parameter detected!" ); + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + m_aListenerContainer.addInterface( cppu::UnoType<css::lang::XEventListener>::get(), xListener ); +} + +void SAL_CALL Desktop::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Safe impossible cases + // Method not defined for all incoming parameter. + SAL_WARN_IF( !xListener.is(), "fwk.desktop", "Desktop::removeEventListener(): Invalid parameter detected!" ); + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_SOFTEXCEPTIONS ); + + m_aListenerContainer.removeInterface( cppu::UnoType<css::lang::XEventListener>::get(), xListener ); +} + +/*-************************************************************************************************************ + @interface XDispatchResultListener + @short callback for dispatches + @descr To support our method "loadComponentFromURL()" we are listener on temp. created dispatcher. + They call us back in this method "statusChanged()". As source of given state event, they give us a + reference to the target frame, in which dispatch was loaded! So we can use it to return his component + to caller! If no target exist ... ??!! + + @seealso method loadComponentFromURL() + + @param "aEvent", state event which (hopefully) valid information + @threadsafe yes +*//*-*************************************************************************************************************/ +void SAL_CALL Desktop::dispatchFinished( const css::frame::DispatchResultEvent& aEvent ) +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + SolarMutexGuard g; + if( m_eLoadState != E_INTERACTION ) + { + m_eLoadState = E_FAILED; + if( aEvent.State == css::frame::DispatchResultState::SUCCESS ) + { + css::uno::Reference< css::frame::XFrame > xLastFrame; /// last target of "loadComponentFromURL()"! + if ( aEvent.Result >>= xLastFrame ) + m_eLoadState = E_SUCCESSFUL; + } + } +} + +/*-************************************************************************************************************ + @interface XEventListener + @short not implemented! + @descr We are a status listener ... and so we must be an event listener too ... But we don't need it really! + We are a temp. listener only and our lifetime isn't smaller then of our temp. used dispatcher. + + @seealso method loadComponentFromURL() +*//*-*************************************************************************************************************/ +void SAL_CALL Desktop::disposing( const css::lang::EventObject& ) +{ + SAL_WARN( "fwk.desktop", "Desktop::disposing(): Algorithm error! Normally desktop is temp. listener ... not all the time. So this method shouldn't be called." ); +} + +/*-************************************************************************************************************ + @interface XInteractionHandler + @short callback for loadComponentFromURL for detected exceptions during load process + @descr In this case we must cancel loading and throw these detected exception again as result + of our own called method. + + @attention a) + Normal loop in loadComponentFromURL() breaks on set member m_eLoadState during callback statusChanged(). + But these interaction feature implements second way to do so! So we must look on different callbacks + for same operation ... and live with it. + b) + Search for given continuations too. If any XInteractionAbort exist ... use it to abort further operations + for currently running operation! + + @seealso method loadComponentFromURL() + @seealso member m_eLoadState + + @param "xRequest", request for interaction - normal a wrapped target exception from bottom services + @threadsafe yes +*//*-*************************************************************************************************************/ +void SAL_CALL Desktop::handle( const css::uno::Reference< css::task::XInteractionRequest >& xRequest ) +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + // Don't check incoming request! + // If somewhere starts interaction without right parameter - he made something wrong. + // loadComponentFromURL() waits for these event - otherwise it yield for ever! + + // get packed request and work on it first + // Attention: Don't set it on internal member BEFORE interaction is finished - because + // "loadComponentFromURL()" yield tills this member is changed. If we do it before + // interaction finish we can't guarantee right functionality. May be we cancel load process to earlier... + css::uno::Any aRequest = xRequest->getRequest(); + + // extract continuations from request + css::uno::Sequence< css::uno::Reference< css::task::XInteractionContinuation > > lContinuations = xRequest->getContinuations(); + css::uno::Reference< css::task::XInteractionAbort > xAbort; + css::uno::Reference< css::task::XInteractionApprove > xApprove; + css::uno::Reference< css::document::XInteractionFilterSelect > xFilterSelect; + bool bAbort = false; + + sal_Int32 nCount=lContinuations.getLength(); + for( sal_Int32 nStep=0; nStep<nCount; ++nStep ) + { + if( ! xAbort.is() ) + xAbort.set( lContinuations[nStep], css::uno::UNO_QUERY ); + + if( ! xApprove.is() ) + xApprove.set( lContinuations[nStep], css::uno::UNO_QUERY ); + + if( ! xFilterSelect.is() ) + xFilterSelect.set( lContinuations[nStep], css::uno::UNO_QUERY ); + } + + // differ between abortable interactions (error, unknown filter...) + // and other ones (ambiguous but not unknown filter...) + css::task::ErrorCodeRequest aErrorCodeRequest; + if( aRequest >>= aErrorCodeRequest ) + { + bool bWarning = ErrCode(aErrorCodeRequest.ErrCode).IsWarning(); + if (xApprove.is() && bWarning) + xApprove->select(); + else + if (xAbort.is()) + { + xAbort->select(); + bAbort = true; + } + } + else if( xAbort.is() ) + { + xAbort->select(); + bAbort = true; + } + + // Ok now it's time to break yield loop of loadComponentFromURL(). + // But only for really aborted requests! + // For example warnings will be approved and we wait for any success story ... + if (bAbort) + { + SolarMutexGuard g; + m_eLoadState = E_INTERACTION; + } +} + +::sal_Int32 SAL_CALL Desktop::leaseNumber( const css::uno::Reference< css::uno::XInterface >& xComponent ) +{ + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + return m_xTitleNumberGenerator->leaseNumber (xComponent); +} + +void SAL_CALL Desktop::releaseNumber( ::sal_Int32 nNumber ) +{ + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + m_xTitleNumberGenerator->releaseNumber (nNumber); +} + +void SAL_CALL Desktop::releaseNumberForComponent( const css::uno::Reference< css::uno::XInterface >& xComponent ) +{ + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + m_xTitleNumberGenerator->releaseNumberForComponent (xComponent); +} + +OUString SAL_CALL Desktop::getUntitledPrefix() +{ + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + return m_xTitleNumberGenerator->getUntitledPrefix (); +} + +/*-************************************************************************************************************ + @short try to convert a property value + @descr This method is called from helperclass "OPropertySetHelper". + Don't use this directly! + You must try to convert the value of given PropHandle and + return results of this operation. This will be used to ask vetoable + listener. If no listener has a veto, we will change value really! + ( in method setFastPropertyValue_NoBroadcast(...) ) + + @attention Methods of OPropertySethelper are safed by using our shared osl mutex! (see ctor!) + So we must use different locks to make our implementation threadsafe. + + @seealso class OPropertySetHelper + @seealso method setFastPropertyValue_NoBroadcast() + + @param "aConvertedValue" new converted value of property + @param "aOldValue" old value of property + @param "nHandle" handle of property + @param "aValue" new value of property + @return sal_True if value will be changed, sal_FALSE otherway + + @onerror IllegalArgumentException, if you call this with an invalid argument + @threadsafe yes +*//*-*************************************************************************************************************/ +sal_Bool SAL_CALL Desktop::convertFastPropertyValue( css::uno::Any& aConvertedValue , + css::uno::Any& aOldValue , + sal_Int32 nHandle , + const css::uno::Any& aValue ) +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + // Initialize state with sal_False !!! + // (Handle can be invalid) + bool bReturn = false; + + switch( nHandle ) + { + case PropHandle::SuspendQuickstartVeto: + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(m_bSuspendQuickstartVeto), + aValue, + aOldValue, + aConvertedValue); + break; + case PropHandle::DispatchRecorderSupplier : + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(m_xDispatchRecorderSupplier), + aValue, + aOldValue, + aConvertedValue); + break; + case PropHandle::Title : + bReturn = PropHelper::willPropertyBeChanged( + css::uno::Any(m_sTitle), + aValue, + aOldValue, + aConvertedValue); + break; + } + + // Return state of operation. + return bReturn; +} + +/*-************************************************************************************************************ + @short set value of a transient property + @descr This method is calling from helperclass "OPropertySetHelper". + Don't use this directly! + Handle and value are valid everyway! You must set the new value only. + After this, baseclass send messages to all listener automatically. + + @seealso class OPropertySetHelper + + @param "nHandle" handle of property to change + @param "aValue" new value of property + @onerror An exception is thrown. + @threadsafe yes +*//*-*************************************************************************************************************/ +void SAL_CALL Desktop::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle , + const css::uno::Any& aValue ) +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + switch( nHandle ) + { + case PropHandle::SuspendQuickstartVeto: aValue >>= m_bSuspendQuickstartVeto; + break; + case PropHandle::DispatchRecorderSupplier: aValue >>= m_xDispatchRecorderSupplier; + break; + case PropHandle::Title: aValue >>= m_sTitle; + break; + } +} + +/*-************************************************************************************************************ + @short get value of a transient property + @descr This method is calling from helperclass "OPropertySetHelper". + Don't use this directly! + + @attention We don't need any mutex or lock here ... We use threadsafe container or methods here only! + + @seealso class OPropertySetHelper + + @param "nHandle" handle of property to change + @param "aValue" current value of property + @threadsafe yes +*//*-*************************************************************************************************************/ +void SAL_CALL Desktop::getFastPropertyValue( css::uno::Any& aValue , + sal_Int32 nHandle ) const +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + switch( nHandle ) + { + case PropHandle::ActiveFrame : aValue <<= m_aChildTaskContainer.getActive(); + break; + case PropHandle::IsPlugged : aValue <<= false; + break; + case PropHandle::SuspendQuickstartVeto: aValue <<= m_bSuspendQuickstartVeto; + break; + case PropHandle::DispatchRecorderSupplier: aValue <<= m_xDispatchRecorderSupplier; + break; + case PropHandle::Title: aValue <<= m_sTitle; + break; + } +} + +::cppu::IPropertyArrayHelper& SAL_CALL Desktop::getInfoHelper() +{ + static cppu::OPropertyArrayHelper HELPER = + [] () { + return cppu::OPropertyArrayHelper { + {{"ActiveFrame", PropHandle::ActiveFrame, + cppu::UnoType<css::lang::XComponent>::get(), + (css::beans::PropertyAttribute::TRANSIENT + | css::beans::PropertyAttribute::READONLY)}, + {"DispatchRecorderSupplier", + PropHandle::DispatchRecorderSupplier, + cppu::UnoType<css::frame::XDispatchRecorderSupplier>::get(), + css::beans::PropertyAttribute::TRANSIENT}, + {"IsPlugged", + PropHandle::IsPlugged, cppu::UnoType<bool>::get(), + (css::beans::PropertyAttribute::TRANSIENT + | css::beans::PropertyAttribute::READONLY)}, + {"SuspendQuickstartVeto", PropHandle::SuspendQuickstartVeto, + cppu::UnoType<bool>::get(), + css::beans::PropertyAttribute::TRANSIENT}, + {"Title", PropHandle::Title, cppu::UnoType<OUString>::get(), + css::beans::PropertyAttribute::TRANSIENT}}, + true}; + }(); + return HELPER; +} + +/*-************************************************************************************************************ + @short return propertysetinfo + @descr You can call this method to get information about transient properties + of this object. + + @attention You must use global lock (method use static variable) ... and it must be the shareable osl mutex of it. + Because; our baseclass use this mutex to make his code threadsafe. We use our lock! + So we could have two different mutex/lock mechanism at the same object. + + @seealso class OPropertySetHelper + @seealso interface XPropertySet + @seealso interface XMultiPropertySet + @return reference to object with information [XPropertySetInfo] + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL Desktop::getPropertySetInfo() +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + // Create structure of propertysetinfo for baseclass "OPropertySetHelper". + // (Use method "getInfoHelper()".) + static css::uno::Reference< css::beans::XPropertySetInfo > xInfo( + cppu::OPropertySetHelper::createPropertySetInfo( getInfoHelper() ) ); + + return xInfo; +} + +/*-************************************************************************************************************ + @short return current component of current frame + @descr The desktop himself has no component. But every frame in subtree. + If somewhere call getCurrentComponent() at this class, we try to find the right frame and + then we try to become his component. It can be a VCL-component, the model or the controller + of founded frame. + + @attention We don't work on internal member ... so we don't need any lock here. + + @seealso method getCurrentComponent(); + + @param "xFrame", reference to valid frame in hierarchy. Method is not defined for invalid values. + But we don't check these. It's an IMPL-method and caller must use it right! + @return A reference to found component. + + @onerror A null reference is returned. + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::lang::XComponent > Desktop::impl_getFrameComponent( const css::uno::Reference< css::frame::XFrame >& xFrame ) const +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Register transaction and reject wrong calls. + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + // Set default return value, if method failed. + css::uno::Reference< css::lang::XComponent > xComponent; + // Does no controller exists? + css::uno::Reference< css::frame::XController > xController = xFrame->getController(); + if( !xController.is() ) + { + // Controller not exist - use the VCL-component. + xComponent = xFrame->getComponentWindow(); + } + else + { + // Does no model exists? + css::uno::Reference< css::frame::XModel > xModel = xController->getModel(); + if( xModel.is() ) + { + // Model exist - use the model as component. + xComponent = xModel; + } + else + { + // Model not exist - use the controller as component. + xComponent = xController; + } + } + + return xComponent; +} + +bool Desktop::impl_sendQueryTerminationEvent(Desktop::TTerminateListenerList& lCalledListener) +{ + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + comphelper::OInterfaceContainerHelper2* pContainer = m_aListenerContainer.getContainer( cppu::UnoType<css::frame::XTerminateListener>::get()); + if ( ! pContainer ) + return true; + + css::lang::EventObject aEvent( static_cast< ::cppu::OWeakObject* >(this) ); + + comphelper::OInterfaceIteratorHelper2 aIterator( *pContainer ); + while ( aIterator.hasMoreElements() ) + { + try + { + css::uno::Reference< css::frame::XTerminateListener > xListener(aIterator.next(), css::uno::UNO_QUERY); + if ( ! xListener.is() ) + continue; + xListener->queryTermination( aEvent ); + lCalledListener.push_back(xListener); + } + catch( const css::frame::TerminationVetoException& ) + { + // first veto will stop the query loop. + return false; + } + catch( const css::uno::Exception& ) + { + // clean up container. + // E.g. dead remote listener objects can make trouble otherwise. + // Iterator implementation allows removing objects during it's used ! + aIterator.remove(); + } + } + + return true; +} + +void Desktop::impl_sendCancelTerminationEvent(const Desktop::TTerminateListenerList& lCalledListener) +{ + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + css::lang::EventObject aEvent( static_cast< ::cppu::OWeakObject* >(this) ); + for (const css::uno::Reference<css::frame::XTerminateListener>& xListener : lCalledListener) + { + try + { + // Note: cancelTermination() is a new and optional interface method ! + css::uno::Reference< css::frame::XTerminateListener2 > xListenerGeneration2(xListener, css::uno::UNO_QUERY); + if ( ! xListenerGeneration2.is() ) + continue; + xListenerGeneration2->cancelTermination( aEvent ); + } + catch( const css::uno::Exception& ) + {} + } +} + +void Desktop::impl_sendTerminateToClipboard() +{ + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + comphelper::OInterfaceContainerHelper2* pContainer = m_aListenerContainer.getContainer( cppu::UnoType<css::frame::XTerminateListener>::get()); + if ( ! pContainer ) + return; + + comphelper::OInterfaceIteratorHelper2 aIterator( *pContainer ); + while ( aIterator.hasMoreElements() ) + { + try + { + css::frame::XTerminateListener* pTerminateListener = + static_cast< css::frame::XTerminateListener* >(aIterator.next()); + css::uno::Reference< css::lang::XServiceInfo > xInfo( pTerminateListener, css::uno::UNO_QUERY ); + if ( !xInfo.is() ) + continue; + + if ( xInfo->getImplementationName() != "com.sun.star.comp.svt.TransferableHelperTerminateListener" ) + continue; + + css::lang::EventObject aEvent( static_cast< ::cppu::OWeakObject* >(this) ); + pTerminateListener->notifyTermination( aEvent ); + + // don't notify twice + aIterator.remove(); + } + catch( const css::uno::Exception& ) + { + // clean up container. + // E.g. dead remote listener objects can make trouble otherwise. + // Iterator implementation allows removing objects during it's used ! + aIterator.remove(); + } + } +} + +void Desktop::impl_sendNotifyTerminationEvent() +{ + TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS ); + + comphelper::OInterfaceContainerHelper2* pContainer = m_aListenerContainer.getContainer( cppu::UnoType<css::frame::XTerminateListener>::get()); + if ( ! pContainer ) + return; + + css::lang::EventObject aEvent( static_cast< ::cppu::OWeakObject* >(this) ); + + comphelper::OInterfaceIteratorHelper2 aIterator( *pContainer ); + while ( aIterator.hasMoreElements() ) + { + try + { + static_cast< css::frame::XTerminateListener* >(aIterator.next())->notifyTermination( aEvent ); + } + catch( const css::uno::Exception& ) + { + // clean up container. + // E.g. dead remote listener objects can make trouble otherwise. + // Iterator implementation allows removing objects during it's used ! + aIterator.remove(); + } + } +} + +bool Desktop::impl_closeFrames(bool bAllowUI) +{ + SolarMutexClearableGuard aReadLock; + css::uno::Sequence< css::uno::Reference< css::frame::XFrame > > lFrames = m_aChildTaskContainer.getAllElements(); + aReadLock.clear(); + + ::sal_Int32 c = lFrames.getLength(); + ::sal_Int32 i = 0; + ::sal_Int32 nNonClosedFrames = 0; + + for( i=0; i<c; ++i ) + { + try + { + css::uno::Reference< css::frame::XFrame > xFrame = lFrames[i]; + + // XController.suspend() will show a UI ... + // Use it in case it was allowed from outside only. + bool bSuspended = false; + css::uno::Reference< css::frame::XController > xController = xFrame->getController(); + if ( bAllowUI && xController.is() ) + { + bSuspended = xController->suspend( true ); + if ( ! bSuspended ) + { + ++nNonClosedFrames; + if(m_bSession) + break; + else + continue; + } + } + + // Try to close frame (in case no UI was allowed without calling XController->suspend() before!) + // But don't deliver ownership to any other one! + // This method can be called again. + css::uno::Reference< css::util::XCloseable > xClose( xFrame, css::uno::UNO_QUERY ); + if ( xClose.is() ) + { + try + { + xClose->close(false); + } + catch(const css::util::CloseVetoException&) + { + // Any internal process of this frame disagree with our request. + // Safe this state but don't break these loop. Other frames has to be closed! + ++nNonClosedFrames; + + // Reactivate controller. + // It can happen that XController.suspend() returned true... but a registered close listener + // threw these veto exception. Then the controller has to be reactivated. Otherwise + // these document doesn't work any more. + if ( bSuspended && xController.is()) + xController->suspend(false); + } + + // If interface XClosable interface exists and was used... + // it's not allowed to use XComponent->dispose() also! + continue; + } + + // XClosable not supported ? + // Then we have to dispose these frame hardly. + if ( xFrame.is() ) + xFrame->dispose(); + + // Don't remove these frame from our child container! + // A frame do it by itself inside close()/dispose() method. + } + catch(const css::lang::DisposedException&) + { + // Dispose frames are closed frames. + // So we can count it here .-) + } + } + + // reset the session + m_bSession = false; + + return (nNonClosedFrames < 1); +} + +} // namespace framework + +namespace { + +rtl::Reference<framework::Desktop> createDesktop( + css::uno::Reference<css::uno::XComponentContext> const & context) +{ + SolarMutexGuard g; // tdf#114025 init with SolarMutex to avoid deadlock + rtl::Reference<framework::Desktop> desktop(new framework::Desktop(context)); + desktop->constructorInit(); + return desktop; +} + +} + +const rtl::Reference<framework::Desktop> & framework::getDesktop( + css::uno::Reference<css::uno::XComponentContext> const & context) +{ + static auto const instance = createDesktop(context); + return instance; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_Desktop_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(framework::getDesktop(context).get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/dispatchhelper.cxx b/framework/source/services/dispatchhelper.cxx new file mode 100644 index 000000000..8f3d77d32 --- /dev/null +++ b/framework/source/services/dispatchhelper.cxx @@ -0,0 +1,218 @@ +/* -*- 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 <framework/dispatchhelper.hxx> + +#include <com/sun/star/frame/XNotifyingDispatch.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> + +#include <comphelper/profilezone.hxx> +#include <unotools/mediadescriptor.hxx> +#include <utility> +#include <vcl/threadex.hxx> +#include <cppuhelper/supportsservice.hxx> + +namespace framework +{ +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL DispatchHelper::getImplementationName() +{ + return "com.sun.star.comp.framework.services.DispatchHelper"; +} + +sal_Bool SAL_CALL DispatchHelper::supportsService(const OUString& sServiceName) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence<OUString> SAL_CALL DispatchHelper::getSupportedServiceNames() +{ + return { "com.sun.star.frame.DispatchHelper" }; +} + +/** ctor. + + @param xSMGR the global uno service manager, which can be used to create own needed services. +*/ +DispatchHelper::DispatchHelper(css::uno::Reference<css::uno::XComponentContext> xContext) + : m_xContext(std::move(xContext)) + , m_aBlockFlag(false) +{ +} + +/** dtor. +*/ +DispatchHelper::~DispatchHelper() {} + +/** capsulate all steps of a dispatch request and provide so an easy way for dispatches. + + @param xDispatchProvider + identifies the object, which provides may be valid dispatch objects for this execute. + + @param sURL + describes the requested feature. + + @param sTargetFrameName + points to the frame, which must be used (or may be created) for this dispatch. + + @param nSearchFlags + in case the <var>sTargetFrameName</var> isn't unique, these flags regulate further searches. + + @param lArguments + optional arguments for this request. + + @return An Any which capsulate a possible result of the internal wrapped dispatch. + */ +css::uno::Any SAL_CALL DispatchHelper::executeDispatch( + const css::uno::Reference<css::frame::XDispatchProvider>& xDispatchProvider, + const OUString& sURL, const OUString& sTargetFrameName, sal_Int32 nSearchFlags, + const css::uno::Sequence<css::beans::PropertyValue>& lArguments) +{ + // check for valid parameters + if ((!xDispatchProvider.is()) || (!m_xContext.is()) || (sURL.isEmpty())) + { + return css::uno::Any(); + } + + // parse given URL + css::uno::Reference<css::util::XURLTransformer> xParser; + /* SAFE { */ + { + std::scoped_lock aReadLock(m_mutex); + xParser = css::util::URLTransformer::create(m_xContext); + } + /* } SAFE */ + + css::util::URL aURL; + aURL.Complete = sURL; + xParser->parseStrict(aURL); + + // search dispatcher + css::uno::Reference<css::frame::XDispatch> xDispatch + = xDispatchProvider->queryDispatch(aURL, sTargetFrameName, nSearchFlags); + + utl::MediaDescriptor aDescriptor(lArguments); + bool bOnMainThread = aDescriptor.getUnpackedValueOrDefault("OnMainThread", false); + + if (bOnMainThread) + return vcl::solarthread::syncExecute([this, &xDispatch, &aURL, &lArguments]() { + return executeDispatch(xDispatch, aURL, true, lArguments); + }); + else + return executeDispatch(xDispatch, aURL, true, lArguments); +} + +const css::uno::Any& +DispatchHelper::executeDispatch(const css::uno::Reference<css::frame::XDispatch>& xDispatch, + const css::util::URL& aURL, bool SyncronFlag, + const css::uno::Sequence<css::beans::PropertyValue>& lArguments) +{ + comphelper::ProfileZone aZone("executeDispatch"); + css::uno::Reference<css::uno::XInterface> xTHIS(static_cast<::cppu::OWeakObject*>(this), + css::uno::UNO_QUERY); + m_aResult.clear(); + + // check for valid parameters + if (xDispatch.is()) + { + css::uno::Reference<css::frame::XNotifyingDispatch> xNotifyDispatch(xDispatch, + css::uno::UNO_QUERY); + + // make sure that synchronous execution is used (if possible) + css::uno::Sequence<css::beans::PropertyValue> aArguments(lArguments); + sal_Int32 nLength = lArguments.getLength(); + aArguments.realloc(nLength + 1); + auto pArguments = aArguments.getArray(); + pArguments[nLength].Name = "SynchronMode"; + pArguments[nLength].Value <<= SyncronFlag; + + if (xNotifyDispatch.is()) + { + // dispatch it with guaranteed notification + // Here we can hope for a result ... instead of the normal dispatch. + css::uno::Reference<css::frame::XDispatchResultListener> xListener(xTHIS, + css::uno::UNO_QUERY); + /* SAFE { */ + { + std::scoped_lock aWriteLock(m_mutex); + m_xBroadcaster = xNotifyDispatch; + m_aBlockFlag = false; + } + /* } SAFE */ + + // dispatch it and wait for a notification + // TODO/MBA: waiting in main thread?! + xNotifyDispatch->dispatchWithNotification(aURL, aArguments, xListener); + + std::unique_lock aWriteLock(m_mutex); + m_aBlock.wait(aWriteLock, [this] { return m_aBlockFlag; }); // wait for result + } + else + { + // dispatch it without any chance to get a result + xDispatch->dispatch(aURL, aArguments); + } + } + + return m_aResult; +} + +/** callback for started dispatch with guaranteed notifications. + + We must save the result, so the method executeDispatch() can return it. + Further we must release the broadcaster (otherwise it can't die) + and unblock the waiting executeDispatch() request. + + @param aResult + describes the result of the dispatch operation + */ +void SAL_CALL DispatchHelper::dispatchFinished(const css::frame::DispatchResultEvent& aResult) +{ + std::scoped_lock g(m_mutex); + m_aResult <<= aResult; + m_aBlockFlag = true; + m_aBlock.notify_one(); + m_xBroadcaster.clear(); +} + +/** we have to release our broadcaster reference. + + @param aEvent + describe the source of this event and MUST be our save broadcaster! + */ +void SAL_CALL DispatchHelper::disposing(const css::lang::EventObject&) +{ + std::scoped_lock g(m_mutex); + m_aResult.clear(); + m_aBlockFlag = true; + m_aBlock.notify_one(); + m_xBroadcaster.clear(); +} +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_DispatchHelper_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new framework::DispatchHelper(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/frame.cxx b/framework/source/services/frame.cxx new file mode 100644 index 000000000..5fc6309aa --- /dev/null +++ b/framework/source/services/frame.cxx @@ -0,0 +1,3340 @@ +/* -*- 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 <dispatch/dispatchprovider.hxx> +#include <dispatch/interceptionhelper.hxx> +#include <dispatch/windowcommanddispatch.hxx> +#include <loadenv/loadenv.hxx> +#include <helper/oframes.hxx> +#include <framework/framecontainer.hxx> +#include <framework/titlehelper.hxx> +#include <svtools/openfiledroptargetlistener.hxx> +#include <classes/taskcreator.hxx> +#include <loadenv/targethelper.hxx> +#include <framework/framelistanalyzer.hxx> +#include <helper/dockingareadefaultacceptor.hxx> +#include <dispatch/dispatchinformationprovider.hxx> + +#include <pattern/window.hxx> +#include <properties.h> +#include <targets.h> + +#include <com/sun/star/awt/Toolkit.hpp> +#include <com/sun/star/awt/XDevice.hpp> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyExistException.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp> +#include <com/sun/star/frame/XFrame2.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/XTitleChangeBroadcaster.hpp> +#include <com/sun/star/frame/LayoutManager.hpp> +#include <com/sun/star/frame/XDesktop.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/task/StatusIndicatorFactory.hpp> +#include <com/sun/star/task/theJobExecutor.hpp> +#include <com/sun/star/task/XJobExecutor.hpp> +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <comphelper/multiinterfacecontainer3.hxx> +#include <comphelper/multicontainer2.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <sal/log.hxx> +#include <vcl/window.hxx> +#include <vcl/wrkwin.hxx> +#include <vcl/svapp.hxx> + +#include <toolkit/helper/vclunohelper.hxx> +#include <unotools/moduleoptions.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/cmdoptions.hxx> +#include <vcl/threadex.hxx> +#include <mutex> + +using namespace framework; + +namespace { + +// This enum can be used to set different active states of frames +enum EActiveState +{ + E_INACTIVE, // I am not a member of active path in tree and i don't have the focus. + E_ACTIVE, // I am in the middle of an active path in tree and i don't have the focus. + E_FOCUS // I have the focus now. I must a member of an active path! +}; + +/*-************************************************************************************************************ + @short implements a normal frame of hierarchy + @descr An instance of these class can be a normal node in frame tree. A frame support influencing of his + subtree, find of subframes, activate- and deactivate-mechanism as well as + set/get of a frame window, component or controller. +*//*-*************************************************************************************************************/ +class XFrameImpl: + private cppu::BaseMutex, + public cppu::PartialWeakComponentImplHelper< + css::lang::XServiceInfo, css::frame::XFrame2, css::awt::XWindowListener, + css::awt::XTopWindowListener, css::awt::XFocusListener, + css::document::XActionLockable, css::util::XCloseable, + css::frame::XComponentLoader, css::frame::XTitle, + css::frame::XTitleChangeBroadcaster, css::beans::XPropertySet, + css::beans::XPropertySetInfo> +{ +public: + + explicit XFrameImpl(css::uno::Reference< css::uno::XComponentContext > xContext); + + /// Initialization function after having acquire()'d. + void initListeners(); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.Frame"; + } + + 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.frame.Frame"}; + } + + // XComponentLoader + + virtual css::uno::Reference< css::lang::XComponent > SAL_CALL loadComponentFromURL( + const OUString& sURL, + const OUString& sTargetFrameName, + sal_Int32 nSearchFlags, + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) override; + + // XFramesSupplier + + virtual css::uno::Reference < css::frame::XFrames > SAL_CALL getFrames() override; + virtual css::uno::Reference < css::frame::XFrame > SAL_CALL getActiveFrame() override; + virtual void SAL_CALL setActiveFrame(const css::uno::Reference < css::frame::XFrame > & xFrame) override; + + // XFrame + + virtual void SAL_CALL initialize(const css::uno::Reference < css::awt::XWindow > & xWindow) override; + virtual css::uno::Reference < css::awt::XWindow > SAL_CALL getContainerWindow() override; + virtual void SAL_CALL setCreator(const css::uno::Reference < css::frame::XFramesSupplier > & xCreator) override; + virtual css::uno::Reference < css::frame::XFramesSupplier > SAL_CALL getCreator() override; + virtual OUString SAL_CALL getName() override; + virtual void SAL_CALL setName(const OUString & sName) override; + virtual css::uno::Reference < css::frame::XFrame > SAL_CALL findFrame( + const OUString & sTargetFrameName, + sal_Int32 nSearchFlags) override; + virtual sal_Bool SAL_CALL isTop() override; + virtual void SAL_CALL activate() override; + virtual void SAL_CALL deactivate() override; + virtual sal_Bool SAL_CALL isActive() override; + virtual void SAL_CALL contextChanged() override; + virtual sal_Bool SAL_CALL setComponent( + const css::uno::Reference < css::awt::XWindow > & xComponentWindow, + const css::uno::Reference < css::frame::XController > & xController) override; + virtual css::uno::Reference < css::awt::XWindow > SAL_CALL getComponentWindow() override; + virtual css::uno::Reference < css::frame::XController > SAL_CALL getController() override; + virtual void SAL_CALL addFrameActionListener(const css::uno::Reference < css::frame::XFrameActionListener > & xListener) override; + virtual void SAL_CALL removeFrameActionListener(const css::uno::Reference < css::frame::XFrameActionListener > & xListener) override; + + // XComponent + + virtual void SAL_CALL disposing() override; + virtual void SAL_CALL addEventListener(const css::uno::Reference < css::lang::XEventListener > & xListener) override; + virtual void SAL_CALL removeEventListener(const css::uno::Reference < css::lang::XEventListener > & xListener) override; + + // XStatusIndicatorFactory + + virtual css::uno::Reference < css::task::XStatusIndicator > SAL_CALL createStatusIndicator() override; + + // XDispatchProvider + + virtual css::uno::Reference < css::frame::XDispatch > SAL_CALL queryDispatch(const css::util::URL & aURL, + const OUString & sTargetFrameName, + sal_Int32 nSearchFlags) override; + virtual css::uno::Sequence < css::uno::Reference < css::frame::XDispatch > > SAL_CALL queryDispatches( + const css::uno::Sequence < css::frame::DispatchDescriptor > & lDescriptor) override; + + // XDispatchProviderInterception + + virtual void SAL_CALL registerDispatchProviderInterceptor( + const css::uno::Reference < css::frame::XDispatchProviderInterceptor > & xInterceptor) override; + virtual void SAL_CALL releaseDispatchProviderInterceptor( + const css::uno::Reference < css::frame::XDispatchProviderInterceptor > & xInterceptor) override; + + // XDispatchInformationProvider + + virtual css::uno::Sequence < sal_Int16 > SAL_CALL getSupportedCommandGroups() override; + virtual css::uno::Sequence < css::frame::DispatchInformation > SAL_CALL getConfigurableDispatchInformation(sal_Int16 nCommandGroup) override; + + // XWindowListener + // Attention: windowResized() and windowShown() are implement only! All other are empty! + + virtual void SAL_CALL windowResized(const css::awt::WindowEvent & aEvent) override; + virtual void SAL_CALL windowMoved(const css::awt::WindowEvent & /*aEvent*/ ) override {}; + virtual void SAL_CALL windowShown(const css::lang::EventObject & aEvent) override; + virtual void SAL_CALL windowHidden(const css::lang::EventObject & aEvent) override; + + // XFocusListener + // Attention: focusLost() not implemented yet! + + virtual void SAL_CALL focusGained(const css::awt::FocusEvent & aEvent) override; + virtual void SAL_CALL focusLost(const css::awt::FocusEvent & /*aEvent*/ ) override {}; + + // XTopWindowListener + // Attention: windowActivated(), windowDeactivated() and windowClosing() are implement only! All other are empty! + + virtual void SAL_CALL windowActivated(const css::lang::EventObject & aEvent) override; + virtual void SAL_CALL windowDeactivated(const css::lang::EventObject & aEvent) override; + virtual void SAL_CALL windowOpened(const css::lang::EventObject & /*aEvent*/ ) override {}; + virtual void SAL_CALL windowClosing(const css::lang::EventObject & aEvent) override; + virtual void SAL_CALL windowClosed(const css::lang::EventObject & /*aEvent*/ ) override {}; + virtual void SAL_CALL windowMinimized(const css::lang::EventObject & /*aEvent*/ ) override {}; + virtual void SAL_CALL windowNormalized(const css::lang::EventObject & /*aEvent*/ ) override {}; + + // XEventListener + + virtual void SAL_CALL disposing(const css::lang::EventObject & aEvent) override; + + // XActionLockable + + virtual sal_Bool SAL_CALL isActionLocked() override; + virtual void SAL_CALL addActionLock() override; + virtual void SAL_CALL removeActionLock() override; + virtual void SAL_CALL setActionLocks(sal_Int16 nLock) override; + virtual sal_Int16 SAL_CALL resetActionLocks() override; + + // XCloseable + + virtual void SAL_CALL close(sal_Bool bDeliverOwnership) override; + + // XCloseBroadcaster + + virtual void SAL_CALL addCloseListener(const css::uno::Reference < css::util::XCloseListener > & xListener) override; + virtual void SAL_CALL removeCloseListener(const css::uno::Reference < css::util::XCloseListener > & xListener) override; + + // XTitle + + virtual OUString SAL_CALL getTitle() override; + virtual void SAL_CALL setTitle(const OUString & sTitle) override; + + // XTitleChangeBroadcaster + + virtual void SAL_CALL addTitleChangeListener(const css::uno::Reference < css::frame::XTitleChangeListener > & xListener) override; + virtual void SAL_CALL removeTitleChangeListener(const css::uno::Reference < css::frame::XTitleChangeListener > & xListenr) override; + + // XFrame2 attributes + + virtual css::uno::Reference < css::container::XNameContainer > SAL_CALL getUserDefinedAttributes() override; + + virtual css::uno::Reference < css::frame::XDispatchRecorderSupplier > SAL_CALL getDispatchRecorderSupplier() override; + virtual void SAL_CALL setDispatchRecorderSupplier(const css::uno::Reference < css::frame::XDispatchRecorderSupplier > & ) override; + + virtual css::uno::Reference < css::uno::XInterface > SAL_CALL getLayoutManager() override; + virtual void SAL_CALL setLayoutManager(const css::uno::Reference < css::uno::XInterface > & ) override; + + // XPropertySet + virtual css::uno::Reference < css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + + virtual void SAL_CALL setPropertyValue(const OUString & sProperty, const css::uno::Any & aValue) override; + + virtual css::uno::Any SAL_CALL getPropertyValue(const OUString & sProperty) override; + + virtual void SAL_CALL addPropertyChangeListener( + const OUString & sProperty, + const css::uno::Reference < css::beans::XPropertyChangeListener > & xListener) override; + + virtual void SAL_CALL removePropertyChangeListener( + const OUString & sProperty, + const css::uno::Reference < css::beans::XPropertyChangeListener > & xListener) override; + + virtual void SAL_CALL addVetoableChangeListener( + const OUString & sProperty, + const css::uno::Reference < css::beans::XVetoableChangeListener > & xListener) override; + + virtual void SAL_CALL removeVetoableChangeListener( + const OUString & sProperty, + const css::uno::Reference < css::beans::XVetoableChangeListener > & xListener) override; + + // XPropertySetInfo + virtual css::uno::Sequence < css::beans::Property > SAL_CALL getProperties() override; + + virtual css::beans::Property SAL_CALL getPropertyByName(const OUString & sName) override; + + virtual sal_Bool SAL_CALL hasPropertyByName(const OUString & sName) override; + + +private: + + void impl_setPropertyValue(sal_Int32 nHandle, + const css::uno::Any& aValue); + + css::uno::Any impl_getPropertyValue(sal_Int32 nHandle); + + /** set a new owner for this helper. + * + * This owner is used as source for all broadcasted events. + * Further we hold it weak, because we don't wish to be disposed() .-) + */ + void impl_setPropertyChangeBroadcaster(const css::uno::Reference< css::uno::XInterface >& xBroadcaster); + + /** add a new property info to the set of supported ones. + * + * @param aProperty + * describes the new property. + * + * @throw [css::beans::PropertyExistException] + * if a property with the same name already exists. + * + * Note: The consistence of the whole set of properties is not checked here. + * Means e.g. ... a handle which exists more than once is not detected. + * The owner of this class has to be sure, that every new property does + * not clash with any existing one. + */ + void impl_addPropertyInfo(const css::beans::Property& aProperty); + + /** mark the object as "dead". + */ + void impl_disablePropertySet(); + + bool impl_existsVeto(const css::beans::PropertyChangeEvent& aEvent); + + void impl_notifyChangeListener(const css::beans::PropertyChangeEvent& aEvent); + + /*-**************************************************************************************************** + @short helper methods + @descr Follow methods are needed at different points of our code (more than ones!). + + @attention Threadsafe methods are signed by "implts_..."! + *//*-*****************************************************************************************************/ + + // threadsafe + void implts_sendFrameActionEvent ( const css::frame::FrameAction& aAction ); + void implts_resizeComponentWindow ( ); + void implts_setIconOnWindow ( ); + void implts_startWindowListening ( ); + void implts_stopWindowListening ( ); + void implts_checkSuicide ( ); + void implts_forgetSubFrames ( ); + + // non threadsafe + void impl_checkMenuCloser ( ); + void impl_setCloser ( const css::uno::Reference< css::frame::XFrame2 >& xFrame , bool bState ); + + void disableLayoutManager(const css::uno::Reference< css::frame::XLayoutManager2 >& xLayoutManager); + + void checkDisposed() { + osl::MutexGuard g(rBHelper.rMutex); + if (rBHelper.bInDispose || rBHelper.bDisposed) { + throw css::lang::DisposedException("Frame disposed"); + } + } + +// variables +// -threadsafe by SolarMutex + + /// reference to factory, which has create this instance + css::uno::Reference< css::uno::XComponentContext > m_xContext; + /// reference to factory helper to create status indicator objects + css::uno::Reference< css::task::XStatusIndicatorFactory > m_xIndicatorFactoryHelper; + /// points to an external set progress, which should be used instead of the internal one. + css::uno::WeakReference< css::task::XStatusIndicator > m_xIndicatorInterception; + /// helper for XDispatch/Provider and interception interfaces + css::uno::Reference< css::frame::XDispatchProvider > m_xDispatchHelper; + /// helper for XFrames, XIndexAccess and XElementAccess interfaces + css::uno::Reference< css::frame::XFrames > m_xFramesHelper; + /// container for ALL Listener + comphelper::OMultiTypeInterfaceContainerHelper2 m_aListenerContainer; + /// parent of this frame + css::uno::Reference< css::frame::XFramesSupplier > m_xParent; + /// containerwindow of this frame for embedded components + css::uno::Reference< css::awt::XWindow > m_xContainerWindow; + /// window of the actual component + css::uno::Reference< css::awt::XWindow > m_xComponentWindow; + /// controller of the actual frame + css::uno::Reference< css::frame::XController > m_xController; + /// listen to drag & drop + css::uno::Reference< css::datatransfer::dnd::XDropTargetListener > m_xDropTargetListener; + /// state, if I am a member of active path in tree or I have the focus or... + EActiveState m_eActiveState; + /// name of this frame + OUString m_sName; + /// frame has no parent or the parent is a task or the desktop + bool m_bIsFrameTop; + /// due to FrameActionEvent + bool m_bConnected; + sal_Int16 m_nExternalLockCount; + /// is used for dispatch recording and will be set/get from outside. Frame provide it only! + css::uno::Reference< css::frame::XDispatchRecorderSupplier > m_xDispatchRecorderSupplier; + /// ref counted class to support disabling commands defined by configuration file + SvtCommandOptions m_aCommandOptions; + /// in case of CloseVetoException on method close() was thrown by ourself - we must close ourself later if no internal processes are running + bool m_bSelfClose; + /// indicates, if this frame is used in hidden mode or not + bool m_bIsHidden; + /// The container window has WindowExtendedStyle::DocHidden set. + bool m_bDocHidden = false; + /// is used to layout the child windows of the frame. + css::uno::Reference< css::frame::XLayoutManager2 > m_xLayoutManager; + css::uno::Reference< css::frame::XDispatchInformationProvider > m_xDispatchInfoHelper; + css::uno::Reference< css::frame::XTitle > m_xTitleHelper; + + std::unique_ptr<WindowCommandDispatch> m_pWindowCommandDispatch; + + typedef std::unordered_map<OUString, css::beans::Property> TPropInfoHash; + TPropInfoHash m_lProps; + + comphelper::OMultiTypeInterfaceContainerHelperVar3<css::beans::XPropertyChangeListener, OUString> m_lSimpleChangeListener; + comphelper::OMultiTypeInterfaceContainerHelperVar3<css::beans::XVetoableChangeListener, OUString> m_lVetoChangeListener; + + // hold it weak ... otherwise this helper has to be "killed" explicitly .-) + css::uno::WeakReference< css::uno::XInterface > m_xBroadcaster; + + FrameContainer m_aChildFrameContainer; /// array of child frames + /** + * URL of the file that is being loaded, when the load already started by we have no controller + * yet. + */ + OUString m_aURL; +}; + + +/*-**************************************************************************************************** + @short standard constructor to create instance by factory + @descr This constructor initialize a new instance of this class by valid factory, + and will be set valid values on his member and baseclasses. + + @attention a) Don't use your own reference during a UNO-Service-ctor! There is no guarantee, that you + will get over this. (e.g. using of your reference as parameter to initialize some member) + Do such things in DEFINE_INIT_SERVICE() method, which is called automatically after your ctor!!! + b) Baseclass OBroadcastHelper is a typedef in namespace cppu! + The microsoft compiler has some problems to handle it right BY using namespace explicitly ::cppu::OBroadcastHelper. + If we write it without a namespace or expand the typedef to OBroadcastHelperVar<...> -> it will be OK!? + I don't know why! (other compiler not tested .. but it works!) + + @seealso method DEFINE_INIT_SERVICE() + + @param xContext is the multi service manager, which creates this instance. + The value must be different from NULL! + @onerror ASSERT in debug version or nothing in release version. +*//*-*****************************************************************************************************/ +XFrameImpl::XFrameImpl( css::uno::Reference< css::uno::XComponentContext > xContext ) + : PartialWeakComponentImplHelper(m_aMutex) + // init member + , m_xContext (std::move( xContext )) + , m_aListenerContainer ( m_aMutex ) + , m_eActiveState ( E_INACTIVE ) + , m_bIsFrameTop ( true ) // I think we are top without a parent ... and there is no parent yet! + , m_bConnected ( false ) // There exist no component inside of use => sal_False, we are not connected! + , m_nExternalLockCount ( 0 ) + , m_bSelfClose ( false ) // Important! + , m_bIsHidden ( true ) + , m_lSimpleChangeListener ( m_aMutex ) + , m_lVetoChangeListener ( m_aMutex ) +{ +} + +void XFrameImpl::initListeners() +{ + css::uno::Reference< css::uno::XInterface > xThis(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY_THROW); + + // Initialize a new dispatchhelper-object to handle dispatches. + // We use these helper as slave for our interceptor helper ... not directly! + // But he is event listener on THIS instance! + rtl::Reference<DispatchProvider> xDispatchProvider = new DispatchProvider( m_xContext, this ); + + m_xDispatchInfoHelper = new DispatchInformationProvider(m_xContext, this); + + // Initialize a new interception helper object to handle dispatches and implement an interceptor mechanism. + // Set created dispatch provider as slowest slave of it. + // Hold interception helper by reference only - not by pointer! + // So it's easier to destroy it. + m_xDispatchHelper = new InterceptionHelper( this, xDispatchProvider ); + + // Initialize a new XFrames-helper-object to handle XIndexAccess and XElementAccess. + // We hold member as reference ... not as pointer too! + // Attention: We share our frame container with this helper. Container is threadsafe himself ... So I think we can do that. + // But look on dispose() for right order of deinitialization. + m_xFramesHelper = new OFrames( this, &m_aChildFrameContainer ); + + // Initialize the drop target listener. + // We hold member as reference ... not as pointer too! + m_xDropTargetListener = new OpenFileDropTargetListener( m_xContext, this ); + + // Safe impossible cases + // We can't work without these helpers! + SAL_WARN_IF( !xDispatchProvider.is(), "fwk.frame", "XFrameImpl::XFrameImpl(): Slowest slave for dispatch- and interception helper " + "is not valid. XDispatchProvider, XDispatch, XDispatchProviderInterception are not full supported!" ); + SAL_WARN_IF( !m_xDispatchHelper.is(), "fwk.frame", "XFrameImpl::XFrameImpl(): Interception helper is not valid. XDispatchProvider, " + "XDispatch, XDispatchProviderInterception are not full supported!" ); + SAL_WARN_IF( !m_xFramesHelper.is(), "fwk.frame", "XFrameImpl::XFrameImpl(): Frames helper is not valid. XFrames, " + "XIndexAccess and XElementAccess are not supported!" ); + SAL_WARN_IF( !m_xDropTargetListener.is(), "fwk.frame", "XFrameImpl::XFrameImpl(): DropTarget helper is not valid. " + "Drag and drop without functionality!" ); + + // establish notifies for changing of "disabled commands" configuration during runtime + m_aCommandOptions.EstablishFrameCallback(this); + + // Create an initial layout manager + // Create layout manager and connect it to the newly created frame + m_xLayoutManager = css::frame::LayoutManager::create(m_xContext); + + // set information about all supported properties + impl_setPropertyChangeBroadcaster(static_cast< css::frame::XFrame* >(this)); + impl_addPropertyInfo( + css::beans::Property( + FRAME_PROPNAME_ASCII_DISPATCHRECORDERSUPPLIER, + FRAME_PROPHANDLE_DISPATCHRECORDERSUPPLIER, + cppu::UnoType<css::frame::XDispatchRecorderSupplier>::get(), + css::beans::PropertyAttribute::TRANSIENT)); + impl_addPropertyInfo( + css::beans::Property( + FRAME_PROPNAME_ASCII_INDICATORINTERCEPTION, + FRAME_PROPHANDLE_INDICATORINTERCEPTION, + cppu::UnoType<css::task::XStatusIndicator>::get(), + css::beans::PropertyAttribute::TRANSIENT)); + impl_addPropertyInfo( + css::beans::Property( + FRAME_PROPNAME_ASCII_ISHIDDEN, + FRAME_PROPHANDLE_ISHIDDEN, + cppu::UnoType<bool>::get(), + css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY)); + impl_addPropertyInfo( + css::beans::Property( + FRAME_PROPNAME_ASCII_LAYOUTMANAGER, + FRAME_PROPHANDLE_LAYOUTMANAGER, + cppu::UnoType<css::frame::XLayoutManager>::get(), + css::beans::PropertyAttribute::TRANSIENT)); + impl_addPropertyInfo( + css::beans::Property( + FRAME_PROPNAME_ASCII_TITLE, + FRAME_PROPHANDLE_TITLE, + cppu::UnoType<OUString>::get(), + css::beans::PropertyAttribute::TRANSIENT)); + impl_addPropertyInfo(css::beans::Property(FRAME_PROPNAME_ASCII_URL, FRAME_PROPHANDLE_URL, + cppu::UnoType<OUString>::get(), + css::beans::PropertyAttribute::TRANSIENT)); +} + +/*-************************************************************************************************************ + @interface XComponentLoader + @short try to load given URL into a task + @descr You can give us some information about the content, which you will load into a frame. + We search or create this target for you, make a type detection of given URL and try to load it. + As result of this operation we return the new created component or nothing, if loading failed. + @param "sURL" , URL, which represent the content + @param "sTargetFrameName" , name of target frame or special value like "_self", "_blank" ... + @param "nSearchFlags" , optional arguments for frame search, if target is not a special one + @param "lArguments" , optional arguments for loading + @return A valid component reference, if loading was successful. + A null reference otherwise. + + @onerror We return a null reference. + @threadsafe yes +*//*-*************************************************************************************************************/ +css::uno::Reference< css::lang::XComponent > SAL_CALL XFrameImpl::loadComponentFromURL( + const OUString& sURL, + const OUString& sTargetFrameName, + sal_Int32 nSearchFlags, + const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) +{ + checkDisposed(); + + css::uno::Reference< css::frame::XComponentLoader > xThis(this); + + utl::MediaDescriptor aDescriptor(lArguments); + bool bOnMainThread = aDescriptor.getUnpackedValueOrDefault("OnMainThread", false); + + if (bOnMainThread) + { + // Make sure that we own the solar mutex, otherwise later + // vcl::SolarThreadExecutor::execute() will release the solar mutex, even if it's owned by + // another thread, leading to an std::abort() at the end. + SolarMutexGuard g; + + return vcl::solarthread::syncExecute(std::bind(&LoadEnv::loadComponentFromURL, xThis, + m_xContext, sURL, sTargetFrameName, + nSearchFlags, lArguments)); + } + else + return LoadEnv::loadComponentFromURL(xThis, m_xContext, sURL, sTargetFrameName, + nSearchFlags, lArguments); +} + +/*-**************************************************************************************************** + @short return access to append or remove children on desktop + @descr We don't implement these interface directly. We use a helper class to do this. + If you wish to add or delete children to/from the container, call these method to get + a reference to the helper. + + @seealso class OFrames + @return A reference to the helper which answer your queries. + + @onerror A null reference is returned. +*//*-*****************************************************************************************************/ +css::uno::Reference< css::frame::XFrames > SAL_CALL XFrameImpl::getFrames() +{ + checkDisposed(); + + SolarMutexGuard g; + // Return access to all child frames to caller. + // Our childframe container is implemented in helper class OFrames and used as a reference m_xFramesHelper! + return m_xFramesHelper; +} + +/*-**************************************************************************************************** + @short get the current active child frame + @descr It must be a frameto. Direct children of a frame are frames only! No task or desktop is accepted. + We don't save this information directly in this class. We use our container-helper + to do that. + + @seealso class OFrameContainer + @seealso method setActiveFrame() + @return A reference to our current active childframe, if anyone exist. + @return A null reference, if nobody is active. + + @onerror A null reference is returned. +*//*-*****************************************************************************************************/ +css::uno::Reference< css::frame::XFrame > SAL_CALL XFrameImpl::getActiveFrame() +{ + checkDisposed(); + + SolarMutexGuard g; + // Return current active frame. + // This information is available on the container. + return m_aChildFrameContainer.getActive(); +} + +/*-**************************************************************************************************** + @short set the new active direct child frame + @descr It must be a frame to. Direct children of frame are frames only! No task or desktop is accepted. + We don't save this information directly in this class. We use our container-helper + to do that. + + @seealso class OFrameContainer + @seealso method getActiveFrame() + + @param "xFrame", reference to new active child. It must be an already existing child! + @onerror An assertion is thrown and element is ignored, if given frame isn't already a child of us. +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::setActiveFrame( const css::uno::Reference< css::frame::XFrame >& xFrame ) +{ + checkDisposed(); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexResettableGuard aWriteLock; + + // Copy necessary member for threadsafe access! + // m_aChildFrameContainer is threadsafe himself and he live if we live!!! + css::uno::Reference< css::frame::XFrame > xActiveChild = m_aChildFrameContainer.getActive(); + EActiveState eActiveState = m_eActiveState; + + aWriteLock.clear(); + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + + // Don't work, if "new" active frame isn't different from current one! + // (xFrame==NULL is allowed to UNSET it!) + if( xActiveChild != xFrame ) + { + // ... otherwise set new and deactivate old one. + m_aChildFrameContainer.setActive( xFrame ); + if ( + ( eActiveState != E_INACTIVE ) && + xActiveChild.is() + ) + { + xActiveChild->deactivate(); + } + } + + if( xFrame.is() ) + { + // If last active frame had focus ... + // ... reset state to ACTIVE and send right FrameActionEvent for focus lost. + if( eActiveState == E_FOCUS ) + { + aWriteLock.reset(); + eActiveState = E_ACTIVE; + m_eActiveState = eActiveState; + aWriteLock.clear(); + implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_UI_DEACTIVATING ); + } + + // If last active frame was active ... + // but new one is not it ... + // ... set it as active one. + if ( eActiveState == E_ACTIVE && !xFrame->isActive() ) + { + xFrame->activate(); + } + } + else + // If this frame is active and has no active subframe anymore it is UI active too + if( eActiveState == E_ACTIVE ) + { + aWriteLock.reset(); + eActiveState = E_FOCUS; + m_eActiveState = eActiveState; + aWriteLock.clear(); + implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_UI_ACTIVATED ); + } +} + +/*-**************************************************************************************************** + initialize new created layout manager +**/ +void lcl_enableLayoutManager(const css::uno::Reference< css::frame::XLayoutManager2 >& xLayoutManager, + const css::uno::Reference< css::frame::XFrame >& xFrame ) +{ + // Provide container window to our layout manager implementation + xLayoutManager->attachFrame(xFrame); + + xFrame->addFrameActionListener(xLayoutManager); + + rtl::Reference<DockingAreaDefaultAcceptor> xDockingAreaAcceptor = new DockingAreaDefaultAcceptor(xFrame); + xLayoutManager->setDockingAreaAcceptor(xDockingAreaAcceptor); +} + +/*-**************************************************************************************************** + deinitialize layout manager +**/ +void XFrameImpl::disableLayoutManager(const css::uno::Reference< css::frame::XLayoutManager2 >& xLayoutManager) +{ + removeFrameActionListener(xLayoutManager); + xLayoutManager->setDockingAreaAcceptor(css::uno::Reference< css::ui::XDockingAreaAcceptor >()); + xLayoutManager->attachFrame(css::uno::Reference< css::frame::XFrame >()); +} + +/*-**************************************************************************************************** + @short initialize frame instance + @descr A frame needs a window. This method set a new one ... but should called one times only! + We use this window to listen for window events and forward it to our set component. + It's used as parent of component window too. + + @seealso method getContainerWindow() + @seealso method setComponent() + @seealso member m_xContainerWindow + + @param "xWindow", reference to new container window - must be valid! + @onerror We do nothing. +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::initialize( const css::uno::Reference< css::awt::XWindow >& xWindow ) +{ + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + if (!xWindow.is()) + throw css::uno::RuntimeException( + "XFrameImpl::initialize() called without a valid container window reference.", + static_cast< css::frame::XFrame* >(this)); + + checkDisposed(); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexResettableGuard aWriteLock; + + if ( m_xContainerWindow.is() ) + throw css::uno::RuntimeException( + "XFrameImpl::initialized() is called more than once, which is not useful nor allowed.", + static_cast< css::frame::XFrame* >(this)); + + // This must be the first call of this method! + // We should initialize our object and open it for working. + // Set the new window. + SAL_WARN_IF( m_xContainerWindow.is(), "fwk.frame", "XFrameImpl::initialize(): Leak detected! This state should never occur ..." ); + m_xContainerWindow = xWindow; + + // if window is initially visible, we will never get a windowShowing event + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow); + if (pWindow) + { + if (pWindow->IsVisible()) + m_bIsHidden = false; + m_bDocHidden + = static_cast<bool>(pWindow->GetExtendedStyle() & WindowExtendedStyle::DocHidden); + } + + css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager = m_xLayoutManager; + + // Release lock ... because we call some impl methods, which are threadsafe by himself. + // If we hold this lock - we will produce our own deadlock! + aWriteLock.clear(); + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + + // Avoid enabling the layout manager for hidden frames: it's expensive and + // provides little value. + if (xLayoutManager.is() && !m_bDocHidden) + lcl_enableLayoutManager(xLayoutManager, this); + + // create progress helper + css::uno::Reference< css::frame::XFrame > xThis (static_cast< css::frame::XFrame* >(this), + css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::task::XStatusIndicatorFactory > xIndicatorFactory = + css::task::StatusIndicatorFactory::createWithFrame(m_xContext, xThis, + false/*DisableReschedule*/, true/*AllowParentShow*/ ); + + // SAFE -> ---------------------------------- + aWriteLock.reset(); + m_xIndicatorFactoryHelper = xIndicatorFactory; + aWriteLock.clear(); + // <- SAFE ---------------------------------- + + // Start listening for events after setting it on helper class ... + // So superfluous messages are filtered to NULL :-) + implts_startWindowListening(); + + m_pWindowCommandDispatch.reset(new WindowCommandDispatch(m_xContext, this)); + + // Initialize title functionality + m_xTitleHelper = new TitleHelper( m_xContext, xThis, nullptr ); +} + +/*-**************************************************************************************************** + @short returns current set container window + @descr The ContainerWindow property is used as a container for the component + in this frame. So this object implements a container interface too. + The instantiation of the container window is done by the user of this class. + The frame is the owner of its container window. + + @seealso method initialize() + @return A reference to current set containerwindow. + + @onerror A null reference is returned. +*//*-*****************************************************************************************************/ +css::uno::Reference< css::awt::XWindow > SAL_CALL XFrameImpl::getContainerWindow() +{ + SolarMutexGuard g; + return m_xContainerWindow; +} + +/*-**************************************************************************************************** + @short set parent frame + @descr We need a parent to support some functionality! e.g. findFrame() + By the way we use the chance to set an internal information about our top state. + So we must not check this information during every isTop() call. + We are top, if our parent is the desktop instance or we haven't any parent. + + @seealso getCreator() + @seealso findFrame() + @seealso isTop() + @seealso m_bIsFrameTop + + @param xCreator + valid reference to our new owner frame, which should implement a supplier interface + + @threadsafe yes +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::setCreator( const css::uno::Reference< css::frame::XFramesSupplier >& xCreator ) +{ + checkDisposed(); + + /* SAFE { */ + { + SolarMutexGuard aWriteLock; + m_xParent = xCreator; + } + /* } SAFE */ + + css::uno::Reference< css::frame::XDesktop > xIsDesktop( xCreator, css::uno::UNO_QUERY ); + m_bIsFrameTop = ( xIsDesktop.is() || ! xCreator.is() ); +} + +/*-**************************************************************************************************** + @short returns current parent frame + @descr The Creator is the parent frame container. If it is NULL, the frame is the uppermost one. + + @seealso method setCreator() + @return A reference to current set parent frame container. + + @onerror A null reference is returned. +*//*-*****************************************************************************************************/ +css::uno::Reference< css::frame::XFramesSupplier > SAL_CALL XFrameImpl::getCreator() +{ + checkDisposed(); + SolarMutexGuard g; + return m_xParent; +} + +/*-**************************************************************************************************** + @short returns current set name of frame + @descr This name is used to find target of findFrame() or queryDispatch() calls. + + @seealso method setName() + @return Current set name of frame. + + @onerror An empty string is returned. +*//*-*****************************************************************************************************/ +OUString SAL_CALL XFrameImpl::getName() +{ + SolarMutexGuard g; + return m_sName; +} + +/*-**************************************************************************************************** + @short set new name for frame + @descr This name is used to find target of findFrame() or queryDispatch() calls. + + @attention Special names like "_blank", "_self" aren't allowed... + "_beamer" excepts this rule! + + @seealso method getName() + + @param "sName", new frame name. + @onerror We do nothing. +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::setName( const OUString& sName ) +{ + SolarMutexGuard g; + // Set new name... but look for invalid special target names! + // They are not allowed to set. + if (TargetHelper::isValidNameForFrame(sName)) + m_sName = sName; +} + +/*-**************************************************************************************************** + @short search for frames + @descr This method searches for a frame with the specified name. + Frames may contain other frames (e.g. a frameset) and may + be contained in other frames. This hierarchy is searched by + this method. + First some special names are taken into account, i.e. "", + "_self", "_top", "_blank" etc. The nSearchFlags are ignored + when comparing these names with sTargetFrameName, further steps are + controlled by the search flags. If allowed, the name of the frame + itself is compared with the desired one, then ( again if allowed ) + the method findFrame() is called for all children, for siblings + and as last for the parent frame. + If no frame with the given name is found until the top frames container, + a new top one is created, if this is allowed by a special + flag. The new frame also gets the desired name. + + @param sTargetFrameName + special names (_blank, _self) or real name of target frame + @param nSearchFlags + optional flags which regulate search for non special target frames + + @return A reference to found or may be new created frame. + @threadsafe yes +*//*-*****************************************************************************************************/ +css::uno::Reference< css::frame::XFrame > SAL_CALL XFrameImpl::findFrame( const OUString& sTargetFrameName, + sal_Int32 nSearchFlags ) +{ + css::uno::Reference< css::frame::XFrame > xTarget; + + // 0) Ignore wrong parameter! + // We don't support search for following special targets. + // If we reject this requests - we must not check for such names + // in following code again and again. If we do not so -wrong + // search results can occur! + + if ( sTargetFrameName == SPECIALTARGET_DEFAULT ) // valid for dispatches - not for findFrame()! + { + return nullptr; + } + + // I) check for special defined targets first which must be handled exclusive. + // force using of "if() else if() ..." + + // get threadsafe some necessary member which are necessary for following functionality + /* SAFE { */ + SolarMutexResettableGuard aReadLock; + css::uno::Reference< css::frame::XFrame > xParent = m_xParent; + bool bIsTopFrame = m_bIsFrameTop; + bool bIsTopWindow = WindowHelper::isTopWindow(m_xContainerWindow); + aReadLock.clear(); + /* } SAFE */ + + // I.I) "_blank" + // Not allowed for a normal frame - but for the desktop. + // Use helper class to do so. It use the desktop automatically. + + if ( sTargetFrameName==SPECIALTARGET_BLANK ) + { + TaskCreator aCreator(m_xContext); + xTarget = aCreator.createTask(sTargetFrameName, utl::MediaDescriptor()); + } + + // I.II) "_parent" + // It doesn't matter if we have a valid parent or not. User ask for him and get it. + // An empty result is a valid result too. + + else if ( sTargetFrameName==SPECIALTARGET_PARENT ) + { + xTarget = xParent; + } + + // I.III) "_top" + // If we are not the top frame in this hierarchy, we must forward request to our parent. + // Otherwise we must return ourself. + + else if ( sTargetFrameName==SPECIALTARGET_TOP ) + { + if (bIsTopFrame) + xTarget = this; + else if (xParent.is()) // If we are not top - the parent MUST exist. But may it's better to check it again .-) + xTarget = xParent->findFrame(SPECIALTARGET_TOP,0); + } + + // I.IV) "_self", "" + // This mean this frame in every case. + + else if ( + ( sTargetFrameName==SPECIALTARGET_SELF ) || + ( sTargetFrameName.isEmpty() ) + ) + { + xTarget = this; + } + + // I.V) "_beamer" + // This is a special sub frame of any task. We must return it if we found it on our direct children + // or create it there if it not already exists. + // Note: Such beamer exists for task(top) frames only! + + else if ( sTargetFrameName==SPECIALTARGET_BEAMER ) + { + // We are a task => search or create the beamer + if (bIsTopWindow) + { + xTarget = m_aChildFrameContainer.searchOnDirectChildrens(SPECIALTARGET_BEAMER); + if ( ! xTarget.is() ) + { + /* TODO + Creation not supported yet! + Wait for new layout manager service because we can't plug it + inside already opened document of this frame... + */ + } + } + // We aren't a task => forward request to our parent or ignore it. + else if (xParent.is()) + xTarget = xParent->findFrame(SPECIALTARGET_BEAMER,0); + } + + else + { + + // II) otherwise use optional given search flags + // force using of combinations of such flags. means no "else" part of use if() statements. + // But we ust break further searches if target was already found. + // Order of using flags is fix: SELF - CHILDREN - SIBLINGS - PARENT + // TASK and CREATE are handled special. + + // get threadsafe some necessary member which are necessary for following functionality + /* SAFE { */ + aReadLock.reset(); + OUString sOwnName = m_sName; + aReadLock.clear(); + /* } SAFE */ + + // II.I) SELF + // Check for right name. If it's the searched one return ourself - otherwise + // ignore this flag. + + if ( + (nSearchFlags & css::frame::FrameSearchFlag::SELF) && + (sOwnName == sTargetFrameName ) + ) + { + xTarget = this; + } + + // II.II) CHILDREN + // Search on all children for the given target name. + // An empty name value can't occur here - because it must be already handled as "_self" + // before. Used helper function of container doesn't create any frame. + // It makes a deep search only. + + if ( + ( ! xTarget.is() ) && + (nSearchFlags & css::frame::FrameSearchFlag::CHILDREN) + ) + { + xTarget = m_aChildFrameContainer.searchOnAllChildrens(sTargetFrameName); + } + + // II.III) TASKS + // This is a special flag. It regulate search on this task tree only or allow search on + // all other ones (which are sibling trees of us) too. + // Upper search must stop at this frame if we are the topest one and the TASK flag is not set + // or we can ignore it if we have no valid parent. + + if ( + ( bIsTopFrame && (nSearchFlags & css::frame::FrameSearchFlag::TASKS) ) || + ( ! bIsTopFrame ) + ) + { + + // II.III.I) SIBLINGS + // Search on all our direct siblings - means all children of our parent. + // Use this flag in combination with TASK. We must suppress such upper search if + // user has not set it and if we are a top frame. + // Attention: Don't forward this request to our parent as a findFrame() call. + // In such case we must protect us against recursive calls. + // Use snapshot of our parent. But don't use queryFrames() of XFrames interface. + // Because it's return all siblings and all her children including our children too + // if we call it with the CHILDREN flag. We don't need that - we need the direct container + // items of our parent only to start searches there. So we must use the container interface + // XIndexAccess instead of XFrames. + + if ( + ( ! xTarget.is() ) && + (nSearchFlags & css::frame::FrameSearchFlag::SIBLINGS) && + ( xParent.is() ) // search on siblings is impossible without a parent + ) + { + css::uno::Reference< css::frame::XFramesSupplier > xSupplier( xParent, css::uno::UNO_QUERY ); + if (xSupplier.is()) + { + css::uno::Reference< css::container::XIndexAccess > xContainer = xSupplier->getFrames(); + if (xContainer.is()) + { + sal_Int32 nCount = xContainer->getCount(); + for( sal_Int32 i=0; i<nCount; ++i ) + { + css::uno::Reference< css::frame::XFrame > xSibling; + if ( + // control unpacking + ( !(xContainer->getByIndex(i)>>=xSibling) ) || + // check for valid items + ( ! xSibling.is() ) || + // ignore ourself! (We are a part of this container too - but search on our children was already done.) + ( xSibling==static_cast< ::cppu::OWeakObject* >(this) ) + ) + { + continue; + } + + // Don't allow upper search here! Use right flags to regulate it. + // And allow deep search on children only - if it was allowed for us too. + sal_Int32 nRightFlags = css::frame::FrameSearchFlag::SELF; + if (nSearchFlags & css::frame::FrameSearchFlag::CHILDREN) + nRightFlags |= css::frame::FrameSearchFlag::CHILDREN; + xTarget = xSibling->findFrame(sTargetFrameName, nRightFlags ); + // perform search be breaking further search if a result exist. + if (xTarget.is()) + break; + } + } + } + } + + // II.III.II) PARENT + // Forward search to our parent (if he exists.) + // To prevent us against recursive and superfluous calls (which can occur if we allow him + // to search on his children too) we must change used search flags. + + if ( + ( ! xTarget.is() ) && + (nSearchFlags & css::frame::FrameSearchFlag::PARENT) && + ( xParent.is() ) + ) + { + if (xParent->getName() == sTargetFrameName) + xTarget = xParent; + else + { + sal_Int32 nRightFlags = nSearchFlags & ~css::frame::FrameSearchFlag::CHILDREN; + xTarget = xParent->findFrame(sTargetFrameName, nRightFlags); + } + } + } + + // II.IV) CREATE + // If we haven't found any valid target frame by using normal flags - but user allowed us to create + // a new one ... we should do that. Used TaskCreator use Desktop instance automatically as parent! + + if ( + ( ! xTarget.is() ) && + (nSearchFlags & css::frame::FrameSearchFlag::CREATE) + ) + { + TaskCreator aCreator(m_xContext); + xTarget = aCreator.createTask(sTargetFrameName, utl::MediaDescriptor()); + } + } + + return xTarget; +} + +/*-**************************************************************************************************** + @descr Returns sal_True, if this frame is a "top frame", otherwise sal_False. + The "m_bIsFrameTop" member must be set in the ctor or setCreator() method. + A top frame is a member of the top frame container or a member of the + task frame container. Both containers can create new frames if the findFrame() + method of their css::frame::XFrame interface is called with a frame name not yet known. + + @seealso ctor + @seealso method setCreator() + @seealso method findFrame() + @return true, if is it a top frame ... false otherwise. + + @onerror No error should occur! +*//*-*****************************************************************************************************/ +sal_Bool SAL_CALL XFrameImpl::isTop() +{ + checkDisposed(); + SolarMutexGuard g; + // This information is set in setCreator(). + // We are top, if our parent is a task or the desktop or if no parent exist! + return m_bIsFrameTop; +} + +/*-**************************************************************************************************** + @short activate frame in hierarchy + @descr This feature is used to mark active paths in our frame hierarchy. + You can be a listener for this event to react for it ... change some internal states or something else. + + @seealso method deactivate() + @seealso method isActivate() + @seealso enum EActiveState + @seealso listener mechanism +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::activate() +{ + checkDisposed(); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexResettableGuard aWriteLock; + + // Copy necessary member and free the lock. + // It's not necessary for m_aChildFrameContainer ... because + // he is threadsafe himself and live if we live. + css::uno::Reference< css::frame::XFrame > xActiveChild = m_aChildFrameContainer.getActive(); + css::uno::Reference< css::frame::XFramesSupplier > xParent = m_xParent; + css::uno::Reference< css::frame::XFrame > xThis(this); + EActiveState eState = m_eActiveState; + + aWriteLock.clear(); + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + + // 1) If I am not active before... + if( eState == E_INACTIVE ) + { + // ... do it then. + aWriteLock.reset(); + eState = E_ACTIVE; + m_eActiveState = eState; + aWriteLock.clear(); + // Deactivate sibling path and forward activation to parent ... if any parent exist! + if( xParent.is() ) + { + // Every time set THIS frame as active child of parent and activate it. + // We MUST have a valid path from bottom to top as active path! + // But we must deactivate the old active sibling path first. + + // Attention: Deactivation of an active path, deactivate the whole path ... from bottom to top! + // But we wish to deactivate founded sibling-tree only. + // [ see deactivate() / step 4) for further information! ] + + xParent->setActiveFrame( xThis ); + + // Then we can activate from here to top. + // Attention: We are ACTIVE now. And the parent will call activate() at us! + // But we do nothing then! We are already activated. + xParent->activate(); + } + // It's necessary to send event NOW - not before. + // Activation goes from bottom to top! + // That's the reason to activate parent first and send event now. + implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_ACTIVATED ); + } + + // 2) I was active before or current activated and there is a path from here to bottom, who CAN be active. + // But our direct child of path is not active yet. + // (It can be, if activation occur in the middle of a current path!) + // In these case we activate path to bottom to set focus on right frame! + if ( eState == E_ACTIVE && xActiveChild.is() && !xActiveChild->isActive() ) + { + xActiveChild->activate(); + } + + // 3) I was active before or current activated. But if I have no active child => I will get the focus! + if ( eState == E_ACTIVE && !xActiveChild.is() ) + { + aWriteLock.reset(); + eState = E_FOCUS; + m_eActiveState = eState; + aWriteLock.clear(); + implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_UI_ACTIVATED ); + } +} + +/*-**************************************************************************************************** + @short deactivate frame in hierarchy + @descr This feature is used to deactivate paths in our frame hierarchy. + You can be a listener for this event to react for it... change some internal states or something else. + + @seealso method activate() + @seealso method isActivate() + @seealso enum EActiveState + @seealso listener mechanism +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::deactivate() +{ + checkDisposed(); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexResettableGuard aWriteLock; + + // Copy necessary member and free the lock. + css::uno::Reference< css::frame::XFrame > xActiveChild = m_aChildFrameContainer.getActive(); + css::uno::Reference< css::frame::XFramesSupplier > xParent = m_xParent; + css::uno::Reference< css::frame::XFrame > xThis(this); + EActiveState eState = m_eActiveState; + + aWriteLock.clear(); + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + + // Work only, if there something to do! + if( eState == E_INACTIVE ) + return; + + + // 1) Deactivate all active children. + if ( xActiveChild.is() && xActiveChild->isActive() ) + { + xActiveChild->deactivate(); + } + + // 2) If I have the focus - I will lost it now. + if( eState == E_FOCUS ) + { + // Set new state INACTIVE(!) and send message to all listener. + // Don't set ACTIVE as new state. This frame is deactivated for next time - due to activate(). + aWriteLock.reset(); + eState = E_ACTIVE; + m_eActiveState = eState; + aWriteLock.clear(); + implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_UI_DEACTIVATING ); + } + + // 3) If I am active - I will be deactivated now. + if( eState == E_ACTIVE ) + { + // Set new state and send message to all listener. + aWriteLock.reset(); + eState = E_INACTIVE; + m_eActiveState = eState; + aWriteLock.clear(); + implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_DEACTIVATING ); + } + + // 4) If there is a path from here to my parent... + // ... I am on the top or in the middle of deactivated subtree and action was started here. + // I must deactivate all frames from here to top, which are members of current path. + // Stop, if THESE frame not the active frame of our parent! + if ( xParent.is() && xParent->getActiveFrame() == xThis ) + { + // We MUST break the path - otherwise we will get the focus - not our parent! ... + // Attention: Our parent don't call us again - WE ARE NOT ACTIVE YET! + // [ see step 3 and condition "if ( m_eActiveState!=INACTIVE ) ..." in this method! ] + xParent->deactivate(); + } +} + +/*-**************************************************************************************************** + @short returns active state + @descr Call it to get information about current active state of this frame. + + @seealso method activate() + @seealso method deactivate() + @seealso enum EActiveState + @return true if active, false otherwise. + + @onerror No error should occur. +*//*-*****************************************************************************************************/ +sal_Bool SAL_CALL XFrameImpl::isActive() +{ + checkDisposed(); + SolarMutexGuard g; + return m_eActiveState == E_ACTIVE || m_eActiveState == E_FOCUS; +} + +/*-**************************************************************************************************** + @short ??? +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::contextChanged() +{ + // Sometimes called during closing object... + // Impl-method is threadsafe himself! + // Send event to all listener for frame actions. + implts_sendFrameActionEvent( css::frame::FrameAction_CONTEXT_CHANGED ); +} + +/*-**************************************************************************************************** + @short set new component inside the frame + @descr A frame is a container for a component. Use this method to set, change or release it! + We accept null references! The xComponentWindow will be a child of our container window + and get all window events from us. + + @attention (a) A current set component can disagree with the suspend() request! + We don't set the new one and return with false then. + (b) It's possible to set: + (b1) a simple component here which supports the window only - no controller; + (b2) a full featured component which supports window and controller; + (b3) or both to NULL if outside code which to forget this component. + + @seealso method getComponentWindow() + @seealso method getController() + + @param xComponentWindow + valid reference to new component window which will be a child of internal container window + May <NULL/> for releasing. + @param xController + reference to new component controller + (may <NULL/> for releasing or setting of a simple component) + + @return <TRUE/> if operation was successful, <FALSE/> otherwise. + + @onerror We return <FALSE/>. + @threadsafe yes +*//*-*****************************************************************************************************/ +sal_Bool SAL_CALL XFrameImpl::setComponent(const css::uno::Reference< css::awt::XWindow >& xComponentWindow, + const css::uno::Reference< css::frame::XController >& xController ) +{ + + // Ignore this HACK of sfx2! + // He call us with a valid controller without a valid window... that's not allowed! + if ( xController.is() && ! xComponentWindow.is() ) + return true; + + checkDisposed(); + + // Get threadsafe some copies of used members. + /* SAFE { */ + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow; + css::uno::Reference< css::awt::XWindow > xOldComponentWindow = m_xComponentWindow; + css::uno::Reference< css::frame::XController > xOldController = m_xController; + VclPtr<vcl::Window> pOwnWindow = VCLUnoHelper::GetWindow( xContainerWindow ); + bool bHadFocus = pOwnWindow != nullptr && pOwnWindow->HasChildPathFocus(); + bool bWasConnected = m_bConnected; + aReadLock.clear(); + /* } SAFE */ + + // stop listening on old window + // May it produce some trouble. + // But don't forget to listen on new window again ... or reactivate listening + // if we reject this setComponent() request and leave this method without changing the old window. + implts_stopWindowListening(); + + // Notify all listener, that this component (if current one exist) will be unloaded. + if (bWasConnected) + implts_sendFrameActionEvent( css::frame::FrameAction_COMPONENT_DETACHING ); + + // otherwise release old component first + // Always release controller before releasing window, + // because controller may want to access its window! + // But check for real changes - may the new controller is the old one. + if ( + (xOldController.is() ) && + (xOldController != xController) + ) + { + /* ATTENTION + Don't suspend the old controller here. Because the outside caller must do that + by definition. We have to dispose it here only. + */ + + // Before we dispose this controller we should hide it inside this frame instance. + // We hold it alive for next calls by using xOldController! + /* SAFE {*/ + { + SolarMutexGuard aWriteLock; + m_xController = nullptr; + + auto pInterceptionHelper = dynamic_cast<InterceptionHelper*>(m_xDispatchHelper.get()); + if (pInterceptionHelper) + { + css::uno::Reference<css::frame::XDispatchProvider> xDispatchProvider = pInterceptionHelper->GetSlave(); + auto pDispatchProvider = dynamic_cast<DispatchProvider*>(xDispatchProvider.get()); + if (pDispatchProvider) + { + pDispatchProvider->ClearProtocolHandlers(); + } + } + } + /* } SAFE */ + + if (xOldController.is()) + { + try + { + xOldController->dispose(); + } + catch(const css::lang::DisposedException&) + {} + } + xOldController = nullptr; + } + + // Now it's time to release the component window. + // If controller wasn't released successfully - this code line shouldn't be reached. + // Because in case of "suspend()==false" we return immediately with false ... + // see before + // Check for real changes too. + if ( + (xOldComponentWindow.is() ) && + (xOldComponentWindow != xComponentWindow) + ) + { + /* SAFE { */ + { + SolarMutexGuard aWriteLock; + m_xComponentWindow = nullptr; + } + /* } SAFE */ + + if (xOldComponentWindow.is()) + { + try + { + xOldComponentWindow->dispose(); + } + catch(const css::lang::DisposedException&) + { + } + } + xOldComponentWindow = nullptr; + } + + // Now it's time to set the new component ... + // By the way - find out our new "load state" - means if we have a valid component inside. + /* SAFE { */ + SolarMutexResettableGuard aWriteLock; + m_xComponentWindow = xComponentWindow; + m_xController = xController; + + // Clear the URL on the frame itself, now that the controller has it. + m_aURL.clear(); + + m_bConnected = (m_xComponentWindow.is() || m_xController.is()); + bool bIsConnected = m_bConnected; + aWriteLock.clear(); + /* } SAFE */ + + // notifies all interest listener, that current component was changed or a new one was loaded + if (bIsConnected && bWasConnected) + implts_sendFrameActionEvent( css::frame::FrameAction_COMPONENT_REATTACHED ); + else if (bIsConnected && !bWasConnected) + implts_sendFrameActionEvent( css::frame::FrameAction_COMPONENT_ATTACHED ); + + // A new component window doesn't know anything about current active/focus states. + // Set this information on it! + if ( bHadFocus && xComponentWindow.is() ) + { + xComponentWindow->setFocus(); + } + + // If it was a new component window - we must resize it to fill out + // our container window. + implts_resizeComponentWindow(); + // New component should change our current icon ... + implts_setIconOnWindow(); + // OK - start listening on new window again - or do nothing if it is an empty one. + implts_startWindowListening(); + + /* SAFE { */ + aWriteLock.reset(); + impl_checkMenuCloser(); + aWriteLock.clear(); + /* } SAFE */ + + return true; +} + +/*-**************************************************************************************************** + @short returns current set component window + @descr Frames are used to display components. The actual displayed component is + held by the m_xComponentWindow property. If the component implements only a + XComponent interface, the communication between the frame and the + component is very restricted. Better integration is achievable through a + XController interface. + If the component wants other objects to be able to get information about its + ResourceDescriptor it has to implement a XModel interface. + This frame is the owner of the component window. + + @seealso method setComponent() + @return css::uno::Reference to current set component window. + + @onerror A null reference is returned. +*//*-*****************************************************************************************************/ +css::uno::Reference< css::awt::XWindow > SAL_CALL XFrameImpl::getComponentWindow() +{ + checkDisposed(); + SolarMutexGuard g; + return m_xComponentWindow; +} + +/*-**************************************************************************************************** + @short returns current set controller + @descr Frames are used to display components. The actual displayed component is + held by the m_xComponentWindow property. If the component implements only a + XComponent interface, the communication between the frame and the + component is very restricted. Better integration is achievable through a + XController interface. + If the component wants other objects to be able to get information about its + ResourceDescriptor it has to implement a XModel interface. + This frame is the owner of the component window. + + @seealso method setComponent() + @return css::uno::Reference to current set controller. + + @onerror A null reference is returned. +*//*-*****************************************************************************************************/ +css::uno::Reference< css::frame::XController > SAL_CALL XFrameImpl::getController() +{ + SolarMutexGuard g; + return m_xController; +} + +/*-**************************************************************************************************** + @short add/remove listener for activate/deactivate/contextChanged events + @seealso method activate() + @seealso method deactivate() + @seealso method contextChanged() + + @param "xListener" reference to your listener object + @onerror Listener is ignored. +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::addFrameActionListener( const css::uno::Reference< css::frame::XFrameActionListener >& xListener ) +{ + checkDisposed(); + m_aListenerContainer.addInterface( cppu::UnoType<css::frame::XFrameActionListener>::get(), xListener ); +} + +void SAL_CALL XFrameImpl::removeFrameActionListener( const css::uno::Reference< css::frame::XFrameActionListener >& xListener ) +{ + m_aListenerContainer.removeInterface( cppu::UnoType<css::frame::XFrameActionListener>::get(), xListener ); +} + +/*-**************************************************************************************************** + @short support two way mechanism to release a frame + @descr This method ask internal component (controller) if he accept this close request. + In case of <TRUE/> nothing will be happen (from point of caller of this close method). + In case of <FALSE/> a CloseVetoException is thrown. After such exception given parameter + <var>bDeliverOwnership</var> regulate which will be the new owner of this instance. + + @attention It's the replacement for XTask::close() which is marked as obsolete method. + + @param bDeliverOwnership + If parameter is set to <FALSE/> the original caller will be the owner after thrown + veto exception and must try to close this frame at later time again. Otherwise the + source of thrown exception is the right one. May it will be the frame himself. + + @throws CloseVetoException + if any internal things will not be closed + + @threadsafe yes +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::close( sal_Bool bDeliverOwnership ) +{ + checkDisposed(); + + // At the end of this method may we must dispose ourself... + // and may nobody from outside hold a reference to us... + // then it's a good idea to do that by ourself. + css::uno::Reference< css::uno::XInterface > xSelfHold( static_cast< ::cppu::OWeakObject* >(this) ); + + // Check any close listener before we look for currently running internal processes. + // Because if a listener disagree with this close() request - we have time to finish this + // internal operations too... + // Note: container is threadsafe himself. + css::lang::EventObject aSource (static_cast< ::cppu::OWeakObject*>(this)); + comphelper::OInterfaceContainerHelper2* pContainer = m_aListenerContainer.getContainer( cppu::UnoType<css::util::XCloseListener>::get()); + if (pContainer!=nullptr) + { + comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer); + while (pIterator.hasMoreElements()) + { + try + { + static_cast<css::util::XCloseListener*>(pIterator.next())->queryClosing( aSource, bDeliverOwnership ); + } + catch( const css::uno::RuntimeException& ) + { + pIterator.remove(); + } + } + } + + // Ok - no listener disagreed with this close() request + // check if this frame is used for any load process currently + if (isActionLocked()) + { + if (bDeliverOwnership) + { + SolarMutexGuard g; + m_bSelfClose = true; + } + + throw css::util::CloseVetoException("Frame in use for loading document...",static_cast< ::cppu::OWeakObject*>(this)); + } + + if ( ! setComponent(nullptr,nullptr) ) + throw css::util::CloseVetoException("Component couldn't be detached...",static_cast< ::cppu::OWeakObject*>(this)); + + // If closing is allowed... inform all listeners and dispose this frame! + pContainer = m_aListenerContainer.getContainer( cppu::UnoType<css::util::XCloseListener>::get()); + if (pContainer!=nullptr) + { + comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer); + while (pIterator.hasMoreElements()) + { + try + { + static_cast<css::util::XCloseListener*>(pIterator.next())->notifyClosing( aSource ); + } + catch( const css::uno::RuntimeException& ) + { + pIterator.remove(); + } + } + } + + /* SAFE { */ + { + SolarMutexGuard aWriteLock; + m_bIsHidden = true; + } + /* } SAFE */ + impl_checkMenuCloser(); + + dispose(); +} + +/*-**************************************************************************************************** + @short be a listener for close events! + @descr Adds/remove a CloseListener at this frame instance. If the close() method is called on + this object, the such listener are informed and can disagree with that by throwing + a CloseVetoException. + + @seealso XFrameImpl::close() + + @param xListener + reference to your listener object + + @onerror Listener is ignored. + + @threadsafe yes +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::addCloseListener( const css::uno::Reference< css::util::XCloseListener >& xListener ) +{ + checkDisposed(); + m_aListenerContainer.addInterface( cppu::UnoType<css::util::XCloseListener>::get(), xListener ); +} + +void SAL_CALL XFrameImpl::removeCloseListener( const css::uno::Reference< css::util::XCloseListener >& xListener ) +{ + m_aListenerContainer.removeInterface( cppu::UnoType<css::util::XCloseListener>::get(), xListener ); +} + +OUString SAL_CALL XFrameImpl::getTitle() +{ + checkDisposed(); + + // SAFE -> + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::frame::XTitle > xTitle(m_xTitleHelper, css::uno::UNO_SET_THROW); + aReadLock.clear(); + // <- SAFE + + return xTitle->getTitle(); +} + +void SAL_CALL XFrameImpl::setTitle( const OUString& sTitle ) +{ + checkDisposed(); + + // SAFE -> + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::frame::XTitle > xTitle(m_xTitleHelper, css::uno::UNO_SET_THROW); + aReadLock.clear(); + // <- SAFE + + xTitle->setTitle(sTitle); +} + +void SAL_CALL XFrameImpl::addTitleChangeListener( const css::uno::Reference< css::frame::XTitleChangeListener >& xListener) +{ + checkDisposed(); + + // SAFE -> + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::frame::XTitleChangeBroadcaster > xTitle(m_xTitleHelper, css::uno::UNO_QUERY_THROW); + aReadLock.clear(); + // <- SAFE + + xTitle->addTitleChangeListener(xListener); +} + +void SAL_CALL XFrameImpl::removeTitleChangeListener( const css::uno::Reference< css::frame::XTitleChangeListener >& xListener ) +{ + checkDisposed(); + + // SAFE -> + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::frame::XTitleChangeBroadcaster > xTitle(m_xTitleHelper, css::uno::UNO_QUERY_THROW); + aReadLock.clear(); + // <- SAFE + + xTitle->removeTitleChangeListener(xListener); +} + +css::uno::Reference<css::container::XNameContainer> SAL_CALL XFrameImpl::getUserDefinedAttributes() +{ + // optional attribute + return nullptr; +} + +css::uno::Reference<css::frame::XDispatchRecorderSupplier> SAL_CALL XFrameImpl::getDispatchRecorderSupplier() +{ + SolarMutexGuard g; + return m_xDispatchRecorderSupplier; +} + +void SAL_CALL XFrameImpl::setDispatchRecorderSupplier(const css::uno::Reference<css::frame::XDispatchRecorderSupplier>& p) +{ + checkDisposed(); + SolarMutexGuard g; + m_xDispatchRecorderSupplier.set(p); +} + +css::uno::Reference<css::uno::XInterface> SAL_CALL XFrameImpl::getLayoutManager() +{ + SolarMutexGuard g; + return m_xLayoutManager; +} + +void SAL_CALL XFrameImpl::setLayoutManager(const css::uno::Reference<css::uno::XInterface>& p1) +{ + checkDisposed(); + SolarMutexGuard g; + + css::uno::Reference<css::frame::XLayoutManager2> xOldLayoutManager = m_xLayoutManager; + css::uno::Reference<css::frame::XLayoutManager2> xNewLayoutManager(p1, css::uno::UNO_QUERY); + + if (xOldLayoutManager != xNewLayoutManager) + { + m_xLayoutManager = xNewLayoutManager; + if (xOldLayoutManager.is()) + disableLayoutManager(xOldLayoutManager); + if (xNewLayoutManager.is() && !m_bDocHidden) + lcl_enableLayoutManager(xNewLayoutManager, this); + } +} + +css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL XFrameImpl::getPropertySetInfo() +{ + checkDisposed(); + return css::uno::Reference< css::beans::XPropertySetInfo >(this); +} + +void SAL_CALL XFrameImpl::setPropertyValue(const OUString& sProperty, + const css::uno::Any& aValue ) +{ + // TODO look for e.g. readonly props and reject setProp() call! + + checkDisposed(); + + // SAFE -> + SolarMutexGuard g; + + TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty); + if (pIt == m_lProps.end()) + throw css::beans::UnknownPropertyException(sProperty); + + css::beans::Property aPropInfo = pIt->second; + + css::uno::Any aCurrentValue = impl_getPropertyValue(aPropInfo.Handle); + + bool bWillBeChanged = (aCurrentValue != aValue); + if (! bWillBeChanged) + return; + + css::beans::PropertyChangeEvent aEvent; + aEvent.PropertyName = aPropInfo.Name; + aEvent.Further = false; + aEvent.PropertyHandle = aPropInfo.Handle; + aEvent.OldValue = aCurrentValue; + aEvent.NewValue = aValue; + aEvent.Source.set(m_xBroadcaster.get(), css::uno::UNO_QUERY); + + if (impl_existsVeto(aEvent)) + throw css::beans::PropertyVetoException(); + + impl_setPropertyValue(aPropInfo.Handle, aValue); + + impl_notifyChangeListener(aEvent); +} + +css::uno::Any SAL_CALL XFrameImpl::getPropertyValue(const OUString& sProperty) +{ + checkDisposed(); + + // SAFE -> + SolarMutexGuard aReadLock; + + TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty); + if (pIt == m_lProps.end()) + throw css::beans::UnknownPropertyException(sProperty); + + css::beans::Property aPropInfo = pIt->second; + + return impl_getPropertyValue(aPropInfo.Handle); +} + +void SAL_CALL XFrameImpl::addPropertyChangeListener( + const OUString& sProperty, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) +{ + checkDisposed(); + + // SAFE -> + { + SolarMutexGuard aReadLock; + + TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty); + if (pIt == m_lProps.end()) + throw css::beans::UnknownPropertyException(sProperty); + } + // <- SAFE + + m_lSimpleChangeListener.addInterface(sProperty, xListener); +} + +void SAL_CALL XFrameImpl::removePropertyChangeListener( + const OUString& sProperty, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener) +{ + // SAFE -> + { + SolarMutexGuard aReadLock; + + TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty); + if (pIt == m_lProps.end()) + throw css::beans::UnknownPropertyException(sProperty); + } + // <- SAFE + + m_lSimpleChangeListener.removeInterface(sProperty, xListener); +} + +void SAL_CALL XFrameImpl::addVetoableChangeListener( + const OUString& sProperty, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) +{ + checkDisposed(); + + // SAFE -> + { + SolarMutexGuard aReadLock; + + TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty); + if (pIt == m_lProps.end()) + throw css::beans::UnknownPropertyException(sProperty); + } + // <- SAFE + + m_lVetoChangeListener.addInterface(sProperty, xListener); +} + +void SAL_CALL XFrameImpl::removeVetoableChangeListener( + const OUString& sProperty, + const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener) +{ + // SAFE -> + { + SolarMutexGuard aReadLock; + + TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty); + if (pIt == m_lProps.end()) + throw css::beans::UnknownPropertyException(sProperty); + } + // <- SAFE + + m_lVetoChangeListener.removeInterface(sProperty, xListener); +} + +css::uno::Sequence< css::beans::Property > SAL_CALL XFrameImpl::getProperties() +{ + checkDisposed(); + + SolarMutexGuard g; + + sal_Int32 c = static_cast<sal_Int32>(m_lProps.size()); + css::uno::Sequence< css::beans::Property > lProps(c); + auto lPropsRange = asNonConstRange(lProps); + for (auto const& elem : m_lProps) + { + lPropsRange[--c] = elem.second; + } + + return lProps; +} + +css::beans::Property SAL_CALL XFrameImpl::getPropertyByName(const OUString& sName) +{ + checkDisposed(); + + SolarMutexGuard g; + + TPropInfoHash::const_iterator pIt = m_lProps.find(sName); + if (pIt == m_lProps.end()) + throw css::beans::UnknownPropertyException(sName); + + return pIt->second; +} + +sal_Bool SAL_CALL XFrameImpl::hasPropertyByName(const OUString& sName) +{ + checkDisposed(); + + SolarMutexGuard g; + + TPropInfoHash::iterator pIt = m_lProps.find(sName); + bool bExist = (pIt != m_lProps.end()); + + return bExist; +} + +/*-****************************************************************************************************/ +void XFrameImpl::implts_forgetSubFrames() +{ + // SAFE -> + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::container::XIndexAccess > xContainer(m_xFramesHelper, css::uno::UNO_QUERY_THROW); + aReadLock.clear(); + // <- SAFE + + sal_Int32 c = xContainer->getCount(); + sal_Int32 i = 0; + + for (i=0; i<c; ++i) + { + try + { + css::uno::Reference< css::frame::XFrame > xFrame; + xContainer->getByIndex(i) >>= xFrame; + if (xFrame.is()) + xFrame->setCreator(css::uno::Reference< css::frame::XFramesSupplier >()); + } + catch(const css::uno::Exception&) + { + // Ignore errors here. + // Nobody can guarantee a stable index in multi threaded environments .-) + } + } + + SolarMutexGuard g; + m_xFramesHelper.clear(); // clear uno reference + m_aChildFrameContainer.clear(); // clear container content +} + +/*-**************************************************************************************************** + @short destroy instance + @descr The owner of this object calls the dispose method if the object + should be destroyed. All other objects and components, that are registered + as an EventListener are forced to release their references to this object. + Furthermore this frame is removed from its parent frame container to release + this reference. The reference attributes are disposed and released also. + + @attention Look for globale description at beginning of file too! + (DisposedException, FairRWLock ..., initialize, dispose) + + @seealso method initialize() + @seealso baseclass FairRWLockBase! +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::disposing() +{ + // We should hold a reference to ourself ... + // because our owner dispose us and release our reference ... + // May be we will die before we could finish this method ... + css::uno::Reference< css::frame::XFrame > xThis(this); + + SAL_INFO("fwk.frame", "[Frame] " << m_sName << " send dispose event to listener"); + + // First operation should be... "stop all listening for window events on our container window". + // These events are superfluous but can make trouble! + // We will die, die and die... + implts_stopWindowListening(); + + css::uno::Reference<css::frame::XLayoutManager2> layoutMgr; + { + SolarMutexGuard g; + layoutMgr = m_xLayoutManager; + } + if (layoutMgr.is()) { + disableLayoutManager(layoutMgr); + } + + std::unique_ptr<WindowCommandDispatch> disp; + { + SolarMutexGuard g; + std::swap(disp, m_pWindowCommandDispatch); + } + disp.reset(); + + // Send message to all listener and forget her references. + css::lang::EventObject aEvent( xThis ); + m_aListenerContainer.disposeAndClear( aEvent ); + + // set "end of live" for our property set helper + impl_disablePropertySet(); + + // interception/dispatch chain must be destructed explicitly + // Otherwise some dispatches and/or interception objects won't die. + css::uno::Reference< css::lang::XEventListener > xDispatchHelper; + { + SolarMutexGuard g; + xDispatchHelper.set(m_xDispatchHelper, css::uno::UNO_QUERY_THROW); + } + xDispatchHelper->disposing(aEvent); + xDispatchHelper.clear(); + + // Don't show any dialogs, errors or something else any more! + // If somewhere called dispose() without close() before - normally no dialogs + // should exist. Otherwise it's the problem of the outside caller. + // Note: + // (a) Do it after stopWindowListening(). May that force some active/deactivate + // notifications which we don't need here really. + // (b) Don't forget to save the old value of IsDialogCancelEnabled() to + // restore it afterwards (to not kill headless mode). + DialogCancelMode old = Application::GetDialogCancelMode(); + Application::SetDialogCancelMode( DialogCancelMode::Silent ); + + // We should be alone for ever and further dispose calls are rejected by lines before ... + // I hope it :-) + + // Free references of our frame tree. + // Force parent container to forget this frame too ... + // ( It's contained in m_xParent and so no css::lang::XEventListener for m_xParent! ) + // It's important to do that before we free some other internal structures. + // Because if our parent gets an activate and found us as last possible active frame + // he try to deactivate us ... and we run into some trouble (DisposedExceptions!). + css::uno::Reference<css::frame::XFramesSupplier> parent; + { + SolarMutexGuard g; + std::swap(parent, m_xParent); + } + if( parent.is() ) + { + parent->getFrames()->remove( xThis ); + } + + /* } SAFE */ + // Forget our internal component and her window first. + // So we can release our container window later without problems. + // Because this container window is the parent of the component window ... + // Note: Dispose it hard - because suspending must be done inside close() call! + // But try to dispose the controller first before you destroy the window. + // Because the window is used by the controller too ... + css::uno::Reference< css::lang::XComponent > xDisposableCtrl; + css::uno::Reference< css::lang::XComponent > xDisposableComp; + { + SolarMutexGuard g; + xDisposableCtrl = m_xController; + xDisposableComp = m_xComponentWindow; + } + if (xDisposableCtrl.is()) + xDisposableCtrl->dispose(); + if (xDisposableComp.is()) + xDisposableComp->dispose(); + + impl_checkMenuCloser(); + + css::uno::Reference<css::awt::XWindow> contWin; + { + SolarMutexGuard g; + std::swap(contWin, m_xContainerWindow); + } + if( contWin.is() ) + { + contWin->setVisible( false ); + // All VclComponents are XComponents; so call dispose before discarding + // a css::uno::Reference< XVclComponent >, because this frame is the owner of the window + contWin->dispose(); + } + + /*ATTENTION + Clear container after successful removing from parent container ... + because our parent could be the desktop which stand in dispose too! + If we have already cleared our own container we lost our child before this could be + remove himself at this instance ... + Release m_xFramesHelper after that ... it's the same problem between parent and child! + "m_xParent->getFrames()->remove( xThis );" needs this helper ... + Otherwise we get a null reference and could finish removing successfully. + => You see: Order of calling operations is important!!! + */ + implts_forgetSubFrames(); + + { + SolarMutexGuard g; + + // Release some other references. + // This calls should be easy ... I hope it :-) + m_xDispatchHelper.clear(); + m_xDropTargetListener.clear(); + m_xDispatchRecorderSupplier.clear(); + m_xLayoutManager.clear(); + m_xIndicatorFactoryHelper.clear(); + + // It's important to set default values here! + // If may be later somewhere change the disposed-behaviour of this implementation + // and doesn't throw any DisposedExceptions we must guarantee best matching default values ... + m_eActiveState = E_INACTIVE; + m_sName.clear(); + m_bIsFrameTop = false; + m_bConnected = false; + m_nExternalLockCount = 0; + m_bSelfClose = false; + m_bIsHidden = true; + } + + // Don't forget it restore old value - + // otherwise no dialogs can be shown anymore in other frames. + Application::SetDialogCancelMode( old ); +} + +/*-**************************************************************************************************** + @short Be a listener for dispose events! + @descr Adds/remove an EventListener to this object. If the dispose method is called on + this object, the disposing method of the listener is called. + @param "xListener" reference to your listener object. + @onerror Listener is ignored. +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) +{ + checkDisposed(); + m_aListenerContainer.addInterface( cppu::UnoType<css::lang::XEventListener>::get(), xListener ); +} + +void SAL_CALL XFrameImpl::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) +{ + m_aListenerContainer.removeInterface( cppu::UnoType<css::lang::XEventListener>::get(), xListener ); +} + +/*-**************************************************************************************************** + @short create new status indicator + @descr Use returned status indicator to show progresses and some text information. + All created objects share the same dialog! Only the last one can show his information. + + @seealso class StatusIndicatorFactory + @seealso class StatusIndicator + @return A reference to created object. + + @onerror We return a null reference. +*//*-*****************************************************************************************************/ +css::uno::Reference< css::task::XStatusIndicator > SAL_CALL XFrameImpl::createStatusIndicator() +{ + checkDisposed(); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aReadLock; + + // Make snapshot of necessary member and define default return value. + css::uno::Reference< css::task::XStatusIndicator > xExternal(m_xIndicatorInterception.get(), css::uno::UNO_QUERY); + css::uno::Reference< css::task::XStatusIndicatorFactory > xFactory = m_xIndicatorFactoryHelper; + + aReadLock.clear(); + /* UNSAFE AREA ----------------------------------------------------------------------------------------- */ + + // Was set from outside to intercept any progress activities! + if (xExternal.is()) + return xExternal; + + // Or use our own factory as fallback, to create such progress. + if (xFactory.is()) + return xFactory->createStatusIndicator(); + + return css::uno::Reference< css::task::XStatusIndicator >(); +} + +/*-**************************************************************************************************** + @short search for target to load URL + @descr This method searches for a dispatch for the specified DispatchDescriptor. + The FrameSearchFlags and the FrameName of the DispatchDescriptor are + treated as described for findFrame. + + @seealso method findFrame() + @seealso method queryDispatches() + @seealso method set/getName() + @seealso class TargetFinder + + @param "aURL" , URL for loading + @param "sTargetFrameName" , name of target frame + @param "nSearchFlags" , additional flags to regulate search if sTargetFrameName is not clear + @return css::uno::Reference to dispatch handler. + + @onerror A null reference is returned. +*//*-*****************************************************************************************************/ +css::uno::Reference< css::frame::XDispatch > SAL_CALL XFrameImpl::queryDispatch( const css::util::URL& aURL, + const OUString& sTargetFrameName, + sal_Int32 nSearchFlags) +{ + // Don't check incoming parameter here! Our helper do it for us and it is not a good idea to do it more than once! + + checkDisposed(); + + // Remove uno and cmd protocol part as we want to support both of them. We store only the command part + // in our hash map. All other protocols are stored with the protocol part. + OUString aCommand( aURL.Main ); + if ( aURL.Protocol.equalsIgnoreAsciiCase(".uno:") ) + aCommand = aURL.Path; + + // Make std::unordered_map lookup if the current URL is in the disabled list + if ( m_aCommandOptions.Lookup( SvtCommandOptions::CMDOPTION_DISABLED, aCommand ) ) + return css::uno::Reference< css::frame::XDispatch >(); + else + { + // We use a helper to support these interface and an interceptor mechanism. + css::uno::Reference<css::frame::XDispatchProvider> disp; + { + SolarMutexGuard g; + disp = m_xDispatchHelper; + } + if (!disp.is()) { + throw css::lang::DisposedException("Frame disposed"); + } + return disp->queryDispatch( aURL, sTargetFrameName, nSearchFlags ); + } +} + +/*-**************************************************************************************************** + @short handle more than ones dispatches at same call + @descr Returns a sequence of dispatches. For details see the queryDispatch method. + For failed dispatches we return empty items in list! + + @seealso method queryDispatch() + + @param "lDescriptor" list of dispatch arguments for queryDispatch()! + @return List of dispatch references. Some elements can be NULL! + + @onerror An empty list is returned. +*//*-*****************************************************************************************************/ +css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL XFrameImpl::queryDispatches( + const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptor ) +{ + // Don't check incoming parameter here! Our helper do it for us and it is not a good idea to do it more than ones! + + checkDisposed(); + + // We use a helper to support these interface and an interceptor mechanism. + css::uno::Reference<css::frame::XDispatchProvider> disp; + { + SolarMutexGuard g; + disp = m_xDispatchHelper; + } + if (!disp.is()) { + throw css::lang::DisposedException("Frame disposed"); + } + return disp->queryDispatches( lDescriptor ); +} + +/*-**************************************************************************************************** + @short register/unregister interceptor for dispatch calls + @descr If you wish to handle some dispatches by himself ... you should be + an interceptor for it. Please see class OInterceptionHelper for further information. + + @seealso class OInterceptionHelper + + @param "xInterceptor", reference to your interceptor implementation. + @onerror Interceptor is ignored. +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::registerDispatchProviderInterceptor( + const css::uno::Reference< css::frame::XDispatchProviderInterceptor >& xInterceptor ) +{ + // We use a helper to support these interface and an interceptor mechanism. + // This helper is threadsafe himself and check incoming parameter too. + // I think we don't need any lock here! + + checkDisposed(); + + css::uno::Reference< css::frame::XDispatchProviderInterception > xInterceptionHelper; + { + SolarMutexGuard g; + xInterceptionHelper.set( m_xDispatchHelper, css::uno::UNO_QUERY ); + } + if (xInterceptionHelper.is()) { + xInterceptionHelper->registerDispatchProviderInterceptor( xInterceptor ); + } +} + +void SAL_CALL XFrameImpl::releaseDispatchProviderInterceptor( + const css::uno::Reference< css::frame::XDispatchProviderInterceptor >& xInterceptor ) +{ + // We use a helper to support these interface and an interceptor mechanism. + // This helper is threadsafe himself and check incoming parameter too. + // I think we don't need any lock here! + + // Sometimes we are called during our dispose() method + + css::uno::Reference< css::frame::XDispatchProviderInterception > xInterceptionHelper; + { + SolarMutexGuard g; + xInterceptionHelper.set( m_xDispatchHelper, css::uno::UNO_QUERY ); + } + if (xInterceptionHelper.is()) { + xInterceptionHelper->releaseDispatchProviderInterceptor( xInterceptor ); + } +} + +/*-**************************************************************************************************** + @short provides information about all possible dispatch functions + inside the current frame environment +*//*-*****************************************************************************************************/ +css::uno::Sequence< sal_Int16 > SAL_CALL XFrameImpl::getSupportedCommandGroups() +{ + return m_xDispatchInfoHelper->getSupportedCommandGroups(); +} + +css::uno::Sequence< css::frame::DispatchInformation > SAL_CALL XFrameImpl::getConfigurableDispatchInformation( + sal_Int16 nCommandGroup) +{ + return m_xDispatchInfoHelper->getConfigurableDispatchInformation(nCommandGroup); +} + +/*-**************************************************************************************************** + @short notifications for window events + @descr We are a listener on our container window to forward it to our component window. + + @seealso method setComponent() + @seealso member m_xContainerWindow + @seealso member m_xComponentWindow + + @param "aEvent" describe source of detected event +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::windowResized( const css::awt::WindowEvent& ) +{ + // Part of dispose-mechanism + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + // Impl-method is threadsafe! + // If we have a current component window - we must resize it! + implts_resizeComponentWindow(); +} + +void SAL_CALL XFrameImpl::focusGained( const css::awt::FocusEvent& ) +{ + // Part of dispose() mechanism + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aReadLock; + // Make snapshot of member! + css::uno::Reference< css::awt::XWindow > xComponentWindow = m_xComponentWindow; + aReadLock.clear(); + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + + if( xComponentWindow.is() ) + { + xComponentWindow->setFocus(); + } +} + +/*-**************************************************************************************************** + @short notifications for window events + @descr We are a listener on our container window to forward it to our component window ... + but a XTopWindowListener we are only if we are a top frame! + + @seealso method setComponent() + @seealso member m_xContainerWindow + @seealso member m_xComponentWindow + + @param "aEvent" describe source of detected event +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::windowActivated( const css::lang::EventObject& ) +{ + checkDisposed(); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aReadLock; + // Make snapshot of member! + EActiveState eState = m_eActiveState; + aReadLock.clear(); + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + // Activate the new active path from here to top. + if( eState == E_INACTIVE ) + { + setActiveFrame( css::uno::Reference< css::frame::XFrame >() ); + activate(); + } +} + +void SAL_CALL XFrameImpl::windowDeactivated( const css::lang::EventObject& ) +{ + // Sometimes called during dispose() + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aReadLock; + + css::uno::Reference< css::frame::XFrame > xParent = m_xParent; + css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow; + EActiveState eActiveState = m_eActiveState; + + aReadLock.clear(); + + if( eActiveState == E_INACTIVE ) + return; + + // Deactivation is always done implicitly by activation of another frame. + // Only if no activation is done, deactivations have to be processed if the activated window + // is a parent window of the last active Window! + SolarMutexClearableGuard aSolarGuard; + vcl::Window* pFocusWindow = Application::GetFocusWindow(); + if ( !xContainerWindow.is() || !xParent.is() || + css::uno::Reference< css::frame::XDesktop >( xParent, css::uno::UNO_QUERY ).is() + ) + return; + + css::uno::Reference< css::awt::XWindow > xParentWindow = xParent->getContainerWindow(); + VclPtr<vcl::Window> pParentWindow = VCLUnoHelper::GetWindow( xParentWindow ); + //#i70261#: dialogs opened from an OLE object will cause a deactivate on the frame of the OLE object + // on Solaris/Linux at that time pFocusWindow is still NULL because the focus handling is different; right after + // the deactivation the focus will be set into the dialog! + // currently I see no case where a sub frame could get a deactivate with pFocusWindow being NULL permanently + // so for now this case is omitted from handled deactivations + if( pFocusWindow && pParentWindow->IsChild( pFocusWindow ) ) + { + css::uno::Reference< css::frame::XFramesSupplier > xSupplier( xParent, css::uno::UNO_QUERY ); + if( xSupplier.is() ) + { + aSolarGuard.clear(); + xSupplier->setActiveFrame( css::uno::Reference< css::frame::XFrame >() ); + } + } +} + +void SAL_CALL XFrameImpl::windowClosing( const css::lang::EventObject& ) +{ + checkDisposed(); + + // deactivate this frame ... + deactivate(); + + // ... and try to close it + // But do it asynchronous inside the main thread. + // VCL has no fun to do such things outside his main thread :-( + // Note: The used dispatch make it asynchronous for us .-) + + /*ATTENTION! + Don't try to suspend the controller here! Because it's done inside used dispatch(). + Otherwise the dialog "would you save your changes?" will be shown more than once ... + */ + + css::util::URL aURL; + aURL.Complete = ".uno:CloseFrame"; + css::uno::Reference< css::util::XURLTransformer > xParser(css::util::URLTransformer::create(m_xContext)); + xParser->parseStrict(aURL); + + css::uno::Reference< css::frame::XDispatch > xCloser = queryDispatch(aURL, SPECIALTARGET_SELF, 0); + if (xCloser.is()) + xCloser->dispatch(aURL, css::uno::Sequence< css::beans::PropertyValue >()); + + // Attention: If this dispatch works synchronous ... and fulfill its job ... + // this line of code will never be reached ... + // Or if it will be reached it will be for sure that all your member are gone .-) +} + +/*-**************************************************************************************************** + @short react for a show event for the internal container window + @descr Normally we don't need this information really. But we can use it to + implement the special feature "trigger first visible task". + + Algorithm: - first we have to check if we are a top (task) frame + It's not enough to be a top frame! Because we MUST have the desktop as parent. + But frames without a parent are top too. So it's not possible to check isTop() here! + We have to look for the type of our parent. + - if we are a task frame, then we have to check if we are the first one. + We use a static variable to do so. They will be reset to afterwards be sure + that further calls of this method doesn't do anything then. + - Then we have to trigger the right event string on the global job executor. + + @seealso css::task::JobExecutor + + @param aEvent + describes the source of this event + We are not interested on this information. We are interested on the visible state only. + + @threadsafe yes +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::windowShown( const css::lang::EventObject& ) +{ + static std::mutex aFirstVisibleLock; + + /* SAFE { */ + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::frame::XDesktop > xDesktopCheck( m_xParent, css::uno::UNO_QUERY ); + m_bIsHidden = false; + aReadLock.clear(); + /* } SAFE */ + + impl_checkMenuCloser(); + + if (!xDesktopCheck.is()) + return; + + static bool bFirstVisibleTask = true; + std::unique_lock aGuard(aFirstVisibleLock); + bool bMustBeTriggered = bFirstVisibleTask; + bFirstVisibleTask = false; + aGuard.unlock(); + + if (bMustBeTriggered) + { + css::uno::Reference< css::task::XJobExecutor > xExecutor + = css::task::theJobExecutor::get( m_xContext ); + xExecutor->trigger( "onFirstVisibleTask" ); + } +} + +void SAL_CALL XFrameImpl::windowHidden( const css::lang::EventObject& ) +{ + /* SAFE { */ + { + SolarMutexGuard aReadLock; + m_bIsHidden = true; + } + /* } SAFE */ + + impl_checkMenuCloser(); +} + +/*-**************************************************************************************************** + @short called by dispose of our windows! + @descr This object is forced to release all references to the interfaces given + by the parameter source. We are a listener at our container window and + should listen for his disposing. + + @seealso XWindowListener + @seealso XTopWindowListener + @seealso XFocusListener +*//*-*****************************************************************************************************/ +void SAL_CALL XFrameImpl::disposing( const css::lang::EventObject& aEvent ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexResettableGuard aWriteLock; + + if( aEvent.Source == m_xContainerWindow ) + { + // NECESSARY: Impl-method is threadsafe by himself! + aWriteLock.clear(); + implts_stopWindowListening(); + aWriteLock.reset(); + m_xContainerWindow.clear(); + } +} + +/*-************************************************************************************************************ + @interface com.sun.star.document.XActionLockable + @short implement locking of frame/task from outside + @descr Sometimes we have problems to decide if closing of task is allowed. Because; frame/task + could be used for pending loading jobs. So you can lock this object from outside and + prevent instance against closing during using! But - don't do it in a wrong or expensive manner. + Otherwise task couldn't die anymore!!! + + @seealso interface XActionLockable + @seealso method BaseDispatcher::implts_loadIt() + @seealso method Desktop::loadComponentFromURL() + @return true if frame/task is locked + false otherwise + @threadsafe yes +*//*-*************************************************************************************************************/ +sal_Bool SAL_CALL XFrameImpl::isActionLocked() +{ + SolarMutexGuard g; + return( m_nExternalLockCount!=0); +} + +void SAL_CALL XFrameImpl::addActionLock() +{ + SolarMutexGuard g; + ++m_nExternalLockCount; +} + +void SAL_CALL XFrameImpl::removeActionLock() +{ + { + SolarMutexGuard g; + SAL_WARN_IF( m_nExternalLockCount<=0, "fwk.frame", "XFrameImpl::removeActionLock(): Frame is not locked! " + "Possible multithreading problem detected." ); + --m_nExternalLockCount; + } + + implts_checkSuicide(); +} + +void SAL_CALL XFrameImpl::setActionLocks( sal_Int16 nLock ) +{ + SolarMutexGuard g; + // Attention: If somewhere called resetActionLocks() before and get e.g. 5 locks ... + // and tried to set these 5 ones here after his operations ... + // we can't ignore set requests during these two calls! + // So we must add(!) these 5 locks here. + m_nExternalLockCount = m_nExternalLockCount + nLock; +} + +sal_Int16 SAL_CALL XFrameImpl::resetActionLocks() +{ + sal_Int16 nCurrentLocks = 0; + { + SolarMutexGuard g; + nCurrentLocks = m_nExternalLockCount; + m_nExternalLockCount = 0; + } + + // Attention: + // external lock count is 0 here every time... but if + // member m_bSelfClose is set to true too... we call our own close()/dispose(). + // See close() for further information + implts_checkSuicide(); + + return nCurrentLocks; +} + +void XFrameImpl::impl_setPropertyValue(sal_Int32 nHandle, + const css::uno::Any& aValue) + +{ + /* There is no need to lock any mutex here. Because we share the + solar mutex with our base class. And we said to our base class: "don't release it on calling us" .-) + */ + + /* Attention: You can use nHandle only, if you are sure that all supported + properties has a unique handle. That must be guaranteed + inside method initListeners()! + */ + switch (nHandle) + { + case FRAME_PROPHANDLE_TITLE : + { + OUString sExternalTitle; + aValue >>= sExternalTitle; + setTitle (sExternalTitle); + } + break; + + case FRAME_PROPHANDLE_DISPATCHRECORDERSUPPLIER : + aValue >>= m_xDispatchRecorderSupplier; + break; + + case FRAME_PROPHANDLE_LAYOUTMANAGER : + { + css::uno::Reference< css::frame::XLayoutManager2 > xOldLayoutManager = m_xLayoutManager; + css::uno::Reference< css::frame::XLayoutManager2 > xNewLayoutManager; + aValue >>= xNewLayoutManager; + + if (xOldLayoutManager != xNewLayoutManager) + { + m_xLayoutManager = xNewLayoutManager; + if (xOldLayoutManager.is()) + disableLayoutManager(xOldLayoutManager); + if (xNewLayoutManager.is() && !m_bDocHidden) + lcl_enableLayoutManager(xNewLayoutManager, this); + } + } + break; + + case FRAME_PROPHANDLE_INDICATORINTERCEPTION : + { + css::uno::Reference< css::task::XStatusIndicator > xProgress; + aValue >>= xProgress; + m_xIndicatorInterception = xProgress; + } + break; + + case FRAME_PROPHANDLE_URL: + { + aValue >>= m_aURL; + } + break; + default : + SAL_INFO("fwk.frame", "XFrameImpl::setFastPropertyValue_NoBroadcast(): Invalid handle detected!" ); + break; + } +} + +css::uno::Any XFrameImpl::impl_getPropertyValue(sal_Int32 nHandle) +{ + /* There is no need to lock any mutex here. Because we share the + solar mutex with our base class. And we said to our base class: "don't release it on calling us" .-) + */ + + /* Attention: You can use nHandle only, if you are sure that all supported + properties has a unique handle. That must be guaranteed + inside method initListeners()! + */ + css::uno::Any aValue; + switch (nHandle) + { + case FRAME_PROPHANDLE_TITLE : + aValue <<= getTitle (); + break; + + case FRAME_PROPHANDLE_DISPATCHRECORDERSUPPLIER : + aValue <<= m_xDispatchRecorderSupplier; + break; + + case FRAME_PROPHANDLE_ISHIDDEN : + aValue <<= m_bIsHidden; + break; + + case FRAME_PROPHANDLE_LAYOUTMANAGER : + aValue <<= m_xLayoutManager; + break; + + case FRAME_PROPHANDLE_INDICATORINTERCEPTION : + { + css::uno::Reference< css::task::XStatusIndicator > xProgress(m_xIndicatorInterception.get(), + css::uno::UNO_QUERY); + aValue <<= xProgress; + } + break; + + case FRAME_PROPHANDLE_URL: + { + aValue <<= m_aURL; + } + break; + default : + SAL_INFO("fwk.frame", "XFrameImpl::getFastPropertyValue(): Invalid handle detected!" ); + break; + } + + return aValue; +} + +void XFrameImpl::impl_setPropertyChangeBroadcaster(const css::uno::Reference< css::uno::XInterface >& xBroadcaster) +{ + SolarMutexGuard g; + m_xBroadcaster = xBroadcaster; +} + +void XFrameImpl::impl_addPropertyInfo(const css::beans::Property& aProperty) +{ + SolarMutexGuard g; + + TPropInfoHash::const_iterator pIt = m_lProps.find(aProperty.Name); + if (pIt != m_lProps.end()) + throw css::beans::PropertyExistException(); + + m_lProps[aProperty.Name] = aProperty; +} + +void XFrameImpl::impl_disablePropertySet() +{ + SolarMutexGuard g; + + css::uno::Reference< css::uno::XInterface > xThis(static_cast< css::beans::XPropertySet* >(this), css::uno::UNO_QUERY); + css::lang::EventObject aEvent(xThis); + + m_lSimpleChangeListener.disposeAndClear(aEvent); + m_lVetoChangeListener.disposeAndClear(aEvent); + m_lProps.clear(); +} + +bool XFrameImpl::impl_existsVeto(const css::beans::PropertyChangeEvent& aEvent) +{ + /* Don't use the lock here! + The used helper is threadsafe and it lives for the whole lifetime of + our own object. + */ + ::comphelper::OInterfaceContainerHelper3<css::beans::XVetoableChangeListener>* pVetoListener = m_lVetoChangeListener.getContainer(aEvent.PropertyName); + if (! pVetoListener) + return false; + + ::comphelper::OInterfaceIteratorHelper3 pListener(*pVetoListener); + while (pListener.hasMoreElements()) + { + try + { + pListener.next()->vetoableChange(aEvent); + } + catch(const css::uno::RuntimeException&) + { pListener.remove(); } + catch(const css::beans::PropertyVetoException&) + { return true; } + } + + return false; +} + +void XFrameImpl::impl_notifyChangeListener(const css::beans::PropertyChangeEvent& aEvent) +{ + /* Don't use the lock here! + The used helper is threadsafe and it lives for the whole lifetime of + our own object. + */ + ::comphelper::OInterfaceContainerHelper3<css::beans::XPropertyChangeListener>* pSimpleListener = m_lSimpleChangeListener.getContainer(aEvent.PropertyName); + if (! pSimpleListener) + return; + + ::comphelper::OInterfaceIteratorHelper3 pListener(*pSimpleListener); + while (pListener.hasMoreElements()) + { + try + { + pListener.next()->propertyChange(aEvent); + } + catch(const css::uno::RuntimeException&) + { pListener.remove(); } + } +} + +/*-**************************************************************************************************** + @short send frame action event to our listener + @descr This method is threadsafe AND can be called by our dispose method too! + @param "aAction", describe the event for sending +*//*-*****************************************************************************************************/ +void XFrameImpl::implts_sendFrameActionEvent( const css::frame::FrameAction& aAction ) +{ + // Sometimes used by dispose() + + // Log information about order of events to file! + // (only activated in debug version!) + SAL_INFO( "fwk.frame", + "[Frame] " << m_sName << " send event " << + (aAction == css::frame::FrameAction_COMPONENT_ATTACHED ? OUString("COMPONENT ATTACHED") : + (aAction == css::frame::FrameAction_COMPONENT_DETACHING ? OUString("COMPONENT DETACHING") : + (aAction == css::frame::FrameAction_COMPONENT_REATTACHED ? OUString("COMPONENT REATTACHED") : + (aAction == css::frame::FrameAction_FRAME_ACTIVATED ? OUString("FRAME ACTIVATED") : + (aAction == css::frame::FrameAction_FRAME_DEACTIVATING ? OUString("FRAME DEACTIVATING") : + (aAction == css::frame::FrameAction_CONTEXT_CHANGED ? OUString("CONTEXT CHANGED") : + (aAction == css::frame::FrameAction_FRAME_UI_ACTIVATED ? OUString("FRAME UI ACTIVATED") : + (aAction == css::frame::FrameAction_FRAME_UI_DEACTIVATING ? OUString("FRAME UI DEACTIVATING") : + (aAction == css::frame::FrameAction::FrameAction_MAKE_FIXED_SIZE ? OUString("MAKE_FIXED_SIZE") : + OUString("*invalid*"))))))))))); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + // Send css::frame::FrameAction event to all listener. + // Get container for right listener. + // FOLLOW LINES ARE THREADSAFE!!! + // ( OInterfaceContainerHelper2 is synchronized with m_aListenerContainer! ) + comphelper::OInterfaceContainerHelper2* pContainer = m_aListenerContainer.getContainer( + cppu::UnoType<css::frame::XFrameActionListener>::get()); + + if( pContainer == nullptr ) + return; + + // Build action event. + css::frame::FrameActionEvent aFrameActionEvent( static_cast< ::cppu::OWeakObject* >(this), this, aAction ); + + // Get iterator for access to listener. + comphelper::OInterfaceIteratorHelper2 aIterator( *pContainer ); + // Send message to all listener. + while( aIterator.hasMoreElements() ) + { + try + { + static_cast<css::frame::XFrameActionListener*>(aIterator.next())->frameAction( aFrameActionEvent ); + } + catch( const css::uno::RuntimeException& ) + { + aIterator.remove(); + } + } +} + +/*-**************************************************************************************************** + @short helper to resize our component window + @descr A frame contains 2 windows - a container ~ and a component window. + This method resize inner component window to full size of outer container window. + This method is threadsafe AND can be called by our dispose method too! +*//*-*****************************************************************************************************/ +void XFrameImpl::implts_resizeComponentWindow() +{ + // usually the LayoutManager does the resizing + // in case there is no LayoutManager resizing has to be done here + if ( m_xLayoutManager.is() ) + return; + + css::uno::Reference< css::awt::XWindow > xComponentWindow( getComponentWindow() ); + if( !xComponentWindow.is() ) + return; + + css::uno::Reference< css::awt::XDevice > xDevice( getContainerWindow(), css::uno::UNO_QUERY ); + + // Convert relative size to output size. + css::awt::Rectangle aRectangle = getContainerWindow()->getPosSize(); + css::awt::DeviceInfo aInfo = xDevice->getInfo(); + css::awt::Size aSize( aRectangle.Width - aInfo.LeftInset - aInfo.RightInset, + aRectangle.Height - aInfo.TopInset - aInfo.BottomInset ); + + // Resize our component window. + xComponentWindow->setPosSize( 0, 0, aSize.Width, aSize.Height, css::awt::PosSize::POSSIZE ); +} + +/*-**************************************************************************************************** + @short helper to set icon on our container window (if it is a system window!) + @descr We use our internal set controller (if it exist) to specify which factory he represented. + This information can be used to find right icon. But our controller can say it us directly + too ... we should ask his optional property set first ... + + @seealso method Window::SetIcon() + @onerror We do nothing. +*//*-*****************************************************************************************************/ +void XFrameImpl::implts_setIconOnWindow() +{ + checkDisposed(); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + // Make snapshot of necessary members and release lock. + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow; + css::uno::Reference< css::frame::XController > xController = m_xController; + aReadLock.clear(); + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + + if( !(xContainerWindow.is() && xController.is()) ) + return; + + + // a) set default value to an invalid one. So we can start further searches for right icon id, if + // first steps failed! + // We must reset it to any fallback value - if no search step returns a valid result. + sal_Int32 nIcon = -1; + + // b) try to find information on controller propertyset directly + // Don't forget to catch possible exceptions - because these property is an optional one! + css::uno::Reference< css::beans::XPropertySet > xSet( xController, css::uno::UNO_QUERY ); + if( xSet.is() ) + { + try + { + css::uno::Reference< css::beans::XPropertySetInfo > const xPSI( xSet->getPropertySetInfo(), + css::uno::UNO_SET_THROW ); + if ( xPSI->hasPropertyByName( "IconId" ) ) + xSet->getPropertyValue( "IconId" ) >>= nIcon; + } + catch( css::uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + } + + // c) if b) failed... analyze argument list of currently loaded document inside the frame to find the filter. + // He can be used to detect right factory - and these can be used to match factory to icon... + if( nIcon == -1 ) + { + css::uno::Reference< css::frame::XModel > xModel = xController->getModel(); + if( xModel.is() ) + { + SvtModuleOptions::EFactory eFactory = SvtModuleOptions::ClassifyFactoryByModel(xModel); + if (eFactory != SvtModuleOptions::EFactory::UNKNOWN_FACTORY) + nIcon = SvtModuleOptions().GetFactoryIcon( eFactory ); + } + } + + // d) if all steps failed - use fallback! + if( nIcon == -1 ) + { + nIcon = 0; + } + + // e) set icon on container window now + // Don't forget SolarMutex! We use vcl directly :-( + // Check window pointer for right WorkWindow class too!!! + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + { + SolarMutexGuard aSolarGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xContainerWindow ); + if( + ( pWindow != nullptr ) && + ( pWindow->GetType() == WindowType::WORKWINDOW ) + ) + { + WorkWindow* pWorkWindow = static_cast<WorkWindow*>(pWindow.get()); + pWorkWindow->SetIcon( static_cast<sal_uInt16>(nIcon) ); + } + } + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ +} + +/*-************************************************************************************************************ + @short helper to start/stop listening for window events on container window + @descr If we get a new container window, we must set it on internal member ... + and stop listening at old one ... and start listening on new one! + But sometimes (in dispose() call!) it's necessary to stop listening without starting + on new connections. So we split this functionality to make it easier at use. + + @seealso method initialize() + @seealso method dispose() + @onerror We do nothing! + @threadsafe yes +*//*-*************************************************************************************************************/ +void XFrameImpl::implts_startWindowListening() +{ + checkDisposed(); + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + // Make snapshot of necessary member! + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow; + css::uno::Reference< css::datatransfer::dnd::XDropTargetListener > xDragDropListener = m_xDropTargetListener; + css::uno::Reference< css::awt::XWindowListener > xWindowListener(this); + css::uno::Reference< css::awt::XFocusListener > xFocusListener(this); + css::uno::Reference< css::awt::XTopWindowListener > xTopWindowListener(this); + aReadLock.clear(); + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + + if( !xContainerWindow.is() ) + return; + + xContainerWindow->addWindowListener( xWindowListener); + xContainerWindow->addFocusListener ( xFocusListener ); + + css::uno::Reference< css::awt::XTopWindow > xTopWindow( xContainerWindow, css::uno::UNO_QUERY ); + if( xTopWindow.is() ) + { + xTopWindow->addTopWindowListener( xTopWindowListener ); + + css::uno::Reference< css::awt::XToolkit2 > xToolkit = css::awt::Toolkit::create( m_xContext ); + css::uno::Reference< css::datatransfer::dnd::XDropTarget > xDropTarget = xToolkit->getDropTarget( xContainerWindow ); + if( xDropTarget.is() ) + { + xDropTarget->addDropTargetListener( xDragDropListener ); + xDropTarget->setActive( true ); + } + } +} + +void XFrameImpl::implts_stopWindowListening() +{ + // Sometimes used by dispose() + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + // Make snapshot of necessary member! + SolarMutexClearableGuard aReadLock; + css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow; + css::uno::Reference< css::datatransfer::dnd::XDropTargetListener > xDragDropListener = m_xDropTargetListener; + css::uno::Reference< css::awt::XWindowListener > xWindowListener(this); + css::uno::Reference< css::awt::XFocusListener > xFocusListener(this); + css::uno::Reference< css::awt::XTopWindowListener > xTopWindowListener(this); + aReadLock.clear(); + /* UNSAFE AREA --------------------------------------------------------------------------------------------- */ + + if( !xContainerWindow.is() ) + return; + + xContainerWindow->removeWindowListener( xWindowListener); + xContainerWindow->removeFocusListener ( xFocusListener ); + + css::uno::Reference< css::awt::XTopWindow > xTopWindow( xContainerWindow, css::uno::UNO_QUERY ); + if( !xTopWindow.is() ) + return; + + xTopWindow->removeTopWindowListener( xTopWindowListener ); + + css::uno::Reference< css::awt::XToolkit2 > xToolkit = css::awt::Toolkit::create( m_xContext ); + css::uno::Reference< css::datatransfer::dnd::XDropTarget > xDropTarget = + xToolkit->getDropTarget( xContainerWindow ); + if( xDropTarget.is() ) + { + xDropTarget->removeDropTargetListener( xDragDropListener ); + xDropTarget->setActive( false ); + } +} + +/*-**************************************************************************************************** + @short helper to force broken close() request again + @descr If we self disagree with a close() request, and detect that all external locks are gone ... + then we must try to close this frame again. + + @seealso XCloseable::close() + @seealso XFrameImpl::close() + @seealso XFrameImpl::removeActionLock() + @seealso XFrameImpl::resetActionLock() + @seealso m_bSelfClose + @seealso m_nExternalLockCount + + @threadsafe yes +*//*-*****************************************************************************************************/ +void XFrameImpl::implts_checkSuicide() +{ + /* SAFE */ + SolarMutexClearableGuard aReadLock; + // in case of lock==0 and safed state of previous close() request m_bSelfClose + // we must force close() again. Because we had disagreed with that before. + bool bSuicide = (m_nExternalLockCount==0 && m_bSelfClose); + m_bSelfClose = false; + aReadLock.clear(); + /* } SAFE */ + // force close and deliver ownership to source of possible thrown veto exception + // Attention: Because this method is not designed to throw such exception we must suppress + // it for outside code! + try + { + if (bSuicide) + close(true); + } + catch(const css::util::CloseVetoException&) + {} + catch(const css::lang::DisposedException&) + {} +} + +/** little helper to enable/disable the menu closer at the menubar of the given frame. + + @param xFrame + we use its layout manager to set/reset a special callback. + Its existence regulate visibility of this closer item. + + @param bState + <TRUE/> enable; <FALSE/> disable this state + */ + +void XFrameImpl::impl_setCloser( /*IN*/ const css::uno::Reference< css::frame::XFrame2 >& xFrame , + /*IN*/ bool bState ) +{ + // Note: If start module is not installed - no closer has to be shown! + if (!SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::STARTMODULE)) + return; + + try + { + css::uno::Reference< css::beans::XPropertySet > xFrameProps(xFrame, css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::frame::XLayoutManager > xLayoutManager; + xFrameProps->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager; + css::uno::Reference< css::beans::XPropertySet > xLayoutProps(xLayoutManager, css::uno::UNO_QUERY_THROW); + xLayoutProps->setPropertyValue(LAYOUTMANAGER_PROPNAME_MENUBARCLOSER, css::uno::Any(bState)); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + {} +} + +/** it checks, which of the top level task frames must have the special menu closer for + switching to the backing window mode. + + It analyze the current list of visible top level frames. Only the last real document + frame can have this symbol. Not the help frame nor the backing task itself. + Here we do anything related to this closer. We remove it from the old frame and set it + for the new one. + */ + +void XFrameImpl::impl_checkMenuCloser() +{ + /* SAFE { */ + SolarMutexClearableGuard aReadLock; + + // only top frames, which are part of our desktop hierarchy, can + // do so! By the way - we need the desktop instance to have access + // to all other top level frames too. + css::uno::Reference< css::frame::XDesktop > xDesktop (m_xParent, css::uno::UNO_QUERY); + css::uno::Reference< css::frame::XFramesSupplier > xTaskSupplier(xDesktop , css::uno::UNO_QUERY); + if ( !xDesktop.is() || !xTaskSupplier.is() ) + return; + + aReadLock.clear(); + /* } SAFE */ + + // analyze the list of current open tasks + // Suppress search for other views to the same model ... + // It's not needed here and can be very expensive. + FrameListAnalyzer aAnalyzer( + xTaskSupplier, + this, + FrameAnalyzerFlags::Hidden | FrameAnalyzerFlags::Help | FrameAnalyzerFlags::BackingComponent); + + // specify the new frame, which must have this special state... + css::uno::Reference< css::frame::XFrame2 > xNewCloserFrame; + + // a) + // If there exist at least one other frame - there are two frames currently open. + // But we can enable this closer only, if one of these two tasks includes the help module. + // The "other frame" couldn't be the help. Because then it wouldn't be part of this "other list". + // In such case it will be separated to the reference aAnalyzer.m_xHelp! + // But we must check, if we include ourself the help... + // Check aAnalyzer.m_bReferenceIsHelp! + if ( + (aAnalyzer.m_lOtherVisibleFrames.size()==1) && + ( + (aAnalyzer.m_bReferenceIsHelp ) || + (aAnalyzer.m_bReferenceIsHidden) + ) + ) + { + // others[0] can't be the backing component! + // Because it's set at the special member aAnalyzer.m_xBackingComponent ... :-) + xNewCloserFrame.set( aAnalyzer.m_lOtherVisibleFrames[0], css::uno::UNO_QUERY_THROW ); + } + + // b) + // There is no other frame... means no other document frame. The help module + // will be handled separately and must(!) be ignored here... excepting if we include ourself the help. + else if ( + (aAnalyzer.m_lOtherVisibleFrames.empty()) && + (!aAnalyzer.m_bReferenceIsHelp) && + (!aAnalyzer.m_bReferenceIsHidden) && + (!aAnalyzer.m_bReferenceIsBacking) + ) + { + xNewCloserFrame = this; + } + + // Look for necessary actions ... + // Only if the closer state must be moved from one frame to another one + // or must be enabled/disabled at all. + SolarMutexGuard aGuard; + // Holds the only frame, which must show the special closer menu item (can be NULL!) + static css::uno::WeakReference< css::frame::XFrame2 > s_xCloserFrame; + css::uno::Reference< css::frame::XFrame2 > xCloserFrame (s_xCloserFrame.get(), css::uno::UNO_QUERY); + if (xCloserFrame!=xNewCloserFrame) + { + if (xCloserFrame.is()) + impl_setCloser(xCloserFrame, false); + if (xNewCloserFrame.is()) + impl_setCloser(xNewCloserFrame, true); + s_xCloserFrame = xNewCloserFrame; + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_Frame_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + rtl::Reference<XFrameImpl> inst = new XFrameImpl(context); + css::uno::XInterface *acquired_inst = cppu::acquire(inst.get()); + + inst->initListeners(); + + return acquired_inst; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/mediatypedetectionhelper.cxx b/framework/source/services/mediatypedetectionhelper.cxx new file mode 100644 index 000000000..894f95740 --- /dev/null +++ b/framework/source/services/mediatypedetectionhelper.cxx @@ -0,0 +1,92 @@ +/* -*- 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 <services/mediatypedetectionhelper.hxx> +#include <svl/inettype.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> + +namespace framework +{ + +using namespace ::com::sun::star; + +// constructor + +MediaTypeDetectionHelper::MediaTypeDetectionHelper() +{ +} + +// destructor + +MediaTypeDetectionHelper::~MediaTypeDetectionHelper() +{ +} + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL MediaTypeDetectionHelper::getImplementationName() +{ + return "com.sun.star.comp.framework.MediaTypeDetectionHelper"; +} + +sal_Bool SAL_CALL MediaTypeDetectionHelper::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL MediaTypeDetectionHelper::getSupportedServiceNames() +{ + return { "com.sun.star.frame.MediaTypeDetectionHelper" }; +} + + +// XStringMapping + +sal_Bool SAL_CALL MediaTypeDetectionHelper::mapStrings(uno::Sequence< OUString >& rSeq) +{ + bool bModified = false; + auto rSeqRange = asNonConstRange(rSeq); + for( sal_Int32 i = rSeq.getLength(); i--; ) + { + + OUString& rUrl = rSeqRange[i]; + INetContentType eType = INetContentTypes::GetContentTypeFromURL( rUrl ); + + OUString aType( INetContentTypes::GetContentType( eType ) ); + if (!aType.isEmpty()) + { + rUrl = aType; + bModified = true; + } + } + return bModified; +} + +} // namespace framework + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_MediaTypeDetectionHelper_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::MediaTypeDetectionHelper()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/modulemanager.cxx b/framework/source/services/modulemanager.cxx new file mode 100644 index 000000000..475084c4c --- /dev/null +++ b/framework/source/services/modulemanager.cxx @@ -0,0 +1,350 @@ +/* -*- 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 <com/sun/star/frame/UnknownModuleException.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/XModule.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/frame/XModuleManager2.hpp> +#include <com/sun/star/container/XNameReplace.hpp> +#include <com/sun/star/container/XContainerQuery.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/configurationhelper.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/enumhelper.hxx> +#include <utility> + +namespace { + +class ModuleManager: + public cppu::WeakImplHelper< + css::lang::XServiceInfo, + css::frame::XModuleManager2, + css::container::XContainerQuery > +{ +private: + + /** the global uno service manager. + Must be used to create own needed services. + */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + /** points to the underlying configuration. + This ModuleManager does not cache - it calls directly the + configuration API! + */ + css::uno::Reference< css::container::XNameAccess > m_xCFG; + +public: + + explicit ModuleManager(css::uno::Reference< css::uno::XComponentContext > xContext); + + ModuleManager(const ModuleManager&) = delete; + ModuleManager& operator=(const ModuleManager&) = delete; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + + virtual sal_Bool SAL_CALL supportsService( + OUString const & ServiceName) override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XModuleManager + virtual OUString SAL_CALL identify(const css::uno::Reference< css::uno::XInterface >& xModule) override; + + // XNameReplace + virtual void SAL_CALL replaceByName(const OUString& sName , + const css::uno::Any& aValue) override; + + // XNameAccess + virtual css::uno::Any SAL_CALL getByName(const OUString& sName) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override; + + virtual sal_Bool SAL_CALL hasByName(const OUString& sName) override; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType() override; + + virtual sal_Bool SAL_CALL hasElements() override; + + // XContainerQuery + virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createSubSetEnumerationByQuery(const OUString& sQuery) override; + + virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createSubSetEnumerationByProperties(const css::uno::Sequence< css::beans::NamedValue >& lProperties) override; + +private: + + /** @short makes the real identification of the module. + + @descr It checks for the optional but preferred interface + XModule first. If this module does not exists at the + given component it tries to use XServiceInfo instead. + + Note: This method try to locate a suitable module name. + Nothing else. Selecting the right component and throwing suitable + exceptions must be done outside. + + @see identify() + + @param xComponent + the module for identification. + + @return The identifier of the given module. + Can be empty if given component is not a real module ! + + @threadsafe + */ + OUString implts_identify(const css::uno::Reference< css::uno::XInterface >& xComponent); +}; + +ModuleManager::ModuleManager(css::uno::Reference< css::uno::XComponentContext > xContext) + : m_xContext(std::move(xContext)) +{ + m_xCFG.set( comphelper::ConfigurationHelper::openConfig( + m_xContext, "/org.openoffice.Setup/Office/Factories", + comphelper::EConfigurationModes::ReadOnly ), + css::uno::UNO_QUERY_THROW ); +} + +OUString ModuleManager::getImplementationName() +{ + return "com.sun.star.comp.framework.ModuleManager"; +} + +sal_Bool ModuleManager::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence< OUString > ModuleManager::getSupportedServiceNames() +{ + return { "com.sun.star.frame.ModuleManager" }; +} + +OUString SAL_CALL ModuleManager::identify(const css::uno::Reference< css::uno::XInterface >& xModule) +{ + // valid parameter? + css::uno::Reference< css::frame::XFrame > xFrame (xModule, css::uno::UNO_QUERY); + css::uno::Reference< css::awt::XWindow > xWindow (xModule, css::uno::UNO_QUERY); + css::uno::Reference< css::frame::XController > xController(xModule, css::uno::UNO_QUERY); + css::uno::Reference< css::frame::XModel > xModel (xModule, css::uno::UNO_QUERY); + + if ( + (!xFrame.is() ) && + (!xWindow.is() ) && + (!xController.is()) && + (!xModel.is() ) + ) + { + throw css::lang::IllegalArgumentException( + "Given module is not a frame nor a window, controller or model.", + static_cast< ::cppu::OWeakObject* >(this), + 1); + } + + if (xFrame.is()) + { + xController = xFrame->getController(); + xWindow = xFrame->getComponentWindow(); + } + if (xController.is()) + xModel = xController->getModel(); + + // modules are implemented by the deepest component in hierarchy ... + // Means: model -> controller -> window + // No fallbacks to higher components are allowed ! + // Note : A frame provides access to module components only ... but it's not a module by himself. + + OUString sModule; + if (xModel.is()) + sModule = implts_identify(xModel); + else if (xController.is()) + sModule = implts_identify(xController); + else if (xWindow.is()) + sModule = implts_identify(xWindow); + + if (sModule.isEmpty()) + throw css::frame::UnknownModuleException( + "Can not find suitable module for the given component.", + static_cast< ::cppu::OWeakObject* >(this)); + + return sModule; +} + +void SAL_CALL ModuleManager::replaceByName(const OUString& sName , + const css::uno::Any& aValue) +{ + ::comphelper::SequenceAsHashMap lProps(aValue); + if (lProps.empty() ) + { + throw css::lang::IllegalArgumentException( + "No properties given to replace part of module.", + static_cast< cppu::OWeakObject * >(this), + 2); + } + + // get access to the element + // Note: Don't use impl_getConfig() method here. Because it creates a readonly access only, further + // it cache it as a member of this module manager instance. If we change some props there ... but don't + // flush changes (because an error occurred) we will read them later. If we use a different config access + // we can close it without a flush... and our read data won't be affected .-) + css::uno::Reference< css::uno::XInterface > xCfg = ::comphelper::ConfigurationHelper::openConfig( + m_xContext, + "/org.openoffice.Setup/Office/Factories", + ::comphelper::EConfigurationModes::Standard); + css::uno::Reference< css::container::XNameAccess > xModules (xCfg, css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::container::XNameReplace > xModule ; + + xModules->getByName(sName) >>= xModule; + if (!xModule.is()) + { + throw css::uno::RuntimeException( + "Was not able to get write access to the requested module entry inside configuration.", + static_cast< cppu::OWeakObject * >(this)); + } + + for (auto const& prop : lProps) + { + // let "NoSuchElementException" out ! We support the same API ... + // and without a flush() at the end all changed data before will be ignored ! + xModule->replaceByName(prop.first.maString, prop.second); + } + + ::comphelper::ConfigurationHelper::flush(xCfg); +} + +css::uno::Any SAL_CALL ModuleManager::getByName(const OUString& sName) +{ + // get access to the element + css::uno::Reference< css::container::XNameAccess > xModule; + m_xCFG->getByName(sName) >>= xModule; + if (!xModule.is()) + { + throw css::uno::RuntimeException( + "Was not able to get write access to the requested module entry inside configuration.", + static_cast< cppu::OWeakObject * >(this)); + } + + // convert it to seq< PropertyValue > + const css::uno::Sequence< OUString > lPropNames = xModule->getElementNames(); + comphelper::SequenceAsHashMap lProps; + + lProps[OUString("ooSetupFactoryModuleIdentifier")] <<= sName; + for (const OUString& sPropName : lPropNames) + { + lProps[sPropName] = xModule->getByName(sPropName); + } + + return css::uno::Any(lProps.getAsConstPropertyValueList()); +} + +css::uno::Sequence< OUString > SAL_CALL ModuleManager::getElementNames() +{ + return m_xCFG->getElementNames(); +} + +sal_Bool SAL_CALL ModuleManager::hasByName(const OUString& sName) +{ + return m_xCFG->hasByName(sName); +} + +css::uno::Type SAL_CALL ModuleManager::getElementType() +{ + return cppu::UnoType<css::uno::Sequence< css::beans::PropertyValue >>::get(); +} + +sal_Bool SAL_CALL ModuleManager::hasElements() +{ + return m_xCFG->hasElements(); +} + +css::uno::Reference< css::container::XEnumeration > SAL_CALL ModuleManager::createSubSetEnumerationByQuery(const OUString&) +{ + return css::uno::Reference< css::container::XEnumeration >(); +} + +css::uno::Reference< css::container::XEnumeration > SAL_CALL ModuleManager::createSubSetEnumerationByProperties(const css::uno::Sequence< css::beans::NamedValue >& lProperties) +{ + ::comphelper::SequenceAsHashMap lSearchProps(lProperties); + const css::uno::Sequence< OUString > lModules = getElementNames(); + ::std::vector< css::uno::Any > lResult; + + for (const OUString& rModuleName : lModules) + { + try + { + ::comphelper::SequenceAsHashMap lModuleProps = getByName(rModuleName); + if (lModuleProps.match(lSearchProps)) + lResult.push_back(css::uno::Any(lModuleProps.getAsConstPropertyValueList())); + } + catch(const css::uno::Exception&) + { + } + } + + return new ::comphelper::OAnyEnumeration(comphelper::containerToSequence(lResult)); +} + +OUString ModuleManager::implts_identify(const css::uno::Reference< css::uno::XInterface >& xComponent) +{ + // Search for an optional (!) interface XModule first. + // It's used to overrule an existing service name. Used e.g. by our database form designer + // which uses a writer module internally. + css::uno::Reference< css::frame::XModule > xModule(xComponent, css::uno::UNO_QUERY); + if (xModule.is()) + return xModule->getIdentifier(); + + // detect modules in a generic way... + // comparing service names with configured entries... + css::uno::Reference< css::lang::XServiceInfo > xInfo(xComponent, css::uno::UNO_QUERY); + if (!xInfo.is()) + return OUString(); + + const css::uno::Sequence< OUString > lKnownModules = getElementNames(); + for (const OUString& rName : lKnownModules) + { + if (xInfo->supportsService(rName)) + return rName; + } + + return OUString(); +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ModuleManager_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new ModuleManager(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/pathsettings.cxx b/framework/source/services/pathsettings.cxx new file mode 100644 index 000000000..9457ad862 --- /dev/null +++ b/framework/source/services/pathsettings.cxx @@ -0,0 +1,1422 @@ +/* -*- 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 <string_view> +#include <utility> +#include <unordered_map> + +#include <properties.h> +#include <helper/mischelper.hxx> + +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/beans/XProperty.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/XChangesNotifier.hpp> +#include <com/sun/star/util/PathSubstitution.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/util/XStringSubstitution.hpp> +#include <com/sun/star/util/XChangesListener.hpp> +#include <com/sun/star/util/XPathSettings.hpp> + +#include <tools/urlobj.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ref.hxx> +#include <sal/log.hxx> + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/propshlp.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/configurationhelper.hxx> +#include <unotools/configpaths.hxx> +#include <o3tl/string_view.hxx> + +using namespace framework; + +constexpr OUStringLiteral CFGPROP_USERPATHS = u"UserPaths"; +constexpr OUStringLiteral CFGPROP_WRITEPATH = u"WritePath"; + +/* + 0 : old style "Template" string using ";" as separator + 1 : internal paths "Template_internal" string list + 2 : user paths "Template_user" string list + 3 : write path "Template_write" string + */ + +constexpr OUStringLiteral POSTFIX_INTERNAL_PATHS = u"_internal"; +constexpr OUStringLiteral POSTFIX_USER_PATHS = u"_user"; +constexpr OUStringLiteral POSTFIX_WRITE_PATH = u"_writable"; + +namespace { + +const sal_Int32 IDGROUP_OLDSTYLE = 0; +const sal_Int32 IDGROUP_INTERNAL_PATHS = 1; +const sal_Int32 IDGROUP_USER_PATHS = 2; +const sal_Int32 IDGROUP_WRITE_PATH = 3; + +const sal_Int32 IDGROUP_COUNT = 4; + +sal_Int32 impl_getPropGroup(sal_Int32 nID) +{ + return (nID % IDGROUP_COUNT); +} + +/* enable it if you wish to migrate old user settings (using the old cfg schema) on demand... + disable it in case only the new schema must be used. + */ + +typedef ::cppu::WeakComponentImplHelper< + css::lang::XServiceInfo, + css::util::XChangesListener, // => XEventListener + css::util::XPathSettings> // => XPropertySet + PathSettings_BASE; + +class PathSettings : private cppu::BaseMutex + , public PathSettings_BASE + , public ::cppu::OPropertySetHelper +{ + struct PathInfo + { + public: + + PathInfo() + : bIsSinglePath (false) + , bIsReadonly (false) + {} + + /// an internal name describing this path + OUString sPathName; + + /// contains all paths, which are used internally - but are not visible for the user. + std::vector<OUString> lInternalPaths; + + /// contains all paths configured by the user + std::vector<OUString> lUserPaths; + + /// this special path is used to generate feature depending content there + OUString sWritePath; + + /// indicates real single paths, which uses WritePath property only + bool bIsSinglePath; + + /// simple handling of finalized/mandatory states ... => we know one state READONLY only .-) + bool bIsReadonly; + }; + + typedef std::unordered_map<OUString, PathSettings::PathInfo> PathHash; + + enum EChangeOp + { + E_UNDEFINED, + E_ADDED, + E_CHANGED, + E_REMOVED + }; + +private: + + /** reference to factory, which has create this instance. */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + /** list of all path variables and her corresponding values. */ + PathSettings::PathHash m_lPaths; + + /** describes all properties available on our interface. + Will be generated on demand based on our path list m_lPaths. */ + css::uno::Sequence< css::beans::Property > m_lPropDesc; + + /** helper needed to (re-)substitute all internal save path values. */ + css::uno::Reference< css::util::XStringSubstitution > m_xSubstitution; + + /** provides access to the old configuration schema (which will be migrated on demand). */ + css::uno::Reference< css::container::XNameAccess > m_xCfgOld; + + /** provides access to the new configuration schema. */ + css::uno::Reference< css::container::XNameAccess > m_xCfgNew; + + /** helper to listen for configuration changes without ownership cycle problems */ + css::uno::Reference< css::util::XChangesListener > m_xCfgNewListener; + + std::unique_ptr<::cppu::OPropertyArrayHelper> m_pPropHelp; + +public: + + /** initialize a new instance of this class. + Attention: It's necessary for right function of this class, that the order of base + classes is the right one. Because we transfer information from one base to another + during this ctor runs! */ + explicit PathSettings(css::uno::Reference< css::uno::XComponentContext > xContext); + + /** free all used resources ... if it was not already done. */ + virtual ~PathSettings() override; + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.PathSettings"; + } + + 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.util.PathSettings"}; + } + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& type) override; + virtual void SAL_CALL acquire() noexcept override + { OWeakObject::acquire(); } + virtual void SAL_CALL release() noexcept override + { OWeakObject::release(); } + + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + + // css::util::XChangesListener + virtual void SAL_CALL changesOccurred(const css::util::ChangesEvent& aEvent) override; + + // css::lang::XEventListener + virtual void SAL_CALL disposing(const css::lang::EventObject& aSource) override; + + /** + * XPathSettings attribute methods + */ + virtual OUString SAL_CALL getAddin() override + { return getStringProperty("Addin"); } + virtual void SAL_CALL setAddin(const OUString& p1) override + { setStringProperty("Addin", p1); } + virtual OUString SAL_CALL getAutoCorrect() override + { return getStringProperty("AutoCorrect"); } + virtual void SAL_CALL setAutoCorrect(const OUString& p1) override + { setStringProperty("AutoCorrect", p1); } + virtual OUString SAL_CALL getAutoText() override + { return getStringProperty("AutoText"); } + virtual void SAL_CALL setAutoText(const OUString& p1) override + { setStringProperty("AutoText", p1); } + virtual OUString SAL_CALL getBackup() override + { return getStringProperty("Backup"); } + virtual void SAL_CALL setBackup(const OUString& p1) override + { setStringProperty("Backup", p1); } + virtual OUString SAL_CALL getBasic() override + { return getStringProperty("Basic"); } + virtual void SAL_CALL setBasic(const OUString& p1) override + { setStringProperty("Basic", p1); } + virtual OUString SAL_CALL getBitmap() override + { return getStringProperty("Bitmap"); } + virtual void SAL_CALL setBitmap(const OUString& p1) override + { setStringProperty("Bitmap", p1); } + virtual OUString SAL_CALL getConfig() override + { return getStringProperty("Config"); } + virtual void SAL_CALL setConfig(const OUString& p1) override + { setStringProperty("Config", p1); } + virtual OUString SAL_CALL getDictionary() override + { return getStringProperty("Dictionary"); } + virtual void SAL_CALL setDictionary(const OUString& p1) override + { setStringProperty("Dictionary", p1); } + virtual OUString SAL_CALL getFavorite() override + { return getStringProperty("Favorite"); } + virtual void SAL_CALL setFavorite(const OUString& p1) override + { setStringProperty("Favorite", p1); } + virtual OUString SAL_CALL getFilter() override + { return getStringProperty("Filter"); } + virtual void SAL_CALL setFilter(const OUString& p1) override + { setStringProperty("Filter", p1); } + virtual OUString SAL_CALL getGallery() override + { return getStringProperty("Gallery"); } + virtual void SAL_CALL setGallery(const OUString& p1) override + { setStringProperty("Gallery", p1); } + virtual OUString SAL_CALL getGraphic() override + { return getStringProperty("Graphic"); } + virtual void SAL_CALL setGraphic(const OUString& p1) override + { setStringProperty("Graphic", p1); } + virtual OUString SAL_CALL getHelp() override + { return getStringProperty("Help"); } + virtual void SAL_CALL setHelp(const OUString& p1) override + { setStringProperty("Help", p1); } + virtual OUString SAL_CALL getLinguistic() override + { return getStringProperty("Linguistic"); } + virtual void SAL_CALL setLinguistic(const OUString& p1) override + { setStringProperty("Linguistic", p1); } + virtual OUString SAL_CALL getModule() override + { return getStringProperty("Module"); } + virtual void SAL_CALL setModule(const OUString& p1) override + { setStringProperty("Module", p1); } + virtual OUString SAL_CALL getPalette() override + { return getStringProperty("Palette"); } + virtual void SAL_CALL setPalette(const OUString& p1) override + { setStringProperty("Palette", p1); } + virtual OUString SAL_CALL getPlugin() override + { return getStringProperty("Plugin"); } + virtual void SAL_CALL setPlugin(const OUString& p1) override + { setStringProperty("Plugin", p1); } + virtual OUString SAL_CALL getStorage() override + { return getStringProperty("Storage"); } + virtual void SAL_CALL setStorage(const OUString& p1) override + { setStringProperty("Storage", p1); } + virtual OUString SAL_CALL getTemp() override + { return getStringProperty("Temp"); } + virtual void SAL_CALL setTemp(const OUString& p1) override + { setStringProperty("Temp", p1); } + virtual OUString SAL_CALL getTemplate() override + { return getStringProperty("Template"); } + virtual void SAL_CALL setTemplate(const OUString& p1) override + { setStringProperty("Template", p1); } + virtual OUString SAL_CALL getUIConfig() override + { return getStringProperty("UIConfig"); } + virtual void SAL_CALL setUIConfig(const OUString& p1) override + { setStringProperty("UIConfig", p1); } + virtual OUString SAL_CALL getUserConfig() override + { return getStringProperty("UserConfig"); } + virtual void SAL_CALL setUserConfig(const OUString& p1) override + { setStringProperty("UserConfig", p1); } + virtual OUString SAL_CALL getUserDictionary() override + { return getStringProperty("UserDictionary"); } + virtual void SAL_CALL setUserDictionary(const OUString& p1) override + { setStringProperty("UserDictionary", p1); } + virtual OUString SAL_CALL getWork() override + { return getStringProperty("Work"); } + virtual void SAL_CALL setWork(const OUString& p1) override + { setStringProperty("Work", p1); } + virtual OUString SAL_CALL getBasePathShareLayer() override + { return getStringProperty("UIConfig"); } + virtual void SAL_CALL setBasePathShareLayer(const OUString& p1) override + { setStringProperty("UIConfig", p1); } + virtual OUString SAL_CALL getBasePathUserLayer() override + { return getStringProperty("UserConfig"); } + virtual void SAL_CALL setBasePathUserLayer(const OUString& p1) override + { setStringProperty("UserConfig", p1); } + + /** + * overrides to resolve inheritance ambiguity + */ + virtual void SAL_CALL setPropertyValue(const OUString& p1, const css::uno::Any& p2) override + { ::cppu::OPropertySetHelper::setPropertyValue(p1, p2); } + virtual css::uno::Any SAL_CALL getPropertyValue(const OUString& p1) override + { return ::cppu::OPropertySetHelper::getPropertyValue(p1); } + virtual void SAL_CALL addPropertyChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XPropertyChangeListener>& p2) override + { ::cppu::OPropertySetHelper::addPropertyChangeListener(p1, p2); } + virtual void SAL_CALL removePropertyChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XPropertyChangeListener>& p2) override + { ::cppu::OPropertySetHelper::removePropertyChangeListener(p1, p2); } + virtual void SAL_CALL addVetoableChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XVetoableChangeListener>& p2) override + { ::cppu::OPropertySetHelper::addVetoableChangeListener(p1, p2); } + virtual void SAL_CALL removeVetoableChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XVetoableChangeListener>& p2) override + { ::cppu::OPropertySetHelper::removeVetoableChangeListener(p1, p2); } + /** read all configured paths and create all needed internal structures. */ + void impl_readAll(); + +private: + virtual void SAL_CALL disposing() final override; + + /// @throws css::uno::RuntimeException + OUString getStringProperty(const OUString& p1); + + /// @throws css::uno::RuntimeException + void setStringProperty(const OUString& p1, const OUString& p2); + + /** read a path info using the old cfg schema. + This is needed for "migration on demand" reasons only. + Can be removed for next major release .-) */ + std::vector<OUString> impl_readOldFormat(const OUString& sPath); + + /** read a path info using the new cfg schema. */ + PathSettings::PathInfo impl_readNewFormat(const OUString& sPath); + + /** filter "real user defined paths" from the old configuration schema + and set it as UserPaths on the new schema. + Can be removed with new major release ... */ + + void impl_mergeOldUserPaths( PathSettings::PathInfo& rPath, + const std::vector<OUString>& lOld ); + + /** reload one path directly from the new configuration schema (because + it was updated by any external code) */ + PathSettings::EChangeOp impl_updatePath(const OUString& sPath , + bool bNotifyListener); + + /** replace all might existing placeholder variables inside the given path ... + or check if the given path value uses paths, which can be replaced with predefined + placeholder variables ... + */ + void impl_subst(std::vector<OUString>& lVals , + const css::uno::Reference< css::util::XStringSubstitution >& xSubst , + bool bReSubst); + + void impl_subst(PathSettings::PathInfo& aPath , + bool bReSubst); + + /** converts our new string list schema to the old ";" separated schema ... */ + OUString impl_convertPath2OldStyle(const PathSettings::PathInfo& rPath ) const; + std::vector<OUString> impl_convertOldStyle2Path(std::u16string_view sOldStylePath) const; + + /** remove still known paths from the given lList argument. + So real user defined paths can be extracted from the list of + fix internal paths ! + */ + void impl_purgeKnownPaths(PathSettings::PathInfo& rPath, + std::vector<OUString>& lList); + + /** rebuild the member m_lPropDesc using the path list m_lPaths. */ + void impl_rebuildPropertyDescriptor(); + + /** provides direct access to the list of path values + using its internal property id. + */ + css::uno::Any impl_getPathValue( sal_Int32 nID ) const; + void impl_setPathValue( sal_Int32 nID , + const css::uno::Any& aVal); + + /** check the given handle and return the corresponding PathInfo reference. + These reference can be used then directly to manipulate these path. */ + PathSettings::PathInfo* impl_getPathAccess (sal_Int32 nHandle); + const PathSettings::PathInfo* impl_getPathAccessConst(sal_Int32 nHandle) const; + + /** it checks, if the given path value seems to be a valid URL or system path. */ + bool impl_isValidPath(std::u16string_view sPath) const; + bool impl_isValidPath(const std::vector<OUString>& lPath) const; + + void impl_storePath(const PathSettings::PathInfo& aPath); + + css::uno::Sequence< sal_Int32 > impl_mapPathName2IDList(std::u16string_view sPath); + + void impl_notifyPropListener( std::u16string_view sPath , + const PathSettings::PathInfo* pPathOld, + const PathSettings::PathInfo* pPathNew); + + // OPropertySetHelper + virtual sal_Bool SAL_CALL convertFastPropertyValue( css::uno::Any& aConvertedValue, + css::uno::Any& aOldValue, + sal_Int32 nHandle, + const css::uno::Any& aValue ) override; + virtual void SAL_CALL setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, + const css::uno::Any& aValue ) override; + virtual void SAL_CALL getFastPropertyValue( css::uno::Any& aValue, + sal_Int32 nHandle ) const override; + // Avoid: + // warning: 'virtual css::uno::Any cppu::OPropertySetHelper::getFastPropertyValue(sal_Int32)' was hidden [-Woverloaded-virtual] + // warning: by ‘virtual void {anonymous}::PathSettings::getFastPropertyValue(css::uno::Any&, sal_Int32) const’ [-Woverloaded-virtual] + using cppu::OPropertySetHelper::getFastPropertyValue; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + + /** factory methods to guarantee right (but on demand) initialized members ... */ + css::uno::Reference< css::util::XStringSubstitution > fa_getSubstitution(); + css::uno::Reference< css::container::XNameAccess > fa_getCfgOld(); + css::uno::Reference< css::container::XNameAccess > fa_getCfgNew(); +}; + +PathSettings::PathSettings( css::uno::Reference< css::uno::XComponentContext > xContext ) + : PathSettings_BASE(m_aMutex) + , ::cppu::OPropertySetHelper(cppu::WeakComponentImplHelperBase::rBHelper) + , m_xContext (std::move(xContext)) +{ +} + +PathSettings::~PathSettings() +{ + disposing(); +} + +void SAL_CALL PathSettings::disposing() +{ + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + css::uno::Reference< css::util::XChangesNotifier > + xBroadcaster(m_xCfgNew, css::uno::UNO_QUERY); + if (xBroadcaster.is()) + xBroadcaster->removeChangesListener(m_xCfgNewListener); + + m_xSubstitution.clear(); + m_xCfgOld.clear(); + m_xCfgNew.clear(); + m_xCfgNewListener.clear(); + + m_pPropHelp.reset(); +} + +css::uno::Any SAL_CALL PathSettings::queryInterface( const css::uno::Type& _rType ) +{ + css::uno::Any aRet = PathSettings_BASE::queryInterface( _rType ); + if ( !aRet.hasValue() ) + aRet = ::cppu::OPropertySetHelper::queryInterface( _rType ); + return aRet; +} + +css::uno::Sequence< css::uno::Type > SAL_CALL PathSettings::getTypes( ) +{ + return comphelper::concatSequences( + PathSettings_BASE::getTypes(), + ::cppu::OPropertySetHelper::getTypes() + ); +} + +void SAL_CALL PathSettings::changesOccurred(const css::util::ChangesEvent& aEvent) +{ + sal_Int32 c = aEvent.Changes.getLength(); + sal_Int32 i = 0; + bool bUpdateDescriptor = false; + + for (i=0; i<c; ++i) + { + const css::util::ElementChange& aChange = aEvent.Changes[i]; + + OUString sChanged; + aChange.Accessor >>= sChanged; + + OUString sPath = ::utl::extractFirstFromConfigurationPath(sChanged); + if (!sPath.isEmpty()) + { + PathSettings::EChangeOp eOp = impl_updatePath(sPath, true); + if ( + (eOp == PathSettings::E_ADDED ) || + (eOp == PathSettings::E_REMOVED) + ) + bUpdateDescriptor = true; + } + } + + if (bUpdateDescriptor) + impl_rebuildPropertyDescriptor(); +} + +void SAL_CALL PathSettings::disposing(const css::lang::EventObject& aSource) +{ + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + if (aSource.Source == m_xCfgNew) + m_xCfgNew.clear(); +} + +OUString PathSettings::getStringProperty(const OUString& p1) +{ + css::uno::Any a = ::cppu::OPropertySetHelper::getPropertyValue(p1); + OUString s; + a >>= s; + return s; +} + +void PathSettings::setStringProperty(const OUString& p1, const OUString& p2) +{ + ::cppu::OPropertySetHelper::setPropertyValue(p1, css::uno::Any(p2)); +} + +void PathSettings::impl_readAll() +{ + try + { + // TODO think about me + css::uno::Reference< css::container::XNameAccess > xCfg = fa_getCfgNew(); + css::uno::Sequence< OUString > lPaths = xCfg->getElementNames(); + + sal_Int32 c = lPaths.getLength(); + for (sal_Int32 i = 0; i < c; ++i) + { + const OUString& sPath = lPaths[i]; + impl_updatePath(sPath, false); + } + } + catch(const css::uno::RuntimeException& ) + { + } + + impl_rebuildPropertyDescriptor(); +} + +// NO substitution here ! It's done outside ... +std::vector<OUString> PathSettings::impl_readOldFormat(const OUString& sPath) +{ + css::uno::Reference< css::container::XNameAccess > xCfg( fa_getCfgOld() ); + std::vector<OUString> aPathVal; + + if( xCfg->hasByName(sPath) ) + { + css::uno::Any aVal( xCfg->getByName(sPath) ); + + OUString sStringVal; + css::uno::Sequence< OUString > lStringListVal; + + if (aVal >>= sStringVal) + { + aPathVal.push_back(sStringVal); + } + else if (aVal >>= lStringListVal) + { + aPathVal = comphelper::sequenceToContainer<std::vector<OUString>>(lStringListVal); + } + } + + return aPathVal; +} + +// NO substitution here ! It's done outside ... +PathSettings::PathInfo PathSettings::impl_readNewFormat(const OUString& sPath) +{ + css::uno::Reference< css::container::XNameAccess > xCfg = fa_getCfgNew(); + + // get access to the "queried" path + css::uno::Reference< css::container::XNameAccess > xPath; + xCfg->getByName(sPath) >>= xPath; + + PathSettings::PathInfo aPathVal; + + // read internal path list + css::uno::Reference< css::container::XNameAccess > xIPath; + xPath->getByName("InternalPaths") >>= xIPath; + aPathVal.lInternalPaths = comphelper::sequenceToContainer<std::vector<OUString>>(xIPath->getElementNames()); + + // read user defined path list + css::uno::Sequence<OUString> vTmpUserPathsSeq; + xPath->getByName(CFGPROP_USERPATHS) >>= vTmpUserPathsSeq; + aPathVal.lUserPaths = comphelper::sequenceToContainer<std::vector<OUString>>(vTmpUserPathsSeq); + + // read the writeable path + xPath->getByName(CFGPROP_WRITEPATH) >>= aPathVal.sWritePath; + + // avoid duplicates, by removing the writeable path from + // the user defined path list if it happens to be there too + std::vector<OUString>::iterator aI = std::find(aPathVal.lUserPaths.begin(), aPathVal.lUserPaths.end(), aPathVal.sWritePath); + if (aI != aPathVal.lUserPaths.end()) + aPathVal.lUserPaths.erase(aI); + + // read state props + xPath->getByName("IsSinglePath") >>= aPathVal.bIsSinglePath; + + // analyze finalized/mandatory states + aPathVal.bIsReadonly = false; + css::uno::Reference< css::beans::XProperty > xInfo(xPath, css::uno::UNO_QUERY); + if (xInfo.is()) + { + css::beans::Property aInfo = xInfo->getAsProperty(); + bool bFinalized = ((aInfo.Attributes & css::beans::PropertyAttribute::READONLY ) == css::beans::PropertyAttribute::READONLY ); + + // Note: 'till we support finalized/mandatory on our API more in detail we handle + // all states simple as READONLY! But because all really needed paths are "mandatory" by default + // we have to handle "finalized" as the real "readonly" indicator. + aPathVal.bIsReadonly = bFinalized; + } + + return aPathVal; +} + +void PathSettings::impl_storePath(const PathSettings::PathInfo& aPath) +{ + css::uno::Reference< css::container::XNameAccess > xCfgNew = fa_getCfgNew(); + css::uno::Reference< css::container::XNameAccess > xCfgOld = fa_getCfgOld(); + + // try to replace path-parts with well known and supported variables. + // So an office can be moved easily to another location without losing + // its related paths. + PathInfo aResubstPath(aPath); + impl_subst(aResubstPath, true); + + // update new configuration + if (! aResubstPath.bIsSinglePath) + { + ::comphelper::ConfigurationHelper::writeRelativeKey(xCfgNew, + aResubstPath.sPathName, + CFGPROP_USERPATHS, + css::uno::Any(comphelper::containerToSequence(aResubstPath.lUserPaths))); + } + + ::comphelper::ConfigurationHelper::writeRelativeKey(xCfgNew, + aResubstPath.sPathName, + CFGPROP_WRITEPATH, + css::uno::Any(aResubstPath.sWritePath)); + + ::comphelper::ConfigurationHelper::flush(xCfgNew); + + // remove the whole path from the old configuration! + // Otherwise we can't make sure that the diff between new and old configuration + // on loading time really represents a user setting!!! + + // Check if the given path exists inside the old configuration. + // Because our new configuration knows more than the list of old paths ... ! + if (xCfgOld->hasByName(aResubstPath.sPathName)) + { + css::uno::Reference< css::beans::XPropertySet > xProps(xCfgOld, css::uno::UNO_QUERY_THROW); + xProps->setPropertyValue(aResubstPath.sPathName, css::uno::Any()); + ::comphelper::ConfigurationHelper::flush(xCfgOld); + } +} + +void PathSettings::impl_mergeOldUserPaths( PathSettings::PathInfo& rPath, + const std::vector<OUString>& lOld ) +{ + for (auto const& old : lOld) + { + if (rPath.bIsSinglePath) + { + SAL_WARN_IF(lOld.size()>1, "fwk", "PathSettings::impl_mergeOldUserPaths(): Single path has more than one path value inside old configuration (Common.xcu)!"); + if ( rPath.sWritePath != old ) + rPath.sWritePath = old; + } + else + { + if ( + ( std::find(rPath.lInternalPaths.begin(), rPath.lInternalPaths.end(), old) == rPath.lInternalPaths.end()) && + ( std::find(rPath.lUserPaths.begin(), rPath.lUserPaths.end(), old) == rPath.lUserPaths.end() ) && + ( rPath.sWritePath != old ) + ) + rPath.lUserPaths.push_back(old); + } + } +} + +PathSettings::EChangeOp PathSettings::impl_updatePath(const OUString& sPath , + bool bNotifyListener) +{ + // SAFE -> + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + PathSettings::PathInfo* pPathOld = nullptr; + PathSettings::PathInfo* pPathNew = nullptr; + PathSettings::EChangeOp eOp = PathSettings::E_UNDEFINED; + PathSettings::PathInfo aPath; + + try + { + aPath = impl_readNewFormat(sPath); + aPath.sPathName = sPath; + // replace all might existing variables with real values + // Do it before these old paths will be compared against the + // new path configuration. Otherwise some strings uses different variables ... but substitution + // will produce strings with same content (because some variables are redundant!) + impl_subst(aPath, false); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::container::NoSuchElementException&) + { eOp = PathSettings::E_REMOVED; } + catch(const css::uno::Exception&) + { throw; } + + try + { + // migration of old user defined values on demand + // can be disabled for a new major + std::vector<OUString> lOldVals = impl_readOldFormat(sPath); + // replace all might existing variables with real values + // Do it before these old paths will be compared against the + // new path configuration. Otherwise some strings uses different variables ... but substitution + // will produce strings with same content (because some variables are redundant!) + impl_subst(lOldVals, fa_getSubstitution(), false); + impl_mergeOldUserPaths(aPath, lOldVals); + } + catch(const css::uno::RuntimeException&) + { throw; } + // Normal(!) exceptions can be ignored! + // E.g. in case an addon installs a new path, which was not well known for an OOo 1.x installation + // we can't find a value for it inside the "old" configuration. So a NoSuchElementException + // will be normal .-) + catch(const css::uno::Exception&) + {} + + PathSettings::PathHash::iterator pPath = m_lPaths.find(sPath); + if (eOp == PathSettings::E_UNDEFINED) + { + if (pPath != m_lPaths.end()) + eOp = PathSettings::E_CHANGED; + else + eOp = PathSettings::E_ADDED; + } + + switch(eOp) + { + case PathSettings::E_ADDED : + { + if (bNotifyListener) + { + pPathOld = nullptr; + pPathNew = &aPath; + impl_notifyPropListener(sPath, pPathOld, pPathNew); + } + m_lPaths[sPath] = aPath; + } + break; + + case PathSettings::E_CHANGED : + { + if (bNotifyListener) + { + pPathOld = &(pPath->second); + pPathNew = &aPath; + impl_notifyPropListener(sPath, pPathOld, pPathNew); + } + m_lPaths[sPath] = aPath; + } + break; + + case PathSettings::E_REMOVED : + { + if (pPath != m_lPaths.end()) + { + if (bNotifyListener) + { + pPathOld = &(pPath->second); + pPathNew = nullptr; + impl_notifyPropListener(sPath, pPathOld, pPathNew); + } + m_lPaths.erase(pPath); + } + } + break; + + default: // to let compiler be happy + break; + } + + return eOp; +} + +css::uno::Sequence< sal_Int32 > PathSettings::impl_mapPathName2IDList(std::u16string_view sPath) +{ + OUString sInternalProp = OUString::Concat(sPath)+POSTFIX_INTERNAL_PATHS; + OUString sUserProp = OUString::Concat(sPath)+POSTFIX_USER_PATHS; + OUString sWriteProp = OUString::Concat(sPath)+POSTFIX_WRITE_PATH; + + // Attention: The default set of IDs is fix and must follow these schema. + // Otherwise the outside code ant work for new added properties. + // Why? + // The outside code must fire N events for every changed property. + // And the knowing about packaging of variables of the structure PathInfo + // follow these group IDs! But if such ID is not in the range of [0..IDGROUP_COUNT] + // the outside can't determine the right group ... and can not fire the right events .-) + + css::uno::Sequence<sal_Int32> lIDs{ IDGROUP_OLDSTYLE, IDGROUP_INTERNAL_PATHS, + IDGROUP_USER_PATHS, IDGROUP_WRITE_PATH }; + assert(lIDs.getLength() == IDGROUP_COUNT); + auto plIDs = lIDs.getArray(); + + sal_Int32 c = m_lPropDesc.getLength(); + sal_Int32 i = 0; + for (i=0; i<c; ++i) + { + const css::beans::Property& rProp = m_lPropDesc[i]; + + if (rProp.Name == sPath) + plIDs[IDGROUP_OLDSTYLE] = rProp.Handle; + else + if (rProp.Name == sInternalProp) + plIDs[IDGROUP_INTERNAL_PATHS] = rProp.Handle; + else + if (rProp.Name == sUserProp) + plIDs[IDGROUP_USER_PATHS] = rProp.Handle; + else + if (rProp.Name == sWriteProp) + plIDs[IDGROUP_WRITE_PATH] = rProp.Handle; + } + + return lIDs; +} + +void PathSettings::impl_notifyPropListener( std::u16string_view sPath, + const PathSettings::PathInfo* pPathOld, + const PathSettings::PathInfo* pPathNew) +{ + css::uno::Sequence< sal_Int32 > lHandles(1); + auto plHandles = lHandles.getArray(); + css::uno::Sequence< css::uno::Any > lOldVals(1); + auto plOldVals = lOldVals.getArray(); + css::uno::Sequence< css::uno::Any > lNewVals(1); + auto plNewVals = lNewVals.getArray(); + + css::uno::Sequence< sal_Int32 > lIDs = impl_mapPathName2IDList(sPath); + sal_Int32 c = lIDs.getLength(); + sal_Int32 i = 0; + sal_Int32 nMaxID = m_lPropDesc.getLength()-1; + for (i=0; i<c; ++i) + { + sal_Int32 nID = lIDs[i]; + + if ( + (nID < 0 ) || + (nID > nMaxID) + ) + continue; + + plHandles[0] = nID; + switch(impl_getPropGroup(nID)) + { + case IDGROUP_OLDSTYLE : + { + if (pPathOld) + { + OUString sVal = impl_convertPath2OldStyle(*pPathOld); + plOldVals[0] <<= sVal; + } + if (pPathNew) + { + OUString sVal = impl_convertPath2OldStyle(*pPathNew); + plNewVals[0] <<= sVal; + } + } + break; + + case IDGROUP_INTERNAL_PATHS : + { + if (pPathOld) + plOldVals[0] <<= comphelper::containerToSequence(pPathOld->lInternalPaths); + if (pPathNew) + plNewVals[0] <<= comphelper::containerToSequence(pPathNew->lInternalPaths); + } + break; + + case IDGROUP_USER_PATHS : + { + if (pPathOld) + plOldVals[0] <<= comphelper::containerToSequence(pPathOld->lUserPaths); + if (pPathNew) + plNewVals[0] <<= comphelper::containerToSequence(pPathNew->lUserPaths); + } + break; + + case IDGROUP_WRITE_PATH : + { + if (pPathOld) + plOldVals[0] <<= pPathOld->sWritePath; + if (pPathNew) + plNewVals[0] <<= pPathNew->sWritePath; + } + break; + } + + fire(plHandles, + plNewVals, + plOldVals, + 1, + false); + } +} + +void PathSettings::impl_subst(std::vector<OUString>& lVals , + const css::uno::Reference< css::util::XStringSubstitution >& xSubst , + bool bReSubst) +{ + for (auto & old : lVals) + { + OUString sNew; + if (bReSubst) + sNew = xSubst->reSubstituteVariables(old); + else + sNew = xSubst->substituteVariables(old, false); + + old = sNew; + } +} + +void PathSettings::impl_subst(PathSettings::PathInfo& aPath , + bool bReSubst) +{ + css::uno::Reference< css::util::XStringSubstitution > xSubst = fa_getSubstitution(); + + impl_subst(aPath.lInternalPaths, xSubst, bReSubst); + impl_subst(aPath.lUserPaths , xSubst, bReSubst); + if (bReSubst) + aPath.sWritePath = xSubst->reSubstituteVariables(aPath.sWritePath); + else + aPath.sWritePath = xSubst->substituteVariables(aPath.sWritePath, false); +} + +OUString PathSettings::impl_convertPath2OldStyle(const PathSettings::PathInfo& rPath) const +{ + OUStringBuffer sPathVal(256); + + for (auto const& internalPath : rPath.lInternalPaths) + { + if (sPathVal.getLength()) + sPathVal.append(";"); + sPathVal.append(internalPath); + } + for (auto const& userPath : rPath.lUserPaths) + { + if (sPathVal.getLength()) + sPathVal.append(";"); + sPathVal.append(userPath); + } + if (!rPath.sWritePath.isEmpty()) + { + if (sPathVal.getLength()) + sPathVal.append(";"); + sPathVal.append(rPath.sWritePath); + } + + return sPathVal.makeStringAndClear(); +} + +std::vector<OUString> PathSettings::impl_convertOldStyle2Path(std::u16string_view sOldStylePath) const +{ + std::vector<OUString> lList; + sal_Int32 nToken = 0; + do + { + OUString sToken( o3tl::getToken(sOldStylePath, 0, ';', nToken) ); + if (!sToken.isEmpty()) + lList.push_back(sToken); + } + while(nToken >= 0); + + return lList; +} + +void PathSettings::impl_purgeKnownPaths(PathSettings::PathInfo& rPath, + std::vector<OUString>& lList) +{ + // Erase items in the internal path list from lList. + // Also erase items in the internal path list from the user path list. + for (auto const& internalPath : rPath.lInternalPaths) + { + std::vector<OUString>::iterator pItem = std::find(lList.begin(), lList.end(), internalPath); + if (pItem != lList.end()) + lList.erase(pItem); + pItem = std::find(rPath.lUserPaths.begin(), rPath.lUserPaths.end(), internalPath); + if (pItem != rPath.lUserPaths.end()) + rPath.lUserPaths.erase(pItem); + } + + // Erase items not in lList from the user path list. + rPath.lUserPaths.erase(std::remove_if(rPath.lUserPaths.begin(), rPath.lUserPaths.end(), + [&lList](const OUString& rItem) { + return std::find(lList.begin(), lList.end(), rItem) == lList.end(); + }), + rPath.lUserPaths.end()); + + // Erase items in the user path list from lList. + for (auto const& userPath : rPath.lUserPaths) + { + std::vector<OUString>::iterator pItem = std::find(lList.begin(), lList.end(), userPath); + if (pItem != lList.end()) + lList.erase(pItem); + } + + // Erase the write path from lList + std::vector<OUString>::iterator pItem = std::find(lList.begin(), lList.end(), rPath.sWritePath); + if (pItem != lList.end()) + lList.erase(pItem); +} + +void PathSettings::impl_rebuildPropertyDescriptor() +{ + // SAFE -> + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + sal_Int32 c = static_cast<sal_Int32>(m_lPaths.size()); + sal_Int32 i = 0; + m_lPropDesc.realloc(c*IDGROUP_COUNT); + auto plPropDesc = m_lPropDesc.getArray(); + + for (auto const& path : m_lPaths) + { + const PathSettings::PathInfo& rPath = path.second; + css::beans::Property* pProp = nullptr; + + pProp = &(plPropDesc[i]); + pProp->Name = rPath.sPathName; + pProp->Handle = i; + pProp->Type = cppu::UnoType<OUString>::get(); + pProp->Attributes = css::beans::PropertyAttribute::BOUND; + if (rPath.bIsReadonly) + pProp->Attributes |= css::beans::PropertyAttribute::READONLY; + ++i; + + pProp = &(plPropDesc[i]); + pProp->Name = rPath.sPathName+POSTFIX_INTERNAL_PATHS; + pProp->Handle = i; + pProp->Type = cppu::UnoType<css::uno::Sequence< OUString >>::get(); + pProp->Attributes = css::beans::PropertyAttribute::BOUND | + css::beans::PropertyAttribute::READONLY; + ++i; + + pProp = &(plPropDesc[i]); + pProp->Name = rPath.sPathName+POSTFIX_USER_PATHS; + pProp->Handle = i; + pProp->Type = cppu::UnoType<css::uno::Sequence< OUString >>::get(); + pProp->Attributes = css::beans::PropertyAttribute::BOUND; + if (rPath.bIsReadonly) + pProp->Attributes |= css::beans::PropertyAttribute::READONLY; + ++i; + + pProp = &(plPropDesc[i]); + pProp->Name = rPath.sPathName+POSTFIX_WRITE_PATH; + pProp->Handle = i; + pProp->Type = cppu::UnoType<OUString>::get(); + pProp->Attributes = css::beans::PropertyAttribute::BOUND; + if (rPath.bIsReadonly) + pProp->Attributes |= css::beans::PropertyAttribute::READONLY; + ++i; + } + + m_pPropHelp.reset(new ::cppu::OPropertyArrayHelper(m_lPropDesc, false)); // false => not sorted ... must be done inside helper + + // <- SAFE +} + +css::uno::Any PathSettings::impl_getPathValue(sal_Int32 nID) const +{ + const PathSettings::PathInfo* pPath = impl_getPathAccessConst(nID); + if (! pPath) + throw css::lang::IllegalArgumentException(); + + css::uno::Any aVal; + switch(impl_getPropGroup(nID)) + { + case IDGROUP_OLDSTYLE : + { + OUString sVal = impl_convertPath2OldStyle(*pPath); + aVal <<= sVal; + } + break; + + case IDGROUP_INTERNAL_PATHS : + { + aVal <<= comphelper::containerToSequence(pPath->lInternalPaths); + } + break; + + case IDGROUP_USER_PATHS : + { + aVal <<= comphelper::containerToSequence(pPath->lUserPaths); + } + break; + + case IDGROUP_WRITE_PATH : + { + aVal <<= pPath->sWritePath; + } + break; + } + + return aVal; +} + +void PathSettings::impl_setPathValue( sal_Int32 nID , + const css::uno::Any& aVal) +{ + PathSettings::PathInfo* pOrgPath = impl_getPathAccess(nID); + if (! pOrgPath) + throw css::container::NoSuchElementException(); + + // We work on a copied path ... so we can be sure that errors during this operation + // does not make our internal cache invalid .-) + PathSettings::PathInfo aChangePath(*pOrgPath); + + switch(impl_getPropGroup(nID)) + { + case IDGROUP_OLDSTYLE : + { + OUString sVal; + aVal >>= sVal; + std::vector<OUString> lList = impl_convertOldStyle2Path(sVal); + impl_subst(lList, fa_getSubstitution(), false); + impl_purgeKnownPaths(aChangePath, lList); + if (! impl_isValidPath(lList)) + throw css::lang::IllegalArgumentException(); + + if (aChangePath.bIsSinglePath) + { + SAL_WARN_IF(lList.size()>1, "fwk", "PathSettings::impl_setPathValue(): You try to set more than path value for a defined SINGLE_PATH!"); + if ( !lList.empty() ) + aChangePath.sWritePath = *(lList.begin()); + else + aChangePath.sWritePath.clear(); + } + else + { + for (auto const& elem : lList) + { + aChangePath.lUserPaths.push_back(elem); + } + } + } + break; + + case IDGROUP_INTERNAL_PATHS : + { + if (aChangePath.bIsSinglePath) + { + throw css::uno::Exception( + "The path '" + aChangePath.sPathName + + "' is defined as SINGLE_PATH. It's sub set of internal paths can't be set.", + static_cast< ::cppu::OWeakObject* >(this)); + } + + css::uno::Sequence<OUString> lTmpList; + aVal >>= lTmpList; + std::vector<OUString> lList = comphelper::sequenceToContainer<std::vector<OUString>>(lTmpList); + if (! impl_isValidPath(lList)) + throw css::lang::IllegalArgumentException(); + aChangePath.lInternalPaths = lList; + } + break; + + case IDGROUP_USER_PATHS : + { + if (aChangePath.bIsSinglePath) + { + throw css::uno::Exception( + "The path '" + aChangePath.sPathName + + "' is defined as SINGLE_PATH. It's sub set of internal paths can't be set.", + static_cast< ::cppu::OWeakObject* >(this)); + } + + css::uno::Sequence<OUString> lTmpList; + aVal >>= lTmpList; + std::vector<OUString> lList = comphelper::sequenceToContainer<std::vector<OUString>>(lTmpList); + if (! impl_isValidPath(lList)) + throw css::lang::IllegalArgumentException(); + aChangePath.lUserPaths = lList; + } + break; + + case IDGROUP_WRITE_PATH : + { + OUString sVal; + aVal >>= sVal; + if (! impl_isValidPath(sVal)) + throw css::lang::IllegalArgumentException(); + aChangePath.sWritePath = sVal; + } + break; + } + + // TODO check if path has at least one path value set + // At least it depends from the feature using this path, if an empty path list is allowed. + + // first we should try to store the changed (copied!) path ... + // In case an error occurs on saving time an exception is thrown ... + // If no exception occurs we can update our internal cache (means + // we can overwrite pOrgPath ! + impl_storePath(aChangePath); + *pOrgPath = std::move(aChangePath); +} + +bool PathSettings::impl_isValidPath(const std::vector<OUString>& lPath) const +{ + for (auto const& path : lPath) + { + if (! impl_isValidPath(path)) + return false; + } + + return true; +} + +bool PathSettings::impl_isValidPath(std::u16string_view sPath) const +{ + // allow empty path to reset a path. +// idea by LLA to support empty paths +// if (sPath.getLength() == 0) +// { +// return sal_True; +// } + + return (! INetURLObject(sPath).HasError()); +} + +OUString impl_extractBaseFromPropName(const OUString& sPropName) +{ + sal_Int32 i = sPropName.indexOf(POSTFIX_INTERNAL_PATHS); + if (i > -1) + return sPropName.copy(0, i); + i = sPropName.indexOf(POSTFIX_USER_PATHS); + if (i > -1) + return sPropName.copy(0, i); + i = sPropName.indexOf(POSTFIX_WRITE_PATH); + if (i > -1) + return sPropName.copy(0, i); + + return sPropName; +} + +PathSettings::PathInfo* PathSettings::impl_getPathAccess(sal_Int32 nHandle) +{ + // SAFE -> + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + if (nHandle > (m_lPropDesc.getLength()-1)) + return nullptr; + + const css::beans::Property& rProp = m_lPropDesc[nHandle]; + OUString sProp = impl_extractBaseFromPropName(rProp.Name); + PathSettings::PathHash::iterator rPath = m_lPaths.find(sProp); + + if (rPath != m_lPaths.end()) + return &(rPath->second); + + return nullptr; + // <- SAFE +} + +const PathSettings::PathInfo* PathSettings::impl_getPathAccessConst(sal_Int32 nHandle) const +{ + // SAFE -> + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + + if (nHandle > (m_lPropDesc.getLength()-1)) + return nullptr; + + const css::beans::Property& rProp = m_lPropDesc[nHandle]; + OUString sProp = impl_extractBaseFromPropName(rProp.Name); + PathSettings::PathHash::const_iterator rPath = m_lPaths.find(sProp); + + if (rPath != m_lPaths.end()) + return &(rPath->second); + + return nullptr; + // <- SAFE +} + +sal_Bool SAL_CALL PathSettings::convertFastPropertyValue( css::uno::Any& aConvertedValue, + css::uno::Any& aOldValue , + sal_Int32 nHandle , + const css::uno::Any& aValue ) +{ + // throws NoSuchElementException ! + css::uno::Any aCurrentVal = impl_getPathValue(nHandle); + + return PropHelper::willPropertyBeChanged( + aCurrentVal, + aValue, + aOldValue, + aConvertedValue); +} + +void SAL_CALL PathSettings::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, + const css::uno::Any& aValue ) +{ + // throws NoSuchElement- and IllegalArgumentException ! + impl_setPathValue(nHandle, aValue); +} + +void SAL_CALL PathSettings::getFastPropertyValue(css::uno::Any& aValue , + sal_Int32 nHandle) const +{ + aValue = impl_getPathValue(nHandle); +} + +::cppu::IPropertyArrayHelper& SAL_CALL PathSettings::getInfoHelper() +{ + return *m_pPropHelp; +} + +css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL PathSettings::getPropertySetInfo() +{ + return ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper()); +} + +css::uno::Reference< css::util::XStringSubstitution > PathSettings::fa_getSubstitution() +{ + css::uno::Reference< css::util::XStringSubstitution > xSubst; + { // SAFE -> + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + xSubst = m_xSubstitution; + } + + if (! xSubst.is()) + { + // create the needed substitution service. + // We must replace all used variables inside read path values. + // In case we can't do so... the whole office can't work really. + // That's why it seems to be OK to throw a RuntimeException then. + xSubst = css::util::PathSubstitution::create(m_xContext); + + { // SAFE -> + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_xSubstitution = xSubst; + } + } + + return xSubst; +} + +css::uno::Reference< css::container::XNameAccess > PathSettings::fa_getCfgOld() +{ + css::uno::Reference< css::container::XNameAccess > xCfg; + { // SAFE -> + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + xCfg = m_xCfgOld; + } // <- SAFE + + if (! xCfg.is()) + { + xCfg.set( ::comphelper::ConfigurationHelper::openConfig( + m_xContext, + "org.openoffice.Office.Common/Path/Current", + ::comphelper::EConfigurationModes::Standard), // not readonly! Sometimes we need write access there !!! + css::uno::UNO_QUERY_THROW); + + { // SAFE -> + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_xCfgOld = xCfg; + } + } + + return xCfg; +} + +css::uno::Reference< css::container::XNameAccess > PathSettings::fa_getCfgNew() +{ + css::uno::Reference< css::container::XNameAccess > xCfg; + { // SAFE -> + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + xCfg = m_xCfgNew; + } // <- SAFE + + if (! xCfg.is()) + { + xCfg.set( ::comphelper::ConfigurationHelper::openConfig( + m_xContext, + "org.openoffice.Office.Paths/Paths", + ::comphelper::EConfigurationModes::Standard), + css::uno::UNO_QUERY_THROW); + + { // SAFE -> + osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex); + m_xCfgNew = xCfg; + m_xCfgNewListener = new WeakChangesListener(this); + } + + css::uno::Reference< css::util::XChangesNotifier > xBroadcaster(xCfg, css::uno::UNO_QUERY_THROW); + xBroadcaster->addChangesListener(m_xCfgNewListener); + } + + return xCfg; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_PathSettings_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + rtl::Reference<PathSettings> xPathSettings = new PathSettings(context); + // fill cache + xPathSettings->impl_readAll(); + + return cppu::acquire(xPathSettings.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/sessionlistener.cxx b/framework/source/services/sessionlistener.cxx new file mode 100644 index 000000000..7ba5c82de --- /dev/null +++ b/framework/source/services/sessionlistener.cxx @@ -0,0 +1,413 @@ +/* -*- 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/types.h> +#include <sal/log.hxx> + +#include <framework/desktop.hxx> + +#include <tools/diagnose_ex.h> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/theAutoRecovery.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/FeatureStateEvent.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XSessionManagerListener2.hpp> +#include <com/sun/star/frame/XSessionManagerClient.hpp> +#include <com/sun/star/frame/XStatusListener.hpp> +#include <com/sun/star/lang/EventObject.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/util/URL.hpp> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <utility> + +using namespace css; +using namespace com::sun::star::uno; +using namespace com::sun::star::util; +using namespace com::sun::star::beans; +using namespace framework; + +namespace { + +/// @HTML +/** @short implements flat/deep detection of file/stream formats and provides + further read/write access to the global office type configuration. + + @descr Using of this class makes it possible to get information about the + format type of a given URL or stream. The returned internal type name + can be used to get more information about this format. Further this + class provides full access to the configuration data and following + implementations will support some special query modes. + + @docdate 10.03.2003 by as96863 + + @todo <ul> + <li>implementation of query mode</li> + <li>simple restore mechanism of last consistent cache state, + if flush failed</li> + </ul> + */ +typedef cppu::WeakImplHelper< + css::lang::XInitialization, + css::frame::XSessionManagerListener2, + css::frame::XStatusListener, + css::lang::XServiceInfo> SessionListener_BASE; + +class SessionListener : public SessionListener_BASE +{ +private: + osl::Mutex m_aMutex; + + /** reference to the uno service manager, which created this service. + It can be used to create own needed helper services. */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + css::uno::Reference< css::frame::XSessionManagerClient > m_rSessionManager; + + // restore handling + bool m_bRestored; + + bool m_bSessionStoreRequested; + + bool m_bAllowUserInteractionOnQuit; + bool m_bTerminated; + + // in case of synchronous call the caller should do saveDone() call himself! + void StoreSession( bool bAsync ); + + // let session quietly close the documents, remove lock files, store configuration and etc. + void QuitSessionQuietly(); + +public: + explicit SessionListener(css::uno::Reference< css::uno::XComponentContext > xContext); + + virtual ~SessionListener() override; + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.frame.SessionListener"; + } + + 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.frame.SessionListener"}; + } + + virtual void SAL_CALL disposing(const css::lang::EventObject&) override; + + // XInitialization + virtual void SAL_CALL initialize(const css::uno::Sequence< css::uno::Any >& args) override; + + // XSessionManagerListener + virtual void SAL_CALL doSave( sal_Bool bShutdown, sal_Bool bCancelable ) override; + virtual void SAL_CALL approveInteraction( sal_Bool bInteractionGranted ) override; + virtual void SAL_CALL shutdownCanceled() override; + virtual sal_Bool SAL_CALL doRestore() override; + + // XSessionManagerListener2 + virtual void SAL_CALL doQuit() override; + + // XStatusListener + virtual void SAL_CALL statusChanged(const css::frame::FeatureStateEvent& event) override; +}; + +SessionListener::SessionListener(css::uno::Reference< css::uno::XComponentContext > rxContext ) + : m_xContext(std::move( rxContext )) + , m_bRestored( false ) + , m_bSessionStoreRequested( false ) + , m_bAllowUserInteractionOnQuit( false ) + , m_bTerminated( false ) +{ + SAL_INFO("fwk.session", "SessionListener::SessionListener"); +} + +SessionListener::~SessionListener() +{ + SAL_INFO("fwk.session", "SessionListener::~SessionListener"); + if (m_rSessionManager.is()) + { + css::uno::Reference< XSessionManagerListener> me(this); + m_rSessionManager->removeSessionManagerListener(me); + } +} + +void SessionListener::StoreSession( bool bAsync ) +{ + SAL_INFO("fwk.session", "SessionListener::StoreSession"); + osl::MutexGuard g(m_aMutex); + try + { + // xd create SERVICENAME_AUTORECOVERY -> frame::XDispatch + // xd->dispatch("vnd.sun.star.autorecovery:/doSessionSave, async=bAsync + // on stop event m_rSessionManager->saveDone(this); in case of asynchronous call + // in case of synchronous call the caller should do saveDone() call himself! + + css::uno::Reference< frame::XDispatch > xDispatch = css::frame::theAutoRecovery::get( m_xContext ); + css::uno::Reference< XURLTransformer > xURLTransformer = URLTransformer::create( m_xContext ); + URL aURL; + aURL.Complete = "vnd.sun.star.autorecovery:/doSessionSave"; + xURLTransformer->parseStrict(aURL); + + // in case of asynchronous call the notification will trigger saveDone() + if ( bAsync ) + xDispatch->addStatusListener(this, aURL); + + Sequence< PropertyValue > args{ PropertyValue("DispatchAsynchron",-1,Any(bAsync), + PropertyState_DIRECT_VALUE) }; + xDispatch->dispatch(aURL, args); + } catch (const css::uno::Exception&) { + TOOLS_WARN_EXCEPTION("fwk.session", ""); + // save failed, but tell manager to go on if we haven't yet dispatched the request + // in case of synchronous saving the notification is done by the caller + if ( bAsync && m_rSessionManager.is() ) + m_rSessionManager->saveDone(this); + } +} + +void SessionListener::QuitSessionQuietly() +{ + SAL_INFO("fwk.session", "SessionListener::QuitSessionQuietly"); + osl::MutexGuard g(m_aMutex); + try + { + // xd create SERVICENAME_AUTORECOVERY -> frame::XDispatch + // xd->dispatch("vnd.sun.star.autorecovery:/doSessionQuietQuit, async=false + // it is done synchronously to avoid conflict with normal quit process + + css::uno::Reference< frame::XDispatch > xDispatch = css::frame::theAutoRecovery::get( m_xContext ); + css::uno::Reference< XURLTransformer > xURLTransformer = URLTransformer::create( m_xContext ); + URL aURL; + aURL.Complete = "vnd.sun.star.autorecovery:/doSessionQuietQuit"; + xURLTransformer->parseStrict(aURL); + + Sequence< PropertyValue > args{ PropertyValue("DispatchAsynchron",-1,Any(false), + PropertyState_DIRECT_VALUE) }; + xDispatch->dispatch(aURL, args); + } catch (const css::uno::Exception&) { + TOOLS_WARN_EXCEPTION("fwk.session", ""); + } +} + +void SAL_CALL SessionListener::disposing(const css::lang::EventObject& Source) +{ + SAL_INFO("fwk.session", "SessionListener::disposing"); + if (Source.Source == m_rSessionManager) { + m_rSessionManager.clear(); + } +} + +void SAL_CALL SessionListener::initialize(const Sequence< Any >& args) +{ + SAL_INFO("fwk.session", "SessionListener::initialize"); + + OUString aSMgr("com.sun.star.frame.SessionManagerClient"); + if ( (args.getLength() == 1) && (args[0] >>= m_bAllowUserInteractionOnQuit) ) + ;// do nothing + else if (args.hasElements()) + { + NamedValue v; + for (const Any& rArg : args) + { + if (rArg >>= v) + { + if ( v.Name == "SessionManagerName" ) + v.Value >>= aSMgr; + else if ( v.Name == "SessionManager" ) + v.Value >>= m_rSessionManager; + else if ( v.Name == "AllowUserInteractionOnQuit" ) + v.Value >>= m_bAllowUserInteractionOnQuit; + } + } + } + + SAL_INFO("fwk.session.debug", " m_bAllowUserInteractionOnQuit = " << (m_bAllowUserInteractionOnQuit ? "true" : "false")); + if (!m_rSessionManager.is()) + m_rSessionManager = css::uno::Reference< frame::XSessionManagerClient > + (m_xContext->getServiceManager()->createInstanceWithContext(aSMgr, m_xContext), UNO_QUERY); + + if (m_rSessionManager.is()) + { + m_rSessionManager->addSessionManagerListener(this); + } +} + +void SAL_CALL SessionListener::statusChanged(const frame::FeatureStateEvent& event) +{ + SAL_INFO("fwk.session", "SessionListener::statusChanged"); + + SAL_INFO("fwk.session.debug", " ev.Feature = " << event.FeatureURL.Complete << + ", ev.Descript = " << event.FeatureDescriptor); + if ( event.FeatureURL.Complete == "vnd.sun.star.autorecovery:/doSessionRestore" ) + { + if (event.FeatureDescriptor == "update") + m_bRestored = true; // a document was restored + + } + else if ( event.FeatureURL.Complete == "vnd.sun.star.autorecovery:/doAutoSave" ) + { // the "doSessionSave" was never set, look to framework/source/services/autorecovery.cxx + // it always testing but never setting (enum AutoRecovery::E_SESSION_SAVE) + if (event.FeatureDescriptor == "update") + { + if (m_rSessionManager.is()) + m_rSessionManager->saveDone(this); // done with save + } + } +} + +sal_Bool SAL_CALL SessionListener::doRestore() +{ + SAL_INFO("fwk.session", "SessionListener::doRestore"); + osl::MutexGuard g(m_aMutex); + m_bRestored = false; + try { + css::uno::Reference< frame::XDispatch > xDispatch = css::frame::theAutoRecovery::get( m_xContext ); + + URL aURL; + aURL.Complete = "vnd.sun.star.autorecovery:/doSessionRestore"; + css::uno::Reference< XURLTransformer > xURLTransformer(URLTransformer::create(m_xContext)); + xURLTransformer->parseStrict(aURL); + Sequence< PropertyValue > args; + xDispatch->addStatusListener(this, aURL); + xDispatch->dispatch(aURL, args); + m_bRestored = true; + + } catch (const css::uno::Exception&) { + TOOLS_WARN_EXCEPTION("fwk.session", ""); + } + + return m_bRestored; +} + +void SAL_CALL SessionListener::doSave( sal_Bool bShutdown, sal_Bool /*bCancelable*/ ) +{ + SAL_INFO("fwk.session", "SessionListener::doSave"); + + SAL_INFO("fwk.session.debug", " m_bAllowUserInteractionOnQuit = " << (m_bAllowUserInteractionOnQuit ? "true" : "false") << + ", bShutdown = " << (bShutdown ? "true" : "false")); + if (bShutdown) + { + m_bSessionStoreRequested = true; // there is no need to protect it with mutex + if ( m_bAllowUserInteractionOnQuit && m_rSessionManager.is() ) + m_rSessionManager->queryInteraction( static_cast< css::frame::XSessionManagerListener* >( this ) ); + else + StoreSession( true ); + } + // we don't have anything to do so tell the session manager we're done + else if( m_rSessionManager.is() ) + m_rSessionManager->saveDone( this ); +} + +void SAL_CALL SessionListener::approveInteraction( sal_Bool bInteractionGranted ) +{ + SAL_INFO("fwk.session", "SessionListener::approveInteraction"); + // do AutoSave as the first step + osl::MutexGuard g(m_aMutex); + + if ( bInteractionGranted ) + { + // close the office documents in normal way + try + { + // first of all let the session be stored to be sure that we lose no information + StoreSession( false ); + + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( m_xContext ); + // honestly: how many implementations of XDesktop will we ever have? + // so casting this directly to the implementation + Desktop* pDesktop(dynamic_cast<Desktop*>(xDesktop.get())); + if(pDesktop) + { + SAL_INFO("fwk.session", " XDesktop is a framework::Desktop -- good."); + m_bTerminated = pDesktop->terminateQuickstarterToo(); + } + else + { + SAL_WARN("fwk.session", " XDesktop is not a framework::Desktop -- this should never happen."); + m_bTerminated = xDesktop->terminate(); + } + + if ( m_rSessionManager.is() ) + { + // false means that the application closing has been cancelled + if ( !m_bTerminated ) + m_rSessionManager->cancelShutdown(); + else + m_rSessionManager->interactionDone( this ); + } + } + catch( const css::uno::Exception& ) + { + StoreSession( true ); + m_rSessionManager->interactionDone( this ); + } + + if ( m_rSessionManager.is() && m_bTerminated ) + m_rSessionManager->saveDone(this); + } + else + { + StoreSession( true ); + } +} + +void SessionListener::shutdownCanceled() +{ + SAL_INFO("fwk.session", "SessionListener::shutdownCanceled"); + // set the state back + m_bSessionStoreRequested = false; // there is no need to protect it with mutex + + if ( m_rSessionManager.is() ) + m_rSessionManager->saveDone(this); +} + +void SessionListener::doQuit() +{ + SAL_INFO("fwk.session", "SessionListener::doQuit"); + if ( m_bSessionStoreRequested && !m_bTerminated ) + { + // let the session be closed quietly in this case + QuitSessionQuietly(); + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_frame_SessionListener_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + SAL_INFO("fwk.session", "com_sun_star_comp_frame_SessionListener_get_implementation"); + + return cppu::acquire(new SessionListener(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/substitutepathvars.cxx b/framework/source/services/substitutepathvars.cxx new file mode 100644 index 000000000..b57523306 --- /dev/null +++ b/framework/source/services/substitutepathvars.cxx @@ -0,0 +1,695 @@ +/* -*- 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 <config_folders.h> + +#include <comphelper/lok.hxx> +#include <comphelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <unotools/bootstrap.hxx> +#include <unotools/configmgr.hxx> +#include <osl/file.hxx> +#include <osl/security.hxx> +#include <osl/thread.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <tools/urlobj.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/bootstrap.hxx> + +#include <officecfg/Office/Paths.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/util/XStringSubstitution.hpp> + +#include <unordered_map> + +using namespace com::sun::star::uno; +using namespace com::sun::star::container; + +namespace { + +enum PreDefVariable +{ + PREDEFVAR_INST, + PREDEFVAR_PROG, + PREDEFVAR_USER, + PREDEFVAR_WORK, + PREDEFVAR_HOME, + PREDEFVAR_TEMP, + PREDEFVAR_PATH, + PREDEFVAR_USERNAME, + PREDEFVAR_LANGID, + PREDEFVAR_VLANG, + PREDEFVAR_INSTPATH, + PREDEFVAR_PROGPATH, + PREDEFVAR_USERPATH, + PREDEFVAR_INSTURL, + PREDEFVAR_PROGURL, + PREDEFVAR_USERURL, + PREDEFVAR_WORKDIRURL, + // New variable of hierarchy service (#i32656#) + PREDEFVAR_BASEINSTURL, + PREDEFVAR_USERDATAURL, + PREDEFVAR_BRANDBASEURL, + PREDEFVAR_COUNT +}; + +struct FixedVariable +{ + const char* pVarName; + bool bAbsPath; +}; + +// Table with all fixed/predefined variables supported. +const FixedVariable aFixedVarTable[PREDEFVAR_COUNT] = +{ + { "$(inst)", true }, // PREDEFVAR_INST + { "$(prog)", true }, // PREDEFVAR_PROG + { "$(user)", true }, // PREDEFVAR_USER + { "$(work)", true }, // PREDEFVAR_WORK, special variable + // (transient) + { "$(home)", true }, // PREDEFVAR_HOME + { "$(temp)", true }, // PREDEFVAR_TEMP + { "$(path)", true }, // PREDEFVAR_PATH + { "$(username)", false }, // PREDEFVAR_USERNAME + { "$(langid)", false }, // PREDEFVAR_LANGID + { "$(vlang)", false }, // PREDEFVAR_VLANG + { "$(instpath)", true }, // PREDEFVAR_INSTPATH + { "$(progpath)", true }, // PREDEFVAR_PROGPATH + { "$(userpath)", true }, // PREDEFVAR_USERPATH + { "$(insturl)", true }, // PREDEFVAR_INSTURL + { "$(progurl)", true }, // PREDEFVAR_PROGURL + { "$(userurl)", true }, // PREDEFVAR_USERURL + { "$(workdirurl)", true }, // PREDEFVAR_WORKDIRURL, special variable + // (transient) and don't use for + // resubstitution + { "$(baseinsturl)", true }, // PREDEFVAR_BASEINSTURL + { "$(userdataurl)", true }, // PREDEFVAR_USERDATAURL + { "$(brandbaseurl)", true } // PREDEFVAR_BRANDBASEURL +}; + +struct PredefinedPathVariables +{ + // Predefined variables supported by substitute variables + LanguageType m_eLanguageType; // Language type of Office + OUString m_FixedVar[ PREDEFVAR_COUNT ]; // Variable value access by PreDefVariable + OUString m_FixedVarNames[ PREDEFVAR_COUNT ]; // Variable name access by PreDefVariable +}; + +struct ReSubstFixedVarOrder +{ + sal_Int32 nVarValueLength; + PreDefVariable eVariable; + + bool operator< ( const ReSubstFixedVarOrder& aFixedVarOrder ) const + { + // Reverse operator< to have high to low ordering + return ( nVarValueLength > aFixedVarOrder.nVarValueLength ); + } +}; + +typedef comphelper::WeakComponentImplHelper< + css::util::XStringSubstitution, + css::lang::XServiceInfo > SubstitutePathVariables_BASE; + +class SubstitutePathVariables : public SubstitutePathVariables_BASE +{ +public: + explicit SubstitutePathVariables(); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.PathSubstitution"; + } + + 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.util.PathSubstitution"}; + } + + // XStringSubstitution + virtual OUString SAL_CALL substituteVariables( const OUString& aText, sal_Bool bSubstRequired ) override; + virtual OUString SAL_CALL reSubstituteVariables( const OUString& aText ) override; + virtual OUString SAL_CALL getSubstituteVariableValue( const OUString& variable ) override; + +protected: + void SetPredefinedPathVariables(); + + // Special case (transient) values can change during runtime! + // Don't store them in the pre defined struct + OUString GetWorkPath() const; + OUString GetWorkVariableValue() const; + OUString GetPathVariableValue() const; + + OUString GetHomeVariableValue() const; + + // XStringSubstitution implementation methods + /// @throws css::container::NoSuchElementException + /// @throws css::uno::RuntimeException + OUString impl_substituteVariable( const OUString& aText, bool bSustRequired ); + /// @throws css::uno::RuntimeException + OUString impl_reSubstituteVariables( const OUString& aText ); + /// @throws css::container::NoSuchElementException + /// @throws css::uno::RuntimeException + OUString const & impl_getSubstituteVariableValue( const OUString& variable ); + +private: + typedef std::unordered_map<OUString, PreDefVariable> + VarNameToIndexMap; + + VarNameToIndexMap m_aPreDefVarMap; // Mapping from pre-def variable names to enum for array access + PredefinedPathVariables m_aPreDefVars; // All predefined variables + std::vector<ReSubstFixedVarOrder> m_aReSubstFixedVarOrder; // To speed up resubstitution fixed variables (order for lookup) +}; + +SubstitutePathVariables::SubstitutePathVariables() +{ + SetPredefinedPathVariables(); + + // Init the predefined/fixed variable to index hash map + for ( int i = 0; i < PREDEFVAR_COUNT; i++ ) + { + // Store variable name into struct of predefined/fixed variables + m_aPreDefVars.m_FixedVarNames[i] = OUString::createFromAscii( aFixedVarTable[i].pVarName ); + + // Create hash map entry + m_aPreDefVarMap.emplace( m_aPreDefVars.m_FixedVarNames[i], PreDefVariable(i) ); + } + + // Sort predefined/fixed variable to path length + for ( int i = 0; i < PREDEFVAR_COUNT; i++ ) + { + if (( i != PREDEFVAR_WORKDIRURL ) && ( i != PREDEFVAR_PATH )) + { + // Special path variables, don't include into automatic resubstitution search! + // $(workdirurl) is not allowed to resubstitute! This variable is the value of path settings entry + // and it could be possible that it will be resubstituted by itself!! + // Example: WORK_PATH=c:\test, $(workdirurl)=WORK_PATH => WORK_PATH=$(workdirurl) and this cannot be substituted! + ReSubstFixedVarOrder aFixedVar; + aFixedVar.eVariable = PreDefVariable(i); + aFixedVar.nVarValueLength = m_aPreDefVars.m_FixedVar[static_cast<sal_Int32>(aFixedVar.eVariable)].getLength(); + m_aReSubstFixedVarOrder.push_back( aFixedVar ); + } + } + sort(m_aReSubstFixedVarOrder.begin(),m_aReSubstFixedVarOrder.end()); +} + +// XStringSubstitution +OUString SAL_CALL SubstitutePathVariables::substituteVariables( const OUString& aText, sal_Bool bSubstRequired ) +{ + std::unique_lock g(m_aMutex); + return impl_substituteVariable( aText, bSubstRequired ); +} + +OUString SAL_CALL SubstitutePathVariables::reSubstituteVariables( const OUString& aText ) +{ + std::unique_lock g(m_aMutex); + return impl_reSubstituteVariables( aText ); +} + +OUString SAL_CALL SubstitutePathVariables::getSubstituteVariableValue( const OUString& aVariable ) +{ + std::unique_lock g(m_aMutex); + return impl_getSubstituteVariableValue( aVariable ); +} + +OUString SubstitutePathVariables::GetWorkPath() const +{ + OUString aWorkPath; + css::uno::Reference< css::container::XHierarchicalNameAccess > xPaths(officecfg::Office::Paths::Paths::get(), css::uno::UNO_QUERY_THROW); + if (!(xPaths->getByHierarchicalName("['Work']/WritePath") >>= aWorkPath)) + // fallback in case config layer does not return a usable work dir value. + aWorkPath = GetWorkVariableValue(); + + return aWorkPath; +} + +OUString SubstitutePathVariables::GetWorkVariableValue() const +{ + OUString aWorkPath; + std::optional<OUString> x(officecfg::Office::Paths::Variables::Work::get()); + if (!x) + { + // fallback to $HOME in case platform dependent config layer does not return + // a usable work dir value. + osl::Security aSecurity; + aSecurity.getHomeDir( aWorkPath ); + } + else + aWorkPath = *x; + return aWorkPath; +} + +OUString SubstitutePathVariables::GetHomeVariableValue() const +{ + osl::Security aSecurity; + OUString aHomePath; + + aSecurity.getHomeDir( aHomePath ); + return aHomePath; +} + +OUString SubstitutePathVariables::GetPathVariableValue() const +{ + OUString aRetStr; + const char* pEnv = getenv( "PATH" ); + + if ( pEnv ) + { + const int PATH_EXTEND_FACTOR = 200; + OUString aTmp; + OUString aPathList( pEnv, strlen( pEnv ), osl_getThreadTextEncoding() ); + OUStringBuffer aPathStrBuffer( aPathList.getLength() * PATH_EXTEND_FACTOR / 100 ); + + bool bAppendSep = false; + sal_Int32 nToken = 0; + do + { + OUString sToken = aPathList.getToken(0, SAL_PATHSEPARATOR, nToken); + if (!sToken.isEmpty() && + osl::FileBase::getFileURLFromSystemPath( sToken, aTmp ) == + osl::FileBase::RC::E_None ) + { + if ( bAppendSep ) + aPathStrBuffer.append( ";" ); // Office uses ';' as path separator + aPathStrBuffer.append( aTmp ); + bAppendSep = true; + } + } + while(nToken>=0); + + aRetStr = aPathStrBuffer.makeStringAndClear(); + } + + return aRetStr; +} + +OUString SubstitutePathVariables::impl_substituteVariable( const OUString& rText, bool bSubstRequired ) +{ + // This is maximal recursive depth supported! + const sal_Int32 nMaxRecursiveDepth = 8; + + OUString aWorkText = rText; + OUString aResult; + + // Use vector with strings to detect endless recursions! + std::vector< OUString > aEndlessRecursiveDetector; + + // Search for first occurrence of "$(...". + sal_Int32 nDepth = 0; + bool bSubstitutionCompleted = false; + sal_Int32 nPosition = aWorkText.indexOf( "$(" ); + sal_Int32 nLength = 0; // = count of letters from "$(" to ")" in string + bool bVarNotSubstituted = false; + + // Have we found any variable like "$(...)"? + if ( nPosition != -1 ) + { + // Yes; Get length of found variable. + // If no ")" was found - nLength is set to 0 by default! see before. + sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition ); + if ( nEndPosition != -1 ) + nLength = nEndPosition - nPosition + 1; + } + + // Is there something to replace ? + bool bWorkRetrieved = false; + bool bWorkDirURLRetrieved = false; + while (nDepth < nMaxRecursiveDepth) + { + while ( ( nPosition != -1 ) && ( nLength > 3 ) ) // "$(" ")" + { + // YES; Get the next variable for replace. + sal_Int32 nReplaceLength = 0; + OUString aReplacement; + OUString aSubString = aWorkText.copy( nPosition, nLength ); + + // Path variables are not case sensitive! + OUString aSubVarString = aSubString.toAsciiLowerCase(); + VarNameToIndexMap::const_iterator pNTOIIter = m_aPreDefVarMap.find( aSubVarString ); + if ( pNTOIIter != m_aPreDefVarMap.end() ) + { + // Fixed/Predefined variable found + PreDefVariable nIndex = pNTOIIter->second; + + // Determine variable value and length from array/table + if ( nIndex == PREDEFVAR_WORK && !bWorkRetrieved ) + { + // Transient value, retrieve it again + m_aPreDefVars.m_FixedVar[ nIndex ] = GetWorkVariableValue(); + bWorkRetrieved = true; + } + else if ( nIndex == PREDEFVAR_WORKDIRURL && !bWorkDirURLRetrieved ) + { + // Transient value, retrieve it again + m_aPreDefVars.m_FixedVar[ nIndex ] = GetWorkPath(); + bWorkDirURLRetrieved = true; + } + + // Check preconditions to substitute path variables. + // 1. A path variable can only be substituted if it follows a ';'! + // 2. It's located exactly at the start of the string being substituted! + if (( aFixedVarTable[ int( nIndex ) ].bAbsPath && (( nPosition == 0 ) || (( nPosition > 0 ) && ( aWorkText[nPosition-1] == ';')))) || + ( !aFixedVarTable[ int( nIndex ) ].bAbsPath )) + { + aReplacement = m_aPreDefVars.m_FixedVar[ nIndex ]; + nReplaceLength = nLength; + } + } + + // Have we found something to replace? + if ( nReplaceLength > 0 ) + { + // Yes ... then do it. + aWorkText = aWorkText.replaceAt( nPosition, nReplaceLength, aReplacement ); + } + else + { + // Variable not known + bVarNotSubstituted = true; + nPosition += nLength; + } + + // Step after replaced text! If no text was replaced (unknown variable!), + // length of aReplacement is 0 ... and we don't step then. + nPosition += aReplacement.getLength(); + + // We must control index in string before call something at OUString! + // The OUString-implementation don't do it for us :-( but the result is not defined otherwise. + if ( nPosition + 1 > aWorkText.getLength() ) + { + // Position is out of range. Break loop! + nPosition = -1; + nLength = 0; + } + else + { + // Else; Position is valid. Search for next variable to replace. + nPosition = aWorkText.indexOf( "$(", nPosition ); + // Have we found any variable like "$(...)"? + if ( nPosition != -1 ) + { + // Yes; Get length of found variable. If no ")" was found - nLength must set to 0! + nLength = 0; + sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition ); + if ( nEndPosition != -1 ) + nLength = nEndPosition - nPosition + 1; + } + } + } + + nPosition = aWorkText.indexOf( "$(" ); + if ( nPosition == -1 ) + { + bSubstitutionCompleted = true; + break; // All variables are substituted + } + else + { + // Check for recursion + const sal_uInt32 nCount = aEndlessRecursiveDetector.size(); + for ( sal_uInt32 i=0; i < nCount; i++ ) + { + if ( aEndlessRecursiveDetector[i] == aWorkText ) + { + if ( bVarNotSubstituted ) + break; // Not all variables could be substituted! + else + { + nDepth = nMaxRecursiveDepth; + break; // Recursion detected! + } + } + } + + aEndlessRecursiveDetector.push_back( aWorkText ); + + // Initialize values for next + sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition ); + if ( nEndPosition != -1 ) + nLength = nEndPosition - nPosition + 1; + bVarNotSubstituted = false; + ++nDepth; + } + } + + // Fill return value with result + if ( bSubstitutionCompleted ) + { + // Substitution successful! + aResult = aWorkText; + } + else + { + // Substitution not successful! + if ( nDepth == nMaxRecursiveDepth ) + { + // recursion depth reached! + if ( bSubstRequired ) + { + throw NoSuchElementException( "Endless recursion detected. Cannot substitute variables!", static_cast<cppu::OWeakObject *>(this) ); + } + aResult = rText; + } + else + { + // variable in text but unknown! + if ( bSubstRequired ) + { + throw NoSuchElementException( "Unknown variable found!", static_cast<cppu::OWeakObject *>(this) ); + } + aResult = aWorkText; + } + } + + return aResult; +} + +OUString SubstitutePathVariables::impl_reSubstituteVariables( const OUString& rURL ) +{ + OUString aURL; + + INetURLObject aUrl( rURL ); + if ( !aUrl.HasError() ) + aURL = aUrl.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + else + { + // Convert a system path to a UCB compliant URL before resubstitution + OUString aTemp; + if ( osl::FileBase::getFileURLFromSystemPath( rURL, aTemp ) == osl::FileBase::E_None ) + { + aURL = INetURLObject( aTemp ).GetMainURL( INetURLObject::DecodeMechanism::NONE ); + if( aURL.isEmpty() ) + return rURL; + } + else + { + // rURL is not a valid URL nor a osl system path. Give up and return error! + return rURL; + } + } + + // Get transient predefined path variable $(work) value before starting resubstitution + m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORK ] = GetWorkVariableValue(); + + for (;;) + { + bool bVariableFound = false; + + for (auto const & i: m_aReSubstFixedVarOrder) + { + OUString aValue = m_aPreDefVars.m_FixedVar[i.eVariable]; + sal_Int32 nPos = aURL.indexOf( aValue ); + if ( nPos >= 0 ) + { + bool bMatch = true; + if ( !aFixedVarTable[i.eVariable].bAbsPath ) + { + // Special path variables as they can occur in the middle of a path. Only match if they + // describe a whole directory and not only a substring of a directory! + // (Ideally, all substitutions should stick to syntactical + // boundaries within the given URL, like not covering only + // part of a URL path segment; however, at least when saving + // an Impress document, one URL that is passed in is of the + // form <file:///.../share/palette%3Bfile:///.../user/ + // config/standard.sob>, re-substituted to + // <$(inst)/share/palette%3B$(user)/config/standard.sob>.) + const sal_Unicode* pStr = aURL.getStr(); + + if ( nPos > 0 ) + bMatch = ( aURL[ nPos-1 ] == '/' ); + + if ( bMatch ) + { + if ( nPos + aValue.getLength() < aURL.getLength() ) + bMatch = ( pStr[ nPos + aValue.getLength() ] == '/' ); + } + } + + if ( bMatch ) + { + aURL = aURL.replaceAt( + nPos, aValue.getLength(), + m_aPreDefVars.m_FixedVarNames[i.eVariable]); + bVariableFound = true; // Resubstitution not finished yet! + break; + } + } + } + + if ( !bVariableFound ) + { + return aURL; + } + } +} + +// This method support both request schemes "$("<varname>")" or "<varname>". +OUString const & SubstitutePathVariables::impl_getSubstituteVariableValue( const OUString& rVariable ) +{ + OUString aVariable; + + sal_Int32 nPos = rVariable.indexOf( "$(" ); + if ( nPos == -1 ) + { + // Prepare variable name before hash map access + aVariable = "$(" + rVariable + ")"; + } + + VarNameToIndexMap::const_iterator pNTOIIter = m_aPreDefVarMap.find( ( nPos == -1 ) ? aVariable : rVariable ); + + // Fixed/Predefined variable + if ( pNTOIIter == m_aPreDefVarMap.end() ) + { + throw NoSuchElementException("Unknown variable!", static_cast<cppu::OWeakObject *>(this)); + } + PreDefVariable nIndex = pNTOIIter->second; + return m_aPreDefVars.m_FixedVar[static_cast<sal_Int32>(nIndex)]; +} + +void SubstitutePathVariables::SetPredefinedPathVariables() +{ + + m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL] = "$BRAND_BASE_DIR"; + rtl::Bootstrap::expandMacros( + m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL]); + + // Get inspath and userpath from bootstrap mechanism in every case as file URL + ::utl::Bootstrap::PathStatus aState; + OUString sVal; + + aState = utl::Bootstrap::locateUserData( sVal ); + //There can be the valid case that there is no user installation. + //TODO: Is that still the case? (With OOo 3.4, "unopkg sync" was run as part + // of the setup. Then no user installation was required.) + //Therefore we do not assert here. + // It's not possible to detect when an empty value would actually be used. + // (note: getenv is a hack to detect if we're running in a unit test) + // Also, it's okay to have an empty user installation path in case of LOK + if (aState == ::utl::Bootstrap::PATH_EXISTS || getenv("SRC_ROOT") || + (comphelper::LibreOfficeKit::isActive() && aState == ::utl::Bootstrap::PATH_VALID)) + { + m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ] = sVal; + } + + // Set $(inst), $(instpath), $(insturl) + m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ] = m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL]; + m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTURL ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ]; + m_aPreDefVars.m_FixedVar[ PREDEFVAR_INST ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ]; + // New variable of hierarchy service (#i32656#) + m_aPreDefVars.m_FixedVar[ PREDEFVAR_BASEINSTURL ]= m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ]; + + // Set $(user), $(userpath), $(userurl) + m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERURL ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ]; + m_aPreDefVars.m_FixedVar[ PREDEFVAR_USER ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ]; + // New variable of hierarchy service (#i32656#) + m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERDATAURL ]= m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ]; + + // Detect the program directory + // Set $(prog), $(progpath), $(progurl) + INetURLObject aProgObj( + m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL] ); + if ( !aProgObj.HasError() && aProgObj.insertName( u"" LIBO_BIN_FOLDER ) ) + { + m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ] = aProgObj.GetMainURL(INetURLObject::DecodeMechanism::NONE); + m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGURL ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ]; + m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROG ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ]; + } + + // Set $(username) + OUString aSystemUser; + ::osl::Security aSecurity; + aSecurity.getUserName( aSystemUser, false ); + m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERNAME ] = aSystemUser; + + // Detect the language type of the current office + m_aPreDefVars.m_eLanguageType = LANGUAGE_ENGLISH_US; + OUString aLocaleStr( utl::ConfigManager::getUILocale() ); + m_aPreDefVars.m_eLanguageType = LanguageTag::convertToLanguageTypeWithFallback( aLocaleStr ); + // We used to have an else branch here with a SAL_WARN, but that + // always fired in some unit tests when this code was built with + // debug=t, so it seems fairly pointless, especially as + // m_aPreDefVars.m_eLanguageType has been initialized to a + // default value above anyway. + + // Set $(vlang) + m_aPreDefVars.m_FixedVar[ PREDEFVAR_VLANG ] = aLocaleStr; + + // Set $(langid) + m_aPreDefVars.m_FixedVar[ PREDEFVAR_LANGID ] = OUString::number( static_cast<sal_uInt16>(m_aPreDefVars.m_eLanguageType) ); + + // Set the other pre defined path variables + // Set $(work) + m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORK ] = GetWorkVariableValue(); + m_aPreDefVars.m_FixedVar[ PREDEFVAR_HOME ] = GetHomeVariableValue(); + + // Set $(workdirurl) this is the value of the path PATH_WORK which doesn't make sense + // anymore because the path settings service has this value! It can deliver this value more + // quickly than the substitution service! + m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORKDIRURL ] = GetWorkPath(); + + // Set $(path) variable + m_aPreDefVars.m_FixedVar[ PREDEFVAR_PATH ] = GetPathVariableValue(); + + // Set $(temp) + OUString aTmp; + osl::FileBase::getTempDirURL( aTmp ); + m_aPreDefVars.m_FixedVar[ PREDEFVAR_TEMP ] = aTmp; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_PathSubstitution_get_implementation( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SubstitutePathVariables()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/taskcreatorsrv.cxx b/framework/source/services/taskcreatorsrv.cxx new file mode 100644 index 000000000..12e7c2c34 --- /dev/null +++ b/framework/source/services/taskcreatorsrv.cxx @@ -0,0 +1,357 @@ +/* -*- 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 <helper/persistentwindowstate.hxx> +#include <helper/tagwindowasmodified.hxx> +#include <helper/titlebarupdate.hxx> +#include <loadenv/targethelper.hxx> +#include <taskcreatordefs.hxx> + +#include <com/sun/star/frame/Frame.hpp> +#include <com/sun/star/frame/XFrame2.hpp> +#include <com/sun/star/frame/XDesktop.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/Toolkit.hpp> +#include <com/sun/star/awt/WindowDescriptor.hpp> +#include <com/sun/star/awt/WindowAttribute.hpp> +#include <com/sun/star/awt/VclWindowPeerAttribute.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> + +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <svtools/colorcfg.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> + +using namespace framework; + +namespace { + +typedef comphelper::WeakComponentImplHelper< + css::lang::XServiceInfo, + css::lang::XSingleServiceFactory> TaskCreatorService_BASE; + +class TaskCreatorService : public TaskCreatorService_BASE +{ +private: + + /** @short the global uno service manager. + @descr Must be used to create own needed services. + */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + +public: + + explicit TaskCreatorService(css::uno::Reference< css::uno::XComponentContext > xContext); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.TaskCreator"; + } + + 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.frame.TaskCreator"}; + } + + // XSingleServiceFactory + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstance() override; + + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstanceWithArguments(const css::uno::Sequence< css::uno::Any >& lArguments) override; + +private: + + css::uno::Reference< css::awt::XWindow > implts_createContainerWindow( const css::uno::Reference< css::awt::XWindow >& xParentWindow , + const css::awt::Rectangle& aPosSize , + bool bTopWindow ); + + void implts_applyDocStyleToWindow(const css::uno::Reference< css::awt::XWindow >& xWindow) const; + + css::uno::Reference< css::frame::XFrame2 > implts_createFrame( const css::uno::Reference< css::frame::XFrame >& xParentFrame , + const css::uno::Reference< css::awt::XWindow >& xContainerWindow , + const OUString& sName ); + + void implts_establishWindowStateListener( const css::uno::Reference< css::frame::XFrame2 >& xFrame ); + void implts_establishTitleBarUpdate( const css::uno::Reference< css::frame::XFrame2 >& xFrame ); + + void implts_establishDocModifyListener( const css::uno::Reference< css::frame::XFrame2 >& xFrame ); + + OUString impl_filterNames( const OUString& sName ); +}; + +TaskCreatorService::TaskCreatorService(css::uno::Reference< css::uno::XComponentContext > xContext) + : m_xContext (std::move(xContext )) +{ +} + +css::uno::Reference< css::uno::XInterface > SAL_CALL TaskCreatorService::createInstance() +{ + return createInstanceWithArguments(css::uno::Sequence< css::uno::Any >()); +} + +css::uno::Reference< css::uno::XInterface > SAL_CALL TaskCreatorService::createInstanceWithArguments(const css::uno::Sequence< css::uno::Any >& lArguments) +{ + ::comphelper::SequenceAsHashMap lArgs(lArguments); + + css::uno::Reference< css::frame::XFrame > xParentFrame = lArgs.getUnpackedValueOrDefault(ARGUMENT_PARENTFRAME , css::uno::Reference< css::frame::XFrame >()); + OUString sFrameName = lArgs.getUnpackedValueOrDefault(ARGUMENT_FRAMENAME , OUString() ); + bool bVisible = lArgs.getUnpackedValueOrDefault(ARGUMENT_MAKEVISIBLE , false ); + bool bCreateTopWindow = lArgs.getUnpackedValueOrDefault(ARGUMENT_CREATETOPWINDOW , true ); + // only possize=[0,0,0,0] triggers default handling of vcl ! + css::awt::Rectangle aPosSize = lArgs.getUnpackedValueOrDefault(ARGUMENT_POSSIZE , css::awt::Rectangle(0, 0, 0, 0) ); + css::uno::Reference< css::awt::XWindow > xContainerWindow = lArgs.getUnpackedValueOrDefault(ARGUMENT_CONTAINERWINDOW , css::uno::Reference< css::awt::XWindow >() ); + bool bSupportPersistentWindowState = lArgs.getUnpackedValueOrDefault(ARGUMENT_SUPPORTPERSISTENTWINDOWSTATE , false ); + bool bEnableTitleBarUpdate = lArgs.getUnpackedValueOrDefault(ARGUMENT_ENABLE_TITLEBARUPDATE , true ); + // If the frame is explicitly requested to be hidden. + bool bHidden = lArgs.getUnpackedValueOrDefault("HiddenForConversion", false); + + // We use FrameName property to set it as API name of the new created frame later. + // But those frame names must be different from the set of special target names as e.g. _blank, _self etcpp ! + OUString sRightName = impl_filterNames(sFrameName); + + // if no external frame window was given ... create a new one. + if ( ! xContainerWindow.is()) + { + css::uno::Reference< css::awt::XWindow > xParentWindow; + if (xParentFrame.is()) + xParentWindow = xParentFrame->getContainerWindow(); + + // Parent has no own window ... + // So we have to create a top level window always ! + if ( ! xParentWindow.is()) + bCreateTopWindow = true; + + xContainerWindow = implts_createContainerWindow(xParentWindow, aPosSize, bCreateTopWindow); + } + + // #i53630# + // Mark all document windows as "special ones", so VCL can bind + // special features to it. Because VCL doesn't know anything about documents ... + // Note: Doing so it's no longer supported, that e.g. our wizards can use findFrame(_blank) + // to create it's previous frames. They must do it manually by using WindowDescriptor+Toolkit! + css::uno::Reference< css::frame::XDesktop > xDesktop(xParentFrame, css::uno::UNO_QUERY); + bool bTopLevelDocumentWindow = ( + sRightName.isEmpty() && + ( + (! xParentFrame.is() ) || + ( xDesktop.is() ) + ) + ); + if (bTopLevelDocumentWindow) + implts_applyDocStyleToWindow(xContainerWindow); + //-------------------> + + // create the new frame + VclPtr<vcl::Window> pContainerWindow = VCLUnoHelper::GetWindow(xContainerWindow); + if (pContainerWindow && bHidden) + { + WindowExtendedStyle eStyle = pContainerWindow->GetExtendedStyle(); + eStyle |= WindowExtendedStyle::DocHidden; + pContainerWindow->SetExtendedStyle(eStyle); + } + css::uno::Reference< css::frame::XFrame2 > xFrame = implts_createFrame(xParentFrame, xContainerWindow, sRightName); + + // special freature: + // A special listener will restore pos/size states in case + // a component was loaded into the frame first time. + if (bSupportPersistentWindowState) + implts_establishWindowStateListener(xFrame); + + // special feature: On Mac we need tagging the window in case + // the underlying model was modified. + // VCL will ignore our calls in case different platform then Mac + // is used ... + if (bTopLevelDocumentWindow) + implts_establishDocModifyListener (xFrame); + + // special freature: + // A special listener will update title bar (text and icon) + // if component of frame will be changed. + if (bEnableTitleBarUpdate) + implts_establishTitleBarUpdate(xFrame); + + // Make it visible directly here... + // if it's required from outside. + if (bVisible) + xContainerWindow->setVisible(bVisible); + + return css::uno::Reference< css::uno::XInterface >(xFrame, css::uno::UNO_QUERY_THROW); +} + +void TaskCreatorService::implts_applyDocStyleToWindow(const css::uno::Reference< css::awt::XWindow >& xWindow) const +{ + // SYNCHRONIZED -> + SolarMutexGuard aSolarGuard; + VclPtr<vcl::Window> pVCLWindow = VCLUnoHelper::GetWindow(xWindow); + if (pVCLWindow) + pVCLWindow->SetExtendedStyle(WindowExtendedStyle::Document); + // <- SYNCHRONIZED +} + +css::uno::Reference< css::awt::XWindow > TaskCreatorService::implts_createContainerWindow( const css::uno::Reference< css::awt::XWindow >& xParentWindow , + const css::awt::Rectangle& aPosSize , + bool bTopWindow ) +{ + // get toolkit to create task container window + css::uno::Reference< css::awt::XToolkit2 > xToolkit = css::awt::Toolkit::create( m_xContext ); + + // Check if child frames can be created really. We need at least a valid window at the parent frame ... + css::uno::Reference< css::awt::XWindowPeer > xParentWindowPeer; + if ( ! bTopWindow) + { + if ( ! xParentWindow.is()) + bTopWindow = false; + else + xParentWindowPeer.set(xParentWindow, css::uno::UNO_QUERY_THROW); + } + + // describe window properties. + css::awt::WindowDescriptor aDescriptor; + if (bTopWindow) + { + aDescriptor.Type = css::awt::WindowClass_TOP; + aDescriptor.WindowServiceName = "window"; + aDescriptor.ParentIndex = -1; + aDescriptor.Parent.clear(); + aDescriptor.Bounds = aPosSize; + aDescriptor.WindowAttributes = css::awt::WindowAttribute::BORDER | + css::awt::WindowAttribute::MOVEABLE | + css::awt::WindowAttribute::SIZEABLE | + css::awt::WindowAttribute::CLOSEABLE | + css::awt::VclWindowPeerAttribute::CLIPCHILDREN; + } + else + { + aDescriptor.Type = css::awt::WindowClass_TOP; + aDescriptor.WindowServiceName = "dockingwindow"; + aDescriptor.ParentIndex = 1; + aDescriptor.Parent = xParentWindowPeer; + aDescriptor.Bounds = aPosSize; + aDescriptor.WindowAttributes = css::awt::VclWindowPeerAttribute::CLIPCHILDREN; + } + + // create a new blank container window and get access to parent container to append new created task. + css::uno::Reference< css::awt::XWindowPeer > xPeer = xToolkit->createWindow( aDescriptor ); + css::uno::Reference< css::awt::XWindow > xWindow ( xPeer, css::uno::UNO_QUERY_THROW ); + + sal_Int32 nBackground = 0xffffffff; + + if (bTopWindow) + { + try + { + nBackground = sal_Int32(::svtools::ColorConfig().GetColorValue(::svtools::APPBACKGROUND).nColor); + } + catch (const css::uno::Exception &) + { + // Ignore + } + } + xPeer->setBackground(nBackground); + + return xWindow; +} + +css::uno::Reference< css::frame::XFrame2 > TaskCreatorService::implts_createFrame( const css::uno::Reference< css::frame::XFrame >& xParentFrame , + const css::uno::Reference< css::awt::XWindow >& xContainerWindow, + const OUString& sName ) +{ + // create new frame. + css::uno::Reference< css::frame::XFrame2 > xNewFrame = css::frame::Frame::create( m_xContext ); + + // Set window on frame. + // Do it before calling any other interface methods ... + // The new created frame must be initialized before you can do anything else there. + xNewFrame->initialize( xContainerWindow ); + + // Put frame to the frame tree. + // Note: The property creator/parent will be set on the new putted frame automatically ... by the parent container. + if (xParentFrame.is()) + { + css::uno::Reference< css::frame::XFramesSupplier > xSupplier (xParentFrame, css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::frame::XFrames > xContainer = xSupplier->getFrames(); + xContainer->append( css::uno::Reference<css::frame::XFrame>(xNewFrame, css::uno::UNO_QUERY_THROW) ); + } + + // Set it's API name (if there is one from outside) + if (!sName.isEmpty()) + xNewFrame->setName( sName ); + + return xNewFrame; +} + +void TaskCreatorService::implts_establishWindowStateListener( const css::uno::Reference< css::frame::XFrame2 >& xFrame ) +{ + // Special feature: It's allowed for frames using a top level window only! + // We must create a special listener service and couple it with the new created task frame. + // He will restore or save the window state of it ... + // See used classes for further information too. + rtl::Reference<PersistentWindowState> pPersistentStateHandler = new PersistentWindowState( m_xContext ); + + css::uno::Sequence< css::uno::Any > lInitData{ css::uno::Any(xFrame) }; + pPersistentStateHandler->initialize(lInitData); +} + +void TaskCreatorService::implts_establishDocModifyListener( const css::uno::Reference< css::frame::XFrame2 >& xFrame ) +{ + // Special feature: It's allowed for frames using a top level window only! + // We must create a special listener service and couple it with the new created task frame. + // It will tag the window as modified if the underlying model was modified ... + rtl::Reference<TagWindowAsModified> pTag = new TagWindowAsModified(); + + css::uno::Sequence< css::uno::Any > lInitData{ css::uno::Any(xFrame) }; + pTag->initialize(lInitData); +} + +void TaskCreatorService::implts_establishTitleBarUpdate( const css::uno::Reference< css::frame::XFrame2 >& xFrame ) +{ + rtl::Reference<TitleBarUpdate> pHelper = new TitleBarUpdate (m_xContext); + + css::uno::Sequence< css::uno::Any > lInitData{ css::uno::Any(xFrame) }; + pHelper->initialize(lInitData); +} + +OUString TaskCreatorService::impl_filterNames( const OUString& sName ) +{ + OUString sFiltered; + if (TargetHelper::isValidNameForFrame(sName)) + sFiltered = sName; + return sFiltered; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_TaskCreator_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new TaskCreatorService(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/uriabbreviation.cxx b/framework/source/services/uriabbreviation.cxx new file mode 100644 index 000000000..6faa0c8e3 --- /dev/null +++ b/framework/source/services/uriabbreviation.cxx @@ -0,0 +1,75 @@ +/* -*- 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 <services/uriabbreviation.hxx> + +#include <sal/config.h> + +#include <tools/urlobj.hxx> +#include <cppuhelper/supportsservice.hxx> + +// framework namespace +namespace framework +{ + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL UriAbbreviation::getImplementationName() +{ + return "com.sun.star.comp.framework.UriAbbreviation"; +} + +sal_Bool SAL_CALL UriAbbreviation::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL UriAbbreviation::getSupportedServiceNames() +{ + return { "com.sun.star.util.UriAbbreviation" }; +} + +UriAbbreviation::UriAbbreviation(css::uno::Reference< css::uno::XComponentContext > const & ) +{ +} + +// css::util::XStringAbbreviation: +OUString SAL_CALL UriAbbreviation::abbreviateString(const css::uno::Reference< css::util::XStringWidth > & xStringWidth, ::sal_Int32 nWidth, const OUString & aString) +{ + OUString aResult( aString ); + if ( xStringWidth.is() ) + { + // Use INetURLObject to abbreviate URLs + INetURLObject aURL( aString ); + aResult = aURL.getAbbreviated( xStringWidth, nWidth, INetURLObject::DecodeMechanism::Unambiguous ); + } + + return aResult; +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_UriAbbreviation_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::UriAbbreviation(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/services/urltransformer.cxx b/framework/source/services/urltransformer.cxx new file mode 100644 index 000000000..b4fd3de16 --- /dev/null +++ b/framework/source/services/urltransformer.cxx @@ -0,0 +1,310 @@ +/* -*- 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 <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <tools/urlobj.hxx> +#include <rtl/ustrbuf.hxx> + +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/util/URL.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +namespace { + +class URLTransformer : public ::cppu::WeakImplHelper< css::util::XURLTransformer, css::lang::XServiceInfo> +{ +public: + URLTransformer() {} + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.URLTransformer"; + } + + 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.util.URLTransformer"}; + } + + virtual sal_Bool SAL_CALL parseStrict( css::util::URL& aURL ) override; + + virtual sal_Bool SAL_CALL parseSmart( css::util::URL& aURL, const OUString& sSmartProtocol ) override; + + virtual sal_Bool SAL_CALL assemble( css::util::URL& aURL ) override; + + virtual OUString SAL_CALL getPresentation( const css::util::URL& aURL, sal_Bool bWithPassword ) override; +}; + +void lcl_ParserHelper(INetURLObject& _rParser, css::util::URL& _rURL,bool _bUseIntern) +{ + // Get all information about this URL. + _rURL.Protocol = INetURLObject::GetScheme( _rParser.GetProtocol() ); + _rURL.User = _rParser.GetUser ( INetURLObject::DecodeMechanism::WithCharset ); + _rURL.Password = _rParser.GetPass ( INetURLObject::DecodeMechanism::WithCharset ); + _rURL.Server = _rParser.GetHost ( INetURLObject::DecodeMechanism::WithCharset ); + _rURL.Port = static_cast<sal_Int16>(_rParser.GetPort()); + + sal_Int32 nCount = _rParser.getSegmentCount( false ); + if ( nCount > 0 ) + { + // Don't add last segment as it is the name! + --nCount; + + OUStringBuffer aPath(128); + for ( sal_Int32 nIndex = 0; nIndex < nCount; nIndex++ ) + { + aPath.append( '/'); + aPath.append( _rParser.getName( nIndex, false, INetURLObject::DecodeMechanism::NONE )); + } + + if ( nCount > 0 ) + aPath.append( '/' ); // final slash! + + _rURL.Path = aPath.makeStringAndClear(); + _rURL.Name = _rParser.getName( INetURLObject::LAST_SEGMENT, false, INetURLObject::DecodeMechanism::NONE ); + } + else + { + _rURL.Path = _rParser.GetURLPath( INetURLObject::DecodeMechanism::NONE ); + _rURL.Name = _rParser.GetLastName(); + } + + _rURL.Arguments = _rParser.GetParam(); + _rURL.Mark = _rParser.GetMark( INetURLObject::DecodeMechanism::WithCharset ); + + // INetURLObject supports only an intelligent method of parsing URL's. So write + // back Complete to have a valid encoded URL in all cases! + _rURL.Complete = _rParser.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + if ( _bUseIntern ) + _rURL.Complete = _rURL.Complete.intern(); + + _rParser.SetMark( u"" ); + _rParser.SetParam( u"" ); + + _rURL.Main = _rParser.GetMainURL( INetURLObject::DecodeMechanism::NONE ); +} + +// XURLTransformer +sal_Bool SAL_CALL URLTransformer::parseStrict( css::util::URL& aURL ) +{ + // Safe impossible cases. + if ( aURL.Complete.isEmpty() ) + { + return false; + } + // Try to extract the protocol + sal_Int32 nURLIndex = aURL.Complete.indexOf( ':' ); + if ( nURLIndex <= 1 ) + return false; + + std::u16string_view aProtocol = aURL.Complete.subView( 0, nURLIndex+1 ); + + // If INetURLObject knows this protocol let it parse + if ( INetURLObject::CompareProtocolScheme( aProtocol ) != INetProtocol::NotValid ) + { + // Initialize parser with given URL. + INetURLObject aParser( aURL.Complete ); + + // Get all information about this URL. + INetProtocol eINetProt = aParser.GetProtocol(); + if ( eINetProt == INetProtocol::NotValid ) + { + return false; + } + else if ( !aParser.HasError() ) + { + lcl_ParserHelper(aParser,aURL,false); + // Return "URL is parsed". + return true; + } + } + else + { + // Minimal support for unknown protocols. This is mandatory to support the "Protocol Handlers" implemented + // in framework! + aURL.Protocol = aProtocol; + aURL.Main = aURL.Complete; + aURL.Path = aURL.Complete.copy( nURLIndex+1 ); + + // Return "URL is parsed". + return true; + } + + return false; +} + +// XURLTransformer + +sal_Bool SAL_CALL URLTransformer::parseSmart( css::util::URL& aURL, + const OUString& sSmartProtocol ) +{ + // Safe impossible cases. + if ( aURL.Complete.isEmpty() ) + { + return false; + } + + // Initialize parser with given URL. + INetURLObject aParser; + + aParser.SetSmartProtocol( INetURLObject::CompareProtocolScheme( sSmartProtocol )); + bool bOk = aParser.SetSmartURL( aURL.Complete ); + if ( bOk ) + { + lcl_ParserHelper(aParser,aURL,true); + // Return "URL is parsed". + return true; + } + else + { + // Minimal support for unknown protocols. This is mandatory to support the "Protocol Handlers" implemented + // in framework! + if ( INetURLObject::CompareProtocolScheme( sSmartProtocol ) == INetProtocol::NotValid ) + { + // Try to extract the protocol + sal_Int32 nIndex = aURL.Complete.indexOf( ':' ); + if ( nIndex > 1 ) + { + OUString aProtocol = aURL.Complete.copy( 0, nIndex+1 ); + + // If INetURLObject knows this protocol something is wrong as detected before => + // give up and return false! + if ( INetURLObject::CompareProtocolScheme( aProtocol ) != INetProtocol::NotValid ) + return false; + else + aURL.Protocol = aProtocol; + } + else + return false; + + aURL.Main = aURL.Complete; + aURL.Path = aURL.Complete.copy( nIndex+1 ); + return true; + } + else + return false; + } +} + +// XURLTransformer +sal_Bool SAL_CALL URLTransformer::assemble( css::util::URL& aURL ) +{ + // Initialize parser. + INetURLObject aParser; + + if ( INetURLObject::CompareProtocolScheme( aURL.Protocol ) != INetProtocol::NotValid ) + { + OUStringBuffer aCompletePath( aURL.Path ); + + // Concat the name if it is provided, just support a final slash + if ( !aURL.Name.isEmpty() ) + { + sal_Int32 nIndex = aURL.Path.lastIndexOf( '/' ); + if ( nIndex == ( aURL.Path.getLength() -1 )) + aCompletePath.append( aURL.Name ); + else + { + aCompletePath.append( '/' ); + aCompletePath.append( aURL.Name ); + } + } + + bool bResult = aParser.ConcatData( + INetURLObject::CompareProtocolScheme( aURL.Protocol ) , + aURL.User , + aURL.Password , + aURL.Server , + aURL.Port , + aCompletePath.makeStringAndClear() ); + + if ( !bResult ) + return false; + + // First parse URL WITHOUT ... + aURL.Main = aParser.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + // ...and then WITH parameter and mark. + aParser.SetParam( aURL.Arguments); + aParser.SetMark ( aURL.Mark, INetURLObject::EncodeMechanism::All ); + aURL.Complete = aParser.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + // Return "URL is assembled". + return true; + } + else if ( !aURL.Protocol.isEmpty() ) + { + // Minimal support for unknown protocols + aURL.Complete = aURL.Protocol + aURL.Path; + aURL.Main = aURL.Complete; + return true; + } + + return false; +} + +// XURLTransformer + +OUString SAL_CALL URLTransformer::getPresentation( const css::util::URL& aURL, + sal_Bool bWithPassword ) +{ + // Safe impossible cases. + if ( aURL.Complete.isEmpty() ) + { + return OUString(); + } + + // Check given URL + css::util::URL aTestURL = aURL; + bool bParseResult = parseSmart( aTestURL, aTestURL.Protocol ); + if ( bParseResult ) + { + if ( !bWithPassword && !aTestURL.Password.isEmpty() ) + { + // Exchange password text with other placeholder string + aTestURL.Password = "<******>"; + assemble( aTestURL ); + } + + // Convert internal URLs to "praesentation"-URLs! + OUString sPraesentationURL; + INetURLObject::translateToExternal( aTestURL.Complete, sPraesentationURL, INetURLObject::DecodeMechanism::Unambiguous ); + + return sPraesentationURL; + } + else + return OUString(); +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_URLTransformer_get_implementation( + css::uno::XComponentContext *, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new URLTransformer()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/CommandImageResolver.cxx b/framework/source/uiconfiguration/CommandImageResolver.cxx new file mode 100644 index 000000000..a431ae320 --- /dev/null +++ b/framework/source/uiconfiguration/CommandImageResolver.cxx @@ -0,0 +1,152 @@ +/* -*- 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/. + */ + +#include "CommandImageResolver.hxx" +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <rtl/ustrbuf.hxx> +#include <tools/urlobj.hxx> + +using css::uno::Sequence; + +namespace vcl +{ + +namespace +{ + +const o3tl::enumarray<ImageType, const char*> ImageType_Prefixes = +{ + "cmd/sc_", + "cmd/lc_", + "cmd/32/" +}; + +OUString lclConvertToCanonicalName(const OUString& rFileName) +{ + bool bRemoveSlash(true); + sal_Int32 nLength = rFileName.getLength(); + const sal_Unicode* pString = rFileName.getStr(); + + OUStringBuffer aBuffer(nLength*2); + + for (sal_Int32 i = 0; i < nLength; i++) + { + const sal_Unicode cCurrentChar = pString[i]; + switch (cCurrentChar) + { + // map forbidden characters to escape + case '/': + if (!bRemoveSlash) + aBuffer.append("%2f"); + break; + case '\\': aBuffer.append("%5c"); bRemoveSlash = false; break; + case ':': aBuffer.append("%3a"); bRemoveSlash = false; break; + case '*': aBuffer.append("%2a"); bRemoveSlash = false; break; + case '?': aBuffer.append("%3f"); bRemoveSlash = false; break; + case '<': aBuffer.append("%3c"); bRemoveSlash = false; break; + case '>': aBuffer.append("%3e"); bRemoveSlash = false; break; + case '|': aBuffer.append("%7c"); bRemoveSlash = false; break; + default: + aBuffer.append(cCurrentChar); bRemoveSlash = false; break; + } + } + return aBuffer.makeStringAndClear(); +} + +} // end anonymous namespace + +CommandImageResolver::CommandImageResolver() +{ +} + +CommandImageResolver::~CommandImageResolver() +{ +} + +void CommandImageResolver::registerCommands(const Sequence<OUString>& aCommandSequence) +{ + sal_Int32 nSequenceSize = aCommandSequence.getLength(); + + m_aImageCommandNameVector.resize(nSequenceSize); + m_aImageNameVector.resize(nSequenceSize); + + for (sal_Int32 i = 0; i < nSequenceSize; ++i) + { + OUString aCommandName(aCommandSequence[i]); + OUString aImageName; + + m_aImageCommandNameVector[i] = aCommandName; + + if (aCommandName.indexOf(".uno:") != 0) + { + INetURLObject aUrlObject(aCommandName, INetURLObject::EncodeMechanism::All); + aImageName = aUrlObject.GetURLPath(); + aImageName = lclConvertToCanonicalName(aImageName); + } + else + { + // just remove the schema + if (aCommandName.getLength() > 5) + aImageName = aCommandName.copy(5); + + // Search for query part. + if (aImageName.indexOf('?') != -1) + aImageName = lclConvertToCanonicalName(aImageName); + } + + // Image names are not case-dependent. Always use lower case characters to + // reflect this. + aImageName = aImageName.toAsciiLowerCase() + ".png"; + + m_aImageNameVector[i] = aImageName; + m_aCommandToImageNameMap[aCommandName] = aImageName; + } +} + +bool CommandImageResolver::hasImage(const OUString& rCommandURL) +{ + CommandToImageNameMap::const_iterator pIterator = m_aCommandToImageNameMap.find(rCommandURL); + return pIterator != m_aCommandToImageNameMap.end(); +} + +ImageList* CommandImageResolver::getImageList(ImageType nImageType) +{ + const OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme(); + + if (sIconTheme != m_sIconTheme) + { + m_sIconTheme = sIconTheme; + for (auto& rp : m_pImageList) + rp.reset(); + } + + if (!m_pImageList[nImageType]) + { + OUString sIconPath = OUString::createFromAscii(ImageType_Prefixes[nImageType]); + m_pImageList[nImageType].reset( new ImageList(m_aImageNameVector, sIconPath) ); + } + + return m_pImageList[nImageType].get(); +} + +Image CommandImageResolver::getImageFromCommandURL(ImageType nImageType, const OUString& rCommandURL) +{ + CommandToImageNameMap::const_iterator pIterator = m_aCommandToImageNameMap.find(rCommandURL); + if (pIterator != m_aCommandToImageNameMap.end()) + { + ImageList* pImageList = getImageList(nImageType); + return pImageList->GetImage(pIterator->second); + } + return Image(); +} + +} // end namespace vcl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/CommandImageResolver.hxx b/framework/source/uiconfiguration/CommandImageResolver.hxx new file mode 100644 index 000000000..0622caf33 --- /dev/null +++ b/framework/source/uiconfiguration/CommandImageResolver.hxx @@ -0,0 +1,53 @@ +/* -*- 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/. + */ + +#pragma once + +#include <vcl/image.hxx> +#include <o3tl/enumarray.hxx> + +#include <com/sun/star/uno/Sequence.hxx> + +#include "ImageList.hxx" + +#include <memory> +#include <unordered_map> +#include <vector> + +namespace vcl +{ +class CommandImageResolver final +{ +private: + typedef std::unordered_map<OUString, OUString> CommandToImageNameMap; + + CommandToImageNameMap m_aCommandToImageNameMap; + std::vector<OUString> m_aImageCommandNameVector; + std::vector<OUString> m_aImageNameVector; + + o3tl::enumarray<ImageType, std::unique_ptr<ImageList>> m_pImageList; + OUString m_sIconTheme; + + ImageList* getImageList(ImageType nImageType); + +public: + CommandImageResolver(); + ~CommandImageResolver(); + + void registerCommands(const css::uno::Sequence<OUString>& aCommandSequence); + Image getImageFromCommandURL(ImageType nImageType, const OUString& rCommandURL); + + std::vector<OUString>& getCommandNames() { return m_aImageCommandNameVector; } + + bool hasImage(const OUString& rCommandURL); +}; + +} // end namespace vcl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/ImageList.cxx b/framework/source/uiconfiguration/ImageList.cxx new file mode 100644 index 000000000..0e64d219c --- /dev/null +++ b/framework/source/uiconfiguration/ImageList.cxx @@ -0,0 +1,201 @@ +/* -*- 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/log.hxx> +#include <tools/debug.hxx> +#include <vcl/image.hxx> +#include "ImageList.hxx" + +ImageList::ImageList() +{ +} + +ImageList::ImageList(const std::vector< OUString >& rNameVector, + const OUString& rPrefix) +{ + SAL_INFO( "vcl", "vcl: ImageList::ImageList(const vector< OUString >& ..." ); + + maImages.reserve( rNameVector.size() ); + + maPrefix = rPrefix; + for( size_t i = 0; i < rNameVector.size(); ++i ) + ImplAddImage( rPrefix, rNameVector[ i ], static_cast< sal_uInt16 >( i ) + 1, Image() ); +} + +// FIXME: Rather a performance hazard +BitmapEx ImageList::GetAsHorizontalStrip() const +{ + sal_uInt16 nCount = maImages.size(); + if( !nCount ) + return BitmapEx(); + + BitmapEx aTempl = maImages[ 0 ]->maImage.GetBitmapEx(); + Size aImageSize(aTempl.GetSizePixel()); + Size aSize(aImageSize.Width() * nCount, aImageSize.Height()); + BitmapEx aResult( aTempl, Point(), aSize ); + + tools::Rectangle aSrcRect( Point( 0, 0 ), aImageSize ); + for (sal_uInt16 nIdx = 0; nIdx < nCount; nIdx++) + { + tools::Rectangle aDestRect( Point( nIdx * aImageSize.Width(), 0 ), aImageSize ); + ImageAryData *pData = maImages[ nIdx ].get(); + BitmapEx aTmp = pData->maImage.GetBitmapEx(); + aResult.CopyPixel( aDestRect, aSrcRect, &aTmp); + } + + return aResult; +} + +void ImageList::InsertFromHorizontalStrip( const BitmapEx &rBitmapEx, + const std::vector< OUString > &rNameVector ) +{ + sal_uInt16 nItems = sal::static_int_cast< sal_uInt16 >( rNameVector.size() ); + + if (!nItems) + return; + + Size aSize( rBitmapEx.GetSizePixel() ); + DBG_ASSERT (rBitmapEx.GetSizePixel().Width() % nItems == 0, + "ImageList::InsertFromHorizontalStrip - very odd size"); + aSize.setWidth( aSize.Width() / nItems ); + maImages.clear(); + maNameHash.clear(); + maImages.reserve( nItems ); + maPrefix.clear(); + + for (sal_uInt16 nIdx = 0; nIdx < nItems; nIdx++) + { + BitmapEx aBitmap( rBitmapEx, Point( nIdx * aSize.Width(), 0 ), aSize ); + ImplAddImage( maPrefix, rNameVector[ nIdx ], nIdx + 1, Image( aBitmap ) ); + } +} + +sal_uInt16 ImageList::ImplGetImageId( const OUString& rImageName ) const +{ + auto it = maNameHash.find( rImageName ); + if (it == maNameHash.end()) + return 0; + return it->second->mnId; +} + +void ImageList::AddImage( const OUString& rImageName, const Image& rImage ) +{ + SAL_WARN_IF( GetImagePos( rImageName ) != IMAGELIST_IMAGE_NOTFOUND, "vcl", "ImageList::AddImage() - ImageName already exists" ); + + ImplAddImage( maPrefix, rImageName, GetImageCount() + 1, rImage ); +} + +void ImageList::ReplaceImage( const OUString& rImageName, const Image& rImage ) +{ + const sal_uInt16 nId = ImplGetImageId( rImageName ); + + if( nId ) + { + // Just replace the bitmap rather than doing RemoveImage / AddImage + // which breaks index-based iteration. + ImageAryData *pImg = maNameHash[ rImageName ]; + pImg->maImage = rImage; + } +} + +void ImageList::RemoveImage( sal_uInt16 nId ) +{ + for( size_t i = 0; i < maImages.size(); ++i ) + { + if( maImages[ i ]->mnId == nId ) + { + ImplRemoveImage( static_cast< sal_uInt16 >( i ) ); + break; + } + } +} + +Image ImageList::GetImage( const OUString& rImageName ) const +{ + auto it = maNameHash.find( rImageName ); + if (it == maNameHash.end()) + return Image(); + return it->second->maImage; +} + +sal_uInt16 ImageList::GetImageCount() const +{ + return static_cast< sal_uInt16 >( maImages.size() ); +} + +sal_uInt16 ImageList::GetImagePos( std::u16string_view rImageName ) const +{ + if( !rImageName.empty() ) + { + for( size_t i = 0; i < maImages.size(); i++ ) + { + if (maImages[i]->maName == rImageName) + return static_cast< sal_uInt16 >( i ); + } + } + + return IMAGELIST_IMAGE_NOTFOUND; +} + +sal_uInt16 ImageList::GetImageId( sal_uInt16 nPos ) const +{ + return maImages[ nPos ]->mnId; +} + +const OUString & ImageList::GetImageName( sal_uInt16 nPos ) const +{ + return maImages[ nPos ]->maName; +} + +void ImageList::GetImageNames( std::vector< OUString >& rNames ) const +{ + SAL_INFO( "vcl", "vcl: ImageList::GetImageNames" ); + + rNames = std::vector< OUString >(); + + for(auto const & pImage : maImages) + { + const OUString& rName( pImage->maName ); + if( !rName.isEmpty()) + rNames.push_back( rName ); + } +} + +void ImageList::ImplAddImage( std::u16string_view aPrefix, const OUString &aName, + sal_uInt16 nId, const Image &aImage ) +{ + Image aInsert = aImage; + if (!aInsert) + aInsert = Image( OUString::Concat("private:graphicrepository/") + aPrefix + aName ); + + ImageAryData *pImg = new ImageAryData{ aName, nId, aInsert }; + maImages.emplace_back( pImg ); + if( !aName.isEmpty() ) + maNameHash [ aName ] = pImg; +} + +void ImageList::ImplRemoveImage( sal_uInt16 nPos ) +{ + ImageAryData *pImg = maImages[ nPos ].get(); + if( !pImg->maName.isEmpty() ) + maNameHash.erase( pImg->maName ); + maImages.erase( maImages.begin() + nPos ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/ImageList.hxx b/framework/source/uiconfiguration/ImageList.hxx new file mode 100644 index 000000000..0951b8369 --- /dev/null +++ b/framework/source/uiconfiguration/ImageList.hxx @@ -0,0 +1,78 @@ +/* -*- 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 . + */ + +#ifndef FRAMEWORK_SOURCE_UICONFIGURATION_IMAGELIST_HXX +#define FRAMEWORK_SOURCE_UICONFIGURATION_IMAGELIST_HXX + +#include <vcl/image.hxx> + +#include <string_view> +#include <unordered_map> +#include <vector> + +// Images identified by either name, or by id +struct ImageAryData +{ + OUString maName; + sal_uInt16 mnId; + Image maImage; +}; + + +class ImageList +{ +public: + explicit ImageList(); + ImageList( const std::vector<OUString>& rNameVector, + const OUString& rPrefix); + + void InsertFromHorizontalStrip( const BitmapEx &rBitmapEx, + const std::vector< OUString > &rNameVector ); + BitmapEx GetAsHorizontalStrip() const; + sal_uInt16 GetImageCount() const; + + void AddImage( const OUString& rImageName, const Image& rImage ); + + void ReplaceImage( const OUString& rImageName, const Image& rImage ); + + void RemoveImage( sal_uInt16 nId ); + + Image GetImage( const OUString& rImageName ) const; + + sal_uInt16 GetImagePos( std::u16string_view rImageName ) const; + + sal_uInt16 GetImageId( sal_uInt16 nPos ) const; + + const OUString & GetImageName( sal_uInt16 nPos ) const; + void GetImageNames( ::std::vector< OUString >& rNames ) const; + +private: + + std::vector< std::unique_ptr<ImageAryData> > maImages; + std::unordered_map< OUString, ImageAryData * > maNameHash; + OUString maPrefix; + + sal_uInt16 ImplGetImageId( const OUString& rImageName ) const; + void ImplAddImage( std::u16string_view aPrefix, const OUString &aName, sal_uInt16 nId, const Image &aImage ); + void ImplRemoveImage( sal_uInt16 nPos ); +}; + +#endif // INCLUDED_VCL_IMAGE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/globalsettings.cxx b/framework/source/uiconfiguration/globalsettings.cxx new file mode 100644 index 000000000..0883cc8af --- /dev/null +++ b/framework/source/uiconfiguration/globalsettings.cxx @@ -0,0 +1,262 @@ +/* -*- 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 <uiconfiguration/globalsettings.hxx> +#include <services.h> + +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XEventListener.hpp> + +#include <rtl/ref.hxx> +#include <comphelper/propertysequence.hxx> +#include <cppuhelper/implbase.hxx> +#include <mutex> +#include <utility> + +// Defines + +using namespace ::com::sun::star; + +// Namespace + +namespace framework +{ + +// Configuration access class for WindowState supplier implementation + +namespace { + +class GlobalSettings_Access : public ::cppu::WeakImplHelper< + css::lang::XComponent, + css::lang::XEventListener> +{ + public: + explicit GlobalSettings_Access( css::uno::Reference< css::uno::XComponentContext > xContext ); + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + // settings access + bool HasToolbarStatesInfo(); + bool GetToolbarStateInfo( GlobalSettings::StateInfo eStateInfo, css::uno::Any& aValue ); + + private: + void impl_initConfigAccess(); + + std::mutex m_mutex; + bool m_bDisposed : 1, + m_bConfigRead : 1; + OUString m_aNodeRefStates; + OUString m_aPropStatesEnabled; + OUString m_aPropLocked; + OUString m_aPropDocked; + css::uno::Reference< css::container::XNameAccess > m_xConfigAccess; + css::uno::Reference< css::uno::XComponentContext> m_xContext; +}; + +} + +GlobalSettings_Access::GlobalSettings_Access( css::uno::Reference< css::uno::XComponentContext > xContext ) : + m_bDisposed( false ), + m_bConfigRead( false ), + m_aNodeRefStates( "States" ), + m_aPropStatesEnabled( "StatesEnabled" ), + m_aPropLocked( "Locked" ), + m_aPropDocked( "Docked" ), + m_xContext(std::move( xContext )) +{ +} + +// XComponent +void SAL_CALL GlobalSettings_Access::dispose() +{ + std::unique_lock g(m_mutex); + m_xConfigAccess.clear(); + m_bDisposed = true; +} + +void SAL_CALL GlobalSettings_Access::addEventListener( const css::uno::Reference< css::lang::XEventListener >& ) +{ +} + +void SAL_CALL GlobalSettings_Access::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& ) +{ +} + +// XEventListener +void SAL_CALL GlobalSettings_Access::disposing( const css::lang::EventObject& ) +{ + std::unique_lock g(m_mutex); + m_xConfigAccess.clear(); +} + +// settings access +bool GlobalSettings_Access::HasToolbarStatesInfo() +{ + std::unique_lock g(m_mutex); + + if ( m_bDisposed ) + return false; + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + impl_initConfigAccess(); + } + + if ( m_xConfigAccess.is() ) + { + try + { + css::uno::Any a; + bool bValue; + a = m_xConfigAccess->getByName( m_aPropStatesEnabled ); + if ( a >>= bValue ) + return bValue; + } + catch ( const css::container::NoSuchElementException& ) + { + } + catch ( const css::uno::Exception& ) + { + } + } + + return false; +} + +bool GlobalSettings_Access::GetToolbarStateInfo( GlobalSettings::StateInfo eStateInfo, css::uno::Any& aValue ) +{ + std::unique_lock g(m_mutex); + + if ( m_bDisposed ) + return false; + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + impl_initConfigAccess(); + } + + if ( !m_xConfigAccess.is() ) + return false; + + try + { + css::uno::Any a = m_xConfigAccess->getByName( m_aNodeRefStates ); + css::uno::Reference< css::container::XNameAccess > xNameAccess; + if ( a >>= xNameAccess ) + { + if ( eStateInfo == GlobalSettings::STATEINFO_LOCKED ) + a = xNameAccess->getByName( m_aPropLocked ); + else if ( eStateInfo == GlobalSettings::STATEINFO_DOCKED ) + a = xNameAccess->getByName( m_aPropDocked ); + + aValue = a; + return true; + } + } + catch ( const css::container::NoSuchElementException& ) + { + } + catch ( const css::uno::Exception& ) + { + } + + return false; +} + +void GlobalSettings_Access::impl_initConfigAccess() +{ + try + { + if ( m_xContext.is() ) + { + css::uno::Reference< css::lang::XMultiServiceFactory > xConfigProvider = + css::configuration::theDefaultProvider::get( m_xContext ); + + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(OUString("/org.openoffice.Office.UI.GlobalSettings/Toolbars"))} + })); + m_xConfigAccess.set(xConfigProvider->createInstanceWithArguments( + SERVICENAME_CFGREADACCESS, aArgs ), + css::uno::UNO_QUERY ); + + css::uno::Reference< css::lang::XComponent >( + xConfigProvider, css::uno::UNO_QUERY_THROW )->addEventListener( + css::uno::Reference< css::lang::XEventListener >(this)); + } + } + catch ( const css::lang::WrappedTargetException& ) + { + } + catch ( const css::uno::Exception& ) + { + } +} + +// global class + +static GlobalSettings_Access* GetGlobalSettings( const css::uno::Reference< css::uno::XComponentContext >& rxContext ) +{ + static rtl::Reference<GlobalSettings_Access> pStaticSettings = new GlobalSettings_Access( rxContext ); + return pStaticSettings.get(); +} + +GlobalSettings::GlobalSettings( css::uno::Reference< css::uno::XComponentContext > xContext ) : + m_xContext(std::move( xContext )) +{ +} + +GlobalSettings::~GlobalSettings() +{ +} + +// settings access +bool GlobalSettings::HasToolbarStatesInfo() const +{ + GlobalSettings_Access* pSettings( GetGlobalSettings( m_xContext )); + + if ( pSettings ) + return pSettings->HasToolbarStatesInfo(); + else + return false; +} + +bool GlobalSettings::GetToolbarStateInfo( StateInfo eStateInfo, css::uno::Any& aValue ) +{ + GlobalSettings_Access* pSettings( GetGlobalSettings( m_xContext )); + + if ( pSettings ) + return pSettings->GetToolbarStateInfo( eStateInfo, aValue ); + else + return false; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/graphicnameaccess.cxx b/framework/source/uiconfiguration/graphicnameaccess.cxx new file mode 100644 index 000000000..6812f5604 --- /dev/null +++ b/framework/source/uiconfiguration/graphicnameaccess.cxx @@ -0,0 +1,80 @@ +/* -*- 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 <uiconfiguration/graphicnameaccess.hxx> + +#include <comphelper/sequence.hxx> + +using namespace ::com::sun::star; + +namespace framework +{ + +GraphicNameAccess::GraphicNameAccess() +{ +} + +GraphicNameAccess::~GraphicNameAccess() +{ +} + +void GraphicNameAccess::addElement( const OUString& rName, const uno::Reference< graphic::XGraphic >& rElement ) +{ + m_aNameToElementMap.emplace( rName, rElement ); +} + +// XNameAccess +uno::Any SAL_CALL GraphicNameAccess::getByName( const OUString& aName ) +{ + NameGraphicHashMap::const_iterator pIter = m_aNameToElementMap.find( aName ); + if ( pIter == m_aNameToElementMap.end() ) + throw container::NoSuchElementException(); + return uno::Any( pIter->second ); +} + +uno::Sequence< OUString > SAL_CALL GraphicNameAccess::getElementNames() +{ + if ( !m_aSeq.hasElements() ) + { + m_aSeq = comphelper::mapKeysToSequence(m_aNameToElementMap); + } + + return m_aSeq; +} + +sal_Bool SAL_CALL GraphicNameAccess::hasByName( const OUString& aName ) +{ + NameGraphicHashMap::const_iterator pIter = m_aNameToElementMap.find( aName ); + return ( pIter != m_aNameToElementMap.end() ); +} + +// XElementAccess +sal_Bool SAL_CALL GraphicNameAccess::hasElements() +{ + return ( !m_aNameToElementMap.empty() ); +} + +uno::Type SAL_CALL GraphicNameAccess::getElementType() +{ + return cppu::UnoType<graphic::XGraphic>::get(); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/imagemanager.cxx b/framework/source/uiconfiguration/imagemanager.cxx new file mode 100644 index 000000000..1e104b6df --- /dev/null +++ b/framework/source/uiconfiguration/imagemanager.cxx @@ -0,0 +1,173 @@ +/* -*- 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 <uiconfiguration/imagemanager.hxx> +#include "imagemanagerimpl.hxx" + +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <vcl/svapp.hxx> + +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::XInterface; +using ::com::sun::star::uno::Any; +using ::com::sun::star::graphic::XGraphic; +using namespace ::com::sun::star; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::embed; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; + +namespace framework +{ + +ImageManager::ImageManager( const uno::Reference< uno::XComponentContext >& rxContext, bool bForModule ) : + m_pImpl( new ImageManagerImpl(rxContext, this, bForModule) ) +{ +} + +ImageManager::~ImageManager() +{ + m_pImpl->clear(); +} + +// XComponent +void SAL_CALL ImageManager::dispose() +{ + m_pImpl->dispose(); +} + +void SAL_CALL ImageManager::addEventListener( const uno::Reference< XEventListener >& xListener ) +{ + m_pImpl->addEventListener(xListener); +} + +void SAL_CALL ImageManager::removeEventListener( const uno::Reference< XEventListener >& xListener ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + m_pImpl->removeEventListener(xListener); +} + +// Non-UNO methods +void ImageManager::setStorage( const uno::Reference< XStorage >& Storage ) +{ + SolarMutexGuard g; + + m_pImpl->m_xUserConfigStorage = Storage; + m_pImpl->implts_initialize(); +} + +// XInitialization +void SAL_CALL ImageManager::initialize( const Sequence< Any >& aArguments ) +{ + m_pImpl->initialize(aArguments); +} + +// XImageManager +void SAL_CALL ImageManager::reset() +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + m_pImpl->reset(); +} + +Sequence< OUString > SAL_CALL ImageManager::getAllImageNames( ::sal_Int16 nImageType ) +{ + return m_pImpl->getAllImageNames( nImageType ); +} + +sal_Bool SAL_CALL ImageManager::hasImage( ::sal_Int16 nImageType, const OUString& aCommandURL ) +{ + return m_pImpl->hasImage(nImageType,aCommandURL); +} + +Sequence< uno::Reference< XGraphic > > SAL_CALL ImageManager::getImages( + ::sal_Int16 nImageType, + const Sequence< OUString >& aCommandURLSequence ) +{ + return m_pImpl->getImages(nImageType,aCommandURLSequence); +} + +void SAL_CALL ImageManager::replaceImages( + ::sal_Int16 nImageType, + const Sequence< OUString >& aCommandURLSequence, + const Sequence< uno::Reference< XGraphic > >& aGraphicsSequence ) +{ + m_pImpl->replaceImages(nImageType,aCommandURLSequence,aGraphicsSequence); +} + +void SAL_CALL ImageManager::removeImages( ::sal_Int16 nImageType, const Sequence< OUString >& aCommandURLSequence ) +{ + m_pImpl->removeImages(nImageType,aCommandURLSequence); +} + +void SAL_CALL ImageManager::insertImages( ::sal_Int16 nImageType, const Sequence< OUString >& aCommandURLSequence, const Sequence< uno::Reference< XGraphic > >& aGraphicSequence ) +{ + m_pImpl->insertImages(nImageType,aCommandURLSequence,aGraphicSequence); +} + +// XUIConfiguration +void SAL_CALL ImageManager::addConfigurationListener( const uno::Reference< css::ui::XUIConfigurationListener >& xListener ) +{ + m_pImpl->addConfigurationListener(xListener); +} + +void SAL_CALL ImageManager::removeConfigurationListener( const uno::Reference< css::ui::XUIConfigurationListener >& xListener ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + m_pImpl->removeConfigurationListener(xListener); +} + +// XUIConfigurationPersistence +void SAL_CALL ImageManager::reload() +{ + m_pImpl->reload(); +} + +void SAL_CALL ImageManager::store() +{ + m_pImpl->store(); +} + +void SAL_CALL ImageManager::storeToStorage( const uno::Reference< XStorage >& Storage ) +{ + m_pImpl->storeToStorage(Storage); +} + +sal_Bool SAL_CALL ImageManager::isModified() +{ + return m_pImpl->isModified(); +} + +sal_Bool SAL_CALL ImageManager::isReadOnly() +{ + return m_pImpl->isReadOnly(); +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ImageManager_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new framework::ImageManager(context, /*bForModule*/false)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/imagemanagerimpl.cxx b/framework/source/uiconfiguration/imagemanagerimpl.cxx new file mode 100644 index 000000000..5211e79b9 --- /dev/null +++ b/framework/source/uiconfiguration/imagemanagerimpl.cxx @@ -0,0 +1,1205 @@ +/* -*- 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 "imagemanagerimpl.hxx" +#include <utility> +#include <xml/imagesconfiguration.hxx> +#include <uiconfiguration/imagetype.hxx> +#include <uiconfiguration/graphicnameaccess.hxx> + +#include <properties.h> + +#include <com/sun/star/frame/theUICommandDescription.hpp> +#include <com/sun/star/ui/ConfigurationEvent.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IllegalAccessException.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/InvalidStorageException.hpp> +#include <com/sun/star/embed/StorageWrappedTargetException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/ui/ImageType.hpp> +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <o3tl/enumrange.hxx> +#include <comphelper/sequence.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <vcl/filter/PngImageReader.hxx> +#include <vcl/pngwrite.hxx> +#include <memory> + +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::XInterface; +using ::com::sun::star::uno::RuntimeException; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::Any; +using ::com::sun::star::graphic::XGraphic; +using namespace ::com::sun::star; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::embed; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::ui; +using namespace ::cppu; + +const sal_Int16 MAX_IMAGETYPE_VALUE = css::ui::ImageType::SIZE_32; + +constexpr OUStringLiteral IMAGE_FOLDER = u"images"; +constexpr OUStringLiteral BITMAPS_FOLDER = u"Bitmaps"; + +const o3tl::enumarray<vcl::ImageType, const char*> IMAGELIST_XML_FILE = +{ + "sc_imagelist.xml", + "lc_imagelist.xml", + "xc_imagelist.xml" +}; + +const o3tl::enumarray<vcl::ImageType, const char*> BITMAP_FILE_NAMES = +{ + "sc_userimages.png", + "lc_userimages.png", + "xc_userimages.png" +}; + +namespace framework +{ + +static GlobalImageList* pGlobalImageList = nullptr; + +static std::mutex& getGlobalImageListMutex() +{ + static std::mutex mutex; + return mutex; +} + +static GlobalImageList* getGlobalImageList( const uno::Reference< uno::XComponentContext >& rxContext ) +{ + std::unique_lock guard( getGlobalImageListMutex() ); + + if ( pGlobalImageList == nullptr ) + pGlobalImageList = new GlobalImageList( rxContext ); + + return pGlobalImageList; +} + +CmdImageList::CmdImageList( uno::Reference< uno::XComponentContext > rxContext, OUString aModuleIdentifier ) : + m_bInitialized(false), + m_aModuleIdentifier(std::move( aModuleIdentifier )), + m_xContext(std::move( rxContext )) +{ +} + +CmdImageList::~CmdImageList() +{ +} + +void CmdImageList::initialize() +{ + if (m_bInitialized) + return; + + const OUString aCommandImageList(UICOMMANDDESCRIPTION_NAMEACCESS_COMMANDIMAGELIST); + + Sequence<OUString> aCommandImageSeq; + uno::Reference<XNameAccess> xCommandDesc = frame::theUICommandDescription::get(m_xContext); + + if (!m_aModuleIdentifier.isEmpty()) + { + // If we have a module identifier - use to retrieve the command image name list from it. + // Otherwise we will use the global command image list + try + { + xCommandDesc->getByName(m_aModuleIdentifier) >>= xCommandDesc; + if (xCommandDesc.is()) + xCommandDesc->getByName(aCommandImageList) >>= aCommandImageSeq; + } + catch (const NoSuchElementException&) + { + // Module unknown we will work with an empty command image list! + return; + } + } + + if (xCommandDesc.is()) + { + try + { + xCommandDesc->getByName(aCommandImageList) >>= aCommandImageSeq; + } + catch (const NoSuchElementException&) + { + } + catch (const WrappedTargetException&) + { + } + } + + m_aResolver.registerCommands(aCommandImageSeq); + + m_bInitialized = true; +} + + +Image CmdImageList::getImageFromCommandURL(vcl::ImageType nImageType, const OUString& rCommandURL) +{ + initialize(); + return m_aResolver.getImageFromCommandURL(nImageType, rCommandURL); +} + +bool CmdImageList::hasImage(vcl::ImageType /*nImageType*/, const OUString& rCommandURL) +{ + initialize(); + return m_aResolver.hasImage(rCommandURL); +} + +std::vector<OUString>& CmdImageList::getImageCommandNames() +{ + return m_aResolver.getCommandNames(); +} + +GlobalImageList::GlobalImageList( const uno::Reference< uno::XComponentContext >& rxContext ) : + CmdImageList( rxContext, OUString() ) +{ +} + +GlobalImageList::~GlobalImageList() +{ + std::unique_lock guard( getGlobalImageListMutex() ); + // remove global pointer as we destroy the object now + pGlobalImageList = nullptr; +} + +Image GlobalImageList::getImageFromCommandURL( vcl::ImageType nImageType, const OUString& rCommandURL ) +{ + std::unique_lock guard( getGlobalImageListMutex() ); + return CmdImageList::getImageFromCommandURL( nImageType, rCommandURL ); +} + +bool GlobalImageList::hasImage( vcl::ImageType nImageType, const OUString& rCommandURL ) +{ + std::unique_lock guard( getGlobalImageListMutex() ); + return CmdImageList::hasImage( nImageType, rCommandURL ); +} + +::std::vector< OUString >& GlobalImageList::getImageCommandNames() +{ + std::unique_lock guard( getGlobalImageListMutex() ); + return CmdImageList::getImageCommandNames(); +} + +static bool implts_checkAndScaleGraphic( uno::Reference< XGraphic >& rOutGraphic, const uno::Reference< XGraphic >& rInGraphic, vcl::ImageType nImageType ) +{ + if ( !rInGraphic.is() ) + { + rOutGraphic = uno::Reference<graphic::XGraphic>(); + return false; + } + + static const o3tl::enumarray<vcl::ImageType, Size> BITMAP_SIZE = + { + Size(16, 16), Size(24, 24), Size(32, 32) + }; + + // Check size and scale it + Graphic aImage(rInGraphic); + if (BITMAP_SIZE[nImageType] != aImage.GetSizePixel()) + { + BitmapEx aBitmap = aImage.GetBitmapEx(); + aBitmap.Scale(BITMAP_SIZE[nImageType]); + aImage = Graphic(aBitmap); + rOutGraphic = aImage.GetXGraphic(); + } + else + rOutGraphic = rInGraphic; + + return true; +} + +static vcl::ImageType implts_convertImageTypeToIndex( sal_Int16 nImageType ) +{ + if (nImageType & css::ui::ImageType::SIZE_LARGE) + return vcl::ImageType::Size26; + else if (nImageType & css::ui::ImageType::SIZE_32) + return vcl::ImageType::Size32; + else + return vcl::ImageType::Size16; +} + +ImageList* ImageManagerImpl::implts_getUserImageList( vcl::ImageType nImageType ) +{ + SolarMutexGuard g; + if ( !m_pUserImageList[nImageType] ) + implts_loadUserImages( nImageType, m_xUserImageStorage, m_xUserBitmapsStorage ); + + return m_pUserImageList[nImageType].get(); +} + +void ImageManagerImpl::implts_initialize() +{ + // Initialize the top-level structures with the storage data + if ( !m_xUserConfigStorage.is() ) + return; + + tools::Long nModes = m_bReadOnly ? ElementModes::READ : ElementModes::READWRITE; + + try + { + m_xUserImageStorage = m_xUserConfigStorage->openStorageElement( IMAGE_FOLDER, + nModes ); + if ( m_xUserImageStorage.is() ) + { + m_xUserBitmapsStorage = m_xUserImageStorage->openStorageElement( BITMAPS_FOLDER, + nModes ); + } + } + catch ( const css::container::NoSuchElementException& ) + { + } + catch ( const css::embed::InvalidStorageException& ) + { + } + catch ( const css::lang::IllegalArgumentException& ) + { + } + catch ( const css::io::IOException& ) + { + } + catch ( const css::embed::StorageWrappedTargetException& ) + { + } +} + +void ImageManagerImpl::implts_loadUserImages( + vcl::ImageType nImageType, + const uno::Reference< XStorage >& xUserImageStorage, + const uno::Reference< XStorage >& xUserBitmapsStorage ) +{ + SolarMutexGuard g; + + if ( xUserImageStorage.is() && xUserBitmapsStorage.is() ) + { + try + { + uno::Reference< XStream > xStream = xUserImageStorage->openStreamElement( OUString::createFromAscii( IMAGELIST_XML_FILE[nImageType] ), + ElementModes::READ ); + uno::Reference< XInputStream > xInputStream = xStream->getInputStream(); + + ImageItemDescriptorList aUserImageListInfo; + ImagesConfiguration::LoadImages( m_xContext, + xInputStream, + aUserImageListInfo ); + if ( !aUserImageListInfo.empty() ) + { + sal_Int32 nCount = aUserImageListInfo.size(); + std::vector< OUString > aUserImagesVector; + aUserImagesVector.reserve(nCount); + for ( sal_Int32 i=0; i < nCount; i++ ) + { + const ImageItemDescriptor& rItem = aUserImageListInfo[i]; + aUserImagesVector.push_back( rItem.aCommandURL ); + } + + uno::Reference< XStream > xBitmapStream = xUserBitmapsStorage->openStreamElement( + OUString::createFromAscii( BITMAP_FILE_NAMES[nImageType] ), + ElementModes::READ ); + + if ( xBitmapStream.is() ) + { + BitmapEx aUserBitmap; + { + std::unique_ptr<SvStream> pSvStream(utl::UcbStreamHelper::CreateStream( xBitmapStream )); + vcl::PngImageReader aPngReader( *pSvStream ); + aUserBitmap = aPngReader.read(); + } + + // Delete old image list and create a new one from the read bitmap + m_pUserImageList[nImageType].reset(new ImageList()); + m_pUserImageList[nImageType]->InsertFromHorizontalStrip + ( aUserBitmap, aUserImagesVector ); + return; + } + } + } + catch ( const css::container::NoSuchElementException& ) + { + } + catch ( const css::embed::InvalidStorageException& ) + { + } + catch ( const css::lang::IllegalArgumentException& ) + { + } + catch ( const css::io::IOException& ) + { + } + catch ( const css::embed::StorageWrappedTargetException& ) + { + } + } + + // Destroy old image list - create a new empty one + m_pUserImageList[nImageType].reset(new ImageList); +} + +bool ImageManagerImpl::implts_storeUserImages( + vcl::ImageType nImageType, + const uno::Reference< XStorage >& xUserImageStorage, + const uno::Reference< XStorage >& xUserBitmapsStorage ) +{ + SolarMutexGuard g; + + if ( !m_bModified ) + return false; + + ImageList* pImageList = implts_getUserImageList( nImageType ); + if ( pImageList->GetImageCount() > 0 ) + { + ImageItemDescriptorList aUserImageListInfo; + + for ( sal_uInt16 i=0; i < pImageList->GetImageCount(); i++ ) + { + ImageItemDescriptor aItem; + aItem.aCommandURL = pImageList->GetImageName( i ); + aUserImageListInfo.push_back( aItem ); + } + + uno::Reference< XTransactedObject > xTransaction; + uno::Reference< XOutputStream > xOutputStream; + uno::Reference< XStream > xStream = xUserImageStorage->openStreamElement( OUString::createFromAscii( IMAGELIST_XML_FILE[nImageType] ), + ElementModes::WRITE|ElementModes::TRUNCATE ); + if ( xStream.is() ) + { + uno::Reference< XStream > xBitmapStream = + xUserBitmapsStorage->openStreamElement( OUString::createFromAscii( BITMAP_FILE_NAMES[nImageType] ), + ElementModes::WRITE|ElementModes::TRUNCATE ); + if ( xBitmapStream.is() ) + { + { + std::unique_ptr<SvStream> pSvStream(utl::UcbStreamHelper::CreateStream( xBitmapStream )); + vcl::PNGWriter aPngWriter( pImageList->GetAsHorizontalStrip() ); + aPngWriter.Write( *pSvStream ); + } + + // Commit user bitmaps storage + xTransaction.set( xUserBitmapsStorage, UNO_QUERY ); + if ( xTransaction.is() ) + xTransaction->commit(); + } + + xOutputStream = xStream->getOutputStream(); + if ( xOutputStream.is() ) + ImagesConfiguration::StoreImages( m_xContext, xOutputStream, aUserImageListInfo ); + + // Commit user image storage + xTransaction.set( xUserImageStorage, UNO_QUERY ); + if ( xTransaction.is() ) + xTransaction->commit(); + } + + return true; + } + else + { + // Remove the streams from the storage, if we have no data. We have to catch + // the NoSuchElementException as it can be possible that there is no stream at all! + try + { + xUserImageStorage->removeElement( OUString::createFromAscii( IMAGELIST_XML_FILE[nImageType] )); + } + catch ( const css::container::NoSuchElementException& ) + { + } + + try + { + xUserBitmapsStorage->removeElement( OUString::createFromAscii( BITMAP_FILE_NAMES[nImageType] )); + } + catch ( const css::container::NoSuchElementException& ) + { + } + + uno::Reference< XTransactedObject > xTransaction; + + // Commit user image storage + xTransaction.set( xUserImageStorage, UNO_QUERY ); + if ( xTransaction.is() ) + xTransaction->commit(); + + // Commit user bitmaps storage + xTransaction.set( xUserBitmapsStorage, UNO_QUERY ); + if ( xTransaction.is() ) + xTransaction->commit(); + + return true; + } + + return false; +} + +const rtl::Reference< GlobalImageList >& ImageManagerImpl::implts_getGlobalImageList() +{ + SolarMutexGuard g; + + if ( !m_pGlobalImageList.is() ) + m_pGlobalImageList = getGlobalImageList( m_xContext ); + return m_pGlobalImageList; +} + +CmdImageList* ImageManagerImpl::implts_getDefaultImageList() +{ + SolarMutexGuard g; + + if ( !m_pDefaultImageList ) + m_pDefaultImageList.reset(new CmdImageList( m_xContext, m_aModuleIdentifier )); + + return m_pDefaultImageList.get(); +} + +ImageManagerImpl::ImageManagerImpl( uno::Reference< uno::XComponentContext > xContext, ::cppu::OWeakObject* pOwner, bool _bUseGlobal ) : + m_xContext(std::move( xContext )) + , m_pOwner(pOwner) + , m_aResourceString( "private:resource/images/moduleimages" ) + , m_bUseGlobal(_bUseGlobal) + , m_bReadOnly( true ) + , m_bInitialized( false ) + , m_bModified( false ) + , m_bDisposed( false ) +{ + for ( vcl::ImageType n : o3tl::enumrange<vcl::ImageType>() ) + { + m_pUserImageList[n] = nullptr; + m_bUserImageListModified[n] = false; + } +} + +ImageManagerImpl::~ImageManagerImpl() +{ + clear(); +} + +void ImageManagerImpl::dispose() +{ + uno::Reference< uno::XInterface > xOwner(m_pOwner); + css::lang::EventObject aEvent( xOwner ); + { + std::unique_lock aGuard(m_mutex); + m_aEventListeners.disposeAndClear( aGuard, aEvent ); + } + { + std::unique_lock aGuard(m_mutex); + m_aConfigListeners.disposeAndClear( aGuard, aEvent ); + } + + { + SolarMutexGuard g; + m_xUserConfigStorage.clear(); + m_xUserImageStorage.clear(); + m_xUserRootCommit.clear(); + m_bModified = false; + m_bDisposed = true; + + // delete user and default image list on dispose + for (auto& n : m_pUserImageList) + { + n.reset(); + } + m_pDefaultImageList.reset(); + } + +} +void ImageManagerImpl::addEventListener( const uno::Reference< XEventListener >& xListener ) +{ + { + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + } + + std::unique_lock aGuard(m_mutex); + m_aEventListeners.addInterface( aGuard, xListener ); +} + +void ImageManagerImpl::removeEventListener( const uno::Reference< XEventListener >& xListener ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + std::unique_lock aGuard(m_mutex); + m_aEventListeners.removeInterface( aGuard, xListener ); +} + +// XInitialization +void ImageManagerImpl::initialize( const Sequence< Any >& aArguments ) +{ + SolarMutexGuard g; + + if ( m_bInitialized ) + return; + + for ( const Any& rArg : aArguments ) + { + PropertyValue aPropValue; + if ( rArg >>= aPropValue ) + { + if ( aPropValue.Name == "UserConfigStorage" ) + { + aPropValue.Value >>= m_xUserConfigStorage; + } + else if ( aPropValue.Name == "ModuleIdentifier" ) + { + aPropValue.Value >>= m_aModuleIdentifier; + } + else if ( aPropValue.Name == "UserRootCommit" ) + { + aPropValue.Value >>= m_xUserRootCommit; + } + } + } + + if ( m_xUserConfigStorage.is() ) + { + uno::Reference< XPropertySet > xPropSet( m_xUserConfigStorage, UNO_QUERY ); + if ( xPropSet.is() ) + { + tools::Long nOpenMode = 0; + if ( xPropSet->getPropertyValue("OpenMode") >>= nOpenMode ) + m_bReadOnly = !( nOpenMode & ElementModes::WRITE ); + } + } + + implts_initialize(); + + m_bInitialized = true; +} + +// XImageManagerImpl +void ImageManagerImpl::reset() +{ + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + + std::vector< OUString > aUserImageNames; + + for ( vcl::ImageType i : o3tl::enumrange<vcl::ImageType>() ) + { + aUserImageNames.clear(); + ImageList* pImageList = implts_getUserImageList(i); + pImageList->GetImageNames( aUserImageNames ); + + Sequence< OUString > aRemoveList( comphelper::containerToSequence(aUserImageNames) ); + + // Remove images + removeImages( sal_Int16( i ), aRemoveList ); + m_bUserImageListModified[i] = true; + } + + m_bModified = true; +} + +Sequence< OUString > ImageManagerImpl::getAllImageNames( ::sal_Int16 nImageType ) +{ + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + + ImageNameMap aImageCmdNameMap; + + vcl::ImageType nIndex = implts_convertImageTypeToIndex( nImageType ); + + sal_uInt32 i( 0 ); + if ( m_bUseGlobal ) + { + rtl::Reference< GlobalImageList > rGlobalImageList = implts_getGlobalImageList(); + + const std::vector< OUString >& rGlobalImageNameVector = rGlobalImageList->getImageCommandNames(); + const sal_uInt32 nGlobalCount = rGlobalImageNameVector.size(); + for ( i = 0; i < nGlobalCount; i++ ) + aImageCmdNameMap.emplace( rGlobalImageNameVector[i], true ); + + const std::vector< OUString >& rModuleImageNameVector = implts_getDefaultImageList()->getImageCommandNames(); + const sal_uInt32 nModuleCount = rModuleImageNameVector.size(); + for ( i = 0; i < nModuleCount; i++ ) + aImageCmdNameMap.emplace( rModuleImageNameVector[i], true ); + } + + ImageList* pImageList = implts_getUserImageList(nIndex); + std::vector< OUString > rUserImageNames; + pImageList->GetImageNames( rUserImageNames ); + const sal_uInt32 nUserCount = rUserImageNames.size(); + for ( i = 0; i < nUserCount; i++ ) + aImageCmdNameMap.emplace( rUserImageNames[i], true ); + + return comphelper::mapKeysToSequence( aImageCmdNameMap ); +} + +bool ImageManagerImpl::hasImage( ::sal_Int16 nImageType, const OUString& aCommandURL ) +{ + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + + if (( nImageType < 0 ) || ( nImageType > MAX_IMAGETYPE_VALUE )) + throw IllegalArgumentException(); + + vcl::ImageType nIndex = implts_convertImageTypeToIndex( nImageType ); + if ( m_bUseGlobal && implts_getGlobalImageList()->hasImage( nIndex, aCommandURL )) + return true; + else + { + if ( m_bUseGlobal && implts_getDefaultImageList()->hasImage( nIndex, aCommandURL )) + return true; + else + { + // User layer + ImageList* pImageList = implts_getUserImageList(nIndex); + if ( pImageList ) + return ( pImageList->GetImagePos( aCommandURL ) != IMAGELIST_IMAGE_NOTFOUND ); + } + } + + return false; +} + +namespace +{ + css::uno::Reference< css::graphic::XGraphic > GetXGraphic(const Image &rImage) + { + return Graphic(rImage).GetXGraphic(); + } +} + +Sequence< uno::Reference< XGraphic > > ImageManagerImpl::getImages( + ::sal_Int16 nImageType, + const Sequence< OUString >& aCommandURLSequence ) +{ + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + + if (( nImageType < 0 ) || ( nImageType > MAX_IMAGETYPE_VALUE )) + throw IllegalArgumentException(); + + Sequence< uno::Reference< XGraphic > > aGraphSeq( aCommandURLSequence.getLength() ); + + vcl::ImageType nIndex = implts_convertImageTypeToIndex( nImageType ); + rtl::Reference< GlobalImageList > rGlobalImageList; + CmdImageList* pDefaultImageList = nullptr; + if ( m_bUseGlobal ) + { + rGlobalImageList = implts_getGlobalImageList(); + pDefaultImageList = implts_getDefaultImageList(); + } + ImageList* pUserImageList = implts_getUserImageList(nIndex); + + // We have to search our image list in the following order: + // 1. user image list (read/write) + // 2. module image list (read) + // 3. global image list (read) + auto aGraphSeqRange = asNonConstRange(aGraphSeq); + sal_Int32 n = 0; + for ( const OUString& rURL : aCommandURLSequence ) + { + Image aImage = pUserImageList->GetImage( rURL ); + if ( !aImage && m_bUseGlobal ) + { + aImage = pDefaultImageList->getImageFromCommandURL( nIndex, rURL ); + if ( !aImage ) + aImage = rGlobalImageList->getImageFromCommandURL( nIndex, rURL ); + } + + aGraphSeqRange[n++] = GetXGraphic(aImage); + } + + return aGraphSeq; +} + +void ImageManagerImpl::replaceImages( + ::sal_Int16 nImageType, + const Sequence< OUString >& aCommandURLSequence, + const Sequence< uno::Reference< XGraphic > >& aGraphicsSequence ) +{ + rtl::Reference<GraphicNameAccess> pInsertedImages; + rtl::Reference<GraphicNameAccess> pReplacedImages; + + { + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + + if (( aCommandURLSequence.getLength() != aGraphicsSequence.getLength() ) || + (( nImageType < 0 ) || ( nImageType > MAX_IMAGETYPE_VALUE ))) + throw IllegalArgumentException(); + + if ( m_bReadOnly ) + throw IllegalAccessException(); + + vcl::ImageType nIndex = implts_convertImageTypeToIndex( nImageType ); + ImageList* pImageList = implts_getUserImageList(nIndex); + + uno::Reference< XGraphic > xGraphic; + for ( sal_Int32 i = 0; i < aCommandURLSequence.getLength(); i++ ) + { + // Check size and scale. If we don't have any graphics ignore it + if ( !implts_checkAndScaleGraphic( xGraphic, aGraphicsSequence[i], nIndex )) + continue; + + sal_uInt16 nPos = pImageList->GetImagePos( aCommandURLSequence[i] ); + if ( nPos == IMAGELIST_IMAGE_NOTFOUND ) + { + pImageList->AddImage(aCommandURLSequence[i], Image(xGraphic)); + if ( !pInsertedImages ) + pInsertedImages = new GraphicNameAccess(); + pInsertedImages->addElement( aCommandURLSequence[i], xGraphic ); + } + else + { + pImageList->ReplaceImage(aCommandURLSequence[i], Image(xGraphic)); + if ( !pReplacedImages ) + pReplacedImages = new GraphicNameAccess(); + pReplacedImages->addElement( aCommandURLSequence[i], xGraphic ); + } + } + + if (( pInsertedImages != nullptr ) || ( pReplacedImages != nullptr )) + { + m_bModified = true; + m_bUserImageListModified[nIndex] = true; + } + } + + uno::Reference< uno::XInterface > xOwner(m_pOwner); + // Notify listeners + if ( pInsertedImages != nullptr ) + { + ConfigurationEvent aInsertEvent; + aInsertEvent.aInfo <<= nImageType; + aInsertEvent.Accessor <<= xOwner; + aInsertEvent.Source = xOwner; + aInsertEvent.ResourceURL = m_aResourceString; + aInsertEvent.Element <<= uno::Reference< XNameAccess >(pInsertedImages); + implts_notifyContainerListener( aInsertEvent, NotifyOp_Insert ); + } + if ( pReplacedImages != nullptr ) + { + ConfigurationEvent aReplaceEvent; + aReplaceEvent.aInfo <<= nImageType; + aReplaceEvent.Accessor <<= xOwner; + aReplaceEvent.Source = xOwner; + aReplaceEvent.ResourceURL = m_aResourceString; + aReplaceEvent.ReplacedElement = Any(); + aReplaceEvent.Element <<= uno::Reference< XNameAccess >(pReplacedImages); + implts_notifyContainerListener( aReplaceEvent, NotifyOp_Replace ); + } +} + +void ImageManagerImpl::removeImages( ::sal_Int16 nImageType, const Sequence< OUString >& aCommandURLSequence ) +{ + rtl::Reference<GraphicNameAccess> pRemovedImages; + rtl::Reference<GraphicNameAccess> pReplacedImages; + + { + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + + if (( nImageType < 0 ) || ( nImageType > MAX_IMAGETYPE_VALUE )) + throw IllegalArgumentException(); + + if ( m_bReadOnly ) + throw IllegalAccessException(); + + vcl::ImageType nIndex = implts_convertImageTypeToIndex( nImageType ); + rtl::Reference< GlobalImageList > rGlobalImageList; + CmdImageList* pDefaultImageList = nullptr; + if ( m_bUseGlobal ) + { + rGlobalImageList = implts_getGlobalImageList(); + pDefaultImageList = implts_getDefaultImageList(); + } + ImageList* pImageList = implts_getUserImageList(nIndex); + uno::Reference<XGraphic> xEmptyGraphic; + + for ( const OUString& rURL : aCommandURLSequence ) + { + sal_uInt16 nPos = pImageList->GetImagePos( rURL ); + if ( nPos != IMAGELIST_IMAGE_NOTFOUND ) + { + sal_uInt16 nId = pImageList->GetImageId( nPos ); + pImageList->RemoveImage( nId ); + + if ( m_bUseGlobal ) + { + // Check, if we have an image in our module/global image list. If we find one => + // this is a replace instead of a remove operation! + Image aNewImage = pDefaultImageList->getImageFromCommandURL( nIndex, rURL ); + if ( !aNewImage ) + aNewImage = rGlobalImageList->getImageFromCommandURL( nIndex, rURL ); + if ( !aNewImage ) + { + if ( !pRemovedImages ) + pRemovedImages = new GraphicNameAccess(); + pRemovedImages->addElement( rURL, xEmptyGraphic ); + } + else + { + if ( !pReplacedImages ) + pReplacedImages = new GraphicNameAccess(); + pReplacedImages->addElement(rURL, GetXGraphic(aNewImage)); + } + } // if ( m_bUseGlobal ) + else + { + if ( !pRemovedImages ) + pRemovedImages = new GraphicNameAccess(); + pRemovedImages->addElement( rURL, xEmptyGraphic ); + } + } + } + + if (( pReplacedImages != nullptr ) || ( pRemovedImages != nullptr )) + { + m_bModified = true; + m_bUserImageListModified[nIndex] = true; + } + } + + // Notify listeners + uno::Reference< uno::XInterface > xOwner(m_pOwner); + if ( pRemovedImages != nullptr ) + { + ConfigurationEvent aRemoveEvent; + aRemoveEvent.aInfo <<= nImageType; + aRemoveEvent.Accessor <<= xOwner; + aRemoveEvent.Source = xOwner; + aRemoveEvent.ResourceURL = m_aResourceString; + aRemoveEvent.Element <<= uno::Reference< XNameAccess >(pRemovedImages); + implts_notifyContainerListener( aRemoveEvent, NotifyOp_Remove ); + } + if ( pReplacedImages != nullptr ) + { + ConfigurationEvent aReplaceEvent; + aReplaceEvent.aInfo <<= nImageType; + aReplaceEvent.Accessor <<= xOwner; + aReplaceEvent.Source = xOwner; + aReplaceEvent.ResourceURL = m_aResourceString; + aReplaceEvent.ReplacedElement = Any(); + aReplaceEvent.Element <<= uno::Reference< XNameAccess >(pReplacedImages); + implts_notifyContainerListener( aReplaceEvent, NotifyOp_Replace ); + } +} + +void ImageManagerImpl::insertImages( ::sal_Int16 nImageType, const Sequence< OUString >& aCommandURLSequence, const Sequence< uno::Reference< XGraphic > >& aGraphicSequence ) +{ + replaceImages(nImageType,aCommandURLSequence,aGraphicSequence); +} + +// XUIConfigurationPersistence +void ImageManagerImpl::reload() +{ + SolarMutexResettableGuard aGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + CommandMap aOldUserCmdImageSet; + std::vector< OUString > aNewUserCmdImageSet; + + if ( !m_bModified ) + return; + + for ( vcl::ImageType i : o3tl::enumrange<vcl::ImageType>() ) + { + if ( !m_bDisposed && m_bUserImageListModified[i] ) + { + std::vector< OUString > aOldUserCmdImageVector; + ImageList* pImageList = implts_getUserImageList(i); + pImageList->GetImageNames( aOldUserCmdImageVector ); + + // Fill hash map to speed up search afterwards + sal_uInt32 j( 0 ); + const sal_uInt32 nOldCount = aOldUserCmdImageVector.size(); + for ( j = 0; j < nOldCount; j++ ) + aOldUserCmdImageSet.emplace( aOldUserCmdImageVector[j], false ); + + // Attention: This can make the old image list pointer invalid! + implts_loadUserImages( i, m_xUserImageStorage, m_xUserBitmapsStorage ); + pImageList = implts_getUserImageList(i); + pImageList->GetImageNames( aNewUserCmdImageSet ); + + rtl::Reference<GraphicNameAccess> pInsertedImages; + rtl::Reference<GraphicNameAccess> pReplacedImages; + rtl::Reference<GraphicNameAccess> pRemovedImages; + + for (auto const& newUserCmdImage : aNewUserCmdImageSet) + { + CommandMap::iterator pIter = aOldUserCmdImageSet.find(newUserCmdImage); + if ( pIter != aOldUserCmdImageSet.end() ) + { + pIter->second = true; // mark entry as replaced + if ( !pReplacedImages ) + pReplacedImages = new GraphicNameAccess(); + pReplacedImages->addElement( newUserCmdImage, + GetXGraphic(pImageList->GetImage(newUserCmdImage)) ); + } + else + { + if ( !pInsertedImages ) + pInsertedImages = new GraphicNameAccess(); + pInsertedImages->addElement( newUserCmdImage, + GetXGraphic(pImageList->GetImage(newUserCmdImage)) ); + } + } + + // Search map for unmarked entries => they have been removed from the user list + // through this reload operation. + // We have to search the module and global image list! + rtl::Reference< GlobalImageList > rGlobalImageList; + CmdImageList* pDefaultImageList = nullptr; + if ( m_bUseGlobal ) + { + rGlobalImageList = implts_getGlobalImageList(); + pDefaultImageList = implts_getDefaultImageList(); + } + uno::Reference<XGraphic> xEmptyGraphic; + for (auto const& oldUserCmdImage : aOldUserCmdImageSet) + { + if ( !oldUserCmdImage.second ) + { + if ( m_bUseGlobal ) + { + Image aImage = pDefaultImageList->getImageFromCommandURL( i, oldUserCmdImage.first ); + if ( !aImage ) + aImage = rGlobalImageList->getImageFromCommandURL( i, oldUserCmdImage.first ); + + if ( !aImage ) + { + // No image in the module/global image list => remove user image + if ( !pRemovedImages ) + pRemovedImages = new GraphicNameAccess(); + pRemovedImages->addElement( oldUserCmdImage.first, xEmptyGraphic ); + } + else + { + // Image has been found in the module/global image list => replace user image + if ( !pReplacedImages ) + pReplacedImages = new GraphicNameAccess(); + pReplacedImages->addElement(oldUserCmdImage.first, GetXGraphic(aImage)); + } + } // if ( m_bUseGlobal ) + else + { + // No image in the user image list => remove user image + if ( !pRemovedImages ) + pRemovedImages = new GraphicNameAccess(); + pRemovedImages->addElement( oldUserCmdImage.first, xEmptyGraphic ); + } + } + } + + aGuard.clear(); + + // Now notify our listeners. Unlock mutex to prevent deadlocks + uno::Reference< uno::XInterface > xOwner(m_pOwner); + if ( pInsertedImages != nullptr ) + { + ConfigurationEvent aInsertEvent; + aInsertEvent.aInfo <<=static_cast<sal_uInt16>(i); + aInsertEvent.Accessor <<= xOwner; + aInsertEvent.Source = xOwner; + aInsertEvent.ResourceURL = m_aResourceString; + aInsertEvent.Element <<= uno::Reference< XNameAccess >( pInsertedImages ); + implts_notifyContainerListener( aInsertEvent, NotifyOp_Insert ); + } + if ( pReplacedImages != nullptr ) + { + ConfigurationEvent aReplaceEvent; + aReplaceEvent.aInfo <<= static_cast<sal_uInt16>(i); + aReplaceEvent.Accessor <<= xOwner; + aReplaceEvent.Source = xOwner; + aReplaceEvent.ResourceURL = m_aResourceString; + aReplaceEvent.ReplacedElement = Any(); + aReplaceEvent.Element <<= uno::Reference< XNameAccess >( pReplacedImages ); + implts_notifyContainerListener( aReplaceEvent, NotifyOp_Replace ); + } + if ( pRemovedImages != nullptr ) + { + ConfigurationEvent aRemoveEvent; + aRemoveEvent.aInfo <<= static_cast<sal_uInt16>(i); + aRemoveEvent.Accessor <<= xOwner; + aRemoveEvent.Source = xOwner; + aRemoveEvent.ResourceURL = m_aResourceString; + aRemoveEvent.Element <<= uno::Reference< XNameAccess >( pRemovedImages ); + implts_notifyContainerListener( aRemoveEvent, NotifyOp_Remove ); + } + + aGuard.reset(); + } + } +} + +void ImageManagerImpl::store() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_bModified ) + return; + + bool bWritten( false ); + for ( vcl::ImageType i : o3tl::enumrange<vcl::ImageType>() ) + { + bool bSuccess = implts_storeUserImages(i, m_xUserImageStorage, m_xUserBitmapsStorage ); + if ( bSuccess ) + bWritten = true; + m_bUserImageListModified[i] = false; + } + + if ( bWritten && + m_xUserConfigStorage.is() ) + { + uno::Reference< XTransactedObject > xUserConfigStorageCommit( m_xUserConfigStorage, UNO_QUERY ); + if ( xUserConfigStorageCommit.is() ) + xUserConfigStorageCommit->commit(); + if ( m_xUserRootCommit.is() ) + m_xUserRootCommit->commit(); + } + + m_bModified = false; +} + +void ImageManagerImpl::storeToStorage( const uno::Reference< XStorage >& Storage ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !(m_bModified && Storage.is()) ) + return; + + tools::Long nModes = ElementModes::READWRITE; + + uno::Reference< XStorage > xUserImageStorage = Storage->openStorageElement( IMAGE_FOLDER, + nModes ); + if ( !xUserImageStorage.is() ) + return; + + uno::Reference< XStorage > xUserBitmapsStorage = xUserImageStorage->openStorageElement( BITMAPS_FOLDER, + nModes ); + for ( vcl::ImageType i : o3tl::enumrange<vcl::ImageType>() ) + { + implts_getUserImageList(i); + implts_storeUserImages( i, xUserImageStorage, xUserBitmapsStorage ); + } + + uno::Reference< XTransactedObject > xTransaction( Storage, UNO_QUERY ); + if ( xTransaction.is() ) + xTransaction->commit(); +} + +bool ImageManagerImpl::isModified() const +{ + SolarMutexGuard g; + return m_bModified; +} + +bool ImageManagerImpl::isReadOnly() const +{ + SolarMutexGuard g; + return m_bReadOnly; +} +// XUIConfiguration +void ImageManagerImpl::addConfigurationListener( const uno::Reference< css::ui::XUIConfigurationListener >& xListener ) +{ + { + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + } + + std::unique_lock aGuard(m_mutex); + m_aConfigListeners.addInterface( aGuard, xListener ); +} + +void ImageManagerImpl::removeConfigurationListener( const uno::Reference< css::ui::XUIConfigurationListener >& xListener ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + std::unique_lock aGuard(m_mutex); + m_aConfigListeners.removeInterface( aGuard, xListener ); +} + +void ImageManagerImpl::implts_notifyContainerListener( const ConfigurationEvent& aEvent, NotifyOp eOp ) +{ + std::unique_lock aGuard(m_mutex); + comphelper::OInterfaceIteratorHelper4 pIterator( aGuard, m_aConfigListeners ); + while ( pIterator.hasMoreElements() ) + { + try + { + switch ( eOp ) + { + case NotifyOp_Replace: + pIterator.next()->elementReplaced( aEvent ); + break; + case NotifyOp_Insert: + pIterator.next()->elementInserted( aEvent ); + break; + case NotifyOp_Remove: + pIterator.next()->elementRemoved( aEvent ); + break; + } + } + catch( const css::uno::RuntimeException& ) + { + pIterator.remove(aGuard); + } + } +} +void ImageManagerImpl::clear() +{ + SolarMutexGuard g; + + for (auto & n : m_pUserImageList) + { + n.reset(); + } +} +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/imagemanagerimpl.hxx b/framework/source/uiconfiguration/imagemanagerimpl.hxx new file mode 100644 index 000000000..f36c9fff7 --- /dev/null +++ b/framework/source/uiconfiguration/imagemanagerimpl.hxx @@ -0,0 +1,187 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/ui/ConfigurationEvent.hpp> +#include <com/sun/star/ui/XUIConfigurationListener.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> + +#include <cppuhelper/weak.hxx> +#include <comphelper/interfacecontainer4.hxx> +#include <rtl/ustring.hxx> + +#include <rtl/ref.hxx> +#include <salhelper/simplereferenceobject.hxx> + +#include <mutex> +#include <unordered_map> +#include <vector> + +#include "CommandImageResolver.hxx" + +namespace framework +{ + class CmdImageList + { + public: + CmdImageList(css::uno::Reference< css::uno::XComponentContext > xContext, OUString aModuleIdentifier); + virtual ~CmdImageList(); + + virtual Image getImageFromCommandURL(vcl::ImageType nImageType, const OUString& rCommandURL); + virtual bool hasImage(vcl::ImageType nImageType, const OUString& rCommandURL); + virtual std::vector<OUString>& getImageCommandNames(); + + protected: + void initialize(); + + private: + bool m_bInitialized; + vcl::CommandImageResolver m_aResolver; + + OUString m_aModuleIdentifier; + css::uno::Reference<css::uno::XComponentContext> m_xContext; + }; + + class GlobalImageList : public CmdImageList, public salhelper::SimpleReferenceObject + { + public: + explicit GlobalImageList(const css::uno::Reference< css::uno::XComponentContext >& rxContext); + virtual ~GlobalImageList() override; + + virtual Image getImageFromCommandURL( vcl::ImageType nImageType, const OUString& rCommandURL ) override; + virtual bool hasImage( vcl::ImageType nImageType, const OUString& rCommandURL ) override; + virtual ::std::vector< OUString >& getImageCommandNames() override; + }; + + class ImageManagerImpl + { + public: + ImageManagerImpl(css::uno::Reference< css::uno::XComponentContext > xContext + ,::cppu::OWeakObject *pOwner + ,bool _bUseGlobal); + ~ImageManagerImpl(); + + void dispose(); + void initialize( const css::uno::Sequence< css::uno::Any >& aArguments ); + /// @throws css::uno::RuntimeException + void addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ); + /// @throws css::uno::RuntimeException + void removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ); + + // XImageManager + /// @throws css::uno::RuntimeException + /// @throws css::lang::IllegalAccessException + void reset(); + /// @throws css::uno::RuntimeException + css::uno::Sequence< OUString > getAllImageNames( ::sal_Int16 nImageType ); + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + bool hasImage( ::sal_Int16 nImageType, const OUString& aCommandURL ); + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + css::uno::Sequence< css::uno::Reference< css::graphic::XGraphic > > getImages( ::sal_Int16 nImageType, const css::uno::Sequence< OUString >& aCommandURLSequence ); + /// @throws css::lang::IllegalArgumentException + /// @throws css::lang::IllegalAccessException + /// @throws css::uno::RuntimeException + void replaceImages( ::sal_Int16 nImageType, const css::uno::Sequence< OUString >& aCommandURLSequence, const css::uno::Sequence< css::uno::Reference< css::graphic::XGraphic > >& aGraphicsSequence ); + /// @throws css::lang::IllegalArgumentException + /// @throws css::lang::IllegalAccessException + /// @throws css::uno::RuntimeException + void removeImages( ::sal_Int16 nImageType, const css::uno::Sequence< OUString >& aResourceURLSequence ); + /// @throws css::container::ElementExistException + /// @throws css::lang::IllegalArgumentException + /// @throws css::lang::IllegalAccessException + /// @throws css::uno::RuntimeException + void insertImages( ::sal_Int16 nImageType, const css::uno::Sequence< OUString >& aCommandURLSequence, const css::uno::Sequence< css::uno::Reference< css::graphic::XGraphic > >& aGraphicSequence ); + + // XUIConfiguration + /// @throws css::uno::RuntimeException + void addConfigurationListener( const css::uno::Reference< css::ui::XUIConfigurationListener >& Listener ); + /// @throws css::uno::RuntimeException + void removeConfigurationListener( const css::uno::Reference< css::ui::XUIConfigurationListener >& Listener ); + + // XUIConfigurationPersistence + /// @throws css::uno::Exception + /// @throws css::uno::RuntimeException + void reload(); + /// @throws css::uno::Exception + /// @throws css::uno::RuntimeException + void store(); + /// @throws css::uno::Exception + /// @throws css::uno::RuntimeException + void storeToStorage( const css::uno::Reference< css::embed::XStorage >& Storage ); + /// @throws css::uno::RuntimeException + bool isModified() const; + /// @throws css::uno::RuntimeException + bool isReadOnly() const; + + void clear(); + + typedef std::unordered_map< OUString, + sal_Bool > ImageNameMap; + + enum NotifyOp + { + NotifyOp_Remove, + NotifyOp_Insert, + NotifyOp_Replace + }; + + typedef ::std::vector< css::ui::ConfigurationEvent > ConfigEventNotifyContainer; + + void implts_initialize(); + void implts_notifyContainerListener( const css::ui::ConfigurationEvent& aEvent, NotifyOp eOp ); + ImageList* implts_getUserImageList( vcl::ImageType nImageType ); + void implts_loadUserImages( vcl::ImageType nImageType, + const css::uno::Reference< css::embed::XStorage >& xUserImageStorage, + const css::uno::Reference< css::embed::XStorage >& xUserBitmapsStorage ); + bool implts_storeUserImages( vcl::ImageType nImageType, + const css::uno::Reference< css::embed::XStorage >& xUserImageStorage, + const css::uno::Reference< css::embed::XStorage >& xUserBitmapsStorage ); + const rtl::Reference< GlobalImageList >& implts_getGlobalImageList(); + CmdImageList* implts_getDefaultImageList(); + + css::uno::Reference< css::embed::XStorage > m_xUserConfigStorage; + css::uno::Reference< css::embed::XStorage > m_xUserImageStorage; + css::uno::Reference< css::embed::XStorage > m_xUserBitmapsStorage; + css::uno::Reference< css::embed::XTransactedObject > m_xUserRootCommit; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + ::cppu::OWeakObject* m_pOwner; + rtl::Reference< GlobalImageList > m_pGlobalImageList; + std::unique_ptr<CmdImageList> m_pDefaultImageList; + OUString m_aModuleIdentifier; + OUString m_aResourceString; + std::mutex m_mutex; + comphelper::OInterfaceContainerHelper4<css::lang::XEventListener> m_aEventListeners; + comphelper::OInterfaceContainerHelper4<css::ui::XUIConfigurationListener> m_aConfigListeners; + o3tl::enumarray<vcl::ImageType,std::unique_ptr<ImageList>> m_pUserImageList; + o3tl::enumarray<vcl::ImageType,bool> m_bUserImageListModified; + bool m_bUseGlobal; + bool m_bReadOnly; + bool m_bInitialized; + bool m_bModified; + bool m_bDisposed; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/moduleuicfgsupplier.cxx b/framework/source/uiconfiguration/moduleuicfgsupplier.cxx new file mode 100644 index 000000000..cdbd647c3 --- /dev/null +++ b/framework/source/uiconfiguration/moduleuicfgsupplier.cxx @@ -0,0 +1,180 @@ +/* -*- 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 <stdtypes.h> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/ui/ModuleUIConfigurationManager.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/ui/XModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XUIConfigurationManager.hpp> +#include <com/sun/star/ui/XModuleUIConfigurationManager2.hpp> +#include <com/sun/star/frame/XModuleManager2.hpp> + +#include <comphelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <unordered_map> + +using namespace com::sun::star::uno; +using namespace com::sun::star::io; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::beans; +using namespace com::sun::star::embed; +using namespace ::com::sun::star::ui; +using namespace ::com::sun::star::frame; +using namespace framework; + +namespace { + +typedef comphelper::WeakComponentImplHelper< + css::lang::XServiceInfo, + css::ui::XModuleUIConfigurationManagerSupplier > + ModuleUIConfigurationManagerSupplier_BASE; + +class ModuleUIConfigurationManagerSupplier : public ModuleUIConfigurationManagerSupplier_BASE +{ +public: + explicit ModuleUIConfigurationManagerSupplier( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + virtual ~ModuleUIConfigurationManagerSupplier() override; + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.ModuleUIConfigurationManagerSupplier"; + } + + 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.ModuleUIConfigurationManagerSupplier"}; + } + + // XModuleUIConfigurationManagerSupplier + virtual css::uno::Reference< css::ui::XUIConfigurationManager > SAL_CALL getUIConfigurationManager( const OUString& ModuleIdentifier ) override; + +private: + virtual void disposing(std::unique_lock<std::mutex>&) final override; + + typedef std::unordered_map< OUString, css::uno::Reference< css::ui::XModuleUIConfigurationManager2 > > ModuleToModuleCfgMgr; + +//TODO_AS void impl_initStorages(); + + ModuleToModuleCfgMgr m_aModuleToModuleUICfgMgrMap; + css::uno::Reference< css::frame::XModuleManager2 > m_xModuleMgr; + css::uno::Reference< css::uno::XComponentContext > m_xContext; +}; + +ModuleUIConfigurationManagerSupplier::ModuleUIConfigurationManagerSupplier( const Reference< XComponentContext >& xContext ) : + m_xModuleMgr( ModuleManager::create( xContext ) ) + , m_xContext( xContext ) +{ + try + { + // Retrieve known modules and insert them into our unordered_map to speed-up access time. + Reference< XNameAccess > xNameAccess( m_xModuleMgr, UNO_QUERY_THROW ); + const Sequence< OUString > aNameSeq = xNameAccess->getElementNames(); + for ( const OUString& rName : aNameSeq ) + m_aModuleToModuleUICfgMgrMap.emplace( rName, Reference< XModuleUIConfigurationManager2 >() ); + } + catch(...) + { + } +} + +ModuleUIConfigurationManagerSupplier::~ModuleUIConfigurationManagerSupplier() +{ + std::unique_lock g(m_aMutex); + disposing(g); +} + +void ModuleUIConfigurationManagerSupplier::disposing(std::unique_lock<std::mutex>&) +{ + // dispose all our module user interface configuration managers + for (auto const& elem : m_aModuleToModuleUICfgMgrMap) + { + Reference< XComponent > xComponent( elem.second, UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + } + m_aModuleToModuleUICfgMgrMap.clear(); + m_xModuleMgr.clear(); +} + +// XModuleUIConfigurationManagerSupplier +Reference< XUIConfigurationManager > SAL_CALL ModuleUIConfigurationManagerSupplier::getUIConfigurationManager( const OUString& sModuleIdentifier ) +{ + std::unique_lock g(m_aMutex); + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + ModuleToModuleCfgMgr::iterator pIter = m_aModuleToModuleUICfgMgrMap.find( sModuleIdentifier ); + if ( pIter == m_aModuleToModuleUICfgMgrMap.end() ) + throw NoSuchElementException(); +//TODO_AS impl_initStorages(); + + // Create instance on demand + if ( !pIter->second.is() ) + { + OUString sShort; + try + { + Sequence< PropertyValue > lProps; + m_xModuleMgr->getByName(sModuleIdentifier) >>= lProps; + for (PropertyValue const & rProp : std::as_const(lProps)) + { + if ( rProp.Name == "ooSetupFactoryShortName" ) + { + rProp.Value >>= sShort; + break; + } + } + } + catch( const Exception& ) + { + sShort.clear(); + } + + if (sShort.isEmpty()) + throw NoSuchElementException(); + + pIter->second = css::ui::ModuleUIConfigurationManager::createDefault(m_xContext, sShort, sModuleIdentifier); + } + + return pIter->second; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ModuleUIConfigurationManagerSupplier_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new ModuleUIConfigurationManagerSupplier(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/moduleuiconfigurationmanager.cxx b/framework/source/uiconfiguration/moduleuiconfigurationmanager.cxx new file mode 100644 index 000000000..ded9d7770 --- /dev/null +++ b/framework/source/uiconfiguration/moduleuiconfigurationmanager.cxx @@ -0,0 +1,1655 @@ +/* -*- 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 <uiconfiguration/imagemanager.hxx> +#include <uielement/constitemcontainer.hxx> +#include <uielement/rootitemcontainer.hxx> +#include <uielement/uielementtypenames.hxx> +#include <menuconfiguration.hxx> +#include <toolboxconfiguration.hxx> + +#include <statusbarconfiguration.hxx> + +#include <com/sun/star/ui/UIElementType.hpp> +#include <com/sun/star/ui/ConfigurationEvent.hpp> +#include <com/sun/star/ui/ModuleAcceleratorConfiguration.hpp> +#include <com/sun/star/ui/XModuleUIConfigurationManager2.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IllegalAccessException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/InvalidStorageException.hpp> +#include <com/sun/star/embed/StorageWrappedTargetException.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/container/ElementExistException.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XComponent.hpp> + +#include <comphelper/propertysequence.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> +#include <comphelper/interfacecontainer4.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/servicehelper.hxx> +#include <o3tl/string_view.hxx> +#include <memory> +#include <mutex> +#include <string_view> + +using namespace css; +using namespace com::sun::star::uno; +using namespace com::sun::star::io; +using namespace com::sun::star::embed; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::beans; +using namespace framework; + +constexpr OUStringLiteral RESOURCETYPE_MENUBAR = u"menubar"; +constexpr OUStringLiteral RESOURCETYPE_TOOLBAR = u"toolbar"; +constexpr OUStringLiteral RESOURCETYPE_STATUSBAR = u"statusbar"; +constexpr OUStringLiteral RESOURCETYPE_POPUPMENU = u"popupmenu"; + +namespace { + +class ModuleUIConfigurationManager : public cppu::WeakImplHelper< + css::lang::XServiceInfo, + css::lang::XComponent, + css::ui::XModuleUIConfigurationManager2 > +{ +public: + ModuleUIConfigurationManager( + const css::uno::Reference< css::uno::XComponentContext >& xServiceManager, + const css::uno::Sequence< css::uno::Any >& aArguments); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.ModuleUIConfigurationManager"; + } + + 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.ModuleUIConfigurationManager"}; + } + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + + // XUIConfiguration + virtual void SAL_CALL addConfigurationListener( const css::uno::Reference< css::ui::XUIConfigurationListener >& Listener ) override; + virtual void SAL_CALL removeConfigurationListener( const css::uno::Reference< css::ui::XUIConfigurationListener >& Listener ) override; + + // XUIConfigurationManager + virtual void SAL_CALL reset() override; + virtual css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > > SAL_CALL getUIElementsInfo( sal_Int16 ElementType ) override; + virtual css::uno::Reference< css::container::XIndexContainer > SAL_CALL createSettings( ) override; + virtual sal_Bool SAL_CALL hasSettings( const OUString& ResourceURL ) override; + virtual css::uno::Reference< css::container::XIndexAccess > SAL_CALL getSettings( const OUString& ResourceURL, sal_Bool bWriteable ) override; + virtual void SAL_CALL replaceSettings( const OUString& ResourceURL, const css::uno::Reference< css::container::XIndexAccess >& aNewData ) override; + virtual void SAL_CALL removeSettings( const OUString& ResourceURL ) override; + virtual void SAL_CALL insertSettings( const OUString& NewResourceURL, const css::uno::Reference< css::container::XIndexAccess >& aNewData ) override; + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL getImageManager() override; + virtual css::uno::Reference< css::ui::XAcceleratorConfiguration > SAL_CALL getShortCutManager() override; + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL getEventsManager() override; + + // XModuleUIConfigurationManager + virtual sal_Bool SAL_CALL isDefaultSettings( const OUString& ResourceURL ) override; + virtual css::uno::Reference< css::container::XIndexAccess > SAL_CALL getDefaultSettings( const OUString& ResourceURL ) override; + + // XUIConfigurationPersistence + virtual void SAL_CALL reload() override; + virtual void SAL_CALL store() override; + virtual void SAL_CALL storeToStorage( const css::uno::Reference< css::embed::XStorage >& Storage ) override; + virtual sal_Bool SAL_CALL isModified() override; + virtual sal_Bool SAL_CALL isReadOnly() override; + +private: + // private data types + enum Layer + { + LAYER_DEFAULT, + LAYER_USERDEFINED, + LAYER_COUNT + }; + + enum NotifyOp + { + NotifyOp_Remove, + NotifyOp_Insert, + NotifyOp_Replace + }; + + struct UIElementInfo + { + UIElementInfo( OUString _aResourceURL, OUString _aUIName ) : + aResourceURL(std::move( _aResourceURL)), aUIName(std::move( _aUIName )) {} + OUString aResourceURL; + OUString aUIName; + }; + + struct UIElementData + { + UIElementData() : bModified( false ), bDefault( true ), bDefaultNode( true ) {}; + + OUString aResourceURL; + OUString aName; + bool bModified; // has been changed since last storing + bool bDefault; // default settings + bool bDefaultNode; // this is a default layer element data + css::uno::Reference< css::container::XIndexAccess > xSettings; + }; + + typedef std::unordered_map< OUString, UIElementData > UIElementDataHashMap; + + struct UIElementType + { + UIElementType() : bModified( false ), + bLoaded( false ), + nElementType( css::ui::UIElementType::UNKNOWN ) {} + + bool bModified; + bool bLoaded; + sal_Int16 nElementType; + UIElementDataHashMap aElementsHashMap; + css::uno::Reference< css::embed::XStorage > xStorage; + }; + + typedef std::vector< UIElementType > UIElementTypesVector; + typedef std::vector< css::ui::ConfigurationEvent > ConfigEventNotifyContainer; + typedef std::unordered_map< OUString, UIElementInfo > UIElementInfoHashMap; + + void impl_Initialize(); + void implts_notifyContainerListener( const css::ui::ConfigurationEvent& aEvent, NotifyOp eOp ); + void impl_fillSequenceWithElementTypeInfo( UIElementInfoHashMap& aUIElementInfoCollection, sal_Int16 nElementType ); + void impl_preloadUIElementTypeList( Layer eLayer, sal_Int16 nElementType ); + UIElementData* impl_findUIElementData( const OUString& aResourceURL, sal_Int16 nElementType, bool bLoad = true ); + void impl_requestUIElementData( sal_Int16 nElementType, Layer eLayer, UIElementData& aUIElementData ); + void impl_storeElementTypeData( const css::uno::Reference< css::embed::XStorage >& xStorage, UIElementType& rElementType, bool bResetModifyState = true ); + void impl_resetElementTypeData( UIElementType& rUserElementType, UIElementType const & rDefaultElementType, ConfigEventNotifyContainer& rRemoveNotifyContainer, ConfigEventNotifyContainer& rReplaceNotifyContainer ); + void impl_reloadElementTypeData( UIElementType& rUserElementType, UIElementType const & rDefaultElementType, ConfigEventNotifyContainer& rRemoveNotifyContainer, ConfigEventNotifyContainer& rReplaceNotifyContainer ); + + UIElementTypesVector m_aUIElements[LAYER_COUNT]; + std::unique_ptr<PresetHandler> m_pStorageHandler[css::ui::UIElementType::COUNT]; + css::uno::Reference< css::embed::XStorage > m_xDefaultConfigStorage; + css::uno::Reference< css::embed::XStorage > m_xUserConfigStorage; + bool m_bReadOnly; + bool m_bModified; + bool m_bDisposed; + OUString m_aXMLPostfix; + OUString m_aPropUIName; + OUString m_aModuleIdentifier; + css::uno::Reference< css::embed::XTransactedObject > m_xUserRootCommit; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + std::mutex m_mutex; + comphelper::OInterfaceContainerHelper4<css::lang::XEventListener> m_aEventListeners; + comphelper::OInterfaceContainerHelper4<css::ui::XUIConfigurationListener> m_aConfigListeners; + rtl::Reference< ImageManager > m_xModuleImageManager; + css::uno::Reference< css::ui::XAcceleratorConfiguration > m_xModuleAcceleratorManager; +}; + +// important: The order and position of the elements must match the constant +// definition of "css::ui::UIElementType" +std::u16string_view UIELEMENTTYPENAMES[] = +{ + u"", // Dummy value for unknown! + u"" UIELEMENTTYPE_MENUBAR_NAME, + u"" UIELEMENTTYPE_POPUPMENU_NAME, + u"" UIELEMENTTYPE_TOOLBAR_NAME, + u"" UIELEMENTTYPE_STATUSBAR_NAME, + u"" UIELEMENTTYPE_FLOATINGWINDOW_NAME, + u"" UIELEMENTTYPE_PROGRESSBAR_NAME, + u"" UIELEMENTTYPE_TOOLPANEL_NAME +}; + +const char RESOURCEURL_PREFIX[] = "private:resource/"; +const sal_Int32 RESOURCEURL_PREFIX_SIZE = strlen(RESOURCEURL_PREFIX); + +sal_Int16 RetrieveTypeFromResourceURL( const OUString& aResourceURL ) +{ + + if (( aResourceURL.startsWith( RESOURCEURL_PREFIX ) ) && + ( aResourceURL.getLength() > RESOURCEURL_PREFIX_SIZE )) + { + std::u16string_view aTmpStr = aResourceURL.subView( RESOURCEURL_PREFIX_SIZE ); + size_t nIndex = aTmpStr.find( '/' ); + if (( nIndex > 0 ) && ( aTmpStr.size() > nIndex )) + { + std::u16string_view aTypeStr( aTmpStr.substr( 0, nIndex )); + for ( int i = 0; i < ui::UIElementType::COUNT; i++ ) + { + if ( aTypeStr == UIELEMENTTYPENAMES[i] ) + return sal_Int16( i ); + } + } + } + + return ui::UIElementType::UNKNOWN; +} + +OUString RetrieveNameFromResourceURL( const OUString& aResourceURL ) +{ + if (( aResourceURL.startsWith( RESOURCEURL_PREFIX ) ) && + ( aResourceURL.getLength() > RESOURCEURL_PREFIX_SIZE )) + { + sal_Int32 nIndex = aResourceURL.lastIndexOf( '/' ); + if (( nIndex > 0 ) && (( nIndex+1 ) < aResourceURL.getLength())) + return aResourceURL.copy( nIndex+1 ); + } + + return OUString(); +} + +void ModuleUIConfigurationManager::impl_fillSequenceWithElementTypeInfo( UIElementInfoHashMap& aUIElementInfoCollection, sal_Int16 nElementType ) +{ + // preload list of element types on demand + impl_preloadUIElementTypeList( LAYER_USERDEFINED, nElementType ); + impl_preloadUIElementTypeList( LAYER_DEFAULT, nElementType ); + + UIElementDataHashMap& rUserElements = m_aUIElements[LAYER_USERDEFINED][nElementType].aElementsHashMap; + + OUString aCustomUrlPrefix( "custom_" ); + for (auto const& userElement : rUserElements) + { + sal_Int32 nIndex = userElement.second.aResourceURL.indexOf( aCustomUrlPrefix, RESOURCEURL_PREFIX_SIZE ); + if ( nIndex > RESOURCEURL_PREFIX_SIZE ) + { + // Performance: Retrieve user interface name only for custom user interface elements. + // It's only used by them! + UIElementData* pDataSettings = impl_findUIElementData( userElement.second.aResourceURL, nElementType ); + if ( pDataSettings ) + { + // Retrieve user interface name from XPropertySet interface + OUString aUIName; + Reference< XPropertySet > xPropSet( pDataSettings->xSettings, UNO_QUERY ); + if ( xPropSet.is() ) + { + Any a = xPropSet->getPropertyValue( m_aPropUIName ); + a >>= aUIName; + } + + UIElementInfo aInfo( userElement.second.aResourceURL, aUIName ); + aUIElementInfoCollection.emplace( userElement.second.aResourceURL, aInfo ); + } + } + else + { + // The user interface name for standard user interface elements is stored in the WindowState.xcu file + UIElementInfo aInfo( userElement.second.aResourceURL, OUString() ); + aUIElementInfoCollection.emplace( userElement.second.aResourceURL, aInfo ); + } + } + + UIElementDataHashMap& rDefaultElements = m_aUIElements[LAYER_DEFAULT][nElementType].aElementsHashMap; + + for (auto const& defaultElement : rDefaultElements) + { + UIElementInfoHashMap::const_iterator pIterInfo = aUIElementInfoCollection.find( defaultElement.second.aResourceURL ); + if ( pIterInfo == aUIElementInfoCollection.end() ) + { + sal_Int32 nIndex = defaultElement.second.aResourceURL.indexOf( aCustomUrlPrefix, RESOURCEURL_PREFIX_SIZE ); + if ( nIndex > RESOURCEURL_PREFIX_SIZE ) + { + // Performance: Retrieve user interface name only for custom user interface elements. + // It's only used by them! + UIElementData* pDataSettings = impl_findUIElementData( defaultElement.second.aResourceURL, nElementType ); + if ( pDataSettings ) + { + // Retrieve user interface name from XPropertySet interface + OUString aUIName; + Reference< XPropertySet > xPropSet( pDataSettings->xSettings, UNO_QUERY ); + if ( xPropSet.is() ) + { + Any a = xPropSet->getPropertyValue( m_aPropUIName ); + a >>= aUIName; + } + UIElementInfo aInfo( defaultElement.second.aResourceURL, aUIName ); + aUIElementInfoCollection.emplace( defaultElement.second.aResourceURL, aInfo ); + } + } + else + { + // The user interface name for standard user interface elements is stored in the WindowState.xcu file + UIElementInfo aInfo( defaultElement.second.aResourceURL, OUString() ); + aUIElementInfoCollection.emplace( defaultElement.second.aResourceURL, aInfo ); + } + } + } +} + +void ModuleUIConfigurationManager::impl_preloadUIElementTypeList( Layer eLayer, sal_Int16 nElementType ) +{ + UIElementType& rElementTypeData = m_aUIElements[eLayer][nElementType]; + + if ( rElementTypeData.bLoaded ) + return; + + Reference< XStorage > xElementTypeStorage = rElementTypeData.xStorage; + if ( !xElementTypeStorage.is() ) + return; + + OUString aResURLPrefix = + OUString::Concat(RESOURCEURL_PREFIX) + + UIELEMENTTYPENAMES[ nElementType ] + + "/"; + + UIElementDataHashMap& rHashMap = rElementTypeData.aElementsHashMap; + const Sequence< OUString > aUIElementNames = xElementTypeStorage->getElementNames(); + for ( OUString const & rElementName : aUIElementNames ) + { + UIElementData aUIElementData; + + // Resource name must be without ".xml" + sal_Int32 nIndex = rElementName.lastIndexOf( '.' ); + if (( nIndex > 0 ) && ( nIndex < rElementName.getLength() )) + { + std::u16string_view aExtension( rElementName.subView( nIndex+1 )); + std::u16string_view aUIElementName( rElementName.subView( 0, nIndex )); + + if (!aUIElementName.empty() && + ( o3tl::equalsIgnoreAsciiCase(aExtension, u"xml"))) + { + aUIElementData.aResourceURL = aResURLPrefix + aUIElementName; + aUIElementData.aName = rElementName; + + if ( eLayer == LAYER_USERDEFINED ) + { + aUIElementData.bModified = false; + aUIElementData.bDefault = false; + aUIElementData.bDefaultNode = false; + } + + // Create std::unordered_map entries for all user interface elements inside the storage. We don't load the + // settings to speed up the process. + rHashMap.emplace( aUIElementData.aResourceURL, aUIElementData ); + } + } + rElementTypeData.bLoaded = true; + } + +} + +void ModuleUIConfigurationManager::impl_requestUIElementData( sal_Int16 nElementType, Layer eLayer, UIElementData& aUIElementData ) +{ + UIElementType& rElementTypeData = m_aUIElements[eLayer][nElementType]; + + Reference< XStorage > xElementTypeStorage = rElementTypeData.xStorage; + if ( xElementTypeStorage.is() && !aUIElementData.aName.isEmpty() ) + { + try + { + Reference< XStream > xStream = xElementTypeStorage->openStreamElement( aUIElementData.aName, ElementModes::READ ); + Reference< XInputStream > xInputStream = xStream->getInputStream(); + + if ( xInputStream.is() ) + { + switch ( nElementType ) + { + case css::ui::UIElementType::UNKNOWN: + break; + + case css::ui::UIElementType::MENUBAR: + case css::ui::UIElementType::POPUPMENU: + { + try + { + MenuConfiguration aMenuCfg( m_xContext ); + Reference< XIndexAccess > xContainer( aMenuCfg.CreateMenuBarConfigurationFromXML( xInputStream )); + auto pRootItemContainer = comphelper::getFromUnoTunnel<RootItemContainer>( xContainer ); + if ( pRootItemContainer ) + aUIElementData.xSettings = new ConstItemContainer( pRootItemContainer, true ); + else + aUIElementData.xSettings = new ConstItemContainer( xContainer, true ); + return; + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + break; + + case css::ui::UIElementType::TOOLBAR: + { + try + { + Reference< XIndexContainer > xIndexContainer( new RootItemContainer() ); + ToolBoxConfiguration::LoadToolBox( m_xContext, xInputStream, xIndexContainer ); + auto pRootItemContainer = comphelper::getFromUnoTunnel<RootItemContainer>( xIndexContainer ); + aUIElementData.xSettings = new ConstItemContainer( pRootItemContainer, true ); + return; + } + catch ( const css::lang::WrappedTargetException& ) + { + } + + break; + } + + case css::ui::UIElementType::STATUSBAR: + { + try + { + Reference< XIndexContainer > xIndexContainer( new RootItemContainer() ); + StatusBarConfiguration::LoadStatusBar( m_xContext, xInputStream, xIndexContainer ); + auto pRootItemContainer = comphelper::getFromUnoTunnel<RootItemContainer>( xIndexContainer ); + aUIElementData.xSettings = new ConstItemContainer( pRootItemContainer, true ); + return; + } + catch ( const css::lang::WrappedTargetException& ) + { + } + + break; + } + + case css::ui::UIElementType::FLOATINGWINDOW: + { + break; + } + } + } + } + catch ( const css::embed::InvalidStorageException& ) + { + } + catch ( const css::lang::IllegalArgumentException& ) + { + } + catch ( const css::io::IOException& ) + { + } + catch ( const css::embed::StorageWrappedTargetException& ) + { + } + } + + // At least we provide an empty settings container! + aUIElementData.xSettings = new ConstItemContainer(); +} + +ModuleUIConfigurationManager::UIElementData* ModuleUIConfigurationManager::impl_findUIElementData( const OUString& aResourceURL, sal_Int16 nElementType, bool bLoad ) +{ + // preload list of element types on demand + impl_preloadUIElementTypeList( LAYER_USERDEFINED, nElementType ); + impl_preloadUIElementTypeList( LAYER_DEFAULT, nElementType ); + + // first try to look into our user-defined vector/unordered_map combination + UIElementDataHashMap& rUserHashMap = m_aUIElements[LAYER_USERDEFINED][nElementType].aElementsHashMap; + UIElementDataHashMap::iterator pIter = rUserHashMap.find( aResourceURL ); + if ( pIter != rUserHashMap.end() ) + { + // Default data settings data must be retrieved from the default layer! + if ( !pIter->second.bDefault ) + { + if ( !pIter->second.xSettings.is() && bLoad ) + impl_requestUIElementData( nElementType, LAYER_USERDEFINED, pIter->second ); + return &(pIter->second); + } + } + + // Not successful, we have to look into our default vector/unordered_map combination + UIElementDataHashMap& rDefaultHashMap = m_aUIElements[LAYER_DEFAULT][nElementType].aElementsHashMap; + pIter = rDefaultHashMap.find( aResourceURL ); + if ( pIter != rDefaultHashMap.end() ) + { + if ( !pIter->second.xSettings.is() && bLoad ) + impl_requestUIElementData( nElementType, LAYER_DEFAULT, pIter->second ); + return &(pIter->second); + } + + // Nothing has been found! + return nullptr; +} + +void ModuleUIConfigurationManager::impl_storeElementTypeData( const Reference< XStorage >& xStorage, UIElementType& rElementType, bool bResetModifyState ) +{ + UIElementDataHashMap& rHashMap = rElementType.aElementsHashMap; + + for (auto & elem : rHashMap) + { + UIElementData& rElement = elem.second; + if ( rElement.bModified ) + { + if ( rElement.bDefault ) + { + xStorage->removeElement( rElement.aName ); + rElement.bModified = false; // mark as not modified + } + else + { + Reference< XStream > xStream = xStorage->openStreamElement( rElement.aName, ElementModes::WRITE|ElementModes::TRUNCATE ); + Reference< XOutputStream > xOutputStream( xStream->getOutputStream() ); + + if ( xOutputStream.is() ) + { + switch( rElementType.nElementType ) + { + case css::ui::UIElementType::MENUBAR: + case css::ui::UIElementType::POPUPMENU: + { + try + { + MenuConfiguration aMenuCfg( m_xContext ); + aMenuCfg.StoreMenuBarConfigurationToXML( + rElement.xSettings, xOutputStream, rElementType.nElementType == css::ui::UIElementType::MENUBAR ); + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + break; + + case css::ui::UIElementType::TOOLBAR: + { + try + { + ToolBoxConfiguration::StoreToolBox( m_xContext, xOutputStream, rElement.xSettings ); + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + break; + + case css::ui::UIElementType::STATUSBAR: + { + try + { + StatusBarConfiguration::StoreStatusBar( m_xContext, xOutputStream, rElement.xSettings ); + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + break; + + default: + break; + } + } + + // mark as not modified if we store to our own storage + if ( bResetModifyState ) + rElement.bModified = false; + } + } + } + + // commit element type storage + Reference< XTransactedObject > xTransactedObject( xStorage, UNO_QUERY ); + if ( xTransactedObject.is() ) + xTransactedObject->commit(); + + // mark UIElementType as not modified if we store to our own storage + if ( bResetModifyState ) + rElementType.bModified = false; +} + +// This is only allowed to be called on the LAYER_USER_DEFINED! +void ModuleUIConfigurationManager::impl_resetElementTypeData( + UIElementType& rUserElementType, + UIElementType const & rDefaultElementType, + ConfigEventNotifyContainer& rRemoveNotifyContainer, + ConfigEventNotifyContainer& rReplaceNotifyContainer ) +{ + UIElementDataHashMap& rHashMap = rUserElementType.aElementsHashMap; + + Reference< XUIConfigurationManager > xThis(this); + Reference< XInterface > xIfac( xThis, UNO_QUERY ); + sal_Int16 nType = rUserElementType.nElementType; + + // Make copies of the event structures to be thread-safe. We have to unlock our mutex before calling + // our listeners! + for (auto & elem : rHashMap) + { + UIElementData& rElement = elem.second; + if ( !rElement.bDefault ) + { + if ( rDefaultElementType.xStorage->hasByName( rElement.aName )) + { + // Replace settings with data from default layer + Reference< XIndexAccess > xOldSettings( rElement.xSettings ); + impl_requestUIElementData( nType, LAYER_DEFAULT, rElement ); + + ui::ConfigurationEvent aReplaceEvent; + aReplaceEvent.ResourceURL = rElement.aResourceURL; + aReplaceEvent.Accessor <<= xThis; + aReplaceEvent.Source = xIfac; + aReplaceEvent.ReplacedElement <<= xOldSettings; + aReplaceEvent.Element <<= rElement.xSettings; + + rReplaceNotifyContainer.push_back( aReplaceEvent ); + + // Mark element as default and not modified. That means "not active" + // in the user layer anymore. + rElement.bModified = false; + rElement.bDefault = true; + } + else + { + // Remove user-defined settings from user layer + ui::ConfigurationEvent aEvent; + aEvent.ResourceURL = rElement.aResourceURL; + aEvent.Accessor <<= xThis; + aEvent.Source = xIfac; + aEvent.Element <<= rElement.xSettings; + + rRemoveNotifyContainer.push_back( aEvent ); + + // Mark element as default and not modified. That means "not active" + // in the user layer anymore. + rElement.bModified = false; + rElement.bDefault = true; + } + } + } + + // Remove all settings from our user interface elements + rHashMap.clear(); +} + +void ModuleUIConfigurationManager::impl_reloadElementTypeData( + UIElementType& rUserElementType, + UIElementType const & rDefaultElementType, + ConfigEventNotifyContainer& rRemoveNotifyContainer, + ConfigEventNotifyContainer& rReplaceNotifyContainer ) +{ + UIElementDataHashMap& rHashMap = rUserElementType.aElementsHashMap; + + Reference< XUIConfigurationManager > xThis(this); + Reference< XInterface > xIfac( xThis, UNO_QUERY ); + sal_Int16 nType = rUserElementType.nElementType; + + for (auto & elem : rHashMap) + { + UIElementData& rElement = elem.second; + if ( rElement.bModified ) + { + if ( rUserElementType.xStorage->hasByName( rElement.aName )) + { + // Replace settings with data from user layer + Reference< XIndexAccess > xOldSettings( rElement.xSettings ); + + impl_requestUIElementData( nType, LAYER_USERDEFINED, rElement ); + + ui::ConfigurationEvent aReplaceEvent; + + aReplaceEvent.ResourceURL = rElement.aResourceURL; + aReplaceEvent.Accessor <<= xThis; + aReplaceEvent.Source = xIfac; + aReplaceEvent.ReplacedElement <<= xOldSettings; + aReplaceEvent.Element <<= rElement.xSettings; + rReplaceNotifyContainer.push_back( aReplaceEvent ); + + rElement.bModified = false; + } + else if ( rDefaultElementType.xStorage->hasByName( rElement.aName )) + { + // Replace settings with data from default layer + Reference< XIndexAccess > xOldSettings( rElement.xSettings ); + + impl_requestUIElementData( nType, LAYER_DEFAULT, rElement ); + + ui::ConfigurationEvent aReplaceEvent; + + aReplaceEvent.ResourceURL = rElement.aResourceURL; + aReplaceEvent.Accessor <<= xThis; + aReplaceEvent.Source = xIfac; + aReplaceEvent.ReplacedElement <<= xOldSettings; + aReplaceEvent.Element <<= rElement.xSettings; + rReplaceNotifyContainer.push_back( aReplaceEvent ); + + // Mark element as default and not modified. That means "not active" + // in the user layer anymore. + rElement.bModified = false; + rElement.bDefault = true; + } + else + { + // Element settings are not in any storage => remove + ui::ConfigurationEvent aRemoveEvent; + + aRemoveEvent.ResourceURL = rElement.aResourceURL; + aRemoveEvent.Accessor <<= xThis; + aRemoveEvent.Source = xIfac; + aRemoveEvent.Element <<= rElement.xSettings; + + rRemoveNotifyContainer.push_back( aRemoveEvent ); + + // Mark element as default and not modified. That means "not active" + // in the user layer anymore. + rElement.bModified = false; + rElement.bDefault = true; + } + } + } + + rUserElementType.bModified = false; +} + +void ModuleUIConfigurationManager::impl_Initialize() +{ + // Initialize the top-level structures with the storage data + if ( m_xUserConfigStorage.is() ) + { + // Try to access our module sub folder + for ( sal_Int16 i = 1; i < css::ui::UIElementType::COUNT; + i++ ) + { + Reference< XStorage > xElementTypeStorage; + try + { + if ( m_pStorageHandler[i] ) + xElementTypeStorage = m_pStorageHandler[i]->getWorkingStorageUser(); + } + catch ( const css::container::NoSuchElementException& ) + { + } + catch ( const css::embed::InvalidStorageException& ) + { + } + catch ( const css::lang::IllegalArgumentException& ) + { + } + catch ( const css::io::IOException& ) + { + } + catch ( const css::embed::StorageWrappedTargetException& ) + { + } + + m_aUIElements[LAYER_USERDEFINED][i].nElementType = i; + m_aUIElements[LAYER_USERDEFINED][i].bModified = false; + m_aUIElements[LAYER_USERDEFINED][i].xStorage = xElementTypeStorage; + } + } + + if ( !m_xDefaultConfigStorage.is() ) + return; + + Reference< XNameAccess > xNameAccess( m_xDefaultConfigStorage, UNO_QUERY_THROW ); + + // Try to access our module sub folder + for ( sal_Int16 i = 1; i < css::ui::UIElementType::COUNT; + i++ ) + { + Reference< XStorage > xElementTypeStorage; + try + { + const OUString sName( UIELEMENTTYPENAMES[i] ); + if( xNameAccess->hasByName( sName ) ) + xNameAccess->getByName( sName ) >>= xElementTypeStorage; + } + catch ( const css::container::NoSuchElementException& ) + { + } + + m_aUIElements[LAYER_DEFAULT][i].nElementType = i; + m_aUIElements[LAYER_DEFAULT][i].bModified = false; + m_aUIElements[LAYER_DEFAULT][i].xStorage = xElementTypeStorage; + } +} + +ModuleUIConfigurationManager::ModuleUIConfigurationManager( + const Reference< XComponentContext >& xContext, + const css::uno::Sequence< css::uno::Any >& aArguments) + : m_bReadOnly( true ) + , m_bModified( false ) + , m_bDisposed( false ) + , m_aXMLPostfix( ".xml" ) + , m_aPropUIName( "UIName" ) + , m_xContext( xContext ) +{ + // Make sure we have a default initialized entry for every layer and user interface element type! + // The following code depends on this! + m_aUIElements[LAYER_DEFAULT].resize( css::ui::UIElementType::COUNT ); + m_aUIElements[LAYER_USERDEFINED].resize( css::ui::UIElementType::COUNT ); + + SolarMutexGuard g; + + OUString aModuleShortName; + if( aArguments.getLength() == 2 && (aArguments[0] >>= aModuleShortName) && (aArguments[1] >>= m_aModuleIdentifier)) + { + } + else + { + ::comphelper::SequenceAsHashMap lArgs(aArguments); + aModuleShortName = lArgs.getUnpackedValueOrDefault("ModuleShortName", OUString()); + m_aModuleIdentifier = lArgs.getUnpackedValueOrDefault("ModuleIdentifier", OUString()); + } + + for ( int i = 1; i < css::ui::UIElementType::COUNT; i++ ) + { + OUString aResourceType; + if ( i == css::ui::UIElementType::MENUBAR ) + aResourceType = RESOURCETYPE_MENUBAR; + else if ( i == css::ui::UIElementType::TOOLBAR ) + aResourceType = RESOURCETYPE_TOOLBAR; + else if ( i == css::ui::UIElementType::STATUSBAR ) + aResourceType = RESOURCETYPE_STATUSBAR; + else if ( i == css::ui::UIElementType::POPUPMENU ) + aResourceType = RESOURCETYPE_POPUPMENU; + + if ( !aResourceType.isEmpty() ) + { + m_pStorageHandler[i].reset( new PresetHandler( m_xContext ) ); + m_pStorageHandler[i]->connectToResource( PresetHandler::E_MODULES, + aResourceType, // this path won't be used later... see next lines! + aModuleShortName, + css::uno::Reference< css::embed::XStorage >()); // no document root used here! + } + } + + // initialize root storages for all resource types + m_xUserRootCommit.set( m_pStorageHandler[css::ui::UIElementType::MENUBAR]->getOrCreateRootStorageUser(), css::uno::UNO_QUERY); // can be empty + m_xDefaultConfigStorage = m_pStorageHandler[css::ui::UIElementType::MENUBAR]->getParentStorageShare(); + m_xUserConfigStorage = m_pStorageHandler[css::ui::UIElementType::MENUBAR]->getParentStorageUser(); + + if ( m_xUserConfigStorage.is() ) + { + Reference< XPropertySet > xPropSet( m_xUserConfigStorage, UNO_QUERY ); + if ( xPropSet.is() ) + { + tools::Long nOpenMode = 0; + Any a = xPropSet->getPropertyValue("OpenMode"); + if ( a >>= nOpenMode ) + m_bReadOnly = !( nOpenMode & ElementModes::WRITE ); + } + } + + impl_Initialize(); +} + +// XComponent +void SAL_CALL ModuleUIConfigurationManager::dispose() +{ + Reference< XComponent > xThis(this); + + css::lang::EventObject aEvent( xThis ); + { + std::unique_lock aGuard(m_mutex); + m_aEventListeners.disposeAndClear( aGuard, aEvent ); + } + { + std::unique_lock aGuard(m_mutex); + m_aConfigListeners.disposeAndClear( aGuard, aEvent ); + } + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + SolarMutexClearableGuard aGuard; + Reference< XComponent > xModuleImageManager( m_xModuleImageManager ); + m_xModuleImageManager.clear(); + m_xModuleAcceleratorManager.clear(); + m_aUIElements[LAYER_USERDEFINED].clear(); + m_aUIElements[LAYER_DEFAULT].clear(); + m_xDefaultConfigStorage.clear(); + m_xUserConfigStorage.clear(); + m_xUserRootCommit.clear(); + m_bModified = false; + m_bDisposed = true; + aGuard.clear(); + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + + try + { + if ( xModuleImageManager.is() ) + xModuleImageManager->dispose(); + } + catch ( const Exception& ) + { + } +} + +void SAL_CALL ModuleUIConfigurationManager::addEventListener( const Reference< XEventListener >& xListener ) +{ + { + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + } + + std::unique_lock aGuard(m_mutex); + m_aEventListeners.addInterface( aGuard, xListener ); +} + +void SAL_CALL ModuleUIConfigurationManager::removeEventListener( const Reference< XEventListener >& xListener ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + std::unique_lock aGuard(m_mutex); + m_aEventListeners.removeInterface( aGuard, xListener ); +} + +// XUIConfiguration +void SAL_CALL ModuleUIConfigurationManager::addConfigurationListener( const Reference< css::ui::XUIConfigurationListener >& xListener ) +{ + { + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + } + + std::unique_lock aGuard(m_mutex); + m_aConfigListeners.addInterface( aGuard, xListener ); +} + +void SAL_CALL ModuleUIConfigurationManager::removeConfigurationListener( const Reference< css::ui::XUIConfigurationListener >& xListener ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + std::unique_lock aGuard(m_mutex); + m_aConfigListeners.removeInterface( aGuard, xListener ); +} + +// XUIConfigurationManager +void SAL_CALL ModuleUIConfigurationManager::reset() +{ + SolarMutexClearableGuard aGuard; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + + if ( isReadOnly() ) + return; + + // Remove all elements from our user-defined storage! + try + { + for ( int i = 1; i < css::ui::UIElementType::COUNT; i++ ) + { + UIElementType& rElementType = m_aUIElements[LAYER_USERDEFINED][i]; + + if ( rElementType.xStorage.is() ) + { + bool bCommitSubStorage( false ); + const Sequence< OUString > aUIElementStreamNames = rElementType.xStorage->getElementNames(); + for ( OUString const & rName : aUIElementStreamNames ) + { + rElementType.xStorage->removeElement( rName ); + bCommitSubStorage = true; + } + + if ( bCommitSubStorage ) + { + Reference< XTransactedObject > xTransactedObject( rElementType.xStorage, UNO_QUERY ); + if ( xTransactedObject.is() ) + xTransactedObject->commit(); + m_pStorageHandler[i]->commitUserChanges(); + } + } + } + + // remove settings from user defined layer and notify listener about removed settings data! + ConfigEventNotifyContainer aRemoveEventNotifyContainer; + ConfigEventNotifyContainer aReplaceEventNotifyContainer; + for ( sal_Int16 j = 1; j < css::ui::UIElementType::COUNT; j++ ) + { + try + { + UIElementType& rUserElementType = m_aUIElements[LAYER_USERDEFINED][j]; + UIElementType& rDefaultElementType = m_aUIElements[LAYER_DEFAULT][j]; + + impl_resetElementTypeData( rUserElementType, rDefaultElementType, aRemoveEventNotifyContainer, aReplaceEventNotifyContainer ); + rUserElementType.bModified = false; + } + catch (const Exception&) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( + "ModuleUIConfigurationManager::reset exception", + css::uno::Reference<css::uno::XInterface>(*this), anyEx); + } + } + + m_bModified = false; + + // Unlock mutex before notify our listeners + aGuard.clear(); + + // Notify our listeners + for ( auto const & k: aRemoveEventNotifyContainer ) + implts_notifyContainerListener( k, NotifyOp_Remove ); + for ( auto const & k: aReplaceEventNotifyContainer ) + implts_notifyContainerListener( k, NotifyOp_Replace ); + } + catch ( const css::lang::IllegalArgumentException& ) + { + } + catch ( const css::container::NoSuchElementException& ) + { + } + catch ( const css::embed::InvalidStorageException& ) + { + } + catch ( const css::embed::StorageWrappedTargetException& ) + { + } +} + +Sequence< Sequence< PropertyValue > > SAL_CALL ModuleUIConfigurationManager::getUIElementsInfo( sal_Int16 ElementType ) +{ + if (( ElementType < 0 ) || ( ElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + + SolarMutexGuard g; + if ( m_bDisposed ) + throw DisposedException(); + + std::vector< Sequence< PropertyValue > > aElementInfoSeq; + UIElementInfoHashMap aUIElementInfoCollection; + + if ( ElementType == css::ui::UIElementType::UNKNOWN ) + { + for ( sal_Int16 i = 0; i < css::ui::UIElementType::COUNT; i++ ) + impl_fillSequenceWithElementTypeInfo( aUIElementInfoCollection, i ); + } + else + impl_fillSequenceWithElementTypeInfo( aUIElementInfoCollection, ElementType ); + + aElementInfoSeq.resize( aUIElementInfoCollection.size() ); + + sal_Int32 n = 0; + for (auto const& elem : aUIElementInfoCollection) + { + Sequence< PropertyValue > aUIElementInfo{ + comphelper::makePropertyValue("ResourceURL", elem.second.aResourceURL), + comphelper::makePropertyValue(m_aPropUIName, elem.second.aUIName) + }; + aElementInfoSeq[n++] = aUIElementInfo; + } + + return comphelper::containerToSequence(aElementInfoSeq); +} + +Reference< XIndexContainer > SAL_CALL ModuleUIConfigurationManager::createSettings() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + // Creates an empty item container which can be filled from outside + return Reference< XIndexContainer >( new RootItemContainer() ); +} + +sal_Bool SAL_CALL ModuleUIConfigurationManager::hasSettings( const OUString& ResourceURL ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( ResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + UIElementData* pDataSettings = impl_findUIElementData( ResourceURL, nElementType, false ); + if ( pDataSettings ) + return true; + + return false; +} + +Reference< XIndexAccess > SAL_CALL ModuleUIConfigurationManager::getSettings( const OUString& ResourceURL, sal_Bool bWriteable ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( ResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + UIElementData* pDataSettings = impl_findUIElementData( ResourceURL, nElementType ); + if ( pDataSettings ) + { + // Create a copy of our data if someone wants to change the data. + if ( bWriteable ) + return Reference< XIndexAccess >( new RootItemContainer( pDataSettings->xSettings ) ); + else + return pDataSettings->xSettings; + } + + throw NoSuchElementException(); +} + +void SAL_CALL ModuleUIConfigurationManager::replaceSettings( const OUString& ResourceURL, const Reference< css::container::XIndexAccess >& aNewData ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( ResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + else if ( m_bReadOnly ) + throw IllegalAccessException(); + else + { + SolarMutexClearableGuard aGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + UIElementData* pDataSettings = impl_findUIElementData( ResourceURL, nElementType ); + if ( !pDataSettings ) + throw NoSuchElementException(); + if ( !pDataSettings->bDefaultNode ) + { + // we have a settings entry in our user-defined layer - replace + Reference< XIndexAccess > xOldSettings = pDataSettings->xSettings; + + // Create a copy of the data if the container is not const + Reference< XIndexReplace > xReplace( aNewData, UNO_QUERY ); + if ( xReplace.is() ) + pDataSettings->xSettings = new ConstItemContainer( aNewData ); + else + pDataSettings->xSettings = aNewData; + pDataSettings->bDefault = false; + pDataSettings->bModified = true; + m_bModified = true; + + // Modify type container + UIElementType& rElementType = m_aUIElements[LAYER_USERDEFINED][nElementType]; + rElementType.bModified = true; + + Reference< XUIConfigurationManager > xThis(this); + Reference< XInterface > xIfac( xThis, UNO_QUERY ); + + // Create event to notify listener about replaced element settings + ui::ConfigurationEvent aEvent; + aEvent.ResourceURL = ResourceURL; + aEvent.Accessor <<= xThis; + aEvent.Source = xIfac; + aEvent.ReplacedElement <<= xOldSettings; + aEvent.Element <<= pDataSettings->xSettings; + + aGuard.clear(); + + implts_notifyContainerListener( aEvent, NotifyOp_Replace ); + } + else + { + // we have no settings in our user-defined layer - insert + UIElementData aUIElementData; + + aUIElementData.bDefault = false; + aUIElementData.bDefaultNode = false; + aUIElementData.bModified = true; + + // Create a copy of the data if the container is not const + Reference< XIndexReplace > xReplace( aNewData, UNO_QUERY ); + if ( xReplace.is() ) + aUIElementData.xSettings = new ConstItemContainer( aNewData ); + else + aUIElementData.xSettings = aNewData; + aUIElementData.aName = RetrieveNameFromResourceURL( ResourceURL ) + m_aXMLPostfix; + aUIElementData.aResourceURL = ResourceURL; + m_bModified = true; + + // Modify type container + UIElementType& rElementType = m_aUIElements[LAYER_USERDEFINED][nElementType]; + rElementType.bModified = true; + + UIElementDataHashMap& rElements = rElementType.aElementsHashMap; + + // Check our user element settings hash map as it can already contain settings that have been set to default! + // If no node can be found, we have to insert it. + UIElementDataHashMap::iterator pIter = rElements.find( ResourceURL ); + if ( pIter != rElements.end() ) + pIter->second = aUIElementData; + else + rElements.emplace( ResourceURL, aUIElementData ); + + Reference< XUIConfigurationManager > xThis(this); + Reference< XInterface > xIfac( xThis, UNO_QUERY ); + + // Create event to notify listener about replaced element settings + ui::ConfigurationEvent aEvent; + + aEvent.ResourceURL = ResourceURL; + aEvent.Accessor <<= xThis; + aEvent.Source = xIfac; + aEvent.ReplacedElement <<= pDataSettings->xSettings; + aEvent.Element <<= aUIElementData.xSettings; + + aGuard.clear(); + + implts_notifyContainerListener( aEvent, NotifyOp_Replace ); + } + } +} + +void SAL_CALL ModuleUIConfigurationManager::removeSettings( const OUString& ResourceURL ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( ResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException( "The ResourceURL is not valid or " + "describes an unknown type. " + "ResourceURL: " + ResourceURL, nullptr, 0 ); + else if ( m_bReadOnly ) + throw IllegalAccessException( "The configuration manager is read-only. " + "ResourceURL: " + ResourceURL, nullptr ); + else + { + SolarMutexClearableGuard aGuard; + + if ( m_bDisposed ) + throw DisposedException( "The configuration manager has been disposed, " + "and can't uphold its method specification anymore. " + "ResourceURL: " + ResourceURL, nullptr ); + + UIElementData* pDataSettings = impl_findUIElementData( ResourceURL, nElementType ); + if ( !pDataSettings ) + throw NoSuchElementException( "The settings data cannot be found. " + "ResourceURL: " + ResourceURL, nullptr ); + // If element settings are default, we don't need to change anything! + if ( pDataSettings->bDefault ) + return; + else + { + Reference< XIndexAccess > xRemovedSettings = pDataSettings->xSettings; + pDataSettings->bDefault = true; + + // check if this is a default layer node + if ( !pDataSettings->bDefaultNode ) + pDataSettings->bModified = true; // we have to remove this node from the user layer! + pDataSettings->xSettings.clear(); + m_bModified = true; // user layer must be written + + // Modify type container + UIElementType& rElementType = m_aUIElements[LAYER_USERDEFINED][nElementType]; + rElementType.bModified = true; + + Reference< XUIConfigurationManager > xThis(this); + Reference< XInterface > xIfac( xThis, UNO_QUERY ); + + // Check if we have settings in the default layer which replaces the user-defined one! + UIElementData* pDefaultDataSettings = impl_findUIElementData( ResourceURL, nElementType ); + if ( pDefaultDataSettings ) + { + // Create event to notify listener about replaced element settings + ui::ConfigurationEvent aEvent; + + aEvent.ResourceURL = ResourceURL; + aEvent.Accessor <<= xThis; + aEvent.Source = xIfac; + aEvent.Element <<= xRemovedSettings; + aEvent.ReplacedElement <<= pDefaultDataSettings->xSettings; + + aGuard.clear(); + + implts_notifyContainerListener( aEvent, NotifyOp_Replace ); + } + else + { + // Create event to notify listener about removed element settings + ui::ConfigurationEvent aEvent; + + aEvent.ResourceURL = ResourceURL; + aEvent.Accessor <<= xThis; + aEvent.Source = xIfac; + aEvent.Element <<= xRemovedSettings; + + aGuard.clear(); + + implts_notifyContainerListener( aEvent, NotifyOp_Remove ); + } + } + } +} + +void SAL_CALL ModuleUIConfigurationManager::insertSettings( const OUString& NewResourceURL, const Reference< XIndexAccess >& aNewData ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( NewResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + else if ( m_bReadOnly ) + throw IllegalAccessException(); + else + { + SolarMutexClearableGuard aGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + UIElementData* pDataSettings = impl_findUIElementData( NewResourceURL, nElementType ); + if ( !(!pDataSettings) ) + throw ElementExistException(); + UIElementData aUIElementData; + + aUIElementData.bDefault = false; + aUIElementData.bDefaultNode = false; + aUIElementData.bModified = true; + + // Create a copy of the data if the container is not const + Reference< XIndexReplace > xReplace( aNewData, UNO_QUERY ); + if ( xReplace.is() ) + aUIElementData.xSettings = new ConstItemContainer( aNewData ); + else + aUIElementData.xSettings = aNewData; + aUIElementData.aName = RetrieveNameFromResourceURL( NewResourceURL ) + m_aXMLPostfix; + aUIElementData.aResourceURL = NewResourceURL; + m_bModified = true; + + UIElementType& rElementType = m_aUIElements[LAYER_USERDEFINED][nElementType]; + rElementType.bModified = true; + + UIElementDataHashMap& rElements = rElementType.aElementsHashMap; + rElements.emplace( NewResourceURL, aUIElementData ); + + Reference< XIndexAccess > xInsertSettings( aUIElementData.xSettings ); + Reference< XUIConfigurationManager > xThis(this); + + // Create event to notify listener about removed element settings + ui::ConfigurationEvent aEvent; + + aEvent.ResourceURL = NewResourceURL; + aEvent.Accessor <<= xThis; + aEvent.Source = xThis; + aEvent.Element <<= xInsertSettings; + + aGuard.clear(); + + implts_notifyContainerListener( aEvent, NotifyOp_Insert ); + } +} + +Reference< XInterface > SAL_CALL ModuleUIConfigurationManager::getImageManager() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_xModuleImageManager.is() ) + { + m_xModuleImageManager = new ImageManager( m_xContext, /*bForModule*/true ); + + uno::Sequence<uno::Any> aPropSeq(comphelper::InitAnyPropertySequence( + { + {"UserConfigStorage", uno::Any(m_xUserConfigStorage)}, + {"ModuleIdentifier", uno::Any(m_aModuleIdentifier)}, + {"UserRootCommit", uno::Any(m_xUserRootCommit)}, + })); + m_xModuleImageManager->initialize( aPropSeq ); + } + + return Reference< XInterface >( static_cast<cppu::OWeakObject*>(m_xModuleImageManager.get()), UNO_QUERY ); +} + +Reference< ui::XAcceleratorConfiguration > SAL_CALL ModuleUIConfigurationManager::getShortCutManager() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_xModuleAcceleratorManager.is() ) try + { + m_xModuleAcceleratorManager = ui::ModuleAcceleratorConfiguration:: + createWithModuleIdentifier(m_xContext, m_aModuleIdentifier); + } + catch ( const css::uno::DeploymentException& ) + { + SAL_WARN("fwk.uiconfiguration", "ModuleAcceleratorConfiguration" + " not available. This should happen only on mobile platforms."); + } + + return m_xModuleAcceleratorManager; +} + +Reference< XInterface > SAL_CALL ModuleUIConfigurationManager::getEventsManager() +{ + return Reference< XInterface >(); +} + +// XModuleUIConfigurationManager +sal_Bool SAL_CALL ModuleUIConfigurationManager::isDefaultSettings( const OUString& ResourceURL ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( ResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + UIElementData* pDataSettings = impl_findUIElementData( ResourceURL, nElementType, false ); + if ( pDataSettings && pDataSettings->bDefaultNode ) + return true; + + return false; +} + +Reference< XIndexAccess > SAL_CALL ModuleUIConfigurationManager::getDefaultSettings( const OUString& ResourceURL ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( ResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + // preload list of element types on demand + impl_preloadUIElementTypeList( LAYER_DEFAULT, nElementType ); + + // Look into our default vector/unordered_map combination + UIElementDataHashMap& rDefaultHashMap = m_aUIElements[LAYER_DEFAULT][nElementType].aElementsHashMap; + UIElementDataHashMap::iterator pIter = rDefaultHashMap.find( ResourceURL ); + if ( pIter != rDefaultHashMap.end() ) + { + if ( !pIter->second.xSettings.is() ) + impl_requestUIElementData( nElementType, LAYER_DEFAULT, pIter->second ); + return pIter->second.xSettings; + } + + // Nothing has been found! + throw NoSuchElementException(); +} + +// XUIConfigurationPersistence +void SAL_CALL ModuleUIConfigurationManager::reload() +{ + SolarMutexClearableGuard aGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_xUserConfigStorage.is() || !m_bModified || m_bReadOnly ) + return; + + // Try to access our module sub folder + ConfigEventNotifyContainer aRemoveNotifyContainer; + ConfigEventNotifyContainer aReplaceNotifyContainer; + for ( sal_Int16 i = 1; i < css::ui::UIElementType::COUNT; i++ ) + { + try + { + UIElementType& rUserElementType = m_aUIElements[LAYER_USERDEFINED][i]; + + if ( rUserElementType.bModified ) + { + UIElementType& rDefaultElementType = m_aUIElements[LAYER_DEFAULT][i]; + impl_reloadElementTypeData( rUserElementType, rDefaultElementType, aRemoveNotifyContainer, aReplaceNotifyContainer ); + } + } + catch ( const Exception& ) + { + throw IOException(); + } + } + + m_bModified = false; + + // Unlock mutex before notify our listeners + aGuard.clear(); + + // Notify our listeners + for (const ui::ConfigurationEvent & j : aRemoveNotifyContainer) + implts_notifyContainerListener( j, NotifyOp_Remove ); + for (const ui::ConfigurationEvent & k : aReplaceNotifyContainer) + implts_notifyContainerListener( k, NotifyOp_Replace ); +} + +void SAL_CALL ModuleUIConfigurationManager::store() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_xUserConfigStorage.is() || !m_bModified || m_bReadOnly ) + return; + + // Try to access our module sub folder + for ( int i = 1; i < css::ui::UIElementType::COUNT; i++ ) + { + try + { + UIElementType& rElementType = m_aUIElements[LAYER_USERDEFINED][i]; + + if ( rElementType.bModified && rElementType.xStorage.is() ) + { + impl_storeElementTypeData( rElementType.xStorage, rElementType ); + m_pStorageHandler[i]->commitUserChanges(); + } + } + catch ( const Exception& ) + { + throw IOException(); + } + } + + m_bModified = false; +} + +void SAL_CALL ModuleUIConfigurationManager::storeToStorage( const Reference< XStorage >& Storage ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_xUserConfigStorage.is() || !m_bModified || m_bReadOnly ) + return; + + // Try to access our module sub folder + for ( int i = 1; i < css::ui::UIElementType::COUNT; i++ ) + { + try + { + Reference< XStorage > xElementTypeStorage( Storage->openStorageElement( + OUString(UIELEMENTTYPENAMES[i]), ElementModes::READWRITE )); + UIElementType& rElementType = m_aUIElements[LAYER_USERDEFINED][i]; + + if ( rElementType.bModified && xElementTypeStorage.is() ) + impl_storeElementTypeData( xElementTypeStorage, rElementType, false ); // store data to storage, but don't reset modify flag! + } + catch ( const Exception& ) + { + throw IOException(); + } + } + + Reference< XTransactedObject > xTransactedObject( Storage, UNO_QUERY ); + if ( xTransactedObject.is() ) + xTransactedObject->commit(); +} + +sal_Bool SAL_CALL ModuleUIConfigurationManager::isModified() +{ + SolarMutexGuard g; + + return m_bModified; +} + +sal_Bool SAL_CALL ModuleUIConfigurationManager::isReadOnly() +{ + SolarMutexGuard g; + + return m_bReadOnly; +} + +void ModuleUIConfigurationManager::implts_notifyContainerListener( const ui::ConfigurationEvent& aEvent, NotifyOp eOp ) +{ + std::unique_lock aGuard(m_mutex); + using ListenerMethodType = void (SAL_CALL css::ui::XUIConfigurationListener::*)(const ui::ConfigurationEvent&); + ListenerMethodType aListenerMethod {}; + switch ( eOp ) + { + case NotifyOp_Replace: + aListenerMethod = &css::ui::XUIConfigurationListener::elementReplaced; + break; + case NotifyOp_Insert: + aListenerMethod = &css::ui::XUIConfigurationListener::elementInserted; + break; + case NotifyOp_Remove: + aListenerMethod = &css::ui::XUIConfigurationListener::elementRemoved; + break; + } + m_aConfigListeners.notifyEach(aGuard, aListenerMethod, aEvent); +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ModuleUIConfigurationManager_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &arguments) +{ + return cppu::acquire(new ModuleUIConfigurationManager(context, arguments)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/uicategorydescription.cxx b/framework/source/uiconfiguration/uicategorydescription.cxx new file mode 100644 index 000000000..620384fad --- /dev/null +++ b/framework/source/uiconfiguration/uicategorydescription.cxx @@ -0,0 +1,395 @@ +/* -*- 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 <uielement/uicommanddescription.hxx> + +#include <helper/mischelper.hxx> + +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XContainer.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <sal/log.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unotools/syslocale.hxx> + +#include <comphelper/propertysequence.hxx> + +#include <string_view> +#include <unordered_map> + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::configuration; +using namespace com::sun::star::container; +using namespace framework; + +namespace { + +class ConfigurationAccess_UICategory : public ::cppu::WeakImplHelper<XNameAccess,XContainerListener> +{ + osl::Mutex aMutex; + public: + ConfigurationAccess_UICategory( std::u16string_view aModuleName, const Reference< XNameAccess >& xGenericUICommands, const Reference< XComponentContext >& rxContext ); + virtual ~ConfigurationAccess_UICategory() override; + + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override; + + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType() override; + + virtual sal_Bool SAL_CALL hasElements() override; + + // container.XContainerListener + virtual void SAL_CALL elementInserted( const ContainerEvent& aEvent ) override; + virtual void SAL_CALL elementRemoved ( const ContainerEvent& aEvent ) override; + virtual void SAL_CALL elementReplaced( const ContainerEvent& aEvent ) override; + + // lang.XEventListener + virtual void SAL_CALL disposing( const EventObject& aEvent ) override; + + protected: + Any getUINameFromID( const OUString& rId ); + Any getUINameFromCache( const OUString& rId ); + Sequence< OUString > getAllIds(); + void fillCache(); + + private: + typedef std::unordered_map< OUString, + OUString > IdToInfoCache; + + void initializeConfigAccess(); + + OUString m_aConfigCategoryAccess; + OUString m_aPropUIName; + Reference< XNameAccess > m_xGenericUICategories; + Reference< XMultiServiceFactory > m_xConfigProvider; + Reference< XNameAccess > m_xConfigAccess; + Reference< XContainerListener > m_xConfigListener; + bool m_bConfigAccessInitialized; + bool m_bCacheFilled; + IdToInfoCache m_aIdCache; +}; + +// XInterface, XTypeProvider + +ConfigurationAccess_UICategory::ConfigurationAccess_UICategory( std::u16string_view aModuleName, const Reference< XNameAccess >& rGenericUICategories, const Reference< XComponentContext >& rxContext ) : + // Create configuration hierarchical access name + m_aConfigCategoryAccess( + OUString::Concat("/org.openoffice.Office.UI.") + aModuleName + "/Commands/Categories"), + m_aPropUIName( "Name" ), + m_xGenericUICategories( rGenericUICategories ), + m_xConfigProvider(theDefaultProvider::get( rxContext )), + m_bConfigAccessInitialized( false ), + m_bCacheFilled( false ) +{ +} + +ConfigurationAccess_UICategory::~ConfigurationAccess_UICategory() +{ + // SAFE + osl::MutexGuard g(aMutex); + Reference< XContainer > xContainer( m_xConfigAccess, UNO_QUERY ); + if ( xContainer.is() ) + xContainer->removeContainerListener(m_xConfigListener); +} + +// XNameAccess +Any SAL_CALL ConfigurationAccess_UICategory::getByName( const OUString& rId ) +{ + osl::MutexGuard g(aMutex); + if ( !m_bConfigAccessInitialized ) + { + initializeConfigAccess(); + m_bConfigAccessInitialized = true; + fillCache(); + } + + // SAFE + Any a = getUINameFromID( rId ); + + if ( !a.hasValue() ) + throw NoSuchElementException(); + + return a; +} + +Sequence< OUString > SAL_CALL ConfigurationAccess_UICategory::getElementNames() +{ + return getAllIds(); +} + +sal_Bool SAL_CALL ConfigurationAccess_UICategory::hasByName( const OUString& rId ) +{ + return getByName( rId ).hasValue(); +} + +// XElementAccess +Type SAL_CALL ConfigurationAccess_UICategory::getElementType() +{ + return cppu::UnoType<OUString>::get(); +} + +sal_Bool SAL_CALL ConfigurationAccess_UICategory::hasElements() +{ + // There must be global categories! + return true; +} + +void ConfigurationAccess_UICategory::fillCache() +{ + SAL_INFO( "fwk", "framework (cd100003) ::ConfigurationAccess_UICategory::fillCache" ); + + if ( m_bCacheFilled ) + return; + + OUString aUIName; + const Sequence< OUString > aNameSeq = m_xConfigAccess->getElementNames(); + + for ( OUString const & rName : aNameSeq ) + { + try + { + Reference< XNameAccess > xNameAccess(m_xConfigAccess->getByName( rName ),UNO_QUERY); + if ( xNameAccess.is() ) + { + xNameAccess->getByName( m_aPropUIName ) >>= aUIName; + + m_aIdCache.emplace( rName, aUIName ); + } + } + catch ( const css::lang::WrappedTargetException& ) + { + } + catch ( const css::container::NoSuchElementException& ) + { + } + } + + m_bCacheFilled = true; +} + +Any ConfigurationAccess_UICategory::getUINameFromID( const OUString& rId ) +{ + Any a; + + try + { + a = getUINameFromCache( rId ); + if ( !a.hasValue() ) + { + // Try to ask our global commands configuration access + if ( m_xGenericUICategories.is() ) + { + try + { + return m_xGenericUICategories->getByName( rId ); + } + catch ( const css::lang::WrappedTargetException& ) + { + } + catch ( const css::container::NoSuchElementException& ) + { + } + } + } + } + catch( const css::container::NoSuchElementException& ) + { + } + catch ( const css::lang::WrappedTargetException& ) + { + } + + return a; +} + +Any ConfigurationAccess_UICategory::getUINameFromCache( const OUString& rId ) +{ + Any a; + + IdToInfoCache::const_iterator pIter = m_aIdCache.find( rId ); + if ( pIter != m_aIdCache.end() ) + a <<= pIter->second; + + return a; +} + +Sequence< OUString > ConfigurationAccess_UICategory::getAllIds() +{ + // SAFE + osl::MutexGuard g(aMutex); + + if ( !m_bConfigAccessInitialized ) + { + initializeConfigAccess(); + m_bConfigAccessInitialized = true; + fillCache(); + } + + if ( m_xConfigAccess.is() ) + { + try + { + Sequence< OUString > aNameSeq = m_xConfigAccess->getElementNames(); + + if ( m_xGenericUICategories.is() ) + { + // Create concat list of supported user interface commands of the module + Sequence< OUString > aGenericNameSeq = m_xGenericUICategories->getElementNames(); + sal_uInt32 nCount1 = aNameSeq.getLength(); + sal_uInt32 nCount2 = aGenericNameSeq.getLength(); + + aNameSeq.realloc( nCount1 + nCount2 ); + OUString* pNameSeq = aNameSeq.getArray(); + const OUString* pGenericSeq = aGenericNameSeq.getConstArray(); + for ( sal_uInt32 i = 0; i < nCount2; i++ ) + pNameSeq[nCount1+i] = pGenericSeq[i]; + } + + return aNameSeq; + } + catch( const css::container::NoSuchElementException& ) + { + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + + return Sequence< OUString >(); +} + +void ConfigurationAccess_UICategory::initializeConfigAccess() +{ + try + { + Sequence<Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"nodepath", Any(m_aConfigCategoryAccess)} + })); + + m_xConfigAccess.set( m_xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", aArgs ),UNO_QUERY ); + if ( m_xConfigAccess.is() ) + { + // Add as container listener + Reference< XContainer > xContainer( m_xConfigAccess, UNO_QUERY ); + if ( xContainer.is() ) + { + m_xConfigListener = new WeakContainerListener(this); + xContainer->addContainerListener(m_xConfigListener); + } + } + } + catch ( const WrappedTargetException& ) + { + } + catch ( const Exception& ) + { + } +} + +// container.XContainerListener +void SAL_CALL ConfigurationAccess_UICategory::elementInserted( const ContainerEvent& ) +{ +} + +void SAL_CALL ConfigurationAccess_UICategory::elementRemoved ( const ContainerEvent& ) +{ +} + +void SAL_CALL ConfigurationAccess_UICategory::elementReplaced( const ContainerEvent& ) +{ +} + +// lang.XEventListener +void SAL_CALL ConfigurationAccess_UICategory::disposing( const EventObject& aEvent ) +{ + // SAFE + // remove our reference to the config access + osl::MutexGuard g(aMutex); + + Reference< XInterface > xIfac1( aEvent.Source, UNO_QUERY ); + Reference< XInterface > xIfac2( m_xConfigAccess, UNO_QUERY ); + if ( xIfac1 == xIfac2 ) + m_xConfigAccess.clear(); +} + +class UICategoryDescription : public UICommandDescription +{ +public: + explicit UICategoryDescription( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.UICategoryDescription"; + } + + 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.UICategoryDescription"}; + } + +}; + +UICategoryDescription::UICategoryDescription( const Reference< XComponentContext >& rxContext ) : + UICommandDescription(rxContext,true) +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + Reference< XNameAccess > xEmpty; + OUString aGenericCategories( "GenericCategories" ); + m_xGenericUICommands[rCurrentLanguage] = new ConfigurationAccess_UICategory( aGenericCategories, xEmpty, rxContext ); + + // insert generic categories mappings + m_aModuleToCommandFileMap.emplace( OUString("generic"), aGenericCategories ); + + auto& rMap = m_aUICommandsHashMap[rCurrentLanguage]; + UICommandsHashMap::iterator pCatIter = rMap.find( aGenericCategories ); + if ( pCatIter != rMap.end() ) + pCatIter->second = m_xGenericUICommands[rCurrentLanguage]; + + impl_fillElements("ooSetupFactoryCmdCategoryConfigRef"); +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_UICategoryDescription_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new UICategoryDescription(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/uiconfigurationmanager.cxx b/framework/source/uiconfiguration/uiconfigurationmanager.cxx new file mode 100644 index 000000000..24dbe1639 --- /dev/null +++ b/framework/source/uiconfiguration/uiconfigurationmanager.cxx @@ -0,0 +1,1377 @@ +/* -*- 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 <uiconfiguration/imagemanager.hxx> +#include <uielement/rootitemcontainer.hxx> +#include <uielement/constitemcontainer.hxx> +#include <uielement/uielementtypenames.hxx> +#include <menuconfiguration.hxx> +#include <statusbarconfiguration.hxx> +#include <toolboxconfiguration.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/ElementExistException.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/InvalidStorageException.hpp> +#include <com/sun/star/embed/StorageWrappedTargetException.hpp> +#include <com/sun/star/embed/XTransactedObject.hpp> +#include <com/sun/star/lang/IllegalAccessException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/ui/UIElementType.hpp> +#include <com/sun/star/ui/ConfigurationEvent.hpp> +#include <com/sun/star/ui/DocumentAcceleratorConfiguration.hpp> +#include <com/sun/star/ui/XAcceleratorConfiguration.hpp> +#include <com/sun/star/ui/XUIConfigurationManager2.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/interfacecontainer4.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +#include <mutex> +#include <string_view> +#include <unordered_map> + +using namespace com::sun::star::uno; +using namespace com::sun::star::io; +using namespace com::sun::star::embed; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::beans; +using namespace com::sun::star::ui; +using namespace framework; + +namespace { + +class UIConfigurationManager : public ::cppu::WeakImplHelper< + css::lang::XServiceInfo , + css::ui::XUIConfigurationManager2 > +{ +public: + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.UIConfigurationManager"; + } + + 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.UIConfigurationManager"}; + } + + explicit UIConfigurationManager( css::uno::Reference< css::uno::XComponentContext > xContext ); + + // XComponent + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override; + + // XUIConfiguration + virtual void SAL_CALL addConfigurationListener( const css::uno::Reference< css::ui::XUIConfigurationListener >& Listener ) override; + virtual void SAL_CALL removeConfigurationListener( const css::uno::Reference< css::ui::XUIConfigurationListener >& Listener ) override; + + // XUIConfigurationManager + virtual void SAL_CALL reset() override; + virtual css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > > SAL_CALL getUIElementsInfo( sal_Int16 ElementType ) override; + virtual css::uno::Reference< css::container::XIndexContainer > SAL_CALL createSettings( ) override; + virtual sal_Bool SAL_CALL hasSettings( const OUString& ResourceURL ) override; + virtual css::uno::Reference< css::container::XIndexAccess > SAL_CALL getSettings( const OUString& ResourceURL, sal_Bool bWriteable ) override; + virtual void SAL_CALL replaceSettings( const OUString& ResourceURL, const css::uno::Reference< css::container::XIndexAccess >& aNewData ) override; + virtual void SAL_CALL removeSettings( const OUString& ResourceURL ) override; + virtual void SAL_CALL insertSettings( const OUString& NewResourceURL, const css::uno::Reference< css::container::XIndexAccess >& aNewData ) override; + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL getImageManager() override; + virtual css::uno::Reference< css::ui::XAcceleratorConfiguration > SAL_CALL getShortCutManager() override; + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL getEventsManager() override; + + // XUIConfigurationPersistence + virtual void SAL_CALL reload() override; + virtual void SAL_CALL store() override; + virtual void SAL_CALL storeToStorage( const css::uno::Reference< css::embed::XStorage >& Storage ) override; + virtual sal_Bool SAL_CALL isModified() override; + virtual sal_Bool SAL_CALL isReadOnly() override; + + // XUIConfigurationStorage + virtual void SAL_CALL setStorage( const css::uno::Reference< css::embed::XStorage >& Storage ) override; + virtual sal_Bool SAL_CALL hasStorage() override; + +private: + // private data types + enum NotifyOp + { + NotifyOp_Remove, + NotifyOp_Insert, + NotifyOp_Replace + }; + + struct UIElementInfo + { + UIElementInfo( OUString _aResourceURL, OUString _aUIName ) : + aResourceURL(std::move( _aResourceURL)), aUIName(std::move( _aUIName )) {} + OUString aResourceURL; + OUString aUIName; + }; + + struct UIElementData + { + UIElementData() : bModified( false ), bDefault( true ) {}; + + OUString aResourceURL; + OUString aName; + bool bModified; // has been changed since last storing + bool bDefault; // default settings + css::uno::Reference< css::container::XIndexAccess > xSettings; + }; + + struct UIElementType; + friend struct UIElementType; + typedef std::unordered_map< OUString, UIElementData > UIElementDataHashMap; + + struct UIElementType + { + UIElementType() : bModified( false ), + bLoaded( false ), + nElementType( css::ui::UIElementType::UNKNOWN ) {} + + bool bModified; + bool bLoaded; + sal_Int16 nElementType; + UIElementDataHashMap aElementsHashMap; + css::uno::Reference< css::embed::XStorage > xStorage; + }; + + typedef std::vector< UIElementType > UIElementTypesVector; + typedef std::vector< css::ui::ConfigurationEvent > ConfigEventNotifyContainer; + typedef std::unordered_map< OUString, UIElementInfo > UIElementInfoHashMap; + + void impl_Initialize(); + void implts_notifyContainerListener( const css::ui::ConfigurationEvent& aEvent, NotifyOp eOp ); + void impl_fillSequenceWithElementTypeInfo( UIElementInfoHashMap& aUIElementInfoCollection, sal_Int16 nElementType ); + void impl_preloadUIElementTypeList( sal_Int16 nElementType ); + UIElementData* impl_findUIElementData( const OUString& aResourceURL, sal_Int16 nElementType, bool bLoad = true ); + void impl_requestUIElementData( sal_Int16 nElementType, UIElementData& aUIElementData ); + void impl_storeElementTypeData( css::uno::Reference< css::embed::XStorage > const & xStorage, UIElementType& rElementType, bool bResetModifyState = true ); + void impl_resetElementTypeData( UIElementType& rDocElementType, ConfigEventNotifyContainer& rRemoveNotifyContainer ); + void impl_reloadElementTypeData( UIElementType& rDocElementType, ConfigEventNotifyContainer& rRemoveNotifyContainer, ConfigEventNotifyContainer& rReplaceNotifyContainer ); + + UIElementTypesVector m_aUIElements; + css::uno::Reference< css::embed::XStorage > m_xDocConfigStorage; + bool m_bReadOnly; + bool m_bModified; + bool m_bDisposed; + OUString m_aPropUIName; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + std::mutex m_mutex; + comphelper::OInterfaceContainerHelper4<css::lang::XEventListener> m_aEventListeners; + comphelper::OInterfaceContainerHelper4<css::ui::XUIConfigurationListener> m_aConfigListeners; + rtl::Reference< ImageManager > m_xImageManager; + css::uno::Reference< css::ui::XAcceleratorConfiguration > m_xAccConfig; +}; + +// important: The order and position of the elements must match the constant +// definition of "css::ui::UIElementType" +std::u16string_view UIELEMENTTYPENAMES[] = +{ + u"", // Dummy value for unknown! + u"" UIELEMENTTYPE_MENUBAR_NAME, + u"" UIELEMENTTYPE_POPUPMENU_NAME, + u"" UIELEMENTTYPE_TOOLBAR_NAME, + u"" UIELEMENTTYPE_STATUSBAR_NAME, + u"" UIELEMENTTYPE_FLOATINGWINDOW_NAME, + u"" UIELEMENTTYPE_PROGRESSBAR_NAME, + u"" UIELEMENTTYPE_TOOLPANEL_NAME +}; + +const char RESOURCEURL_PREFIX[] = "private:resource/"; +const sal_Int32 RESOURCEURL_PREFIX_SIZE = 17; + +sal_Int16 RetrieveTypeFromResourceURL( const OUString& aResourceURL ) +{ + + if (( aResourceURL.startsWith( RESOURCEURL_PREFIX ) ) && + ( aResourceURL.getLength() > RESOURCEURL_PREFIX_SIZE )) + { + std::u16string_view aTmpStr = aResourceURL.subView( RESOURCEURL_PREFIX_SIZE ); + size_t nIndex = aTmpStr.find( '/' ); + if (( nIndex > 0 ) && ( aTmpStr.size() > nIndex )) + { + std::u16string_view aTypeStr( aTmpStr.substr( 0, nIndex )); + for ( int i = 0; i < UIElementType::COUNT; i++ ) + { + if ( aTypeStr == UIELEMENTTYPENAMES[i] ) + return sal_Int16( i ); + } + } + } + + return UIElementType::UNKNOWN; +} + +OUString RetrieveNameFromResourceURL( const OUString& aResourceURL ) +{ + if (( aResourceURL.startsWith( RESOURCEURL_PREFIX ) ) && + ( aResourceURL.getLength() > RESOURCEURL_PREFIX_SIZE )) + { + sal_Int32 nIndex = aResourceURL.lastIndexOf( '/' ); + if (( nIndex > 0 ) && (( nIndex+1 ) < aResourceURL.getLength())) + return aResourceURL.copy( nIndex+1 ); + } + + return OUString(); +} + +void UIConfigurationManager::impl_fillSequenceWithElementTypeInfo( UIElementInfoHashMap& aUIElementInfoCollection, sal_Int16 nElementType ) +{ + // preload list of element types on demand + impl_preloadUIElementTypeList( nElementType ); + + UIElementDataHashMap& rUserElements = m_aUIElements[nElementType].aElementsHashMap; + + for (auto const& elem : rUserElements) + { + UIElementData* pDataSettings = impl_findUIElementData( elem.second.aResourceURL, nElementType ); + if ( pDataSettings && !pDataSettings->bDefault ) + { + // Retrieve user interface name from XPropertySet interface + OUString aUIName; + Reference< XPropertySet > xPropSet( pDataSettings->xSettings, UNO_QUERY ); + if ( xPropSet.is() ) + { + Any a = xPropSet->getPropertyValue( m_aPropUIName ); + a >>= aUIName; + } + + UIElementInfo aInfo( elem.second.aResourceURL, aUIName ); + aUIElementInfoCollection.emplace( elem.second.aResourceURL, aInfo ); + } + } +} + +void UIConfigurationManager::impl_preloadUIElementTypeList( sal_Int16 nElementType ) +{ + UIElementType& rElementTypeData = m_aUIElements[nElementType]; + + if ( !rElementTypeData.bLoaded ) + { + Reference< XStorage > xElementTypeStorage = rElementTypeData.xStorage; + if ( xElementTypeStorage.is() ) + { + OUString aResURLPrefix = + OUString::Concat(RESOURCEURL_PREFIX) + + UIELEMENTTYPENAMES[ nElementType ] + + "/"; + + UIElementDataHashMap& rHashMap = rElementTypeData.aElementsHashMap; + const Sequence< OUString > aUIElementNames = xElementTypeStorage->getElementNames(); + for ( OUString const & rElementName : aUIElementNames ) + { + UIElementData aUIElementData; + + // Resource name must be without ".xml" + sal_Int32 nIndex = rElementName.lastIndexOf( '.' ); + if (( nIndex > 0 ) && ( nIndex < rElementName.getLength() )) + { + std::u16string_view aExtension( rElementName.subView( nIndex+1 )); + std::u16string_view aUIElementName( rElementName.subView( 0, nIndex )); + + if (!aUIElementName.empty() && + ( o3tl::equalsIgnoreAsciiCase(aExtension, u"xml"))) + { + aUIElementData.aResourceURL = aResURLPrefix + aUIElementName; + aUIElementData.aName = rElementName; + aUIElementData.bModified = false; + aUIElementData.bDefault = false; + + // Create unordered_map entries for all user interface elements inside the storage. We don't load the + // settings to speed up the process. + rHashMap.emplace( aUIElementData.aResourceURL, aUIElementData ); + } + } + } + } + } + + rElementTypeData.bLoaded = true; +} + +void UIConfigurationManager::impl_requestUIElementData( sal_Int16 nElementType, UIElementData& aUIElementData ) +{ + UIElementType& rElementTypeData = m_aUIElements[nElementType]; + + Reference< XStorage > xElementTypeStorage = rElementTypeData.xStorage; + if ( xElementTypeStorage.is() && !aUIElementData.aName.isEmpty() ) + { + try + { + Reference< XStream > xStream = xElementTypeStorage->openStreamElement( aUIElementData.aName, ElementModes::READ ); + Reference< XInputStream > xInputStream = xStream->getInputStream(); + + if ( xInputStream.is() ) + { + switch ( nElementType ) + { + case css::ui::UIElementType::UNKNOWN: + break; + + case css::ui::UIElementType::MENUBAR: + case css::ui::UIElementType::POPUPMENU: + { + try + { + MenuConfiguration aMenuCfg( m_xContext ); + Reference< XIndexAccess > xContainer( aMenuCfg.CreateMenuBarConfigurationFromXML( xInputStream )); + auto pRootItemContainer = comphelper::getFromUnoTunnel<RootItemContainer>( xContainer ); + if ( pRootItemContainer ) + aUIElementData.xSettings = new ConstItemContainer( pRootItemContainer, true ); + else + aUIElementData.xSettings = new ConstItemContainer( xContainer, true ); + return; + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + break; + + case css::ui::UIElementType::TOOLBAR: + { + try + { + Reference< XIndexContainer > xIndexContainer( new RootItemContainer() ); + ToolBoxConfiguration::LoadToolBox( m_xContext, xInputStream, xIndexContainer ); + auto pRootItemContainer = comphelper::getFromUnoTunnel<RootItemContainer>( xIndexContainer ); + aUIElementData.xSettings = new ConstItemContainer( pRootItemContainer, true ); + return; + } + catch ( const css::lang::WrappedTargetException& ) + { + } + + break; + } + + case css::ui::UIElementType::STATUSBAR: + { + try + { + Reference< XIndexContainer > xIndexContainer( new RootItemContainer() ); + StatusBarConfiguration::LoadStatusBar( m_xContext, xInputStream, xIndexContainer ); + auto pRootItemContainer = comphelper::getFromUnoTunnel<RootItemContainer>( xIndexContainer ); + aUIElementData.xSettings = new ConstItemContainer( pRootItemContainer, true ); + return; + } + catch ( const css::lang::WrappedTargetException& ) + { + } + + break; + } + + case css::ui::UIElementType::FLOATINGWINDOW: + { + break; + } + } + } + } + catch ( const css::embed::InvalidStorageException& ) + { + } + catch ( const css::lang::IllegalArgumentException& ) + { + } + catch ( const css::io::IOException& ) + { + } + catch ( const css::embed::StorageWrappedTargetException& ) + { + } + } + + // At least we provide an empty settings container! + aUIElementData.xSettings = new ConstItemContainer(); +} + +UIConfigurationManager::UIElementData* UIConfigurationManager::impl_findUIElementData( const OUString& aResourceURL, sal_Int16 nElementType, bool bLoad ) +{ + // preload list of element types on demand + impl_preloadUIElementTypeList( nElementType ); + + // try to look into our document vector/unordered_map combination + UIElementDataHashMap& rUserHashMap = m_aUIElements[nElementType].aElementsHashMap; + UIElementDataHashMap::iterator pIter = rUserHashMap.find( aResourceURL ); + if ( pIter != rUserHashMap.end() ) + { + // Default data settings data means removed! + if ( pIter->second.bDefault ) + return &(pIter->second); + else + { + if ( !pIter->second.xSettings.is() && bLoad ) + impl_requestUIElementData( nElementType, pIter->second ); + return &(pIter->second); + } + } + + // Nothing has been found! + return nullptr; +} + +void UIConfigurationManager::impl_storeElementTypeData( Reference< XStorage > const & xStorage, UIElementType& rElementType, bool bResetModifyState ) +{ + UIElementDataHashMap& rHashMap = rElementType.aElementsHashMap; + + for (auto & elem : rHashMap) + { + UIElementData& rElement = elem.second; + if ( rElement.bModified ) + { + if ( rElement.bDefault ) + { + xStorage->removeElement( rElement.aName ); + rElement.bModified = false; // mark as not modified + } + else + { + Reference< XStream > xStream = xStorage->openStreamElement( rElement.aName, ElementModes::WRITE|ElementModes::TRUNCATE ); + Reference< XOutputStream > xOutputStream( xStream->getOutputStream() ); + + if ( xOutputStream.is() ) + { + switch( rElementType.nElementType ) + { + case css::ui::UIElementType::MENUBAR: + case css::ui::UIElementType::POPUPMENU: + { + try + { + MenuConfiguration aMenuCfg( m_xContext ); + aMenuCfg.StoreMenuBarConfigurationToXML( + rElement.xSettings, xOutputStream, rElementType.nElementType == css::ui::UIElementType::MENUBAR ); + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + break; + + case css::ui::UIElementType::TOOLBAR: + { + try + { + ToolBoxConfiguration::StoreToolBox( m_xContext, xOutputStream, rElement.xSettings ); + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + break; + + case css::ui::UIElementType::STATUSBAR: + { + try + { + StatusBarConfiguration::StoreStatusBar( m_xContext, xOutputStream, rElement.xSettings ); + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + break; + + default: + break; + } + } + + // mark as not modified if we store to our own storage + if ( bResetModifyState ) + rElement.bModified = false; + } + } + } + + // commit element type storage + Reference< XTransactedObject > xTransactedObject( xStorage, UNO_QUERY ); + if ( xTransactedObject.is() ) + xTransactedObject->commit(); + + // mark UIElementType as not modified if we store to our own storage + if ( bResetModifyState ) + rElementType.bModified = false; +} + +void UIConfigurationManager::impl_resetElementTypeData( + UIElementType& rDocElementType, + ConfigEventNotifyContainer& rRemoveNotifyContainer ) +{ + UIElementDataHashMap& rHashMap = rDocElementType.aElementsHashMap; + + Reference< XUIConfigurationManager > xThis(this); + Reference< XInterface > xIfac( xThis, UNO_QUERY ); + + // Make copies of the event structures to be thread-safe. We have to unlock our mutex before calling + // our listeners! + for (auto & elem : rHashMap) + { + UIElementData& rElement = elem.second; + if ( !rElement.bDefault ) + { + // Remove user-defined settings from document + ConfigurationEvent aEvent; + aEvent.ResourceURL = rElement.aResourceURL; + aEvent.Accessor <<= xThis; + aEvent.Source = xIfac; + aEvent.Element <<= rElement.xSettings; + + rRemoveNotifyContainer.push_back( aEvent ); + + // Mark element as default. + rElement.bModified = false; + rElement.bDefault = true; + } + else + rElement.bModified = false; + } + + // Remove all settings from our user interface elements + rHashMap.clear(); +} + +void UIConfigurationManager::impl_reloadElementTypeData( + UIElementType& rDocElementType, + ConfigEventNotifyContainer& rRemoveNotifyContainer, + ConfigEventNotifyContainer& rReplaceNotifyContainer ) +{ + UIElementDataHashMap& rHashMap = rDocElementType.aElementsHashMap; + Reference< XStorage > xElementStorage( rDocElementType.xStorage ); + + Reference< XUIConfigurationManager > xThis(this); + Reference< XInterface > xIfac( xThis, UNO_QUERY ); + sal_Int16 nType = rDocElementType.nElementType; + + for (auto & elem : rHashMap) + { + UIElementData& rElement = elem.second; + if ( rElement.bModified ) + { + if ( xElementStorage->hasByName( rElement.aName )) + { + // Replace settings with data from user layer + Reference< XIndexAccess > xOldSettings( rElement.xSettings ); + + impl_requestUIElementData( nType, rElement ); + + ConfigurationEvent aReplaceEvent; + + aReplaceEvent.ResourceURL = rElement.aResourceURL; + aReplaceEvent.Accessor <<= xThis; + aReplaceEvent.Source = xIfac; + aReplaceEvent.ReplacedElement <<= xOldSettings; + aReplaceEvent.Element <<= rElement.xSettings; + rReplaceNotifyContainer.push_back( aReplaceEvent ); + + rElement.bModified = false; + } + else + { + // Element settings are not in any storage => remove + ConfigurationEvent aRemoveEvent; + + aRemoveEvent.ResourceURL = rElement.aResourceURL; + aRemoveEvent.Accessor <<= xThis; + aRemoveEvent.Source = xIfac; + aRemoveEvent.Element <<= rElement.xSettings; + + rRemoveNotifyContainer.push_back( aRemoveEvent ); + + // Mark element as default and not modified. That means "not active" in the document anymore + rElement.bModified = false; + rElement.bDefault = true; + } + } + } + + rDocElementType.bModified = false; +} + +void UIConfigurationManager::impl_Initialize() +{ + // Initialize the top-level structures with the storage data + if ( m_xDocConfigStorage.is() ) + { + tools::Long nModes = m_bReadOnly ? ElementModes::READ : ElementModes::READWRITE; + + // Try to access our module sub folder + for ( sal_Int16 i = 1; i < css::ui::UIElementType::COUNT; + i++ ) + { + Reference< XStorage > xElementTypeStorage; + try + { + xElementTypeStorage = m_xDocConfigStorage->openStorageElement( OUString(UIELEMENTTYPENAMES[i]), nModes ); + } + catch ( const css::container::NoSuchElementException& ) + { + } + catch ( const css::embed::InvalidStorageException& ) + { + } + catch ( const css::lang::IllegalArgumentException& ) + { + } + catch ( const css::io::IOException& ) + { + } + catch ( const css::embed::StorageWrappedTargetException& ) + { + } + + m_aUIElements[i].nElementType = i; + m_aUIElements[i].bModified = false; + m_aUIElements[i].xStorage = xElementTypeStorage; + } + } + else + { + // We have no storage, just initialize ui element types with empty storage! + for ( int i = 1; i < css::ui::UIElementType::COUNT; i++ ) + m_aUIElements[i].xStorage = m_xDocConfigStorage; + } +} + +UIConfigurationManager::UIConfigurationManager( css::uno::Reference< css::uno::XComponentContext > xContext ) : + m_bReadOnly( true ) + , m_bModified( false ) + , m_bDisposed( false ) + , m_aPropUIName( "UIName" ) + , m_xContext(std::move( xContext )) +{ + // Make sure we have a default initialized entry for every layer and user interface element type! + // The following code depends on this! + m_aUIElements.resize( css::ui::UIElementType::COUNT ); +} + +// XComponent +void SAL_CALL UIConfigurationManager::dispose() +{ + Reference< XComponent > xThis(this); + + css::lang::EventObject aEvent( xThis ); + { + std::unique_lock aGuard(m_mutex); + m_aEventListeners.disposeAndClear( aGuard, aEvent ); + } + { + std::unique_lock aGuard(m_mutex); + m_aConfigListeners.disposeAndClear( aGuard, aEvent ); + } + + { + SolarMutexGuard g; + try + { + if ( m_xImageManager.is() ) + m_xImageManager->dispose(); + } + catch ( const Exception& ) + { + } + + m_xImageManager.clear(); + m_aUIElements.clear(); + m_xDocConfigStorage.clear(); + m_bModified = false; + m_bDisposed = true; + } +} + +void SAL_CALL UIConfigurationManager::addEventListener( const Reference< XEventListener >& xListener ) +{ + { + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + } + + std::unique_lock aGuard(m_mutex); + m_aEventListeners.addInterface( aGuard, xListener ); +} + +void SAL_CALL UIConfigurationManager::removeEventListener( const Reference< XEventListener >& xListener ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + std::unique_lock aGuard(m_mutex); + m_aEventListeners.removeInterface( aGuard, xListener ); +} + +// XUIConfigurationManager +void SAL_CALL UIConfigurationManager::addConfigurationListener( const Reference< css::ui::XUIConfigurationListener >& xListener ) +{ + { + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + } + + std::unique_lock aGuard(m_mutex); + m_aConfigListeners.addInterface( aGuard, xListener ); +} + +void SAL_CALL UIConfigurationManager::removeConfigurationListener( const Reference< css::ui::XUIConfigurationListener >& xListener ) +{ + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + std::unique_lock aGuard(m_mutex); + m_aConfigListeners.removeInterface( aGuard, xListener ); +} + +void SAL_CALL UIConfigurationManager::reset() +{ + SolarMutexClearableGuard aGuard; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + + if ( isReadOnly() ) + return; + + if ( !m_xDocConfigStorage.is() ) + return; + + try + { + // Remove all elements from our user-defined storage! + bool bCommit( false ); + for ( int i = 1; i < css::ui::UIElementType::COUNT; i++ ) + { + UIElementType& rElementType = m_aUIElements[i]; + + if ( rElementType.xStorage.is() ) + { + bool bCommitSubStorage( false ); + const Sequence< OUString > aUIElementStreamNames = rElementType.xStorage->getElementNames(); + for ( OUString const & rStreamName : aUIElementStreamNames ) + { + rElementType.xStorage->removeElement( rStreamName ); + bCommitSubStorage = true; + bCommit = true; + } + + if ( bCommitSubStorage ) + { + Reference< XTransactedObject > xTransactedObject( rElementType.xStorage, UNO_QUERY ); + if ( xTransactedObject.is() ) + xTransactedObject->commit(); + } + } + } + + // Commit changes + if ( bCommit ) + { + Reference< XTransactedObject > xTransactedObject( m_xDocConfigStorage, UNO_QUERY ); + if ( xTransactedObject.is() ) + xTransactedObject->commit(); + } + + // remove settings from user defined layer and notify listener about removed settings data! + // Try to access our module sub folder + ConfigEventNotifyContainer aRemoveEventNotifyContainer; + for ( sal_Int16 j = 1; j < css::ui::UIElementType::COUNT; j++ ) + { + UIElementType& rDocElementType = m_aUIElements[j]; + + impl_resetElementTypeData( rDocElementType, aRemoveEventNotifyContainer ); + rDocElementType.bModified = false; + } + + m_bModified = false; + + // Unlock mutex before notify our listeners + aGuard.clear(); + + // Notify our listeners + for (const ConfigurationEvent & k : aRemoveEventNotifyContainer) + implts_notifyContainerListener( k, NotifyOp_Remove ); + } + catch ( const css::lang::IllegalArgumentException& ) + { + } + catch ( const css::container::NoSuchElementException& ) + { + } + catch ( const css::embed::InvalidStorageException& ) + { + } + catch ( const css::embed::StorageWrappedTargetException& ) + { + } +} + +Sequence< Sequence< PropertyValue > > SAL_CALL UIConfigurationManager::getUIElementsInfo( sal_Int16 ElementType ) +{ + if (( ElementType < 0 ) || ( ElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + + SolarMutexGuard g; + if ( m_bDisposed ) + throw DisposedException(); + + std::vector< Sequence< PropertyValue > > aElementInfoSeq; + UIElementInfoHashMap aUIElementInfoCollection; + + if ( ElementType == css::ui::UIElementType::UNKNOWN ) + { + for ( sal_Int16 i = 0; i < css::ui::UIElementType::COUNT; i++ ) + impl_fillSequenceWithElementTypeInfo( aUIElementInfoCollection, i ); + } + else + impl_fillSequenceWithElementTypeInfo( aUIElementInfoCollection, ElementType ); + + aElementInfoSeq.resize( aUIElementInfoCollection.size() ); + sal_Int32 n = 0; + for (auto const& elem : aUIElementInfoCollection) + { + Sequence< PropertyValue > aUIElementInfo{ + comphelper::makePropertyValue("ResourceURL", elem.second.aResourceURL), + comphelper::makePropertyValue(m_aPropUIName, elem.second.aUIName) + }; + aElementInfoSeq[n++] = aUIElementInfo; + } + + return comphelper::containerToSequence(aElementInfoSeq); +} + +Reference< XIndexContainer > SAL_CALL UIConfigurationManager::createSettings() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + // Creates an empty item container which can be filled from outside + return Reference< XIndexContainer >( new RootItemContainer() ); +} + +sal_Bool SAL_CALL UIConfigurationManager::hasSettings( const OUString& ResourceURL ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( ResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + UIElementData* pDataSettings = impl_findUIElementData( ResourceURL, nElementType, false ); + if ( pDataSettings && !pDataSettings->bDefault ) + return true; + + return false; +} + +Reference< XIndexAccess > SAL_CALL UIConfigurationManager::getSettings( const OUString& ResourceURL, sal_Bool bWriteable ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( ResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + UIElementData* pDataSettings = impl_findUIElementData( ResourceURL, nElementType ); + if ( pDataSettings && !pDataSettings->bDefault ) + { + // Create a copy of our data if someone wants to change the data. + if ( bWriteable ) + return Reference< XIndexAccess >( new RootItemContainer( pDataSettings->xSettings ) ); + else + return pDataSettings->xSettings; + } + + throw NoSuchElementException(); +} + +void SAL_CALL UIConfigurationManager::replaceSettings( const OUString& ResourceURL, const Reference< css::container::XIndexAccess >& aNewData ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( ResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + else if ( m_bReadOnly ) + throw IllegalAccessException(); + else + { + SolarMutexClearableGuard aGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + UIElementData* pDataSettings = impl_findUIElementData( ResourceURL, nElementType ); + if ( !pDataSettings || pDataSettings->bDefault ) + throw NoSuchElementException(); + // we have a settings entry in our user-defined layer - replace + Reference< XIndexAccess > xOldSettings = pDataSettings->xSettings; + + // Create a copy of the data if the container is not const + Reference< XIndexReplace > xReplace( aNewData, UNO_QUERY ); + if ( xReplace.is() ) + pDataSettings->xSettings = new ConstItemContainer( aNewData ); + else + pDataSettings->xSettings = aNewData; + + pDataSettings->bDefault = false; + pDataSettings->bModified = true; + m_bModified = true; + + // Modify type container + UIElementType& rElementType = m_aUIElements[nElementType]; + rElementType.bModified = true; + + Reference< XUIConfigurationManager > xThis(this); + Reference< XInterface > xIfac( xThis, UNO_QUERY ); + + // Create event to notify listener about replaced element settings + ConfigurationEvent aEvent; + + aEvent.ResourceURL = ResourceURL; + aEvent.Accessor <<= xThis; + aEvent.Source = xIfac; + aEvent.ReplacedElement <<= xOldSettings; + aEvent.Element <<= pDataSettings->xSettings; + + aGuard.clear(); + + implts_notifyContainerListener( aEvent, NotifyOp_Replace ); + } +} + +void SAL_CALL UIConfigurationManager::removeSettings( const OUString& ResourceURL ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( ResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException( "The ResourceURL is not valid or " + "describes an unknown type. " + "ResourceURL: " + ResourceURL, nullptr, 0 ); + else if ( m_bReadOnly ) + throw IllegalAccessException( "The configuration manager is read-only. " + "ResourceURL: " + ResourceURL, nullptr ); + else + { + SolarMutexClearableGuard aGuard; + + if ( m_bDisposed ) + throw DisposedException( "The configuration manager has been disposed, " + "and can't uphold its method specification anymore. " + "ResourceURL: " + ResourceURL, nullptr ); + + UIElementData* pDataSettings = impl_findUIElementData( ResourceURL, nElementType ); + if ( !pDataSettings ) + throw NoSuchElementException( "The settings data cannot be found. " + "ResourceURL: " + ResourceURL, nullptr); + // If element settings are default, we don't need to change anything! + if ( pDataSettings->bDefault ) + return; + else + { + Reference< XIndexAccess > xRemovedSettings = pDataSettings->xSettings; + pDataSettings->bDefault = true; + + // check if this is a default layer node + pDataSettings->bModified = true; // we have to remove this node from the user layer! + pDataSettings->xSettings.clear(); + m_bModified = true; // user layer must be written + + // Modify type container + UIElementType& rElementType = m_aUIElements[nElementType]; + rElementType.bModified = true; + + Reference< XUIConfigurationManager > xThis(this); + Reference< XInterface > xIfac( xThis, UNO_QUERY ); + + // Create event to notify listener about removed element settings + ConfigurationEvent aEvent; + + aEvent.ResourceURL = ResourceURL; + aEvent.Accessor <<= xThis; + aEvent.Source = xIfac; + aEvent.Element <<= xRemovedSettings; + + aGuard.clear(); + + implts_notifyContainerListener( aEvent, NotifyOp_Remove ); + } + } +} + +void SAL_CALL UIConfigurationManager::insertSettings( const OUString& NewResourceURL, const Reference< XIndexAccess >& aNewData ) +{ + sal_Int16 nElementType = RetrieveTypeFromResourceURL( NewResourceURL ); + + if (( nElementType == css::ui::UIElementType::UNKNOWN ) || + ( nElementType >= css::ui::UIElementType::COUNT )) + throw IllegalArgumentException(); + else if ( m_bReadOnly ) + throw IllegalAccessException(); + else + { + SolarMutexClearableGuard aGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + bool bInsertData( false ); + UIElementData aUIElementData; + UIElementData* pDataSettings = impl_findUIElementData( NewResourceURL, nElementType ); + + if ( pDataSettings && !pDataSettings->bDefault ) + throw ElementExistException(); + + if ( !pDataSettings ) + { + pDataSettings = &aUIElementData; + bInsertData = true; + } + + { + pDataSettings->bDefault = false; + pDataSettings->bModified = true; + + // Create a copy of the data if the container is not const + Reference< XIndexReplace > xReplace( aNewData, UNO_QUERY ); + if ( xReplace.is() ) + pDataSettings->xSettings = new ConstItemContainer( aNewData ); + else + pDataSettings->xSettings = aNewData; + + m_bModified = true; + + UIElementType& rElementType = m_aUIElements[nElementType]; + rElementType.bModified = true; + + if ( bInsertData ) + { + pDataSettings->aName = RetrieveNameFromResourceURL( NewResourceURL ) + ".xml"; + pDataSettings->aResourceURL = NewResourceURL; + + UIElementDataHashMap& rElements = rElementType.aElementsHashMap; + rElements.emplace( NewResourceURL, *pDataSettings ); + } + + Reference< XIndexAccess > xInsertSettings( aUIElementData.xSettings ); + Reference< XUIConfigurationManager > xThis(this); + Reference< XInterface > xIfac( xThis, UNO_QUERY ); + + // Create event to notify listener about removed element settings + ConfigurationEvent aEvent; + + aEvent.ResourceURL = NewResourceURL; + aEvent.Accessor <<= xThis; + aEvent.Source = xIfac; + aEvent.Element <<= xInsertSettings; + + aGuard.clear(); + + implts_notifyContainerListener( aEvent, NotifyOp_Insert ); + } + } +} + +Reference< XInterface > SAL_CALL UIConfigurationManager::getImageManager() +{ + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_xImageManager.is() ) + { + m_xImageManager = new ImageManager( m_xContext, /*bForModule*/false ); + + Sequence<Any> aPropSeq(comphelper::InitAnyPropertySequence( + { + {"UserConfigStorage", Any(m_xDocConfigStorage)}, + {"ModuleIdentifier", Any(OUString())}, + })); + + m_xImageManager->initialize( aPropSeq ); + } + + return Reference< XInterface >( static_cast<cppu::OWeakObject*>(m_xImageManager.get()), UNO_QUERY ); +} + +Reference< XAcceleratorConfiguration > SAL_CALL UIConfigurationManager::getShortCutManager() +{ + // SAFE -> + SolarMutexGuard g; + + if (!m_xAccConfig.is()) try + { + m_xAccConfig = DocumentAcceleratorConfiguration:: + createWithDocumentRoot(m_xContext, m_xDocConfigStorage); + } + catch ( const css::uno::DeploymentException& ) + { + SAL_WARN("fwk.uiconfiguration", "DocumentAcceleratorConfiguration" + " not available. This should happen only on mobile platforms."); + } + + return m_xAccConfig; +} + +Reference< XInterface > SAL_CALL UIConfigurationManager::getEventsManager() +{ + return Reference< XInterface >(); +} + +// XUIConfigurationStorage +void SAL_CALL UIConfigurationManager::setStorage( const Reference< XStorage >& Storage ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_xDocConfigStorage.is() ) + { + try + { + // Dispose old storage to be sure that it will be closed + m_xDocConfigStorage->dispose(); + } + catch ( const Exception& ) + { + } + } + + // We store the new storage. Be careful it could be an empty reference! + m_xDocConfigStorage = Storage; + m_bReadOnly = true; + + if ( m_xAccConfig.is() ) + m_xAccConfig->setStorage( m_xDocConfigStorage ); + + if ( m_xImageManager ) + m_xImageManager->setStorage( m_xDocConfigStorage ); + + if ( m_xDocConfigStorage.is() ) + { + Reference< XPropertySet > xPropSet( m_xDocConfigStorage, UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + tools::Long nOpenMode = 0; + Any a = xPropSet->getPropertyValue("OpenMode"); + if ( a >>= nOpenMode ) + m_bReadOnly = !( nOpenMode & ElementModes::WRITE ); + } + catch ( const css::beans::UnknownPropertyException& ) + { + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + } + + impl_Initialize(); +} + +sal_Bool SAL_CALL UIConfigurationManager::hasStorage() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + return m_xDocConfigStorage.is(); +} + +// XUIConfigurationPersistence +void SAL_CALL UIConfigurationManager::reload() +{ + SolarMutexClearableGuard aGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_xDocConfigStorage.is() || !m_bModified || m_bReadOnly ) + return; + + // Try to access our module sub folder + ConfigEventNotifyContainer aRemoveNotifyContainer; + ConfigEventNotifyContainer aReplaceNotifyContainer; + for ( sal_Int16 i = 1; i < css::ui::UIElementType::COUNT; i++ ) + { + try + { + UIElementType& rDocElementType = m_aUIElements[i]; + if ( rDocElementType.bModified ) + impl_reloadElementTypeData( rDocElementType, aRemoveNotifyContainer, aReplaceNotifyContainer ); + } + catch ( const Exception& ) + { + throw IOException(); + } + } + + m_bModified = false; + + // Unlock mutex before notify our listeners + aGuard.clear(); + + // Notify our listeners + for (const ConfigurationEvent & j : aRemoveNotifyContainer) + implts_notifyContainerListener( j, NotifyOp_Remove ); + for (const ConfigurationEvent & k : aReplaceNotifyContainer) + implts_notifyContainerListener( k, NotifyOp_Replace ); +} + +void SAL_CALL UIConfigurationManager::store() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_xDocConfigStorage.is() || !m_bModified || m_bReadOnly ) + return; + + // Try to access our module sub folder + for ( int i = 1; i < css::ui::UIElementType::COUNT; i++ ) + { + try + { + UIElementType& rElementType = m_aUIElements[i]; + + if ( rElementType.bModified && rElementType.xStorage.is() ) + impl_storeElementTypeData( rElementType.xStorage, rElementType ); + } + catch ( const Exception& ) + { + throw IOException(); + } + } + + m_bModified = false; + Reference< XTransactedObject > xTransactedObject( m_xDocConfigStorage, UNO_QUERY ); + if ( xTransactedObject.is() ) + xTransactedObject->commit(); +} + +void SAL_CALL UIConfigurationManager::storeToStorage( const Reference< XStorage >& Storage ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_xDocConfigStorage.is() || !m_bModified || m_bReadOnly ) + return; + + // Try to access our module sub folder + for ( int i = 1; i < css::ui::UIElementType::COUNT; i++ ) + { + try + { + Reference< XStorage > xElementTypeStorage( Storage->openStorageElement( + OUString(UIELEMENTTYPENAMES[i]), ElementModes::READWRITE )); + UIElementType& rElementType = m_aUIElements[i]; + + if ( rElementType.bModified && xElementTypeStorage.is() ) + impl_storeElementTypeData( xElementTypeStorage, rElementType, false ); // store data to storage, but don't reset modify flag! + } + catch ( const Exception& ) + { + throw IOException(); + } + } + + Reference< XTransactedObject > xTransactedObject( Storage, UNO_QUERY ); + if ( xTransactedObject.is() ) + xTransactedObject->commit(); +} + +sal_Bool SAL_CALL UIConfigurationManager::isModified() +{ + SolarMutexGuard g; + + return m_bModified; +} + +sal_Bool SAL_CALL UIConfigurationManager::isReadOnly() +{ + SolarMutexGuard g; + + return m_bReadOnly; +} + +void UIConfigurationManager::implts_notifyContainerListener( const ConfigurationEvent& aEvent, NotifyOp eOp ) +{ + std::unique_lock aGuard(m_mutex); + m_aConfigListeners.forEach(aGuard, [&eOp, &aEvent](const css::uno::Reference<XUIConfigurationListener>& l) { + switch ( eOp ) + { + case NotifyOp_Replace: + l->elementReplaced( aEvent ); + break; + case NotifyOp_Insert: + l->elementInserted( aEvent ); + break; + case NotifyOp_Remove: + l->elementRemoved( aEvent ); + break; + } + }); +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_UIConfigurationManager_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new UIConfigurationManager(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uiconfiguration/windowstateconfiguration.cxx b/framework/source/uiconfiguration/windowstateconfiguration.cxx new file mode 100644 index 000000000..eb7e72aff --- /dev/null +++ b/framework/source/uiconfiguration/windowstateconfiguration.cxx @@ -0,0 +1,1390 @@ +/* -*- 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 <uiconfiguration/windowstateproperties.hxx> +#include <helper/mischelper.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XContainer.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XModuleManager2.hpp> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/ui/DockingArea.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <comphelper/compbase.hxx> +#include <comphelper/string.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/sequence.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +#include <string_view> +#include <unordered_map> +#include <vector> + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace com::sun::star::configuration; +using namespace com::sun::star::container; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::ui; +using namespace framework; + +namespace { + +// Zero based indexes, order must be the same as WindowStateMask && CONFIGURATION_PROPERTIES! +const sal_Int16 PROPERTY_LOCKED = 0; +const sal_Int16 PROPERTY_DOCKED = 1; +const sal_Int16 PROPERTY_VISIBLE = 2; +const sal_Int16 PROPERTY_CONTEXT = 3; +const sal_Int16 PROPERTY_HIDEFROMMENU = 4; +const sal_Int16 PROPERTY_NOCLOSE = 5; +const sal_Int16 PROPERTY_SOFTCLOSE = 6; +const sal_Int16 PROPERTY_CONTEXTACTIVE = 7; +const sal_Int16 PROPERTY_DOCKINGAREA = 8; +const sal_Int16 PROPERTY_POS = 9; +const sal_Int16 PROPERTY_SIZE = 10; +const sal_Int16 PROPERTY_UINAME = 11; +const sal_Int16 PROPERTY_INTERNALSTATE = 12; +const sal_Int16 PROPERTY_STYLE = 13; +const sal_Int16 PROPERTY_DOCKPOS = 14; +const sal_Int16 PROPERTY_DOCKSIZE = 15; + +// Order must be the same as WindowStateMask!! +rtl::OUStringConstExpr CONFIGURATION_PROPERTIES[] +{ + WINDOWSTATE_PROPERTY_LOCKED, + WINDOWSTATE_PROPERTY_DOCKED, + WINDOWSTATE_PROPERTY_VISIBLE, + WINDOWSTATE_PROPERTY_CONTEXT, + WINDOWSTATE_PROPERTY_HIDEFROMENU, + WINDOWSTATE_PROPERTY_NOCLOSE, + WINDOWSTATE_PROPERTY_SOFTCLOSE, + WINDOWSTATE_PROPERTY_CONTEXTACTIVE, + WINDOWSTATE_PROPERTY_DOCKINGAREA, + WINDOWSTATE_PROPERTY_POS, + WINDOWSTATE_PROPERTY_SIZE, + WINDOWSTATE_PROPERTY_UINAME, + WINDOWSTATE_PROPERTY_INTERNALSTATE, + WINDOWSTATE_PROPERTY_STYLE, + WINDOWSTATE_PROPERTY_DOCKPOS, + WINDOWSTATE_PROPERTY_DOCKSIZE +}; + +// Configuration access class for WindowState supplier implementation + +class ConfigurationAccess_WindowState : public ::cppu::WeakImplHelper< XNameContainer, XContainerListener > +{ + public: + ConfigurationAccess_WindowState( std::u16string_view aWindowStateConfigFile, const Reference< XComponentContext >& rxContext ); + virtual ~ConfigurationAccess_WindowState() override; + + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override; + + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XNameContainer + virtual void SAL_CALL removeByName( const OUString& sName ) override; + + virtual void SAL_CALL insertByName( const OUString& sName, const css::uno::Any& aPropertySet ) override; + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& sName, const css::uno::Any& aPropertySet ) override; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType() override; + + virtual sal_Bool SAL_CALL hasElements() override; + + // container.XContainerListener + virtual void SAL_CALL elementInserted( const ContainerEvent& aEvent ) override; + virtual void SAL_CALL elementRemoved ( const ContainerEvent& aEvent ) override; + virtual void SAL_CALL elementReplaced( const ContainerEvent& aEvent ) override; + + // lang.XEventListener + virtual void SAL_CALL disposing( const EventObject& aEvent ) override; + + protected: + enum // WindowStateMask + { + WINDOWSTATE_MASK_DOCKINGAREA = 256, + WINDOWSTATE_MASK_POS = 512, + WINDOWSTATE_MASK_SIZE = 1024, + WINDOWSTATE_MASK_UINAME = 2048, + WINDOWSTATE_MASK_INTERNALSTATE = 4096, + WINDOWSTATE_MASK_STYLE = 8192, + WINDOWSTATE_MASK_DOCKPOS = 16384, + WINDOWSTATE_MASK_DOCKSIZE = 32768 + }; + + // Cache structure. Valid values are described by the eMask member. All other values should not be + // provided to outside code! + struct WindowStateInfo + { + WindowStateInfo() + : bLocked(false) + , bDocked(false) + , bVisible(false) + , bContext(false) + , bHideFromMenu(false) + , bNoClose(false) + , bSoftClose(false) + , bContextActive(false) + , aDockingArea(css::ui::DockingArea_DOCKINGAREA_TOP) + , aDockPos(0, 0) + , aPos(0, 0) + , aSize(0, 0) + , nInternalState(0) + , nStyle(0) + , nMask(0) + { + } + + bool bLocked : 1, + bDocked : 1, + bVisible : 1, + bContext : 1, + bHideFromMenu : 1, + bNoClose : 1, + bSoftClose : 1, + bContextActive : 1; + css::ui::DockingArea aDockingArea; + css::awt::Point aDockPos; + css::awt::Size aDockSize; + css::awt::Point aPos; + css::awt::Size aSize; + OUString aUIName; + sal_uInt32 nInternalState; + sal_uInt16 nStyle; + sal_uInt32 nMask; // see WindowStateMask + }; + + void impl_putPropertiesFromStruct( const WindowStateInfo& rWinStateInfo, Reference< XPropertySet > const & xPropSet ); + Any impl_insertCacheAndReturnSequence( const OUString& rResourceURL, Reference< XNameAccess > const & rNameAccess ); + WindowStateInfo& impl_insertCacheAndReturnWinState( const OUString& rResourceURL, Reference< XNameAccess > const & rNameAccess ); + Any impl_getSequenceFromStruct( const WindowStateInfo& rWinStateInfo ); + void impl_fillStructFromSequence( WindowStateInfo& rWinStateInfo, const Sequence< PropertyValue >& rSeq ); + Any impl_getWindowStateFromResourceURL( const OUString& rResourceURL ); + void impl_initializeConfigAccess(); + + private: + typedef std::unordered_map< OUString, + WindowStateInfo > ResourceURLToInfoCache; + + osl::Mutex m_aMutex; + OUString m_aConfigWindowAccess; + Reference< XMultiServiceFactory > m_xConfigProvider; + Reference< XNameAccess > m_xConfigAccess; + Reference< XContainerListener > m_xConfigListener; + ResourceURLToInfoCache m_aResourceURLToInfoCache; + bool m_bConfigAccessInitialized : 1, + m_bModified : 1; + std::vector< OUString > m_aPropArray; +}; + +ConfigurationAccess_WindowState::ConfigurationAccess_WindowState( std::u16string_view aModuleName, const Reference< XComponentContext >& rxContext ) : + // Create configuration hierarchical access name + m_aConfigWindowAccess( + OUString::Concat("/org.openoffice.Office.UI.") + aModuleName + "/UIElements/States"), + m_xConfigProvider(theDefaultProvider::get( rxContext )), + m_bConfigAccessInitialized( false ), + m_bModified( false ) +{ + // Initialize access array with property names. + for (const rtl::OUStringConstExpr & s : CONFIGURATION_PROPERTIES ) + m_aPropArray.push_back(s); +} + +ConfigurationAccess_WindowState::~ConfigurationAccess_WindowState() +{ + // SAFE + osl::MutexGuard g(m_aMutex); + Reference< XContainer > xContainer( m_xConfigAccess, UNO_QUERY ); + if ( xContainer.is() ) + xContainer->removeContainerListener(m_xConfigListener); +} + +// XNameAccess +Any SAL_CALL ConfigurationAccess_WindowState::getByName( const OUString& rResourceURL ) +{ + // SAFE + osl::MutexGuard g(m_aMutex); + + ResourceURLToInfoCache::const_iterator pIter = m_aResourceURLToInfoCache.find( rResourceURL ); + if ( pIter != m_aResourceURLToInfoCache.end() ) + return impl_getSequenceFromStruct( pIter->second ); + else + { + Any a( impl_getWindowStateFromResourceURL( rResourceURL ) ); + if ( a == Any() ) + throw NoSuchElementException(); + return a; + } +} + +Sequence< OUString > SAL_CALL ConfigurationAccess_WindowState::getElementNames() +{ + // SAFE + osl::MutexGuard g(m_aMutex); + + if ( !m_bConfigAccessInitialized ) + { + impl_initializeConfigAccess(); + m_bConfigAccessInitialized = true; + } + + if ( m_xConfigAccess.is() ) + return m_xConfigAccess->getElementNames(); + else + return Sequence< OUString > (); +} + +sal_Bool SAL_CALL ConfigurationAccess_WindowState::hasByName( const OUString& rResourceURL ) +{ + // SAFE + osl::MutexGuard g(m_aMutex); + + ResourceURLToInfoCache::const_iterator pIter = m_aResourceURLToInfoCache.find( rResourceURL ); + if ( pIter != m_aResourceURLToInfoCache.end() ) + return true; + else + { + Any a( impl_getWindowStateFromResourceURL( rResourceURL ) ); + if ( a == Any() ) + return false; + else + return true; + } +} + +// XElementAccess +Type SAL_CALL ConfigurationAccess_WindowState::getElementType() +{ + return cppu::UnoType<Sequence< PropertyValue >>::get(); +} + +sal_Bool SAL_CALL ConfigurationAccess_WindowState::hasElements() +{ + // SAFE + osl::MutexGuard g(m_aMutex); + + if ( !m_bConfigAccessInitialized ) + { + impl_initializeConfigAccess(); + m_bConfigAccessInitialized = true; + } + + if ( m_xConfigAccess.is() ) + return m_xConfigAccess->hasElements(); + else + return false; +} + +// XNameContainer +void SAL_CALL ConfigurationAccess_WindowState::removeByName( const OUString& rResourceURL ) +{ + // SAFE + osl::ClearableMutexGuard g(m_aMutex); + + ResourceURLToInfoCache::iterator pIter = m_aResourceURLToInfoCache.find( rResourceURL ); + if ( pIter != m_aResourceURLToInfoCache.end() ) + m_aResourceURLToInfoCache.erase( pIter ); + + if ( !m_bConfigAccessInitialized ) + { + impl_initializeConfigAccess(); + m_bConfigAccessInitialized = true; + } + + try + { + // Remove must be write-through => remove element from configuration + Reference< XNameContainer > xNameContainer( m_xConfigAccess, UNO_QUERY ); + if ( xNameContainer.is() ) + { + g.clear(); + + xNameContainer->removeByName( rResourceURL ); + Reference< XChangesBatch > xFlush( m_xConfigAccess, UNO_QUERY ); + if ( xFlush.is() ) + xFlush->commitChanges(); + } + } + catch ( const css::lang::WrappedTargetException& ) + { + } +} + +void SAL_CALL ConfigurationAccess_WindowState::insertByName( const OUString& rResourceURL, const css::uno::Any& aPropertySet ) +{ + // SAFE + osl::ClearableMutexGuard g(m_aMutex); + + Sequence< PropertyValue > aPropSet; + if ( !(aPropertySet >>= aPropSet) ) + throw IllegalArgumentException(); + + ResourceURLToInfoCache::const_iterator pIter = m_aResourceURLToInfoCache.find( rResourceURL ); + if ( pIter != m_aResourceURLToInfoCache.end() ) + throw ElementExistException(); + + if ( !m_bConfigAccessInitialized ) + { + impl_initializeConfigAccess(); + m_bConfigAccessInitialized = true; + } + + // Try to ask our configuration access + if ( !m_xConfigAccess.is() ) + return; + + if ( m_xConfigAccess->hasByName( rResourceURL ) ) + throw ElementExistException(); + + WindowStateInfo aWinStateInfo; + impl_fillStructFromSequence( aWinStateInfo, aPropSet ); + m_aResourceURLToInfoCache.emplace( rResourceURL, aWinStateInfo ); + + // insert must be write-through => insert element into configuration + Reference< XNameContainer > xNameContainer( m_xConfigAccess, UNO_QUERY ); + if ( !xNameContainer.is() ) + return; + + Reference< XSingleServiceFactory > xFactory( m_xConfigAccess, UNO_QUERY ); + g.clear(); + + try + { + Reference< XPropertySet > xPropSet( xFactory->createInstance(), UNO_QUERY ); + if ( xPropSet.is() ) + { + Any a; + impl_putPropertiesFromStruct( aWinStateInfo, xPropSet ); + a <<= xPropSet; + xNameContainer->insertByName( rResourceURL, a ); + Reference< XChangesBatch > xFlush( xFactory, UNO_QUERY ); + if ( xFlush.is() ) + xFlush->commitChanges(); + } + } + catch ( const Exception& ) + { + } +} + +// XNameReplace +void SAL_CALL ConfigurationAccess_WindowState::replaceByName( const OUString& rResourceURL, const css::uno::Any& aPropertySet ) +{ + // SAFE + osl::ClearableMutexGuard g(m_aMutex); + + Sequence< PropertyValue > aPropSet; + if ( !(aPropertySet >>= aPropSet) ) + throw IllegalArgumentException(); + + ResourceURLToInfoCache::iterator pIter = m_aResourceURLToInfoCache.find( rResourceURL ); + if ( pIter != m_aResourceURLToInfoCache.end() ) + { + WindowStateInfo& rWinStateInfo = pIter->second; + impl_fillStructFromSequence( rWinStateInfo, aPropSet ); + m_bModified = true; + } + else + { + if ( !m_bConfigAccessInitialized ) + { + impl_initializeConfigAccess(); + m_bConfigAccessInitialized = true; + } + + // Try to ask our configuration access + Reference< XNameAccess > xNameAccess; + Any a( m_xConfigAccess->getByName( rResourceURL )); + + if ( !(a >>= xNameAccess) ) + throw NoSuchElementException(); + + WindowStateInfo& rWinStateInfo( impl_insertCacheAndReturnWinState( rResourceURL, xNameAccess )); + impl_fillStructFromSequence( rWinStateInfo, aPropSet ); + m_bModified = true; + pIter = m_aResourceURLToInfoCache.find( rResourceURL ); + + } + + if ( !(m_bModified && pIter != m_aResourceURLToInfoCache.end()) ) + return; + + Reference< XNameContainer > xNameContainer( m_xConfigAccess, UNO_QUERY ); + if ( !xNameContainer.is() ) + return; + + WindowStateInfo aWinStateInfo( pIter->second ); + OUString aResourceURL( pIter->first ); + m_bModified = false; + g.clear(); + + try + { + Reference< XPropertySet > xPropSet; + if ( xNameContainer->getByName( aResourceURL ) >>= xPropSet ) + { + impl_putPropertiesFromStruct( aWinStateInfo, xPropSet ); + + Reference< XChangesBatch > xFlush( m_xConfigAccess, UNO_QUERY ); + if ( xFlush.is() ) + xFlush->commitChanges(); + } + } + catch ( const Exception& ) + { + } + +} + +// container.XContainerListener +void SAL_CALL ConfigurationAccess_WindowState::elementInserted( const ContainerEvent& ) +{ + // do nothing - next time someone wants to retrieve this node we will find it in the configuration +} + +void SAL_CALL ConfigurationAccess_WindowState::elementRemoved ( const ContainerEvent& ) +{ +} + +void SAL_CALL ConfigurationAccess_WindowState::elementReplaced( const ContainerEvent& ) +{ +} + +// lang.XEventListener +void SAL_CALL ConfigurationAccess_WindowState::disposing( const EventObject& aEvent ) +{ + // SAFE + // remove our reference to the config access + osl::MutexGuard g(m_aMutex); + + Reference< XInterface > xIfac1( aEvent.Source, UNO_QUERY ); + Reference< XInterface > xIfac2( m_xConfigAccess, UNO_QUERY ); + if ( xIfac1 == xIfac2 ) + m_xConfigAccess.clear(); +} + +// private helper methods +Any ConfigurationAccess_WindowState::impl_getSequenceFromStruct( const WindowStateInfo& rWinStateInfo ) +{ + sal_Int32 i( 0 ); + sal_Int32 nCount( m_aPropArray.size() ); + std::vector< PropertyValue > aPropVec; + + for ( i = 0; i < nCount; i++ ) + { + if ( rWinStateInfo.nMask & ( 1 << i )) + { + // put value into the return sequence + PropertyValue pv; + pv.Name = m_aPropArray[i]; + + switch ( i ) + { + case PROPERTY_LOCKED: + pv.Value <<= rWinStateInfo.bLocked; break; + case PROPERTY_DOCKED: + pv.Value <<= rWinStateInfo.bDocked; break; + case PROPERTY_VISIBLE: + pv.Value <<= rWinStateInfo.bVisible; break; + case PROPERTY_CONTEXT: + pv.Value <<= rWinStateInfo.bContext; break; + case PROPERTY_HIDEFROMMENU: + pv.Value <<= rWinStateInfo.bHideFromMenu; break; + case PROPERTY_NOCLOSE: + pv.Value <<= rWinStateInfo.bNoClose; break; + case PROPERTY_SOFTCLOSE: + pv.Value <<= rWinStateInfo.bSoftClose; break; + case PROPERTY_CONTEXTACTIVE: + pv.Value <<= rWinStateInfo.bContextActive; break; + case PROPERTY_DOCKINGAREA: + pv.Value <<= rWinStateInfo.aDockingArea; break; + case PROPERTY_POS: + pv.Value <<= rWinStateInfo.aPos; break; + case PROPERTY_SIZE: + pv.Value <<= rWinStateInfo.aSize; break; + case PROPERTY_UINAME: + pv.Value <<= rWinStateInfo.aUIName; break; + case PROPERTY_INTERNALSTATE: + pv.Value <<= sal_Int32( rWinStateInfo.nInternalState ); break; + case PROPERTY_STYLE: + pv.Value <<= sal_Int16( rWinStateInfo.nStyle ); break; + case PROPERTY_DOCKPOS: + pv.Value <<= rWinStateInfo.aDockPos; break; + case PROPERTY_DOCKSIZE: + pv.Value <<= rWinStateInfo.aDockSize; break; + default: + assert( false && "Wrong value for ConfigurationAccess_WindowState. Who has forgotten to add this new property!" ); + } + aPropVec.push_back(pv); + } + } + + return Any( comphelper::containerToSequence(aPropVec) ); +} + +Any ConfigurationAccess_WindowState::impl_insertCacheAndReturnSequence( const OUString& rResourceURL, Reference< XNameAccess > const & xNameAccess ) +{ + sal_Int32 nMask( 0 ); + sal_Int32 nCount( m_aPropArray.size() ); + sal_Int32 i( 0 ); + std::vector< PropertyValue > aPropVec; + WindowStateInfo aWindowStateInfo; + + for ( i = 0; i < nCount; i++ ) + { + try + { + bool bAddToSeq( false ); + Any a( xNameAccess->getByName( m_aPropArray[i] ) ); + switch ( i ) + { + case PROPERTY_LOCKED: + case PROPERTY_DOCKED: + case PROPERTY_VISIBLE: + case PROPERTY_CONTEXT: + case PROPERTY_HIDEFROMMENU: + case PROPERTY_NOCLOSE: + case PROPERTY_SOFTCLOSE: + case PROPERTY_CONTEXTACTIVE: + { + bool bValue; + if ( a >>= bValue ) + { + sal_Int32 nValue( 1 << i ); + nMask |= nValue; + bAddToSeq = true; + switch ( i ) + { + case PROPERTY_LOCKED: + aWindowStateInfo.bLocked = bValue; break; + case PROPERTY_DOCKED: + aWindowStateInfo.bDocked = bValue; break; + case PROPERTY_VISIBLE: + aWindowStateInfo.bVisible = bValue; break; + case PROPERTY_CONTEXT: + aWindowStateInfo.bContext = bValue; break; + case PROPERTY_HIDEFROMMENU: + aWindowStateInfo.bHideFromMenu = bValue; break; + case PROPERTY_NOCLOSE: + aWindowStateInfo.bNoClose = bValue; break; + case PROPERTY_SOFTCLOSE: + aWindowStateInfo.bSoftClose = bValue; break; + case PROPERTY_CONTEXTACTIVE: + aWindowStateInfo.bContextActive = bValue; break; + } + } + } + break; + + case PROPERTY_DOCKINGAREA: + { + sal_Int32 nDockingArea = 0; + if ( a >>= nDockingArea ) + { + if (( nDockingArea >= 0 ) && + ( nDockingArea <= sal_Int32( DockingArea_DOCKINGAREA_RIGHT ))) + { + aWindowStateInfo.aDockingArea = static_cast<DockingArea>(nDockingArea); + nMask |= WINDOWSTATE_MASK_DOCKINGAREA; + a <<= aWindowStateInfo.aDockingArea; + bAddToSeq = true; + } + } + } + break; + + case PROPERTY_POS: + case PROPERTY_DOCKPOS: + { + OUString aString; + if ( a >>= aString ) + { + sal_Int32 nToken( 0 ); + std::u16string_view aXStr = o3tl::getToken(aString, 0, ',', nToken ); + if ( nToken > 0 ) + { + css::awt::Point aPos; + aPos.X = o3tl::toInt32(aXStr); + aPos.Y = o3tl::toInt32(o3tl::getToken(aString, 0, ',', nToken )); + + if ( i == PROPERTY_POS ) + { + aWindowStateInfo.aPos = aPos; + nMask |= WINDOWSTATE_MASK_POS; + } + else + { + aWindowStateInfo.aDockPos = aPos; + nMask |= WINDOWSTATE_MASK_DOCKPOS; + } + + a <<= aPos; + bAddToSeq = true; + } + } + } + break; + + case PROPERTY_SIZE: + case PROPERTY_DOCKSIZE: + { + OUString aString; + if ( a >>= aString ) + { + sal_Int32 nToken( 0 ); + std::u16string_view aStr = o3tl::getToken(aString, 0, ',', nToken ); + if ( nToken > 0 ) + { + css::awt::Size aSize; + aSize.Width = o3tl::toInt32(aStr); + aSize.Height = o3tl::toInt32(o3tl::getToken(aString, 0, ',', nToken )); + if ( i == PROPERTY_SIZE ) + { + aWindowStateInfo.aSize = aSize; + nMask |= WINDOWSTATE_MASK_SIZE; + } + else + { + aWindowStateInfo.aDockSize = aSize; + nMask |= WINDOWSTATE_MASK_DOCKSIZE; + } + + a <<= aSize; + bAddToSeq = true; + } + } + } + break; + + case PROPERTY_UINAME: + { + OUString aValue; + if ( a >>= aValue ) + { + nMask |= WINDOWSTATE_MASK_UINAME; + aWindowStateInfo.aUIName = aValue; + bAddToSeq = true; + } + } + break; + + case PROPERTY_INTERNALSTATE: + { + sal_uInt32 nValue = 0; + if ( a >>= nValue ) + { + nMask |= WINDOWSTATE_MASK_INTERNALSTATE; + aWindowStateInfo.nInternalState = nValue; + bAddToSeq = true; + } + } + break; + + case PROPERTY_STYLE: + { + sal_Int32 nValue = 0; + if ( a >>= nValue ) + { + nMask |= WINDOWSTATE_MASK_STYLE; + aWindowStateInfo.nStyle = sal_uInt16( nValue ); + bAddToSeq = true; + } + } + break; + + default: + assert( false && "Wrong value for ConfigurationAccess_WindowState. Who has forgotten to add this new property!" ); + } + + if ( bAddToSeq ) + { + // put value into the return sequence + PropertyValue pv; + pv.Name = m_aPropArray[i]; + pv.Value = a; + aPropVec.push_back(pv); + } + } + catch( const css::container::NoSuchElementException& ) + { + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + + aWindowStateInfo.nMask = nMask; + m_aResourceURLToInfoCache.emplace( rResourceURL, aWindowStateInfo ); + return Any( comphelper::containerToSequence(aPropVec) ); +} + +ConfigurationAccess_WindowState::WindowStateInfo& ConfigurationAccess_WindowState::impl_insertCacheAndReturnWinState( const OUString& rResourceURL, Reference< XNameAccess > const & rNameAccess ) +{ + sal_Int32 nMask( 0 ); + sal_Int32 nCount( m_aPropArray.size() ); + sal_Int32 i( 0 ); + WindowStateInfo aWindowStateInfo; + + for ( i = 0; i < nCount; i++ ) + { + try + { + Any a( rNameAccess->getByName( m_aPropArray[i] ) ); + switch ( i ) + { + case PROPERTY_LOCKED: + case PROPERTY_DOCKED: + case PROPERTY_VISIBLE: + case PROPERTY_CONTEXT: + case PROPERTY_HIDEFROMMENU: + case PROPERTY_NOCLOSE: + case PROPERTY_SOFTCLOSE: + case PROPERTY_CONTEXTACTIVE: + { + bool bValue; + if ( a >>= bValue ) + { + sal_Int32 nValue( 1 << i ); + nMask |= nValue; + switch ( i ) + { + case PROPERTY_LOCKED: + aWindowStateInfo.bLocked = bValue; break; + case PROPERTY_DOCKED: + aWindowStateInfo.bDocked = bValue; break; + case PROPERTY_VISIBLE: + aWindowStateInfo.bVisible = bValue; break; + case PROPERTY_CONTEXT: + aWindowStateInfo.bContext = bValue; break; + case PROPERTY_HIDEFROMMENU: + aWindowStateInfo.bHideFromMenu = bValue; break; + case PROPERTY_NOCLOSE: + aWindowStateInfo.bNoClose = bValue; break; + case PROPERTY_SOFTCLOSE: + aWindowStateInfo.bNoClose = bValue; break; + case PROPERTY_CONTEXTACTIVE: + aWindowStateInfo.bContextActive = bValue; break; + default: + SAL_WARN( "fwk.uiconfiguration", "Unknown boolean property in WindowState found!" ); + } + } + } + break; + + case PROPERTY_DOCKINGAREA: + { + sal_Int32 nDockingArea = 0; + if ( a >>= nDockingArea ) + { + if (( nDockingArea >= 0 ) && + ( nDockingArea <= sal_Int32( DockingArea_DOCKINGAREA_RIGHT ))) + { + aWindowStateInfo.aDockingArea = static_cast<DockingArea>(nDockingArea); + nMask |= WINDOWSTATE_MASK_DOCKINGAREA; + } + } + } + break; + + case PROPERTY_POS: + case PROPERTY_DOCKPOS: + { + OUString aString; + if ( a >>= aString ) + { + sal_Int32 nToken( 0 ); + std::u16string_view aXStr = o3tl::getToken(aString, 0, ',', nToken ); + if ( nToken > 0 ) + { + css::awt::Point aPos; + aPos.X = o3tl::toInt32(aXStr); + aPos.Y = o3tl::toInt32(o3tl::getToken(aString, 0, ',', nToken )); + + if ( i == PROPERTY_POS ) + { + aWindowStateInfo.aPos = aPos; + nMask |= WINDOWSTATE_MASK_POS; + } + else + { + aWindowStateInfo.aDockPos = aPos; + nMask |= WINDOWSTATE_MASK_DOCKPOS; + } + } + } + } + break; + + case PROPERTY_SIZE: + case PROPERTY_DOCKSIZE: + { + OUString aString; + if ( a >>= aString ) + { + sal_Int32 nToken( 0 ); + std::u16string_view aStr = o3tl::getToken(aString, 0, ',', nToken ); + if ( nToken > 0 ) + { + css::awt::Size aSize; + aSize.Width = o3tl::toInt32(aStr); + aSize.Height = o3tl::toInt32(o3tl::getToken(aString, 0, ',', nToken )); + if ( i == PROPERTY_SIZE ) + { + aWindowStateInfo.aSize = aSize; + nMask |= WINDOWSTATE_MASK_SIZE; + } + else + { + aWindowStateInfo.aDockSize = aSize; + nMask |= WINDOWSTATE_MASK_DOCKSIZE; + } + } + } + } + break; + + case PROPERTY_UINAME: + { + OUString aValue; + if ( a >>= aValue ) + { + nMask |= WINDOWSTATE_MASK_UINAME; + aWindowStateInfo.aUIName = aValue; + } + } + break; + + case PROPERTY_INTERNALSTATE: + { + sal_Int32 nValue = 0; + if ( a >>= nValue ) + { + nMask |= WINDOWSTATE_MASK_INTERNALSTATE; + aWindowStateInfo.nInternalState = sal_uInt32( nValue ); + } + } + break; + + case PROPERTY_STYLE: + { + sal_Int32 nValue = 0; + if ( a >>= nValue ) + { + nMask |= WINDOWSTATE_MASK_STYLE; + aWindowStateInfo.nStyle = sal_uInt16( nValue ); + } + } + break; + + default: + assert( false && "Wrong value for ConfigurationAccess_WindowState. Who has forgotten to add this new property!" ); + } + } + catch( const css::container::NoSuchElementException& ) + { + } + catch ( const css::lang::WrappedTargetException& ) + { + } + } + + aWindowStateInfo.nMask = nMask; + ResourceURLToInfoCache::iterator pIter = m_aResourceURLToInfoCache.emplace( rResourceURL, aWindowStateInfo ).first; + return pIter->second; +} + +Any ConfigurationAccess_WindowState::impl_getWindowStateFromResourceURL( const OUString& rResourceURL ) +{ + if ( !m_bConfigAccessInitialized ) + { + impl_initializeConfigAccess(); + m_bConfigAccessInitialized = true; + } + + try + { + // Try to ask our configuration access + if ( m_xConfigAccess.is() && m_xConfigAccess->hasByName( rResourceURL ) ) + { + + Reference< XNameAccess > xNameAccess( m_xConfigAccess->getByName( rResourceURL ), UNO_QUERY ); + if ( xNameAccess.is() ) + return impl_insertCacheAndReturnSequence( rResourceURL, xNameAccess ); + } + } + catch( const css::container::NoSuchElementException& ) + { + } + catch ( const css::lang::WrappedTargetException& ) + { + } + + return Any(); +} + +void ConfigurationAccess_WindowState::impl_fillStructFromSequence( WindowStateInfo& rWinStateInfo, const Sequence< PropertyValue >& rSeq ) +{ + sal_Int32 nCompareCount( m_aPropArray.size() ); + sal_Int32 nCount( rSeq.getLength() ); + sal_Int32 i( 0 ); + + for ( i = 0; i < nCount; i++ ) + { + for ( sal_Int32 j = 0; j < nCompareCount; j++ ) + { + if ( rSeq[i].Name == m_aPropArray[j] ) + { + switch ( j ) + { + case PROPERTY_LOCKED: + case PROPERTY_DOCKED: + case PROPERTY_VISIBLE: + case PROPERTY_CONTEXT: + case PROPERTY_HIDEFROMMENU: + case PROPERTY_NOCLOSE: + case PROPERTY_SOFTCLOSE: + case PROPERTY_CONTEXTACTIVE: + { + bool bValue; + if ( rSeq[i].Value >>= bValue ) + { + sal_Int32 nValue( 1 << j ); + rWinStateInfo.nMask |= nValue; + switch ( j ) + { + case PROPERTY_LOCKED: + rWinStateInfo.bLocked = bValue; + break; + case PROPERTY_DOCKED: + rWinStateInfo.bDocked = bValue; + break; + case PROPERTY_VISIBLE: + rWinStateInfo.bVisible = bValue; + break; + case PROPERTY_CONTEXT: + rWinStateInfo.bContext = bValue; + break; + case PROPERTY_HIDEFROMMENU: + rWinStateInfo.bHideFromMenu = bValue; + break; + case PROPERTY_NOCLOSE: + rWinStateInfo.bNoClose = bValue; + break; + case PROPERTY_SOFTCLOSE: + rWinStateInfo.bSoftClose = bValue; + break; + case PROPERTY_CONTEXTACTIVE: + rWinStateInfo.bContextActive = bValue; + break; + } + } + } + break; + + case PROPERTY_DOCKINGAREA: + { + css::ui::DockingArea eDockingArea; + if ( rSeq[i].Value >>= eDockingArea ) + { + rWinStateInfo.aDockingArea = eDockingArea; + rWinStateInfo.nMask |= WINDOWSTATE_MASK_DOCKINGAREA; + } + } + break; + + case PROPERTY_POS: + case PROPERTY_DOCKPOS: + { + css::awt::Point aPoint; + if ( rSeq[i].Value >>= aPoint ) + { + if ( j == PROPERTY_POS ) + { + rWinStateInfo.aPos = aPoint; + rWinStateInfo.nMask |= WINDOWSTATE_MASK_POS; + } + else + { + rWinStateInfo.aDockPos = aPoint; + rWinStateInfo.nMask |= WINDOWSTATE_MASK_DOCKPOS; + } + } + } + break; + + case PROPERTY_SIZE: + case PROPERTY_DOCKSIZE: + { + css::awt::Size aSize; + if ( rSeq[i].Value >>= aSize ) + { + if ( j == PROPERTY_SIZE ) + { + rWinStateInfo.aSize = aSize; + rWinStateInfo.nMask |= WINDOWSTATE_MASK_SIZE; + } + else + { + rWinStateInfo.aDockSize = aSize; + rWinStateInfo.nMask |= WINDOWSTATE_MASK_DOCKSIZE; + } + } + } + break; + + case PROPERTY_UINAME: + { + OUString aValue; + if ( rSeq[i].Value >>= aValue ) + { + rWinStateInfo.aUIName = aValue; + rWinStateInfo.nMask |= WINDOWSTATE_MASK_UINAME; + } + } + break; + + case PROPERTY_INTERNALSTATE: + { + sal_Int32 nValue = 0; + if ( rSeq[i].Value >>= nValue ) + { + rWinStateInfo.nInternalState = sal_uInt32( nValue ); + rWinStateInfo.nMask |= WINDOWSTATE_MASK_INTERNALSTATE; + } + } + break; + + case PROPERTY_STYLE: + { + sal_Int32 nValue = 0; + if ( rSeq[i].Value >>= nValue ) + { + rWinStateInfo.nStyle = sal_uInt16( nValue ); + rWinStateInfo.nMask |= WINDOWSTATE_MASK_STYLE; + } + } + break; + + default: + assert( false && "Wrong value for ConfigurationAccess_WindowState. Who has forgotten to add this new property!" ); + } + + break; + } + } + } +} + +void ConfigurationAccess_WindowState::impl_putPropertiesFromStruct( const WindowStateInfo& rWinStateInfo, Reference< XPropertySet > const & xPropSet ) +{ + sal_Int32 i( 0 ); + sal_Int32 nCount( m_aPropArray.size() ); + OUString aDelim( "," ); + + for ( i = 0; i < nCount; i++ ) + { + if ( rWinStateInfo.nMask & ( 1 << i )) + { + try + { + // put values into the property set + switch ( i ) + { + case PROPERTY_LOCKED: + xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bLocked ) ); break; + case PROPERTY_DOCKED: + xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bDocked ) ); break; + case PROPERTY_VISIBLE: + xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bVisible ) ); break; + case PROPERTY_CONTEXT: + xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bContext ) ); break; + case PROPERTY_HIDEFROMMENU: + xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bHideFromMenu ) ); break; + case PROPERTY_NOCLOSE: + xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bNoClose ) ); break; + case PROPERTY_SOFTCLOSE: + xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bSoftClose ) ); break; + case PROPERTY_CONTEXTACTIVE: + xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.bContextActive ) ); break; + case PROPERTY_DOCKINGAREA: + xPropSet->setPropertyValue( m_aPropArray[i], Any( sal_Int16( rWinStateInfo.aDockingArea ) ) ); break; + case PROPERTY_POS: + case PROPERTY_DOCKPOS: + { + OUString aPosStr; + if ( i == PROPERTY_POS ) + aPosStr = OUString::number( rWinStateInfo.aPos.X ); + else + aPosStr = OUString::number( rWinStateInfo.aDockPos.X ); + aPosStr += aDelim; + if ( i == PROPERTY_POS ) + aPosStr += OUString::number( rWinStateInfo.aPos.Y ); + else + aPosStr += OUString::number( rWinStateInfo.aDockPos.Y ); + xPropSet->setPropertyValue( m_aPropArray[i], Any( aPosStr ) ); + break; + } + case PROPERTY_SIZE: + case PROPERTY_DOCKSIZE: + { + OUString aSizeStr; + if ( i == PROPERTY_SIZE ) + aSizeStr = OUString::number( rWinStateInfo.aSize.Width ); + else + aSizeStr = OUString::number( rWinStateInfo.aDockSize.Width ); + aSizeStr += aDelim; + if ( i == PROPERTY_SIZE ) + aSizeStr += OUString::number( rWinStateInfo.aSize.Height ); + else + aSizeStr += OUString::number( rWinStateInfo.aDockSize.Height ); + xPropSet->setPropertyValue( m_aPropArray[i], Any( aSizeStr ) ); + break; + } + case PROPERTY_UINAME: + xPropSet->setPropertyValue( m_aPropArray[i], Any( rWinStateInfo.aUIName ) ); break; + case PROPERTY_INTERNALSTATE: + xPropSet->setPropertyValue( m_aPropArray[i], Any( sal_Int32( rWinStateInfo.nInternalState )) ); break; + case PROPERTY_STYLE: + xPropSet->setPropertyValue( m_aPropArray[i], Any( sal_Int32( rWinStateInfo.nStyle )) ); break; + default: + assert( false && "Wrong value for ConfigurationAccess_WindowState. Who has forgotten to add this new property!" ); + } + } + catch( const Exception& ) + { + } + } + } +} + +void ConfigurationAccess_WindowState::impl_initializeConfigAccess() +{ + try + { + Sequence<Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"nodepath", Any(m_aConfigWindowAccess)} + })); + m_xConfigAccess.set( m_xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationUpdateAccess", aArgs ), UNO_QUERY ); + if ( m_xConfigAccess.is() ) + { + // Add as container listener + Reference< XContainer > xContainer( m_xConfigAccess, UNO_QUERY ); + if ( xContainer.is() ) + { + m_xConfigListener = new WeakContainerListener(this); + xContainer->addContainerListener(m_xConfigListener); + } + } + } + catch ( const WrappedTargetException& ) + { + } + catch ( const Exception& ) + { + } +} + +typedef comphelper::WeakComponentImplHelper< css::container::XNameAccess, + css::lang::XServiceInfo> WindowStateConfiguration_BASE; + +class WindowStateConfiguration : public WindowStateConfiguration_BASE +{ +public: + explicit WindowStateConfiguration( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + virtual ~WindowStateConfiguration() override; + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.WindowStateConfiguration"; + } + + 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.WindowStateConfiguration"}; + } + + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override; + + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + + typedef std::unordered_map< OUString, + OUString > ModuleToWindowStateFileMap; + + typedef std::unordered_map< OUString, + css::uno::Reference< css::container::XNameAccess > > ModuleToWindowStateConfigHashMap; + +private: + css::uno::Reference< css::uno::XComponentContext> m_xContext; + ModuleToWindowStateFileMap m_aModuleToFileHashMap; + ModuleToWindowStateConfigHashMap m_aModuleToWindowStateHashMap; +}; + +WindowStateConfiguration::WindowStateConfiguration( const Reference< XComponentContext >& rxContext ) : + m_xContext( rxContext ) +{ + css::uno::Reference< css::frame::XModuleManager2 > xModuleManager = + ModuleManager::create( m_xContext ); + Reference< XNameAccess > xEmptyNameAccess; + Sequence< OUString > aElementNames; + try + { + aElementNames = xModuleManager->getElementNames(); + } + catch (const css::uno::RuntimeException &) + { + } + Sequence< PropertyValue > aSeq; + + for ( OUString const & aModuleIdentifier : std::as_const(aElementNames) ) + { + if ( xModuleManager->getByName( aModuleIdentifier ) >>= aSeq ) + { + OUString aWindowStateFileStr; + for ( PropertyValue const & rProp : std::as_const(aSeq) ) + { + if ( rProp.Name == "ooSetupFactoryWindowStateConfigRef" ) + { + rProp.Value >>= aWindowStateFileStr; + break; + } + } + + if ( !aWindowStateFileStr.isEmpty() ) + { + // Create first mapping ModuleIdentifier ==> Window state configuration file + m_aModuleToFileHashMap.emplace( aModuleIdentifier, aWindowStateFileStr ); + + // Create second mapping Command File ==> Window state configuration instance + ModuleToWindowStateConfigHashMap::iterator pIter = m_aModuleToWindowStateHashMap.find( aWindowStateFileStr ); + if ( pIter == m_aModuleToWindowStateHashMap.end() ) + m_aModuleToWindowStateHashMap.emplace( aWindowStateFileStr, xEmptyNameAccess ); + } + } + } +} + +WindowStateConfiguration::~WindowStateConfiguration() +{ + std::unique_lock g(m_aMutex); + m_aModuleToFileHashMap.clear(); + m_aModuleToWindowStateHashMap.clear(); +} + +Any SAL_CALL WindowStateConfiguration::getByName( const OUString& aModuleIdentifier ) +{ + std::unique_lock g(m_aMutex); + + ModuleToWindowStateFileMap::const_iterator pIter = m_aModuleToFileHashMap.find( aModuleIdentifier ); + if ( pIter != m_aModuleToFileHashMap.end() ) + { + Any a; + OUString aWindowStateConfigFile( pIter->second ); + + ModuleToWindowStateConfigHashMap::iterator pModuleIter = m_aModuleToWindowStateHashMap.find( aWindowStateConfigFile ); + if ( pModuleIter != m_aModuleToWindowStateHashMap.end() ) + { + if ( pModuleIter->second.is() ) + a <<= pModuleIter->second; + else + { + Reference< XNameAccess > xResourceURLWindowState = new ConfigurationAccess_WindowState( aWindowStateConfigFile, m_xContext ); + pModuleIter->second = xResourceURLWindowState; + a <<= xResourceURLWindowState; + } + + return a; + } + } + + throw NoSuchElementException(); +} + +Sequence< OUString > SAL_CALL WindowStateConfiguration::getElementNames() +{ + std::unique_lock g(m_aMutex); + + return comphelper::mapKeysToSequence( m_aModuleToFileHashMap ); +} + +sal_Bool SAL_CALL WindowStateConfiguration::hasByName( const OUString& aName ) +{ + std::unique_lock g(m_aMutex); + + ModuleToWindowStateFileMap::const_iterator pIter = m_aModuleToFileHashMap.find( aName ); + return ( pIter != m_aModuleToFileHashMap.end() ); +} + +// XElementAccess +Type SAL_CALL WindowStateConfiguration::getElementType() +{ + return cppu::UnoType<XNameAccess>::get(); +} + +sal_Bool SAL_CALL WindowStateConfiguration::hasElements() +{ + // We always have at least one module. So it is valid to return true! + return true; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_WindowStateConfiguration_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new WindowStateConfiguration(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/FixedImageToolbarController.cxx b/framework/source/uielement/FixedImageToolbarController.cxx new file mode 100644 index 000000000..303913432 --- /dev/null +++ b/framework/source/uielement/FixedImageToolbarController.cxx @@ -0,0 +1,139 @@ +/* -*- 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 <uielement/FixedImageToolbarController.hxx> + +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/InterimItemWindow.hxx> +#include <svtools/miscopt.hxx> +#include <svtools/imgdef.hxx> +#include <framework/addonsoptions.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::util; + +namespace framework +{ +class FixedImageControl final : public InterimItemWindow +{ +public: + FixedImageControl(vcl::Window* pParent, const OUString& rCommand); + virtual ~FixedImageControl() override; + virtual void dispose() override; + DECL_LINK(KeyInputHdl, const ::KeyEvent&, bool); + +private: + std::unique_ptr<weld::Image> m_xWidget; +}; + +FixedImageControl::FixedImageControl(vcl::Window* pParent, const OUString& rCommand) + : InterimItemWindow(pParent, "svt/ui/fixedimagecontrol.ui", "FixedImageControl") + , m_xWidget(m_xBuilder->weld_image("image")) +{ + InitControlBase(m_xWidget.get()); + + m_xWidget->connect_key_press(LINK(this, FixedImageControl, KeyInputHdl)); + + bool bBigImages(SvtMiscOptions().AreCurrentSymbolsLarge()); + auto xImage + = Graphic(AddonsOptions().GetImageFromURL(rCommand, bBigImages, true)).GetXGraphic(); + m_xWidget->set_image(xImage); + + SetSizePixel(get_preferred_size()); +} + +IMPL_LINK(FixedImageControl, KeyInputHdl, const ::KeyEvent&, rKEvt, bool) +{ + return ChildKeyInput(rKEvt); +} + +FixedImageControl::~FixedImageControl() { disposeOnce(); } + +void FixedImageControl::dispose() +{ + m_xWidget.reset(); + InterimItemWindow::dispose(); +} + +FixedImageToolbarController::FixedImageToolbarController( + const Reference<XComponentContext>& rxContext, const Reference<XFrame>& rFrame, + ToolBox* pToolbar, ToolBoxItemId nID, const OUString& rCommand) + : ComplexToolbarController(rxContext, rFrame, pToolbar, nID, rCommand) + , m_eSymbolSize(SvtMiscOptions().GetCurrentSymbolsSize()) +{ + m_pFixedImageControl = VclPtr<FixedImageControl>::Create(m_xToolbar, rCommand); + m_xToolbar->SetItemWindow(m_nID, m_pFixedImageControl); + + SvtMiscOptions().AddListenerLink(LINK(this, FixedImageToolbarController, MiscOptionsChanged)); +} + +void SAL_CALL FixedImageToolbarController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + SvtMiscOptions().RemoveListenerLink( + LINK(this, FixedImageToolbarController, MiscOptionsChanged)); + m_xToolbar->SetItemWindow(m_nID, nullptr); + m_pFixedImageControl.disposeAndClear(); + ComplexToolbarController::dispose(); +} + +void FixedImageToolbarController::executeControlCommand(const css::frame::ControlCommand&) {} + +void FixedImageToolbarController::CheckAndUpdateImages() +{ + SolarMutexGuard aSolarMutexGuard; + + SvtMiscOptions aMiscOptions; + const sal_Int16 eNewSymbolSize = aMiscOptions.GetCurrentSymbolsSize(); + + if (m_eSymbolSize == eNewSymbolSize) + return; + + m_eSymbolSize = eNewSymbolSize; + + // Refresh images if requested + ::Size aSize(16, 16); + if (m_eSymbolSize == SFX_SYMBOLS_SIZE_LARGE) + { + aSize.setWidth(26); + aSize.setHeight(26); + } + else if (m_eSymbolSize == SFX_SYMBOLS_SIZE_32) + { + aSize.setWidth(32); + aSize.setHeight(32); + } + m_pFixedImageControl->SetSizePixel(aSize); +} + +IMPL_LINK_NOARG(FixedImageToolbarController, MiscOptionsChanged, LinkParamNone*, void) +{ + CheckAndUpdateImages(); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/FixedTextToolbarController.cxx b/framework/source/uielement/FixedTextToolbarController.cxx new file mode 100644 index 000000000..02bfd4c5d --- /dev/null +++ b/framework/source/uielement/FixedTextToolbarController.cxx @@ -0,0 +1,127 @@ +/* -*- 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 <uielement/FixedTextToolbarController.hxx> + +#include <comphelper/propertyvalue.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/InterimItemWindow.hxx> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::util; + +namespace framework +{ +class FixedTextControl final : public InterimItemWindow +{ +public: + FixedTextControl(vcl::Window* pParent); + virtual ~FixedTextControl() override; + virtual void dispose() override; + OUString get_label() const { return m_xWidget->get_label(); } + void set_label(const OUString& rLabel) { return m_xWidget->set_label(rLabel); } + DECL_LINK(KeyInputHdl, const ::KeyEvent&, bool); + +private: + std::unique_ptr<weld::Label> m_xWidget; +}; + +FixedTextControl::FixedTextControl(vcl::Window* pParent) + : InterimItemWindow(pParent, "svt/ui/fixedtextcontrol.ui", "FixedTextControl") + , m_xWidget(m_xBuilder->weld_label("label")) +{ + InitControlBase(m_xWidget.get()); + + m_xWidget->connect_key_press(LINK(this, FixedTextControl, KeyInputHdl)); +} + +IMPL_LINK(FixedTextControl, KeyInputHdl, const ::KeyEvent&, rKEvt, bool) +{ + return ChildKeyInput(rKEvt); +} + +FixedTextControl::~FixedTextControl() { disposeOnce(); } + +void FixedTextControl::dispose() +{ + m_xWidget.reset(); + InterimItemWindow::dispose(); +} + +FixedTextToolbarController::FixedTextToolbarController( + const Reference<XComponentContext>& rxContext, const Reference<XFrame>& rFrame, + ToolBox* pToolbar, ToolBoxItemId nID, const OUString& aCommand) + : ComplexToolbarController(rxContext, rFrame, pToolbar, nID, aCommand) +{ + m_pFixedTextControl = VclPtr<FixedTextControl>::Create(m_xToolbar); + m_xToolbar->SetItemWindow(m_nID, m_pFixedTextControl); + m_xToolbar->SetItemBits(m_nID, ToolBoxItemBits::AUTOSIZE | m_xToolbar->GetItemBits(m_nID)); +} + +void SAL_CALL FixedTextToolbarController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + m_xToolbar->SetItemWindow(m_nID, nullptr); + m_pFixedTextControl.disposeAndClear(); + ComplexToolbarController::dispose(); +} + +Sequence<PropertyValue> FixedTextToolbarController::getExecuteArgs(sal_Int16 KeyModifier) const +{ + const OUString aSelectedText = m_pFixedTextControl->get_label(); + + // Add key modifier to argument list + Sequence<PropertyValue> aArgs{ comphelper::makePropertyValue("KeyModifier", KeyModifier), + comphelper::makePropertyValue("Text", aSelectedText) }; + return aArgs; +} + +void FixedTextToolbarController::executeControlCommand( + const css::frame::ControlCommand& rControlCommand) +{ + SolarMutexGuard aSolarMutexGuard; + + if (rControlCommand.Command != "SetText") + return; + + for (const NamedValue& rArg : rControlCommand.Arguments) + { + if (rArg.Name == "Text") + { + OUString aText; + rArg.Value >>= aText; + m_pFixedTextControl->set_label(aText); + m_pFixedTextControl->SetSizePixel(m_pFixedTextControl->get_preferred_size()); + + // send notification + notifyTextChanged(aText); + break; + } + } +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/addonstoolbarwrapper.cxx b/framework/source/uielement/addonstoolbarwrapper.cxx new file mode 100644 index 000000000..6547d1c6b --- /dev/null +++ b/framework/source/uielement/addonstoolbarwrapper.cxx @@ -0,0 +1,171 @@ +/* -*- 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 <uielement/addonstoolbarwrapper.hxx> +#include <uielement/toolbarmanager.hxx> + +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/ui/UIElementType.hpp> + +#include <toolkit/helper/vclunohelper.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::frame; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::awt; +using namespace ::com::sun::star::ui; + +namespace framework +{ + +AddonsToolBarWrapper::AddonsToolBarWrapper( const Reference< XComponentContext >& xContext ) : + UIElementWrapperBase( UIElementType::TOOLBAR ), + m_xContext( xContext ), + m_bCreatedImages( false ) +{ +} + +AddonsToolBarWrapper::~AddonsToolBarWrapper() +{ +} + +// XComponent +void SAL_CALL AddonsToolBarWrapper::dispose() +{ + Reference< XComponent > xThis(this); + + css::lang::EventObject aEvent( xThis ); + m_aListenerContainer.disposeAndClear( aEvent ); + + SolarMutexGuard g; + + if ( m_xToolBarManager.is() ) + m_xToolBarManager->dispose(); + m_xToolBarManager.clear(); + + m_bDisposed = true; +} + +// XInitialization +void SAL_CALL AddonsToolBarWrapper::initialize( const Sequence< Any >& aArguments ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_bInitialized ) + return; + + UIElementWrapperBase::initialize( aArguments ); + + for ( const Any& rArg : aArguments ) + { + PropertyValue aPropValue; + if ( rArg >>= aPropValue ) + { + if ( aPropValue.Name == "ConfigurationData" ) + aPropValue.Value >>= m_aConfigData; + } + } + + Reference< XFrame > xFrame( m_xWeakFrame ); + if ( !(xFrame.is() && m_aConfigData.hasElements()) ) + return; + + // Create VCL based toolbar which will be filled with settings data + VclPtr<ToolBox> pToolBar; + rtl::Reference<ToolBarManager> pToolBarManager; + { + SolarMutexGuard aSolarMutexGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xFrame->getContainerWindow() ); + if ( pWindow ) + { + sal_uLong nStyles = WB_BORDER | WB_SCROLL | WB_MOVEABLE | WB_3DLOOK | WB_DOCKABLE | WB_SIZEABLE | WB_CLOSEABLE; + + pToolBar = VclPtr<ToolBox>::Create( pWindow, nStyles ); + pToolBar->SetLineSpacing(true); + pToolBarManager = new ToolBarManager( m_xContext, xFrame, m_aResourceURL, pToolBar ); + m_xToolBarManager = pToolBarManager; + } + } + + try + { + if ( m_aConfigData.hasElements() && pToolBar && pToolBarManager ) + { + // Fill toolbar with container contents + pToolBarManager->FillAddonToolbar( m_aConfigData ); + pToolBar->EnableCustomize(); + ::Size aActSize( pToolBar->GetSizePixel() ); + ::Size aSize( pToolBar->CalcWindowSizePixel() ); + aSize.setWidth( aActSize.Width() ); + pToolBar->SetSizePixel( aSize ); + } + } + catch ( const NoSuchElementException& ) + { + } +} + +// XUIElement interface +Reference< XInterface > SAL_CALL AddonsToolBarWrapper::getRealInterface() +{ + SolarMutexGuard g; + + if ( m_xToolBarManager.is() ) + { + ToolBarManager* pToolBarManager = static_cast< ToolBarManager *>( m_xToolBarManager.get() ); + if ( pToolBarManager ) + { + vcl::Window* pWindow = pToolBarManager->GetToolBar(); + return Reference< XInterface >( VCLUnoHelper::GetInterface( pWindow ), UNO_QUERY ); + } + } + + return Reference< XInterface >(); +} + +// allow late population of images for add-on toolbars +void AddonsToolBarWrapper::populateImages() +{ + SolarMutexGuard g; + + if (m_bCreatedImages) + return; + + if ( m_xToolBarManager.is() ) + { + ToolBarManager* pToolBarManager = static_cast< ToolBarManager *>( m_xToolBarManager.get() ); + if (pToolBarManager) + { + pToolBarManager->RequestImages(); + m_bCreatedImages = true; + } + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/buttontoolbarcontroller.cxx b/framework/source/uielement/buttontoolbarcontroller.cxx new file mode 100644 index 000000000..4972b6da7 --- /dev/null +++ b/framework/source/uielement/buttontoolbarcontroller.cxx @@ -0,0 +1,280 @@ +/* -*- 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 <uielement/buttontoolbarcontroller.hxx> + +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +#include <cppuhelper/queryinterface.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> + +using namespace ::com::sun::star; +using namespace css::awt; +using namespace css::uno; +using namespace css::beans; +using namespace css::lang; +using namespace css::frame; +using namespace css::util; + +namespace framework +{ + +ButtonToolbarController::ButtonToolbarController( + uno::Reference< uno::XComponentContext > xContext, + ToolBox* pToolBar, + OUString aCommand ) : + m_bInitialized( false ), + m_bDisposed( false ), + m_aCommandURL(std::move( aCommand )), + m_xContext(std::move( xContext )), + m_pToolbar( pToolBar ) +{ +} + +ButtonToolbarController::~ButtonToolbarController() +{ +} + + // XInterface +uno::Any SAL_CALL ButtonToolbarController::queryInterface( const uno::Type& rType ) +{ + Any a = ::cppu::queryInterface( + rType , + static_cast< frame::XStatusListener* >( this ), + static_cast< frame::XToolbarController* >( this ), + static_cast< lang::XInitialization* >( this ), + static_cast< lang::XComponent* >( this ), + static_cast< util::XUpdatable* >( this )); + + if ( a.hasValue() ) + return a; + + return cppu::OWeakObject::queryInterface( rType ); +} + +void SAL_CALL ButtonToolbarController::acquire() noexcept +{ + cppu::OWeakObject::acquire(); +} + +void SAL_CALL ButtonToolbarController::release() noexcept +{ + cppu::OWeakObject::release(); +} + +// XInitialization +void SAL_CALL ButtonToolbarController::initialize( + const css::uno::Sequence< css::uno::Any >& aArguments ) +{ + bool bInitialized( true ); + + { + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + bInitialized = m_bInitialized; + } + + if ( bInitialized ) + return; + + SolarMutexGuard aSolarMutexGuard; + m_bInitialized = true; + + PropertyValue aPropValue; + for ( const css::uno::Any& rArg : aArguments ) + { + if ( rArg >>= aPropValue ) + { + if ( aPropValue.Name == "Frame" ) + m_xFrame.set(aPropValue.Value,UNO_QUERY); + else if ( aPropValue.Name == "CommandURL" ) + aPropValue.Value >>= m_aCommandURL; + else if ( aPropValue.Name == "ServiceManager" ) + { + Reference<XMultiServiceFactory> xServiceManager(aPropValue.Value,UNO_QUERY); + m_xContext = comphelper::getComponentContext(xServiceManager); + } + } + } +} + +// XComponent +void SAL_CALL ButtonToolbarController::dispose() +{ + Reference< XComponent > xThis = this; + + { + SolarMutexGuard aSolarMutexGuard; + if ( m_bDisposed ) + throw DisposedException(); + + m_xContext.clear(); + m_xURLTransformer.clear(); + m_xFrame.clear(); + m_pToolbar.clear(); + m_bDisposed = true; + } +} + +void SAL_CALL ButtonToolbarController::addEventListener( + const css::uno::Reference< css::lang::XEventListener >& ) +{ + // do nothing +} + +void SAL_CALL ButtonToolbarController::removeEventListener( + const css::uno::Reference< css::lang::XEventListener >& ) +{ + // do nothing +} + +// XUpdatable +void SAL_CALL ButtonToolbarController::update() +{ + SolarMutexGuard aSolarMutexGuard; + if ( m_bDisposed ) + throw DisposedException(); +} + +// XEventListener +void SAL_CALL ButtonToolbarController::disposing( + const css::lang::EventObject& Source ) +{ + uno::Reference< uno::XInterface > xSource( Source.Source ); + + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + return; + + uno::Reference< uno::XInterface > xIfac( m_xFrame, uno::UNO_QUERY ); + if ( xIfac == xSource ) + m_xFrame.clear(); +} + +void SAL_CALL ButtonToolbarController::statusChanged( const css::frame::FeatureStateEvent& ) +{ + // do nothing + if ( m_bDisposed ) + throw DisposedException(); +} + +// XToolbarController +void SAL_CALL ButtonToolbarController::execute( sal_Int16 KeyModifier ) +{ + uno::Reference< frame::XDispatch > xDispatch; + uno::Reference< frame::XFrame > xFrame; + uno::Reference< util::XURLTransformer > xURLTransformer; + OUString aCommandURL; + css::util::URL aTargetURL; + + { + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_bInitialized && + m_xFrame.is() && + m_xContext.is() && + !m_aCommandURL.isEmpty() ) + { + if ( !m_xURLTransformer.is() ) + { + m_xURLTransformer = util::URLTransformer::create( m_xContext ); + } + + xFrame = m_xFrame; + aCommandURL = m_aCommandURL; + xURLTransformer = m_xURLTransformer; + } + } + + uno::Reference< frame::XDispatchProvider > xDispatchProvider( xFrame, uno::UNO_QUERY ); + if ( xDispatchProvider.is() ) + { + aTargetURL.Complete = aCommandURL; + xURLTransformer->parseStrict( aTargetURL ); + xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + } + + if ( !xDispatch.is() ) + return; + + try + { + // Provide key modifier information to dispatch function + Sequence<PropertyValue> aArgs{ comphelper::makePropertyValue("KeyModifier", KeyModifier) }; + + xDispatch->dispatch( aTargetURL, aArgs ); + } + catch ( const DisposedException& ) + { + } +} + +void SAL_CALL ButtonToolbarController::click() +{ + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + sal_Int16 nKeyModifier( static_cast<sal_Int16>(m_pToolbar->GetModifier()) ); + execute( nKeyModifier ); +} + +void SAL_CALL ButtonToolbarController::doubleClick() +{ + // do nothing + if ( m_bDisposed ) + throw DisposedException(); +} + +uno::Reference< awt::XWindow > SAL_CALL ButtonToolbarController::createPopupWindow() +{ + if ( m_bDisposed ) + throw DisposedException(); + + return uno::Reference< awt::XWindow >(); +} + +uno::Reference< awt::XWindow > SAL_CALL ButtonToolbarController::createItemWindow( + const css::uno::Reference< css::awt::XWindow >& ) +{ + if ( m_bDisposed ) + throw DisposedException(); + + return uno::Reference< awt::XWindow >(); +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/comboboxtoolbarcontroller.cxx b/framework/source/uielement/comboboxtoolbarcontroller.cxx new file mode 100644 index 000000000..77d6e3e8c --- /dev/null +++ b/framework/source/uielement/comboboxtoolbarcontroller.cxx @@ -0,0 +1,333 @@ +/* -*- 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 <uielement/comboboxtoolbarcontroller.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <vcl/InterimItemWindow.hxx> +#include <svtools/toolboxcontroller.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> + +using namespace ::com::sun::star; +using namespace css::uno; +using namespace css::beans; +using namespace css::lang; +using namespace css::frame; +using namespace css::util; + +namespace framework +{ + +// Wrapper class to notify controller about events from combobox. +// Unfortunately the events are notified through virtual methods instead +// of Listeners. + +class ComboBoxControl final : public InterimItemWindow +{ +public: + ComboBoxControl(vcl::Window* pParent, ComboboxToolbarController* pComboboxToolbarController); + virtual ~ComboBoxControl() override; + virtual void dispose() override; + + void set_active_or_entry_text(const OUString& rText); + OUString get_active_text() const { return m_xWidget->get_active_text(); } + + void clear() { m_xWidget->clear(); } + void remove(int nIndex) { m_xWidget->remove(nIndex); } + void append_text(const OUString& rStr) { m_xWidget->append_text(rStr); } + void insert_text(int pos, const OUString& rStr) { m_xWidget->insert_text(pos, rStr); } + int get_count() const { return m_xWidget->get_count(); } + int find_text(const OUString& rStr) const { return m_xWidget->find_text(rStr); } + + DECL_LINK(FocusInHdl, weld::Widget&, void); + DECL_LINK(FocusOutHdl, weld::Widget&, void); + DECL_LINK(ModifyHdl, weld::ComboBox&, void); + DECL_LINK(ActivateHdl, weld::ComboBox&, bool); + DECL_LINK(KeyInputHdl, const ::KeyEvent&, bool); + +private: + std::unique_ptr<weld::ComboBox> m_xWidget; + ComboboxToolbarController* m_pComboboxToolbarController; +}; + +ComboBoxControl::ComboBoxControl(vcl::Window* pParent, ComboboxToolbarController* pComboboxToolbarController) + : InterimItemWindow(pParent, "svt/ui/combocontrol.ui", "ComboControl") + , m_xWidget(m_xBuilder->weld_combo_box("combobox")) + , m_pComboboxToolbarController(pComboboxToolbarController) +{ + InitControlBase(m_xWidget.get()); + + m_xWidget->connect_focus_in(LINK(this, ComboBoxControl, FocusInHdl)); + m_xWidget->connect_focus_out(LINK(this, ComboBoxControl, FocusOutHdl)); + m_xWidget->connect_changed(LINK(this, ComboBoxControl, ModifyHdl)); + m_xWidget->connect_entry_activate(LINK(this, ComboBoxControl, ActivateHdl)); + m_xWidget->connect_key_press(LINK(this, ComboBoxControl, KeyInputHdl)); + + m_xWidget->set_entry_width_chars(1); // so a smaller than default width can be used by ComboboxToolbarController + SetSizePixel(get_preferred_size()); +} + +IMPL_LINK(ComboBoxControl, KeyInputHdl, const ::KeyEvent&, rKEvt, bool) +{ + return ChildKeyInput(rKEvt); +} + +void ComboBoxControl::set_active_or_entry_text(const OUString& rText) +{ + const int nFound = m_xWidget->find_text(rText); + if (nFound != -1) + m_xWidget->set_active(nFound); + else + m_xWidget->set_entry_text(rText); +} + +ComboBoxControl::~ComboBoxControl() +{ + disposeOnce(); +} + +void ComboBoxControl::dispose() +{ + m_pComboboxToolbarController = nullptr; + m_xWidget.reset(); + InterimItemWindow::dispose(); +} + +IMPL_LINK_NOARG(ComboBoxControl, ModifyHdl, weld::ComboBox&, void) +{ + if (m_pComboboxToolbarController) + { + if (m_xWidget->get_count() && m_xWidget->changed_by_direct_pick()) + m_pComboboxToolbarController->Select(); + else + m_pComboboxToolbarController->Modify(); + } +} + +IMPL_LINK_NOARG(ComboBoxControl, FocusInHdl, weld::Widget&, void) +{ + if (m_pComboboxToolbarController) + m_pComboboxToolbarController->GetFocus(); +} + +IMPL_LINK_NOARG(ComboBoxControl, FocusOutHdl, weld::Widget&, void) +{ + if (m_pComboboxToolbarController) + m_pComboboxToolbarController->LoseFocus(); +} + +IMPL_LINK_NOARG(ComboBoxControl, ActivateHdl, weld::ComboBox&, bool) +{ + if (m_pComboboxToolbarController) + m_pComboboxToolbarController->Activate(); + return true; +} + +ComboboxToolbarController::ComboboxToolbarController( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + ToolBox* pToolbar, + ToolBoxItemId nID, + sal_Int32 nWidth, + const OUString& aCommand ) : + ComplexToolbarController( rxContext, rFrame, pToolbar, nID, aCommand ) + , m_pComboBox( nullptr ) +{ + m_pComboBox = VclPtr<ComboBoxControl>::Create(m_xToolbar, this); + if ( nWidth == 0 ) + nWidth = 100; + + // ComboBoxControl ctor has set a suitable height already + auto nHeight = m_pComboBox->GetSizePixel().Height(); + + m_pComboBox->SetSizePixel( ::Size( nWidth, nHeight )); + m_xToolbar->SetItemWindow( m_nID, m_pComboBox ); +} + +ComboboxToolbarController::~ComboboxToolbarController() +{ +} + +void SAL_CALL ComboboxToolbarController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + + m_xToolbar->SetItemWindow( m_nID, nullptr ); + m_pComboBox.disposeAndClear(); + + ComplexToolbarController::dispose(); +} + +Sequence<PropertyValue> ComboboxToolbarController::getExecuteArgs(sal_Int16 KeyModifier) const +{ + OUString aSelectedText = m_pComboBox->get_active_text(); + + // Add key modifier to argument list + Sequence<PropertyValue> aArgs{ comphelper::makePropertyValue("KeyModifier", KeyModifier), + comphelper::makePropertyValue("Text", aSelectedText) }; + return aArgs; +} + +void ComboboxToolbarController::Select() +{ + vcl::Window::PointerState aState = m_pComboBox->GetPointerState(); + + sal_uInt16 nKeyModifier = sal_uInt16( aState.mnState & KEY_MODIFIERS_MASK ); + execute( nKeyModifier ); +} + +void ComboboxToolbarController::Modify() +{ + notifyTextChanged(m_pComboBox->get_active_text()); +} + +void ComboboxToolbarController::GetFocus() +{ + notifyFocusGet(); +} + +void ComboboxToolbarController::LoseFocus() +{ + notifyFocusLost(); +} + +void ComboboxToolbarController::Activate() +{ + // Call execute only with non-empty text + if (!m_pComboBox->get_active_text().isEmpty()) + execute(0); +} + +void ComboboxToolbarController::executeControlCommand( const css::frame::ControlCommand& rControlCommand ) +{ + if ( rControlCommand.Command == "SetText" ) + { + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "Text" ) + { + OUString aText; + rArg.Value >>= aText; + m_pComboBox->set_active_or_entry_text(aText); + + // send notification + notifyTextChanged( aText ); + break; + } + } + } + else if ( rControlCommand.Command == "SetList" ) + { + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "List" ) + { + Sequence< OUString > aList; + m_pComboBox->clear(); + + rArg.Value >>= aList; + for (OUString const & rName : std::as_const(aList)) + m_pComboBox->append_text(rName); + + // send notification + uno::Sequence< beans::NamedValue > aInfo { { "List", css::uno::Any(aList) } }; + addNotifyInfo( "ListChanged", + getDispatchFromCommand( m_aCommandURL ), + aInfo ); + + break; + } + } + } + else if ( rControlCommand.Command == "AddEntry" ) + { + OUString aText; + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "Text" ) + { + if ( rArg.Value >>= aText ) + m_pComboBox->append_text(aText); + break; + } + } + } + else if ( rControlCommand.Command == "InsertEntry" ) + { + sal_Int32 nPos(-1); + OUString aText; + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "Pos" ) + { + sal_Int32 nTmpPos = 0; + if ( rArg.Value >>= nTmpPos ) + { + if (( nTmpPos >= 0 ) && + ( nTmpPos < m_pComboBox->get_count() )) + nPos = nTmpPos; + } + } + else if ( rArg.Name == "Text" ) + rArg.Value >>= aText; + } + + m_pComboBox->insert_text(nPos, aText); + } + else if ( rControlCommand.Command == "RemoveEntryPos" ) + { + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "Pos" ) + { + sal_Int32 nPos( -1 ); + if ( rArg.Value >>= nPos ) + { + if (0 <= nPos && nPos < m_pComboBox->get_count()) + m_pComboBox->remove(nPos); + } + break; + } + } + } + else if ( rControlCommand.Command == "RemoveEntryText" ) + { + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "Text") + { + OUString aText; + if ( rArg.Value >>= aText ) + { + auto nPos = m_pComboBox->find_text(aText); + if (nPos != -1) + m_pComboBox->remove(nPos); + } + break; + } + } + } +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/complextoolbarcontroller.cxx b/framework/source/uielement/complextoolbarcontroller.cxx new file mode 100644 index 000000000..c3f9f191d --- /dev/null +++ b/framework/source/uielement/complextoolbarcontroller.cxx @@ -0,0 +1,326 @@ +/* -*- 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 <uielement/complextoolbarcontroller.hxx> + +#include <com/sun/star/util/URLTransformer.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/frame/status/ItemStatus.hpp> +#include <com/sun/star/frame/status/Visibility.hpp> +#include <com/sun/star/frame/XControlNotificationListener.hpp> +#include <com/sun/star/frame/XFrame.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <svtools/toolboxcontroller.hxx> +#include <vcl/svapp.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/toolbox.hxx> + +using namespace ::com::sun::star; +using namespace css::awt; +using namespace css::uno; +using namespace css::beans; +using namespace css::lang; +using namespace css::frame; +using namespace css::frame::status; +using namespace css::util; + +namespace framework +{ + +ComplexToolbarController::ComplexToolbarController( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + ToolBox* pToolbar, + ToolBoxItemId nID, + const OUString& aCommand ) : + svt::ToolboxController( rxContext, rFrame, aCommand ) + , m_xToolbar( pToolbar ) + , m_nID( nID ) + , m_bMadeInvisible( false ) +{ + m_xURLTransformer.set( URLTransformer::create(m_xContext) ); +} + +ComplexToolbarController::~ComplexToolbarController() +{ +} + +void SAL_CALL ComplexToolbarController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + + m_xToolbar->SetItemWindow( m_nID, nullptr ); + svt::ToolboxController::dispose(); + + m_xURLTransformer.clear(); + m_xToolbar.clear(); + m_nID = ToolBoxItemId(0); +} + +Sequence<PropertyValue> ComplexToolbarController::getExecuteArgs(sal_Int16 KeyModifier) const +{ + // Add key modifier to argument list + Sequence<PropertyValue> aArgs{ comphelper::makePropertyValue("KeyModifier", KeyModifier) }; + return aArgs; +} + +void SAL_CALL ComplexToolbarController::execute( sal_Int16 KeyModifier ) +{ + Reference< XDispatch > xDispatch; + Reference< XURLTransformer > xURLTransformer; + css::util::URL aTargetURL; + Sequence<PropertyValue> aArgs; + + { + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_bInitialized && + m_xFrame.is() && + !m_aCommandURL.isEmpty() ) + { + xURLTransformer = m_xURLTransformer; + xDispatch = getDispatchFromCommand( m_aCommandURL ); + aTargetURL = getInitializedURL(); + aArgs = getExecuteArgs(KeyModifier); + } + } + + if ( xDispatch.is() && !aTargetURL.Complete.isEmpty() ) + { + // Execute dispatch asynchronously + ExecuteInfo* pExecuteInfo = new ExecuteInfo; + pExecuteInfo->xDispatch = xDispatch; + pExecuteInfo->aTargetURL = aTargetURL; + pExecuteInfo->aArgs = aArgs; + Application::PostUserEvent( LINK(nullptr, ComplexToolbarController , ExecuteHdl_Impl), pExecuteInfo ); + } +} + +void ComplexToolbarController::statusChanged( const FeatureStateEvent& Event ) +{ + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + return; + + if ( !m_xToolbar ) + return; + + m_xToolbar->EnableItem( m_nID, Event.IsEnabled ); + + ToolBoxItemBits nItemBits = m_xToolbar->GetItemBits( m_nID ); + nItemBits &= ~ToolBoxItemBits::CHECKABLE; + TriState eTri = TRISTATE_FALSE; + + bool bValue; + OUString aStrValue; + ItemStatus aItemState; + Visibility aItemVisibility; + ControlCommand aControlCommand; + + if ( Event.State >>= bValue ) + { + // Boolean, treat it as checked/unchecked + if ( m_bMadeInvisible ) + m_xToolbar->ShowItem( m_nID ); + m_xToolbar->CheckItem( m_nID, bValue ); + if ( bValue ) + eTri = TRISTATE_TRUE; + nItemBits |= ToolBoxItemBits::CHECKABLE; + } + else if ( Event.State >>= aStrValue ) + { + OUString aText( MnemonicGenerator::EraseAllMnemonicChars( aStrValue ) ); + m_xToolbar->SetItemText( m_nID, aText ); + m_xToolbar->SetQuickHelpText( m_nID, aText ); + + if ( m_bMadeInvisible ) + m_xToolbar->ShowItem( m_nID ); + } + else if ( Event.State >>= aItemState ) + { + eTri = TRISTATE_INDET; + nItemBits |= ToolBoxItemBits::CHECKABLE; + if ( m_bMadeInvisible ) + m_xToolbar->ShowItem( m_nID ); + } + else if ( Event.State >>= aItemVisibility ) + { + m_xToolbar->ShowItem( m_nID, aItemVisibility.bVisible ); + m_bMadeInvisible = !aItemVisibility.bVisible; + } + else if ( Event.State >>= aControlCommand ) + { + if (aControlCommand.Command == "SetQuickHelpText") + { + for (NamedValue const & rArg : std::as_const(aControlCommand.Arguments)) + { + if (rArg.Name == "HelpText") + { + OUString aHelpText; + rArg.Value >>= aHelpText; + m_xToolbar->SetQuickHelpText(m_nID, aHelpText); + break; + } + } + } + else + { + executeControlCommand( aControlCommand ); + } + if ( m_bMadeInvisible ) + m_xToolbar->ShowItem( m_nID ); + } + + else if ( m_bMadeInvisible ) + m_xToolbar->ShowItem( m_nID ); + + m_xToolbar->SetItemState( m_nID, eTri ); + m_xToolbar->SetItemBits( m_nID, nItemBits ); +} + +IMPL_STATIC_LINK( ComplexToolbarController, ExecuteHdl_Impl, void*, p, void ) +{ + ExecuteInfo* pExecuteInfo = static_cast<ExecuteInfo*>(p); + SolarMutexReleaser aReleaser; + try + { + // Asynchronous execution as this can lead to our own destruction! + // Framework can recycle our current frame and the layout manager disposes all user interface + // elements if a component gets detached from its frame! + pExecuteInfo->xDispatch->dispatch( pExecuteInfo->aTargetURL, pExecuteInfo->aArgs ); + } + catch ( const Exception& ) + { + } + + delete pExecuteInfo; +} + +IMPL_STATIC_LINK( ComplexToolbarController, Notify_Impl, void*, p, void ) +{ + NotifyInfo* pNotifyInfo = static_cast<NotifyInfo*>(p); + SolarMutexReleaser aReleaser; + try + { + // Asynchronous execution: As this can lead to our own destruction! + // Framework can recycle our current frame and the layout manager disposes all user interface + // elements if a component gets detached from its frame! + frame::ControlEvent aEvent; + aEvent.aURL = pNotifyInfo->aSourceURL; + aEvent.Event = pNotifyInfo->aEventName; + aEvent.aInformation = pNotifyInfo->aInfoSeq; + pNotifyInfo->xNotifyListener->controlEvent( aEvent ); + } + catch ( const Exception& ) + { + } + + delete pNotifyInfo; +} + +void ComplexToolbarController::addNotifyInfo( + const OUString& aEventName, + const uno::Reference< frame::XDispatch >& xDispatch, + const uno::Sequence< beans::NamedValue >& rInfo ) +{ + uno::Reference< frame::XControlNotificationListener > xControlNotify( xDispatch, uno::UNO_QUERY ); + + if ( !xControlNotify.is() ) + return; + + // Execute notification asynchronously + NotifyInfo* pNotifyInfo = new NotifyInfo; + + pNotifyInfo->aEventName = aEventName; + pNotifyInfo->xNotifyListener = xControlNotify; + pNotifyInfo->aSourceURL = getInitializedURL(); + + // Add frame as source to the information sequence + sal_Int32 nCount = rInfo.getLength(); + uno::Sequence< beans::NamedValue > aInfoSeq( rInfo ); + aInfoSeq.realloc( nCount+1 ); + auto pInfoSeq = aInfoSeq.getArray(); + pInfoSeq[nCount].Name = "Source"; + pInfoSeq[nCount].Value <<= getFrameInterface(); + pNotifyInfo->aInfoSeq = aInfoSeq; + + Application::PostUserEvent( LINK(nullptr, ComplexToolbarController, Notify_Impl), pNotifyInfo ); +} + +uno::Reference< frame::XDispatch > ComplexToolbarController::getDispatchFromCommand( const OUString& aCommand ) const +{ + uno::Reference< frame::XDispatch > xDispatch; + + if ( m_bInitialized && m_xFrame.is() && !aCommand.isEmpty() ) + { + URLToDispatchMap::const_iterator pIter = m_aListenerMap.find( aCommand ); + if ( pIter != m_aListenerMap.end() ) + xDispatch = pIter->second; + } + + return xDispatch; +} + +const css::util::URL& ComplexToolbarController::getInitializedURL() +{ + if ( m_aURL.Complete.isEmpty() ) + { + m_aURL.Complete = m_aCommandURL; + m_xURLTransformer->parseStrict( m_aURL ); + } + return m_aURL; +} + +void ComplexToolbarController::notifyFocusGet() +{ + // send focus get notification + uno::Sequence< beans::NamedValue > aInfo; + addNotifyInfo( "FocusSet", + getDispatchFromCommand( m_aCommandURL ), + aInfo ); +} + +void ComplexToolbarController::notifyFocusLost() +{ + // send focus lost notification + uno::Sequence< beans::NamedValue > aInfo; + addNotifyInfo( "FocusLost", + getDispatchFromCommand( m_aCommandURL ), + aInfo ); +} + +void ComplexToolbarController::notifyTextChanged( const OUString& aText ) +{ + // send text changed notification + uno::Sequence< beans::NamedValue > aInfo { { "Text", css::uno::Any(aText) } }; + addNotifyInfo( "TextChanged", + getDispatchFromCommand( m_aCommandURL ), + aInfo ); +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/controlmenucontroller.cxx b/framework/source/uielement/controlmenucontroller.cxx new file mode 100644 index 000000000..2c31081bb --- /dev/null +++ b/framework/source/uielement/controlmenucontroller.cxx @@ -0,0 +1,327 @@ +/* -*- 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 <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XStatusListener.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> + +#include <cppuhelper/supportsservice.hxx> +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/image.hxx> +#include <svtools/popupmenucontrollerbase.hxx> +#include <osl/mutex.hxx> +#include <unordered_map> + +#include <classes/fwkresid.hxx> +#include <bitmaps.hlst> +#include <strings.hrc> + +static const char* aCommands[] = +{ + ".uno:ConvertToEdit", + ".uno:ConvertToButton", + ".uno:ConvertToFixed", + ".uno:ConvertToList", + ".uno:ConvertToCheckBox", + ".uno:ConvertToRadio", + ".uno:ConvertToGroup", + ".uno:ConvertToCombo", + ".uno:ConvertToImageBtn", + ".uno:ConvertToFileControl", + ".uno:ConvertToDate", + ".uno:ConvertToTime", + ".uno:ConvertToNumeric", + ".uno:ConvertToCurrency", + ".uno:ConvertToPattern", + ".uno:ConvertToImageControl", + ".uno:ConvertToFormatted", + ".uno:ConvertToScrollBar", + ".uno:ConvertToSpinButton", + ".uno:ConvertToNavigationBar" +}; + +static TranslateId aLabels[] = +{ + RID_STR_PROPTITLE_EDIT, + RID_STR_PROPTITLE_PUSHBUTTON, + RID_STR_PROPTITLE_FIXEDTEXT, + RID_STR_PROPTITLE_LISTBOX, + RID_STR_PROPTITLE_CHECKBOX, + RID_STR_PROPTITLE_RADIOBUTTON, + RID_STR_PROPTITLE_GROUPBOX, + RID_STR_PROPTITLE_COMBOBOX, + RID_STR_PROPTITLE_IMAGEBUTTON, + RID_STR_PROPTITLE_FILECONTROL, + RID_STR_PROPTITLE_DATEFIELD, + RID_STR_PROPTITLE_TIMEFIELD, + RID_STR_PROPTITLE_NUMERICFIELD, + RID_STR_PROPTITLE_CURRENCYFIELD, + RID_STR_PROPTITLE_PATTERNFIELD, + RID_STR_PROPTITLE_IMAGECONTROL, + RID_STR_PROPTITLE_FORMATTED, + RID_STR_PROPTITLE_SCROLLBAR, + RID_STR_PROPTITLE_SPINBUTTON, + RID_STR_PROPTITLE_NAVBAR +}; + +const rtl::OUStringConstExpr aImgIds[] +{ + RID_SVXBMP_EDITBOX, + RID_SVXBMP_BUTTON, + RID_SVXBMP_FIXEDTEXT, + RID_SVXBMP_LISTBOX, + RID_SVXBMP_CHECKBOX, + RID_SVXBMP_RADIOBUTTON, + RID_SVXBMP_GROUPBOX, + RID_SVXBMP_COMBOBOX, + RID_SVXBMP_IMAGEBUTTON, + RID_SVXBMP_FILECONTROL, + RID_SVXBMP_DATEFIELD, + RID_SVXBMP_TIMEFIELD, + RID_SVXBMP_NUMERICFIELD, + RID_SVXBMP_CURRENCYFIELD, + RID_SVXBMP_PATTERNFIELD, + RID_SVXBMP_IMAGECONTROL, + RID_SVXBMP_FORMATTEDFIELD, + RID_SVXBMP_SCROLLBAR, + RID_SVXBMP_SPINBUTTON, + RID_SVXBMP_NAVIGATIONBAR +}; + +using namespace css; +using namespace css::uno; +using namespace css::lang; +using namespace css::frame; +using namespace css::beans; + +namespace { + +class ControlMenuController : public svt::PopupMenuControllerBase +{ + using svt::PopupMenuControllerBase::disposing; + +public: + explicit ControlMenuController( const uno::Reference< uno::XComponentContext >& xContext ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.ControlMenuController"; + } + + 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.frame.PopupMenuController"}; + } + + // XPopupMenuController + virtual void SAL_CALL updatePopupMenu() override; + + // XInitialization + virtual void SAL_CALL initialize( const uno::Sequence< uno::Any >& aArguments ) override; + + // XStatusListener + virtual void SAL_CALL statusChanged( const frame::FeatureStateEvent& Event ) override; + + // XMenuListener + virtual void SAL_CALL itemActivated( const awt::MenuEvent& rEvent ) override; + + // XEventListener + virtual void SAL_CALL disposing( const lang::EventObject& Source ) override; + +private: + class UrlToDispatchMap : public std::unordered_map< OUString, + uno::Reference< frame::XDispatch > > + { + public: + void free() + { + UrlToDispatchMap().swap( *this );// get rid of reserved capacity + } + }; + + void updateImagesPopupMenu(Reference<awt::XPopupMenu> const& rPopupMenu); + void fillPopupMenu(uno::Reference<awt::XPopupMenu> const& rPopupMenu); + + bool m_bShowMenuImages : 1; + UrlToDispatchMap m_aURLToDispatchMap; +}; + +ControlMenuController::ControlMenuController(const css::uno::Reference< css::uno::XComponentContext >& xContext) + : svt::PopupMenuControllerBase(xContext) +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + m_bShowMenuImages = rSettings.GetUseImagesInMenus(); + +} + +// private function +void ControlMenuController::updateImagesPopupMenu(Reference<awt::XPopupMenu> const& rPopupMenu) +{ + if (!rPopupMenu) + return; + for (size_t i=0; i < SAL_N_ELEMENTS(aCommands); ++i) + { + sal_Int16 nItemId = i + 1; + if (m_bShowMenuImages) + { + Image aImage(StockImage::Yes, OUString(aImgIds[i])); + Graphic aGraphic(aImage); + rPopupMenu->setItemImage(nItemId, aGraphic.GetXGraphic(), false); + } + else + rPopupMenu->setItemImage(nItemId, nullptr, false); + } +} + +// private function +void ControlMenuController::fillPopupMenu( Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + resetPopupMenu( rPopupMenu ); + + for (size_t i=0; i < SAL_N_ELEMENTS(aCommands); ++i) + { + sal_Int16 nItemId = i + 1; + OUString sCommand(OUString::createFromAscii(aCommands[i])); + rPopupMenu->insertItem(nItemId, FwkResId(aLabels[i]), 0, i); + rPopupMenu->setCommand(nItemId, sCommand); + rPopupMenu->enableItem(nItemId, false); + } + + updateImagesPopupMenu(rPopupMenu); + + rPopupMenu->hideDisabledEntries(true); +} + +} + +// XEventListener +void SAL_CALL ControlMenuController::disposing( const EventObject& ) +{ + Reference< css::awt::XMenuListener > xHolder(this); + + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + + if ( m_xPopupMenu.is() ) + m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); + m_xPopupMenu.clear(); +} + +// XStatusListener +void SAL_CALL ControlMenuController::statusChanged( const FeatureStateEvent& Event ) +{ + osl::MutexGuard aLock( m_aMutex ); + + if (!m_xPopupMenu) + return; + + sal_Int16 nItemId = 0; + for (size_t i=0; i < SAL_N_ELEMENTS(aCommands); ++i) + { + if ( Event.FeatureURL.Complete.equalsAscii( aCommands[i] )) + { + nItemId = i + 1; + break; + } + } + + if (!nItemId) + return; + + m_xPopupMenu->enableItem(nItemId, Event.IsEnabled); +} + +// XMenuListener +void SAL_CALL ControlMenuController::itemActivated( const css::awt::MenuEvent& ) +{ + osl::MutexGuard aLock( m_aMutex ); + + SolarMutexGuard aSolarMutexGuard; + + // Check if some modes have changed so we have to update our menu images + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + bool bShowMenuImages = rSettings.GetUseImagesInMenus(); + + if (bShowMenuImages != m_bShowMenuImages) + { + m_bShowMenuImages = bShowMenuImages; + updateImagesPopupMenu(m_xPopupMenu); + } +} + +// XPopupMenuController +void SAL_CALL ControlMenuController::updatePopupMenu() +{ + osl::MutexGuard aLock( m_aMutex ); + + throwIfDisposed(); + + if ( !(m_xFrame.is() && m_xPopupMenu.is()) ) + return; + + css::util::URL aTargetURL; + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + fillPopupMenu( m_xPopupMenu ); + m_aURLToDispatchMap.free(); + + for (const char* aCommand : aCommands) + { + aTargetURL.Complete = OUString::createFromAscii( aCommand ); + m_xURLTransformer->parseStrict( aTargetURL ); + + Reference< XDispatch > xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + if ( xDispatch.is() ) + { + xDispatch->addStatusListener( static_cast< XStatusListener* >(this), aTargetURL ); + xDispatch->removeStatusListener( static_cast< XStatusListener* >(this), aTargetURL ); + m_aURLToDispatchMap.emplace( aTargetURL.Complete, xDispatch ); + } + } +} + +// XInitialization +void SAL_CALL ControlMenuController::initialize( const Sequence< Any >& aArguments ) +{ + osl::MutexGuard aLock( m_aMutex ); + svt::PopupMenuControllerBase::initialize(aArguments); + m_aBaseURL.clear(); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ControlMenuController_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new ControlMenuController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/dropdownboxtoolbarcontroller.cxx b/framework/source/uielement/dropdownboxtoolbarcontroller.cxx new file mode 100644 index 000000000..b3478a7b1 --- /dev/null +++ b/framework/source/uielement/dropdownboxtoolbarcontroller.cxx @@ -0,0 +1,282 @@ +/* -*- 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 <uielement/dropdownboxtoolbarcontroller.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <vcl/InterimItemWindow.hxx> +#include <svtools/toolboxcontroller.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> + +using namespace ::com::sun::star; +using namespace css::awt; +using namespace css::uno; +using namespace css::beans; +using namespace css::lang; +using namespace css::frame; +using namespace css::util; + +namespace framework +{ + +// Wrapper class to notify controller about events from ListBox. +// Unfortunaltly the events are notified through virtual methods instead +// of Listeners. + +class ListBoxControl final : public InterimItemWindow +{ +public: + ListBoxControl(vcl::Window* pParent, DropdownToolbarController* pListBoxListener); + virtual ~ListBoxControl() override; + virtual void dispose() override; + + void set_active(int nPos) { m_xWidget->set_active(nPos); } + void append_text(const OUString& rStr) { m_xWidget->append_text(rStr); } + void insert_text(int nPos, const OUString& rStr) { m_xWidget->insert_text(nPos, rStr); } + int get_count() const { return m_xWidget->get_count(); } + int find_text(const OUString& rStr) const { return m_xWidget->find_text(rStr); } + OUString get_active_text() const { return m_xWidget->get_active_text(); } + void clear() { return m_xWidget->clear(); } + void remove(int nPos) { m_xWidget->remove(nPos); } + + DECL_LINK(FocusInHdl, weld::Widget&, void); + DECL_LINK(FocusOutHdl, weld::Widget&, void); + DECL_LINK(ModifyHdl, weld::ComboBox&, void); + DECL_LINK(KeyInputHdl, const ::KeyEvent&, bool); + +private: + std::unique_ptr<weld::ComboBox> m_xWidget; + DropdownToolbarController* m_pListBoxListener; +}; + +ListBoxControl::ListBoxControl(vcl::Window* pParent, DropdownToolbarController* pListBoxListener) + : InterimItemWindow(pParent, "svt/ui/listcontrol.ui", "ListControl") + , m_xWidget(m_xBuilder->weld_combo_box("listbox")) + , m_pListBoxListener( pListBoxListener ) +{ + InitControlBase(m_xWidget.get()); + + m_xWidget->connect_focus_in(LINK(this, ListBoxControl, FocusInHdl)); + m_xWidget->connect_focus_out(LINK(this, ListBoxControl, FocusOutHdl)); + m_xWidget->connect_changed(LINK(this, ListBoxControl, ModifyHdl)); + m_xWidget->connect_key_press(LINK(this, ListBoxControl, KeyInputHdl)); + + m_xWidget->set_size_request(42, -1); // so a later narrow size request can stick + SetSizePixel(get_preferred_size()); +} + +IMPL_LINK(ListBoxControl, KeyInputHdl, const ::KeyEvent&, rKEvt, bool) +{ + return ChildKeyInput(rKEvt); +} + +ListBoxControl::~ListBoxControl() +{ + disposeOnce(); +} + +void ListBoxControl::dispose() +{ + m_pListBoxListener = nullptr; + m_xWidget.reset(); + InterimItemWindow::dispose(); +} + +IMPL_LINK_NOARG(ListBoxControl, ModifyHdl, weld::ComboBox&, void) +{ + if (m_pListBoxListener) + m_pListBoxListener->Select(); +} + +IMPL_LINK_NOARG(ListBoxControl, FocusInHdl, weld::Widget&, void) +{ + if (m_pListBoxListener) + m_pListBoxListener->GetFocus(); +} + +IMPL_LINK_NOARG(ListBoxControl, FocusOutHdl, weld::Widget&, void) +{ + if (m_pListBoxListener) + m_pListBoxListener->LoseFocus(); +} + +DropdownToolbarController::DropdownToolbarController( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + ToolBox* pToolbar, + ToolBoxItemId nID, + sal_Int32 nWidth, + const OUString& aCommand ) : + ComplexToolbarController( rxContext, rFrame, pToolbar, nID, aCommand ) + , m_pListBoxControl( nullptr ) +{ + m_pListBoxControl = VclPtr<ListBoxControl>::Create(m_xToolbar, this); + if ( nWidth == 0 ) + nWidth = 100; + + // ListBoxControl ctor has set a suitable height already + auto nHeight = m_pListBoxControl->GetSizePixel().Height(); + + m_pListBoxControl->SetSizePixel( ::Size( nWidth, nHeight )); + m_xToolbar->SetItemWindow( m_nID, m_pListBoxControl ); +} + +DropdownToolbarController::~DropdownToolbarController() +{ +} + +void SAL_CALL DropdownToolbarController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + + m_xToolbar->SetItemWindow( m_nID, nullptr ); + m_pListBoxControl.disposeAndClear(); + + ComplexToolbarController::dispose(); +} + +Sequence<PropertyValue> DropdownToolbarController::getExecuteArgs(sal_Int16 KeyModifier) const +{ + OUString aSelectedText = m_pListBoxControl->get_active_text(); + + // Add key modifier to argument list + Sequence<PropertyValue> aArgs{ comphelper::makePropertyValue("KeyModifier", KeyModifier), + comphelper::makePropertyValue("Text", aSelectedText) }; + return aArgs; +} + +void DropdownToolbarController::Select() +{ + if (m_pListBoxControl->get_count() > 0) + execute(0); +} + +void DropdownToolbarController::GetFocus() +{ + notifyFocusGet(); +} + +void DropdownToolbarController::LoseFocus() +{ + notifyFocusLost(); +} + +void DropdownToolbarController::executeControlCommand( const css::frame::ControlCommand& rControlCommand ) +{ + if ( rControlCommand.Command == "SetList" ) + { + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "List" ) + { + Sequence< OUString > aList; + m_pListBoxControl->clear(); + + rArg.Value >>= aList; + for (OUString const & rName : std::as_const(aList)) + m_pListBoxControl->append_text(rName); + + m_pListBoxControl->set_active(0); + + // send notification + uno::Sequence< beans::NamedValue > aInfo { { "List", css::uno::Any(aList) } }; + addNotifyInfo( "ListChanged", + getDispatchFromCommand( m_aCommandURL ), + aInfo ); + + break; + } + } + } + else if ( rControlCommand.Command == "AddEntry" ) + { + OUString aText; + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "Text" ) + { + if ( rArg.Value >>= aText ) + m_pListBoxControl->append_text(aText); + break; + } + } + } + else if ( rControlCommand.Command == "InsertEntry" ) + { + sal_Int32 nPos(-1); + OUString aText; + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "Pos" ) + { + sal_Int32 nTmpPos = 0; + if ( rArg.Value >>= nTmpPos ) + { + if (( nTmpPos >= 0 ) && + ( nTmpPos < m_pListBoxControl->get_count() )) + nPos = nTmpPos; + } + } + else if ( rArg.Name == "Text" ) + rArg.Value >>= aText; + } + + m_pListBoxControl->insert_text(nPos, aText); + } + else if ( rControlCommand.Command == "RemoveEntryPos" ) + { + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "Pos" ) + { + sal_Int32 nPos( -1 ); + if ( rArg.Value >>= nPos ) + { + if ( 0 <= nPos && nPos < m_pListBoxControl->get_count() ) + m_pListBoxControl->remove(nPos); + } + break; + } + } + } + else if ( rControlCommand.Command == "RemoveEntryText" ) + { + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "Text" ) + { + OUString aText; + if ( rArg.Value >>= aText ) + { + auto nPos = m_pListBoxControl->find_text(aText); + if (nPos != -1) + m_pListBoxControl->remove(nPos); + } + break; + } + } + } +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/edittoolbarcontroller.cxx b/framework/source/uielement/edittoolbarcontroller.cxx new file mode 100644 index 000000000..1f974e77c --- /dev/null +++ b/framework/source/uielement/edittoolbarcontroller.cxx @@ -0,0 +1,217 @@ +/* -*- 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 <uielement/edittoolbarcontroller.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <vcl/InterimItemWindow.hxx> +#include <svtools/toolboxcontroller.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/event.hxx> + +using namespace ::com::sun::star; +using namespace css::uno; +using namespace css::beans; +using namespace css::lang; +using namespace css::frame; +using namespace css::util; + +namespace framework +{ + +// Wrapper class to notify controller about events from edit. +// Unfortunaltly the events are notified through virtual methods instead +// of Listeners. + +class EditControl final : public InterimItemWindow +{ +public: + EditControl(vcl::Window* pParent, EditToolbarController* pEditToolbarController); + virtual ~EditControl() override; + virtual void dispose() override; + + OUString get_text() const { return m_xWidget->get_text(); } + void set_text(const OUString& rText) { m_xWidget->set_text(rText); } + +private: + std::unique_ptr<weld::Entry> m_xWidget; + EditToolbarController* m_pEditToolbarController; + + DECL_LINK(FocusInHdl, weld::Widget&, void); + DECL_LINK(FocusOutHdl, weld::Widget&, void); + DECL_LINK(ModifyHdl, weld::Entry&, void); + DECL_LINK(ActivateHdl, weld::Entry&, bool); + DECL_LINK(KeyInputHdl, const ::KeyEvent&, bool); +}; + +EditControl::EditControl(vcl::Window* pParent, EditToolbarController* pEditToolbarController) + : InterimItemWindow(pParent, "svt/ui/editcontrol.ui", "EditControl") + , m_xWidget(m_xBuilder->weld_entry("entry")) + , m_pEditToolbarController(pEditToolbarController) +{ + OString sEmpty; + m_xWidget->set_help_id(sEmpty); + m_xContainer->set_help_id(sEmpty); + + InitControlBase(m_xWidget.get()); + + m_xWidget->connect_focus_in(LINK(this, EditControl, FocusInHdl)); + m_xWidget->connect_focus_out(LINK(this, EditControl, FocusOutHdl)); + m_xWidget->connect_changed(LINK(this, EditControl, ModifyHdl)); + m_xWidget->connect_activate(LINK(this, EditControl, ActivateHdl)); + m_xWidget->connect_key_press(LINK(this, EditControl, KeyInputHdl)); + + SetSizePixel(get_preferred_size()); +} + +IMPL_LINK(EditControl, KeyInputHdl, const ::KeyEvent&, rKEvt, bool) +{ + return ChildKeyInput(rKEvt); +} + +EditControl::~EditControl() +{ + disposeOnce(); +} + +void EditControl::dispose() +{ + m_pEditToolbarController = nullptr; + m_xWidget.reset(); + InterimItemWindow::dispose(); +} + +IMPL_LINK_NOARG(EditControl, ModifyHdl, weld::Entry&, void) +{ + if (m_pEditToolbarController) + m_pEditToolbarController->Modify(); +} + +IMPL_LINK_NOARG(EditControl, FocusInHdl, weld::Widget&, void) +{ + if (m_pEditToolbarController) + m_pEditToolbarController->GetFocus(); +} + +IMPL_LINK_NOARG(EditControl, FocusOutHdl, weld::Widget&, void) +{ + if ( m_pEditToolbarController ) + m_pEditToolbarController->LoseFocus(); +} + +IMPL_LINK_NOARG(EditControl, ActivateHdl, weld::Entry&, bool) +{ + if (m_pEditToolbarController) + m_pEditToolbarController->Activate(); + return true; +} + +EditToolbarController::EditToolbarController( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + ToolBox* pToolbar, + ToolBoxItemId nID, + sal_Int32 nWidth, + const OUString& aCommand ) : + ComplexToolbarController( rxContext, rFrame, pToolbar, nID, aCommand ) + , m_pEditControl( nullptr ) +{ + m_pEditControl = VclPtr<EditControl>::Create(m_xToolbar, this); + if ( nWidth == 0 ) + nWidth = 100; + + // EditControl ctor has set a suitable height already + auto nHeight = m_pEditControl->GetSizePixel().Height(); + + m_pEditControl->SetSizePixel( ::Size( nWidth, nHeight )); + m_xToolbar->SetItemWindow( m_nID, m_pEditControl ); +} + +EditToolbarController::~EditToolbarController() +{ +} + +void SAL_CALL EditToolbarController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + + m_xToolbar->SetItemWindow( m_nID, nullptr ); + m_pEditControl.disposeAndClear(); + + ComplexToolbarController::dispose(); +} + +Sequence<PropertyValue> EditToolbarController::getExecuteArgs(sal_Int16 KeyModifier) const +{ + OUString aSelectedText = m_pEditControl->get_text(); + + // Add key modifier to argument list + Sequence<PropertyValue> aArgs{ comphelper::makePropertyValue("KeyModifier", KeyModifier), + comphelper::makePropertyValue("Text", aSelectedText) }; + return aArgs; +} + +void EditToolbarController::Modify() +{ + notifyTextChanged(m_pEditControl->get_text()); +} + +void EditToolbarController::GetFocus() +{ + notifyFocusGet(); +} + +void EditToolbarController::LoseFocus() +{ + notifyFocusLost(); +} + +void EditToolbarController::Activate() +{ + // Call execute only with non-empty text + if (!m_pEditControl->get_text().isEmpty()) + execute(0); +} + +void EditToolbarController::executeControlCommand( const css::frame::ControlCommand& rControlCommand ) +{ + if ( !rControlCommand.Command.startsWith( "SetText" )) + return; + + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name.startsWith( "Text" )) + { + OUString aText; + rArg.Value >>= aText; + m_pEditControl->set_text(aText); + + // send notification + notifyTextChanged( aText ); + break; + } + } +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/fontmenucontroller.cxx b/framework/source/uielement/fontmenucontroller.cxx new file mode 100644 index 000000000..c0dd19e2e --- /dev/null +++ b/framework/source/uielement/fontmenucontroller.cxx @@ -0,0 +1,218 @@ +/* -*- 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 <uielement/fontmenucontroller.hxx> + +#include <services.h> + +#include <com/sun/star/awt/MenuItemStyle.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> + +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/i18nhelp.hxx> +#include <tools/urlobj.hxx> +#include <vcl/mnemonic.hxx> +#include <osl/mutex.hxx> +#include <cppuhelper/supportsservice.hxx> + +// Defines + +using namespace css::uno; +using namespace css::lang; +using namespace css::frame; +using namespace css::beans; +using namespace css::util; + +static bool lcl_I18nCompareString(const OUString& rStr1, const OUString& rStr2) +{ + const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper(); + return rI18nHelper.CompareString( rStr1, rStr2 ) < 0; +} + +namespace framework +{ + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL FontMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.FontMenuController"; +} + +sal_Bool SAL_CALL FontMenuController::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL FontMenuController::getSupportedServiceNames() +{ + return { SERVICENAME_POPUPMENUCONTROLLER }; +} + +FontMenuController::FontMenuController( const css::uno::Reference< css::uno::XComponentContext >& xContext ) : + svt::PopupMenuControllerBase( xContext ) +{ +} + +FontMenuController::~FontMenuController() +{ +} + +// private function +void FontMenuController::fillPopupMenu( const Sequence< OUString >& rFontNameSeq, Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + SolarMutexGuard aSolarMutexGuard; + + resetPopupMenu( rPopupMenu ); + + std::vector<OUString> aVector; + aVector.reserve(rFontNameSeq.getLength()); + for ( OUString const & s : rFontNameSeq ) + { + aVector.push_back(MnemonicGenerator::EraseAllMnemonicChars(s)); + } + sort(aVector.begin(), aVector.end(), lcl_I18nCompareString ); + + static const OUStringLiteral aFontNameCommandPrefix( u".uno:CharFontName?CharFontName.FamilyName:string=" ); + const sal_Int16 nCount = static_cast<sal_Int16>(aVector.size()); + for ( sal_Int16 i = 0; i < nCount; i++ ) + { + const OUString& rName = aVector[i]; + m_xPopupMenu->insertItem( i+1, rName, css::awt::MenuItemStyle::RADIOCHECK | css::awt::MenuItemStyle::AUTOCHECK, i ); + if ( rName == m_aFontFamilyName ) + m_xPopupMenu->checkItem( i+1, true ); + OUString aFontNameCommand = aFontNameCommandPrefix + INetURLObject::encode( rName, INetURLObject::PART_HTTP_QUERY, INetURLObject::EncodeMechanism::All ); + m_xPopupMenu->setCommand(i + 1, aFontNameCommand); // Store font name into item command. + } +} + +// XEventListener +void SAL_CALL FontMenuController::disposing( const EventObject& ) +{ + Reference< css::awt::XMenuListener > xHolder(this); + + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + m_xFontListDispatch.clear(); + + if ( m_xPopupMenu.is() ) + m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); + m_xPopupMenu.clear(); +} + +// XStatusListener +void SAL_CALL FontMenuController::statusChanged( const FeatureStateEvent& Event ) +{ + css::awt::FontDescriptor aFontDescriptor; + Sequence< OUString > aFontNameSeq; + + if ( Event.State >>= aFontDescriptor ) + { + osl::MutexGuard aLock( m_aMutex ); + m_aFontFamilyName = aFontDescriptor.Name; + } + else if ( Event.State >>= aFontNameSeq ) + { + osl::MutexGuard aLock( m_aMutex ); + if ( m_xPopupMenu.is() ) + fillPopupMenu( aFontNameSeq, m_xPopupMenu ); + } +} + +// XMenuListener +void SAL_CALL FontMenuController::itemActivated( const css::awt::MenuEvent& ) +{ + osl::MutexGuard aLock( m_aMutex ); + + if ( !m_xPopupMenu.is() ) + return; + + // find new font name and set check mark! + sal_uInt16 nChecked = 0; + sal_uInt16 nItemCount = m_xPopupMenu->getItemCount(); + for( sal_uInt16 i = 0; i < nItemCount; i++ ) + { + sal_uInt16 nItemId = m_xPopupMenu->getItemId( i ); + + if ( m_xPopupMenu->isItemChecked( nItemId ) ) + nChecked = nItemId; + + OUString aText = m_xPopupMenu->getItemText( nItemId ); + + // TODO: must be replaced by implementation of VCL, when available + sal_Int32 nIndex = aText.indexOf( '~' ); + if ( nIndex >= 0 ) + aText = aText.replaceAt( nIndex, 1, u"" ); + // TODO: must be replaced by implementation of VCL, when available + + if ( aText == m_aFontFamilyName ) + { + m_xPopupMenu->checkItem( nItemId, true ); + return; + } + } + + if ( nChecked ) + m_xPopupMenu->checkItem( nChecked, false ); +} + +// XPopupMenuController +void FontMenuController::impl_setPopupMenu() +{ + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + + css::util::URL aTargetURL; + // Register for font list updates to get the current font list from the controller + aTargetURL.Complete = ".uno:FontNameList"; + m_xURLTransformer->parseStrict( aTargetURL ); + m_xFontListDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); +} + +void SAL_CALL FontMenuController::updatePopupMenu() +{ + svt::PopupMenuControllerBase::updatePopupMenu(); + + osl::ClearableMutexGuard aLock( m_aMutex ); + Reference< XDispatch > xDispatch( m_xFontListDispatch ); + css::util::URL aTargetURL; + aTargetURL.Complete = ".uno:FontNameList"; + m_xURLTransformer->parseStrict( aTargetURL ); + aLock.clear(); + + if ( xDispatch.is() ) + { + xDispatch->addStatusListener( static_cast< XStatusListener* >(this), aTargetURL ); + xDispatch->removeStatusListener( static_cast< XStatusListener* >(this), aTargetURL ); + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_FontMenuController_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::FontMenuController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/fontsizemenucontroller.cxx b/framework/source/uielement/fontsizemenucontroller.cxx new file mode 100644 index 000000000..4c5c68688 --- /dev/null +++ b/framework/source/uielement/fontsizemenucontroller.cxx @@ -0,0 +1,285 @@ +/* -*- 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 <uielement/fontsizemenucontroller.hxx> + +#include <services.h> + +#include <com/sun/star/awt/MenuItemStyle.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/view/XPrintable.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> + +#include <vcl/svapp.hxx> +#include <vcl/i18nhelp.hxx> +#include <vcl/print.hxx> +#include <vcl/settings.hxx> +#include <svtools/ctrltool.hxx> +#include <osl/mutex.hxx> +#include <memory> +#include <cppuhelper/supportsservice.hxx> + +// Defines + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::frame; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace com::sun::star::view; + +namespace framework +{ + +OUString SAL_CALL FontSizeMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.FontSizeMenuController"; +} + +sal_Bool SAL_CALL FontSizeMenuController::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL FontSizeMenuController::getSupportedServiceNames() +{ + return { SERVICENAME_POPUPMENUCONTROLLER }; +} + +FontSizeMenuController::FontSizeMenuController( const css::uno::Reference< css::uno::XComponentContext >& xContext ) : + svt::PopupMenuControllerBase( xContext ) +{ +} + +FontSizeMenuController::~FontSizeMenuController() +{ +} + +// private function +OUString FontSizeMenuController::retrievePrinterName( css::uno::Reference< css::frame::XFrame > const & rFrame ) +{ + OUString aPrinterName; + + if ( rFrame.is() ) + { + Reference< XController > xController = m_xFrame->getController(); + if ( xController.is() ) + { + Reference< XPrintable > xPrintable( xController->getModel(), UNO_QUERY ); + if ( xPrintable.is() ) + { + const Sequence< PropertyValue > aPrinterSeq = xPrintable->getPrinter(); + for ( PropertyValue const & prop : aPrinterSeq ) + { + if ( prop.Name == "Name" ) + { + prop.Value >>= aPrinterName; + break; + } + } + } + } + } + + return aPrinterName; +} + +// private function +void FontSizeMenuController::setCurHeight( tools::Long nHeight, Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + // check menu item + sal_uInt16 nChecked = 0; + sal_uInt16 nItemCount = rPopupMenu->getItemCount(); + for( sal_uInt16 i = 0; i < nItemCount; i++ ) + { + sal_uInt16 nItemId = rPopupMenu->getItemId( i ); + + if ( m_aHeightArray[i] == nHeight ) + { + rPopupMenu->checkItem( nItemId, true ); + return; + } + + if ( rPopupMenu->isItemChecked( nItemId ) ) + nChecked = nItemId; + } + + if ( nChecked ) + rPopupMenu->checkItem( nChecked, false ); +} + +// private function +void FontSizeMenuController::fillPopupMenu( Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + resetPopupMenu( rPopupMenu ); + + std::unique_ptr<FontList> pFontList; + ScopedVclPtr<Printer> pInfoPrinter; + OUString aPrinterName; + + SolarMutexGuard aSolarMutexGuard; + + // try to retrieve printer name of document + aPrinterName = retrievePrinterName( m_xFrame ); + if ( !aPrinterName.isEmpty() ) + { + pInfoPrinter.disposeAndReset(VclPtr<Printer>::Create( aPrinterName )); + if ( pInfoPrinter && pInfoPrinter->GetFontFaceCollectionCount() > 0 ) + pFontList.reset(new FontList( pInfoPrinter.get() )); + } + + if ( !pFontList ) + pFontList.reset(new FontList( Application::GetDefaultDevice() )); + + // setup font size array + m_aHeightArray.clear(); + + sal_uInt16 nPos = 0; // Id is nPos+1 + static const OUStringLiteral aFontHeightCommand( u".uno:FontHeight?FontHeight.Height:float=" ); + + // first insert font size names (for simplified/traditional chinese) + FontSizeNames aFontSizeNames( Application::GetSettings().GetUILanguageTag().getLanguageType() ); + OUString aCommand; + + if (!aFontSizeNames.IsEmpty()) + { + // for scalable fonts all font size names + sal_Int32 nCount = aFontSizeNames.Count(); + for( sal_Int32 i = 0; i < nCount; i++ ) + { + OUString aSizeName = aFontSizeNames.GetIndexName( i ); + sal_Int32 nSize = aFontSizeNames.GetIndexSize( i ); + m_aHeightArray.push_back(nSize); + rPopupMenu->insertItem(nPos + 1, aSizeName, css::awt::MenuItemStyle::RADIOCHECK | css::awt::MenuItemStyle::AUTOCHECK, nPos); + + // Create dispatchable .uno command and set it + float fPoint = float(nSize) / 10; + aCommand = aFontHeightCommand + OUString::number( fPoint ); + rPopupMenu->setCommand(nPos + 1, aCommand); + + ++nPos; + } + } + + // then insert numerical font size values + const vcl::I18nHelper& rI18nHelper = Application::GetSettings().GetUILocaleI18nHelper(); + const int* pAry = FontList::GetStdSizeAry(); + const int* pTempAry = pAry; + while ( *pTempAry ) + { + m_aHeightArray.push_back(*pTempAry); + rPopupMenu->insertItem(nPos + 1, rI18nHelper.GetNum(*pTempAry, 1, true, false), + css::awt::MenuItemStyle::RADIOCHECK | css::awt::MenuItemStyle::AUTOCHECK, nPos); + + // Create dispatchable .uno command and set it + float fPoint = float(*pTempAry) / 10; + aCommand = aFontHeightCommand + OUString::number( fPoint ); + rPopupMenu->setCommand(nPos + 1, aCommand); + + ++nPos; + pTempAry++; + } + + setCurHeight( tools::Long( m_aFontHeight.Height * 10), rPopupMenu ); +} + +// XEventListener +void SAL_CALL FontSizeMenuController::disposing( const EventObject& ) +{ + Reference< css::awt::XMenuListener > xHolder(this); + + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + m_xCurrentFontDispatch.clear(); + if ( m_xPopupMenu.is() ) + m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); + m_xPopupMenu.clear(); +} + +// XStatusListener +void SAL_CALL FontSizeMenuController::statusChanged( const FeatureStateEvent& Event ) +{ + css::awt::FontDescriptor aFontDescriptor; + css::frame::status::FontHeight aFontHeight; + + if ( Event.State >>= aFontDescriptor ) + { + osl::MutexGuard aLock( m_aMutex ); + + if ( m_xPopupMenu.is() ) + fillPopupMenu( m_xPopupMenu ); + } + else if ( Event.State >>= aFontHeight ) + { + osl::MutexGuard aLock( m_aMutex ); + m_aFontHeight = aFontHeight; + + if ( m_xPopupMenu.is() ) + { + SolarMutexGuard aSolarMutexGuard; + setCurHeight( tools::Long( m_aFontHeight.Height * 10), m_xPopupMenu ); + } + } +} + +// XPopupMenuController +void FontSizeMenuController::impl_setPopupMenu() +{ + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + css::util::URL aTargetURL; + // Register for font name updates which gives us info about the current font! + aTargetURL.Complete = ".uno:CharFontName"; + m_xURLTransformer->parseStrict( aTargetURL ); + m_xCurrentFontDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); +} + +void SAL_CALL FontSizeMenuController::updatePopupMenu() +{ + osl::ClearableMutexGuard aLock( m_aMutex ); + + throwIfDisposed(); + + Reference< XDispatch > xDispatch( m_xCurrentFontDispatch ); + css::util::URL aTargetURL; + aTargetURL.Complete = ".uno:CharFontName"; + m_xURLTransformer->parseStrict( aTargetURL ); + aLock.clear(); + + if ( xDispatch.is() ) + { + xDispatch->addStatusListener( static_cast< XStatusListener* >(this), aTargetURL ); + xDispatch->removeStatusListener( static_cast< XStatusListener* >(this), aTargetURL ); + } + + svt::PopupMenuControllerBase::updatePopupMenu(); +} +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_FontSizeMenuController_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::FontSizeMenuController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/footermenucontroller.cxx b/framework/source/uielement/footermenucontroller.cxx new file mode 100644 index 000000000..9a941a825 --- /dev/null +++ b/framework/source/uielement/footermenucontroller.cxx @@ -0,0 +1,73 @@ +/* -*- 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 <uielement/footermenucontroller.hxx> + +#include <services.h> + +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <cppuhelper/supportsservice.hxx> + +// Defines + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace com::sun::star::style; +using namespace com::sun::star::container; + +namespace framework +{ + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL FooterMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.FooterMenuController"; +} + +sal_Bool SAL_CALL FooterMenuController::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL FooterMenuController::getSupportedServiceNames() +{ + return { SERVICENAME_POPUPMENUCONTROLLER }; +} + +FooterMenuController::FooterMenuController( const css::uno::Reference< css::uno::XComponentContext >& xContext ) : + HeaderMenuController( xContext,true ) +{ +} + +FooterMenuController::~FooterMenuController() +{ +} +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_FooterMenuController_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::FooterMenuController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/genericstatusbarcontroller.cxx b/framework/source/uielement/genericstatusbarcontroller.cxx new file mode 100644 index 000000000..4a5aa4605 --- /dev/null +++ b/framework/source/uielement/genericstatusbarcontroller.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 <uielement/genericstatusbarcontroller.hxx> +#include <uielement/statusbarmerger.hxx> + +#include <osl/diagnose.h> +#include <vcl/svapp.hxx> + +#include <com/sun/star/ui/ItemStyle.hpp> +#include <com/sun/star/ui/XStatusbarItem.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/awt/ImageDrawMode.hpp> +#include <com/sun/star/awt/XGraphics2.hpp> +#include <com/sun/star/graphic/GraphicType.hpp> + +using namespace ::cppu; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; + +namespace framework +{ + +GenericStatusbarController::GenericStatusbarController( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rxFrame, + const Reference< ui::XStatusbarItem >& rxItem, + AddonStatusbarItemData *pItemData ) + : svt::StatusbarController( rxContext, rxFrame, OUString(), 0 ) + , m_bEnabled( false ) + , m_bOwnerDraw( false ) + , m_pItemData( pItemData ) +{ + m_xStatusbarItem = rxItem; + if ( m_xStatusbarItem.is() ) + { + assert(m_aCommandURL.pData); + m_aCommandURL = m_xStatusbarItem->getCommand(); + m_nID = m_xStatusbarItem->getItemId(); + m_bOwnerDraw = ( m_xStatusbarItem->getStyle() & ui::ItemStyle::OWNER_DRAW ) == ui::ItemStyle::OWNER_DRAW; + if ( !m_bOwnerDraw && m_pItemData && m_pItemData->aLabel.getLength() ) + m_xStatusbarItem->setText( m_pItemData->aLabel ); + } +} + +GenericStatusbarController::~GenericStatusbarController() +{ +} + +void SAL_CALL GenericStatusbarController::dispose() +{ + svt::StatusbarController::dispose(); + + SolarMutexGuard aGuard; + m_pItemData = nullptr; + m_xGraphic.clear(); + m_xStatusbarItem.clear(); + +} + +void SAL_CALL GenericStatusbarController::statusChanged( + const FeatureStateEvent& rEvent) +{ + SolarMutexGuard aGuard; + + if ( m_bDisposed || !m_xStatusbarItem.is() ) + return; + + m_bEnabled = rEvent.IsEnabled; + + OUString aStrValue; + Reference< graphic::XGraphic > aGraphic; + + if ( rEvent.State >>= aStrValue ) + { + if ( !m_bOwnerDraw ) + m_xStatusbarItem->setText( aStrValue ); + else + { + if ( aStrValue.getLength() ) + { + m_xStatusbarItem->setQuickHelpText( aStrValue ); + } + } + } + else if ( ( rEvent.State >>= aGraphic ) && m_bOwnerDraw ) + { + m_xGraphic = aGraphic; + } + + // when the status is updated, and the controller is responsible for + // painting the statusbar item content, we must trigger a repaint + if ( m_bOwnerDraw && m_xStatusbarItem->getVisible() ) + { + m_xStatusbarItem->repaint(); + } +} + +void SAL_CALL GenericStatusbarController::paint( + const Reference< awt::XGraphics >& xGraphics, + const awt::Rectangle& rOutputRectangle, + ::sal_Int32 /*nStyle*/ ) +{ + SolarMutexGuard aGuard; + + const Reference< awt::XGraphics2 > xGraphics2(xGraphics, UNO_QUERY); + + if ( !m_xStatusbarItem.is() || !xGraphics2.is() ) + return; + + Reference< beans::XPropertySet > xGraphicProps( m_xGraphic, UNO_QUERY ); + + if ( xGraphicProps.is() && m_xGraphic->getType() != graphic::GraphicType::EMPTY ) + { + awt::Size aGraphicSize; + xGraphicProps->getPropertyValue( "SizePixel" ) >>= aGraphicSize; + OSL_ENSURE( aGraphicSize.Height > 0 && aGraphicSize.Width > 0, "Empty status bar graphic!" ); + + sal_Int32 nOffset = m_xStatusbarItem->getOffset( ); + awt::Point aPos; + aPos.X = ( rOutputRectangle.Width + nOffset ) / 2 - aGraphicSize.Width / 2; + aPos.Y = rOutputRectangle.Height / 2 - aGraphicSize.Height / 2; + + xGraphics2->drawImage( rOutputRectangle.X + aPos.X, + rOutputRectangle.Y + aPos.Y, + aGraphicSize.Width, + aGraphicSize.Height, + m_bEnabled ? awt::ImageDrawMode::NONE : awt::ImageDrawMode::DISABLE, + m_xGraphic ); + } + else + { + xGraphics2->clear( rOutputRectangle ); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/generictoolbarcontroller.cxx b/framework/source/uielement/generictoolbarcontroller.cxx new file mode 100644 index 000000000..25f2e0157 --- /dev/null +++ b/framework/source/uielement/generictoolbarcontroller.cxx @@ -0,0 +1,323 @@ +/* -*- 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 <framework/generictoolbarcontroller.hxx> + +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/frame/status/ItemStatus.hpp> +#include <com/sun/star/frame/status/Visibility.hpp> +#include <com/sun/star/frame/ControlCommand.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/weld.hxx> +#include <tools/urlobj.hxx> +#include <strings.hrc> +#include <classes/fwkresid.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::frame::status; + +namespace framework +{ + +static bool isEnumCommand( std::u16string_view rCommand ) +{ + INetURLObject aURL( rCommand ); + + return ( aURL.GetProtocol() == INetProtocol::Uno ) && + ( aURL.GetURLPath().indexOf( '.' ) != -1); +} + +static OUString getEnumCommand( std::u16string_view rCommand ) +{ + INetURLObject aURL( rCommand ); + + OUString aEnumCommand; + OUString aURLPath = aURL.GetURLPath(); + sal_Int32 nIndex = aURLPath.indexOf( '.' ); + if (( nIndex > 0 ) && ( nIndex < aURLPath.getLength() )) + aEnumCommand = aURLPath.copy( nIndex+1 ); + + return aEnumCommand; +} + +static OUString getMasterCommand( const OUString& rCommand ) +{ + OUString aMasterCommand( rCommand ); + INetURLObject aURL( rCommand ); + if ( aURL.GetProtocol() == INetProtocol::Uno ) + { + sal_Int32 nIndex = aURL.GetURLPath().indexOf( '.' ); + if ( nIndex ) + { + aURL.SetURLPath( aURL.GetURLPath().subView( 0, nIndex ) ); + aMasterCommand = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + } + return aMasterCommand; +} + +GenericToolbarController::GenericToolbarController( const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + ToolBox* pToolbar, + ToolBoxItemId nID, + const OUString& aCommand ) : + svt::ToolboxController( rxContext, rFrame, aCommand ) + , m_xToolbar( pToolbar ) + , m_nID( nID ) + , m_bEnumCommand( isEnumCommand( aCommand )) + , m_bMadeInvisible( false ) + , m_aEnumCommand( getEnumCommand( aCommand )) +{ + if ( m_bEnumCommand ) + addStatusListener( getMasterCommand( aCommand ) ); + + addStatusListener( aCommand ); + + // Initialization is done through ctor + m_bInitialized = true; +} + +GenericToolbarController::GenericToolbarController( const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + weld::Toolbar& rToolbar, + const OUString& aCommand ) : + GenericToolbarController( rxContext, rFrame, nullptr, ToolBoxItemId(0), aCommand ) +{ + m_pToolbar = &rToolbar; +} + +GenericToolbarController::~GenericToolbarController() +{ +} + +void SAL_CALL GenericToolbarController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + + svt::ToolboxController::dispose(); + + m_pToolbar = nullptr; + m_xToolbar.clear(); + m_nID = ToolBoxItemId(0); +} + +void SAL_CALL GenericToolbarController::execute( sal_Int16 KeyModifier ) +{ + Reference< XDispatch > xDispatch; + OUString aCommandURL; + + { + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_bInitialized && + m_xFrame.is() && + !m_aCommandURL.isEmpty() ) + { + aCommandURL = m_aCommandURL; + URLToDispatchMap::iterator pIter = m_aListenerMap.find( m_aCommandURL ); + if ( pIter != m_aListenerMap.end() ) + xDispatch = pIter->second; + } + } + + if ( !xDispatch.is() ) + return; + + css::util::URL aTargetURL; + + // Add key modifier to argument list + Sequence<PropertyValue> aArgs{ comphelper::makePropertyValue("KeyModifier", KeyModifier) }; + + // handle also command aliases + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(m_aCommandURL, + vcl::CommandInfoProvider::GetModuleIdentifier(m_xFrame)); + OUString sRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties); + + aTargetURL.Complete = sRealCommand.isEmpty() ? aCommandURL : sRealCommand; + if ( m_xUrlTransformer.is() ) + m_xUrlTransformer->parseStrict( aTargetURL ); + + // Execute dispatch asynchronously + ExecuteInfo* pExecuteInfo = new ExecuteInfo; + pExecuteInfo->xDispatch = xDispatch; + pExecuteInfo->aTargetURL = aTargetURL; + pExecuteInfo->aArgs = aArgs; + Application::PostUserEvent( LINK(nullptr, GenericToolbarController , ExecuteHdl_Impl), pExecuteInfo ); +} + +void GenericToolbarController::statusChanged( const FeatureStateEvent& Event ) +{ + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + return; + + if ( m_pToolbar ) + { + OString sId = m_aCommandURL.toUtf8(); + + m_pToolbar->set_item_sensitive(sId, Event.IsEnabled); + + bool bValue; + OUString aStrValue; + + if ( Event.State >>= bValue ) + { + // Boolean, treat it as checked/unchecked + m_pToolbar->set_item_active(sId, bValue); + } + else if ( Event.State >>= aStrValue ) + { + m_pToolbar->set_item_label(sId, aStrValue); + } + else + m_pToolbar->set_item_active(sId, false); + + return; + } + + if ( !m_xToolbar ) + return; + + m_xToolbar->EnableItem( m_nID, Event.IsEnabled ); + + ToolBoxItemBits nItemBits = m_xToolbar->GetItemBits( m_nID ); + nItemBits &= ~ToolBoxItemBits::CHECKABLE; + TriState eTri = TRISTATE_FALSE; + + bool bValue; + OUString aStrValue; + ItemStatus aItemState; + Visibility aItemVisibility; + ControlCommand aControlCommand; + + if (( Event.State >>= bValue ) && !m_bEnumCommand ) + { + // Boolean, treat it as checked/unchecked + if ( m_bMadeInvisible ) + m_xToolbar->ShowItem( m_nID ); + m_xToolbar->CheckItem( m_nID, bValue ); + if ( bValue ) + eTri = TRISTATE_TRUE; + nItemBits |= ToolBoxItemBits::CHECKABLE; + } + else if ( Event.State >>= aStrValue ) + { + if ( m_bEnumCommand ) + { + bValue = aStrValue == m_aEnumCommand; + + m_xToolbar->CheckItem( m_nID, bValue ); + if ( bValue ) + eTri = TRISTATE_TRUE; + nItemBits |= ToolBoxItemBits::CHECKABLE; + } + else + { + // Replacement for place holders + if ( aStrValue.startsWith("($1)") ) + { + aStrValue = FwkResId(STR_UPDATEDOC) + " " + aStrValue.subView( 4 ); + } + else if ( aStrValue.startsWith("($2)") ) + { + aStrValue = FwkResId(STR_CLOSEDOC_ANDRETURN) + aStrValue.subView( 4 ); + } + else if ( aStrValue.startsWith("($3)") ) + { + aStrValue = FwkResId(STR_SAVECOPYDOC) + aStrValue.subView( 4 ); + } + m_xToolbar->SetItemText( m_nID, aStrValue ); + // tdf#124267 strip mnemonic from tooltip + m_xToolbar->SetQuickHelpText(m_nID, aStrValue.replaceFirst("~", "")); + } + + if ( m_bMadeInvisible ) + m_xToolbar->ShowItem( m_nID ); + } + else if (( Event.State >>= aItemState ) && !m_bEnumCommand ) + { + eTri = TRISTATE_INDET; + nItemBits |= ToolBoxItemBits::CHECKABLE; + if ( m_bMadeInvisible ) + m_xToolbar->ShowItem( m_nID ); + } + else if ( Event.State >>= aItemVisibility ) + { + m_xToolbar->ShowItem( m_nID, aItemVisibility.bVisible ); + m_bMadeInvisible = !aItemVisibility.bVisible; + } + else if ( Event.State >>= aControlCommand ) + { + if (aControlCommand.Command == "SetQuickHelpText") + { + for ( NamedValue const & rArg : std::as_const(aControlCommand.Arguments) ) + { + if (rArg.Name == "HelpText") + { + OUString aHelpText; + rArg.Value >>= aHelpText; + m_xToolbar->SetQuickHelpText(m_nID, aHelpText); + break; + } + } + } + if ( m_bMadeInvisible ) + m_xToolbar->ShowItem( m_nID ); + } + else if ( m_bMadeInvisible ) + m_xToolbar->ShowItem( m_nID ); + + m_xToolbar->SetItemState( m_nID, eTri ); + m_xToolbar->SetItemBits( m_nID, nItemBits ); +} + +IMPL_STATIC_LINK( GenericToolbarController, ExecuteHdl_Impl, void*, p, void ) +{ + ExecuteInfo* pExecuteInfo = static_cast<ExecuteInfo*>(p); + SolarMutexReleaser aReleaser; + try + { + // Asynchronous execution as this can lead to our own destruction! + // Framework can recycle our current frame and the layout manager disposes all user interface + // elements if a component gets detached from its frame! + pExecuteInfo->xDispatch->dispatch( pExecuteInfo->aTargetURL, pExecuteInfo->aArgs ); + } + catch ( const Exception& ) + { + } + + delete pExecuteInfo; +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/headermenucontroller.cxx b/framework/source/uielement/headermenucontroller.cxx new file mode 100644 index 000000000..cbcdc33bc --- /dev/null +++ b/framework/source/uielement/headermenucontroller.cxx @@ -0,0 +1,237 @@ +/* -*- 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 <uielement/headermenucontroller.hxx> + +#include <services.h> + +#include <strings.hrc> +#include <classes/fwkresid.hxx> + +#include <com/sun/star/awt/MenuItemStyle.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <vcl/svapp.hxx> +#include <rtl/ustrbuf.hxx> +#include <osl/mutex.hxx> +#include <cppuhelper/supportsservice.hxx> + +// Defines + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::frame; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace com::sun::star::style; +using namespace com::sun::star::container; + +const sal_uInt16 ALL_MENUITEM_ID = 1; + +namespace framework +{ + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL HeaderMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.HeaderMenuController"; +} + +sal_Bool SAL_CALL HeaderMenuController::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL HeaderMenuController::getSupportedServiceNames() +{ + return { SERVICENAME_POPUPMENUCONTROLLER }; +} + +HeaderMenuController::HeaderMenuController( const css::uno::Reference< css::uno::XComponentContext >& xContext, bool _bFooter ) : + svt::PopupMenuControllerBase( xContext ) + ,m_bFooter(_bFooter) +{ +} + +HeaderMenuController::~HeaderMenuController() +{ +} + +// private function +void HeaderMenuController::fillPopupMenu( const Reference< css::frame::XModel >& rModel, Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + SolarMutexGuard aSolarMutexGuard; + + resetPopupMenu( rPopupMenu ); + + Reference< XStyleFamiliesSupplier > xStyleFamiliesSupplier( rModel, UNO_QUERY ); + if (!xStyleFamiliesSupplier.is()) + return; + + Reference< XNameAccess > xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + + OUString aCmd( ".uno:InsertPageHeader" ); + OUString aHeaderFooterIsOnStr( "HeaderIsOn" ); + if ( m_bFooter ) + { + aCmd = ".uno:InsertPageFooter"; + aHeaderFooterIsOnStr = "FooterIsOn"; + } + static const OUStringLiteral aIsPhysicalStr( u"IsPhysical" ); + static const OUStringLiteral aDisplayNameStr( u"DisplayName" ); + + try + { + Reference< XNameContainer > xNameContainer; + if ( xStyleFamilies->getByName("PageStyles") >>= xNameContainer ) + { + Sequence< OUString > aSeqNames = xNameContainer->getElementNames(); + + sal_uInt16 nId = 2; + sal_uInt16 nCount = 0; + bool bAllOneState( true ); + bool bLastCheck( true ); + bool bFirstChecked( false ); + bool bFirstItemInserted( false ); + for ( sal_Int32 n = 0; n < aSeqNames.getLength(); n++ ) + { + OUString aName = aSeqNames[n]; + Reference< XPropertySet > xPropSet( xNameContainer->getByName( aName ), UNO_QUERY ); + if ( xPropSet.is() ) + { + bool bIsPhysical( false ); + if (( xPropSet->getPropertyValue( aIsPhysicalStr ) >>= bIsPhysical ) && bIsPhysical ) + { + OUString aDisplayName; + bool bHeaderIsOn( false ); + xPropSet->getPropertyValue( aDisplayNameStr ) >>= aDisplayName; + xPropSet->getPropertyValue( aHeaderFooterIsOnStr ) >>= bHeaderIsOn; + + OUStringBuffer aStrBuf( aCmd ); + aStrBuf.append( "?PageStyle:string="); + aStrBuf.append( aDisplayName ); + aStrBuf.append( "&On:bool=" ); + if ( !bHeaderIsOn ) + aStrBuf.append( "true" ); + else + aStrBuf.append( "false" ); + OUString aCommand( aStrBuf.makeStringAndClear() ); + rPopupMenu->insertItem(nId, aDisplayName, css::awt::MenuItemStyle::CHECKABLE, nCount); + if ( !bFirstItemInserted ) + { + bFirstItemInserted = true; + bFirstChecked = bHeaderIsOn; + } + + rPopupMenu->setCommand(nId, aCommand); + rPopupMenu->checkItem(nId, bHeaderIsOn); + ++nId; + + // Check if all entries have the same state + if( bAllOneState && n && bHeaderIsOn != bLastCheck ) + bAllOneState = false; + bLastCheck = bHeaderIsOn; + ++nCount; + } + } + } + + if ( bAllOneState && ( nCount > 1 )) + { + // Insert special item for all command + rPopupMenu->insertItem(ALL_MENUITEM_ID, FwkResId(STR_MENU_HEADFOOTALL), 0, 0); + + OUStringBuffer aStrBuf( aCmd ); + aStrBuf.append( "?On:bool=" ); + + // Command depends on check state of first menu item entry + if ( !bFirstChecked ) + aStrBuf.append( "true" ); + else + aStrBuf.append( "false" ); + + rPopupMenu->setCommand(1, aStrBuf.makeStringAndClear()); + rPopupMenu->insertSeparator(1); + } + } + } + catch ( const css::container::NoSuchElementException& ) + { + } +} + +// XEventListener +void SAL_CALL HeaderMenuController::disposing( const EventObject& ) +{ + Reference< css::awt::XMenuListener > xHolder(this); + + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + + if ( m_xPopupMenu.is() ) + m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); + m_xPopupMenu.clear(); +} + +// XStatusListener +void SAL_CALL HeaderMenuController::statusChanged( const FeatureStateEvent& Event ) +{ + Reference< css::frame::XModel > xModel; + + if ( Event.State >>= xModel ) + { + osl::MutexGuard aLock( m_aMutex ); + m_xModel = xModel; + if ( m_xPopupMenu.is() ) + fillPopupMenu( xModel, m_xPopupMenu ); + } +} + +// XMenuListener +void SAL_CALL HeaderMenuController::updatePopupMenu() +{ + osl::ResettableMutexGuard aLock( m_aMutex ); + + throwIfDisposed(); + + Reference< css::frame::XModel > xModel( m_xModel ); + aLock.clear(); + + if ( !xModel.is() ) + svt::PopupMenuControllerBase::updatePopupMenu(); + + aLock.reset(); + if ( m_xPopupMenu.is() && m_xModel.is() ) + fillPopupMenu( m_xModel, m_xPopupMenu ); +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_HeaderMenuController_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::HeaderMenuController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/imagebuttontoolbarcontroller.cxx b/framework/source/uielement/imagebuttontoolbarcontroller.cxx new file mode 100644 index 000000000..1ab3c0cf8 --- /dev/null +++ b/framework/source/uielement/imagebuttontoolbarcontroller.cxx @@ -0,0 +1,148 @@ +/* -*- 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 <uielement/imagebuttontoolbarcontroller.hxx> + +#include <framework/addonsoptions.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <comphelper/getexpandeduri.hxx> +#include <comphelper/processfactory.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/graph.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/toolbox.hxx> +#include <svtools/miscopt.hxx> +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::util; + +const ::Size aImageSizeSmall( 16, 16 ); +const ::Size aImageSizeBig( 26, 26 ); + +namespace framework +{ + +static void SubstituteVariables( OUString& aURL ) +{ + aURL = comphelper::getExpandedUri( + comphelper::getProcessComponentContext(), aURL); +} + +ImageButtonToolbarController::ImageButtonToolbarController( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + ToolBox* pToolbar, + ToolBoxItemId nID, + const OUString& aCommand ) : + ComplexToolbarController( rxContext, rFrame, pToolbar, nID, aCommand ) +{ + bool bBigImages( SvtMiscOptions().AreCurrentSymbolsLarge() ); + + Image aImage(AddonsOptions().GetImageFromURL(aCommand, bBigImages, true)); + + // Height will be controlled by scaling according to button height + m_xToolbar->SetItemImage( m_nID, aImage ); +} + +ImageButtonToolbarController::~ImageButtonToolbarController() +{ +} + +void SAL_CALL ImageButtonToolbarController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + ComplexToolbarController::dispose(); +} + +void ImageButtonToolbarController::executeControlCommand( const css::frame::ControlCommand& rControlCommand ) +{ + SolarMutexGuard aSolarMutexGuard; + // i73486 to be downward compatible use old and "wrong" also! + if( rControlCommand.Command != "SetImag" && + rControlCommand.Command != "SetImage" ) + return; + + for ( const NamedValue& rArg : rControlCommand.Arguments ) + { + if ( rArg.Name == "URL" ) + { + OUString aURL; + rArg.Value >>= aURL; + + SubstituteVariables( aURL ); + + Image aImage; + if ( ReadImageFromURL( SvtMiscOptions().AreCurrentSymbolsLarge(), + aURL, + aImage )) + { + m_xToolbar->SetItemImage( m_nID, aImage ); + + // send notification + uno::Sequence< beans::NamedValue > aInfo { { "URL", css::uno::Any(aURL) } }; + addNotifyInfo( "ImageChanged", + getDispatchFromCommand( m_aCommandURL ), + aInfo ); + break; + } + } + } +} + +bool ImageButtonToolbarController::ReadImageFromURL( bool bBigImage, const OUString& aImageURL, Image& aImage ) +{ + std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( aImageURL, StreamMode::STD_READ )); + if ( !pStream || ( pStream->GetErrorCode() != ERRCODE_NONE )) + return false; + + // Use graphic class to also support more graphic formats (bmp,png,...) + Graphic aGraphic; + + GraphicFilter& rGF = GraphicFilter::GetGraphicFilter(); + rGF.ImportGraphic( aGraphic, u"", *pStream ); + + BitmapEx aBitmapEx = aGraphic.GetBitmapEx(); + + const ::Size aSize = bBigImage ? aImageSizeBig : aImageSizeSmall; // Sizes used for toolbar images + + ::Size aBmpSize = aBitmapEx.GetSizePixel(); + if ( !aBmpSize.IsEmpty() ) + { + ::Size aNoScaleSize( aBmpSize.Width(), aSize.Height() ); + if ( aBmpSize != aNoScaleSize ) + aBitmapEx.Scale( aNoScaleSize, BmpScaleFlag::BestQuality ); + aImage = Image( aBitmapEx ); + return true; + } + + return false; +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/langselectionmenucontroller.cxx b/framework/source/uielement/langselectionmenucontroller.cxx new file mode 100644 index 000000000..94918c021 --- /dev/null +++ b/framework/source/uielement/langselectionmenucontroller.cxx @@ -0,0 +1,294 @@ +/* -*- 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 <uielement/langselectionmenucontroller.hxx> + +#include <services.h> + +#include <com/sun/star/awt/MenuItemStyle.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> + +#include <vcl/svapp.hxx> + +#include <svl/languageoptions.hxx> +#include <svtools/langtab.hxx> +#include <classes/fwkresid.hxx> + +#include <strings.hrc> + +#include <helper/mischelper.hxx> +#include <osl/mutex.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <map> +#include <set> + +// Defines + +using namespace ::com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::frame; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; + +namespace framework +{ + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL LanguageSelectionMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.LanguageSelectionMenuController"; +} + +sal_Bool SAL_CALL LanguageSelectionMenuController::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL LanguageSelectionMenuController::getSupportedServiceNames() +{ + return { SERVICENAME_POPUPMENUCONTROLLER }; +} + + +LanguageSelectionMenuController::LanguageSelectionMenuController( const css::uno::Reference< css::uno::XComponentContext >& xContext ) + : svt::PopupMenuControllerBase(xContext) + , m_bShowMenu(true) + , m_nScriptType(SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX) + , m_aLangGuessHelper(xContext) +{ +} + +LanguageSelectionMenuController::~LanguageSelectionMenuController() +{ +} + +// XEventListener +void SAL_CALL LanguageSelectionMenuController::disposing( const EventObject& ) +{ + Reference< css::awt::XMenuListener > xHolder(this); + + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + m_xLanguageDispatch.clear(); + + if ( m_xPopupMenu.is() ) + m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); + m_xPopupMenu.clear(); +} + +// XStatusListener +void SAL_CALL LanguageSelectionMenuController::statusChanged( const FeatureStateEvent& Event ) +{ + SolarMutexGuard aSolarMutexGuard; + + if (rBHelper.bDisposed || rBHelper.bInDispose) + return; + + m_bShowMenu = true; + m_nScriptType = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX; //set the default value + + Sequence< OUString > aSeq; + + if ( Event.State >>= aSeq ) + { + if ( aSeq.getLength() == 4 ) + { + // Retrieve all other values from the sequence and + // store it members! + m_aCurLang = aSeq[0]; + m_nScriptType = static_cast< SvtScriptType >(aSeq[1].toInt32()); + m_aKeyboardLang = aSeq[2]; + m_aGuessedTextLang = aSeq[3]; + } + } + else if ( !Event.State.hasValue() ) + { + m_bShowMenu = false; // no language -> no sub-menu entries -> disable menu + } +} + +// XPopupMenuController +void LanguageSelectionMenuController::impl_setPopupMenu() +{ + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + + css::util::URL aTargetURL; + + // Register for language updates + aTargetURL.Complete = m_aLangStatusCommandURL; + m_xURLTransformer->parseStrict( aTargetURL ); + m_xLanguageDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + + // Register for setting languages and opening language dialog + aTargetURL.Complete = m_aMenuCommandURL_Lang; + m_xURLTransformer->parseStrict( aTargetURL ); + m_xMenuDispatch_Lang = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + + // Register for opening character dialog + aTargetURL.Complete = m_aMenuCommandURL_Font; + m_xURLTransformer->parseStrict( aTargetURL ); + m_xMenuDispatch_Font = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + + // Register for opening character dialog with preselected paragraph + aTargetURL.Complete = m_aMenuCommandURL_CharDlgForParagraph; + m_xURLTransformer->parseStrict( aTargetURL ); + m_xMenuDispatch_CharDlgForParagraph = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); +} + +void LanguageSelectionMenuController::fillPopupMenu( Reference< css::awt::XPopupMenu > const & rPopupMenu , const Mode eMode ) +{ + SolarMutexGuard aSolarMutexGuard; + + resetPopupMenu( rPopupMenu ); + if (!m_bShowMenu) + return; + + OUString aCmd_Dialog; + OUString aCmd_Language; + if( eMode == MODE_SetLanguageSelectionMenu ) + { + aCmd_Dialog += ".uno:FontDialog?Page:string=font"; + aCmd_Language += ".uno:LanguageStatus?Language:string=Current_"; + } + else if ( eMode == MODE_SetLanguageParagraphMenu ) + { + aCmd_Dialog += ".uno:FontDialogForParagraph"; + aCmd_Language += ".uno:LanguageStatus?Language:string=Paragraph_"; + } + else if ( eMode == MODE_SetLanguageAllTextMenu ) + { + aCmd_Dialog += ".uno:LanguageStatus?Language:string=*"; + aCmd_Language += ".uno:LanguageStatus?Language:string=Default_"; + } + + // get languages to be displayed in the menu + std::set< OUString > aLangItems; + FillLangItems( aLangItems, m_xFrame, m_aLangGuessHelper, + m_nScriptType, m_aCurLang, m_aKeyboardLang, m_aGuessedTextLang ); + + // now add menu entries + // the different menus purpose will be handled by the different string + // for aCmd_Dialog and aCmd_Language + sal_Int16 nItemId = 0; // in this control the item id is not important for executing the command + static const OUStringLiteral sAsterisk(u"*"); // multiple languages in current selection + const OUString sNone( SvtLanguageTable::GetLanguageString( LANGUAGE_NONE )); + for (auto const& langItem : aLangItems) + { + if (langItem != sNone && + langItem != sAsterisk && + !langItem.isEmpty()) // 'no language found' from language guessing + { + ++nItemId; + rPopupMenu->insertItem(nItemId, langItem, css::awt::MenuItemStyle::CHECKABLE, nItemId - 1); + OUString aCmd = aCmd_Language + langItem; + rPopupMenu->setCommand(nItemId, aCmd); + bool bChecked = langItem == m_aCurLang && eMode == MODE_SetLanguageSelectionMenu; + //make a sign for the current language + rPopupMenu->checkItem(nItemId, bChecked); + } + } + + // entry for LANGUAGE_NONE + ++nItemId; + rPopupMenu->insertItem(nItemId, FwkResId(STR_LANGSTATUS_NONE), 0, nItemId - 1); + OUString aCmd = aCmd_Language + "LANGUAGE_NONE"; + rPopupMenu->setCommand(nItemId, aCmd); + + // entry for 'Reset to default language' + ++nItemId; + rPopupMenu->insertItem(nItemId, FwkResId(STR_RESET_TO_DEFAULT_LANGUAGE), 0, nItemId - 1); + aCmd = aCmd_Language + "RESET_LANGUAGES"; + rPopupMenu->setCommand(nItemId, aCmd); + + // entry for opening the Format/Character dialog + ++nItemId; + rPopupMenu->insertItem(nItemId, FwkResId(STR_LANGSTATUS_MORE), 0, nItemId - 1); + rPopupMenu->setCommand(nItemId, aCmd_Dialog); +} + +void SAL_CALL LanguageSelectionMenuController::updatePopupMenu() +{ + svt::PopupMenuControllerBase::updatePopupMenu(); + + // Force status update to get information about the current languages + osl::ClearableMutexGuard aLock( m_aMutex ); + Reference< XDispatch > xDispatch( m_xLanguageDispatch ); + css::util::URL aTargetURL; + aTargetURL.Complete = m_aLangStatusCommandURL; + m_xURLTransformer->parseStrict( aTargetURL ); + aLock.clear(); + + if ( xDispatch.is() ) + { + xDispatch->addStatusListener( static_cast< XStatusListener* >(this), aTargetURL ); + xDispatch->removeStatusListener( static_cast< XStatusListener* >(this), aTargetURL ); + } + + // TODO: Fill menu with the information retrieved by the status update + + if ( m_aCommandURL == ".uno:SetLanguageSelectionMenu" ) + { + fillPopupMenu(m_xPopupMenu, MODE_SetLanguageSelectionMenu ); + } + else if ( m_aCommandURL == ".uno:SetLanguageParagraphMenu" ) + { + fillPopupMenu(m_xPopupMenu, MODE_SetLanguageParagraphMenu ); + } + else if ( m_aCommandURL == ".uno:SetLanguageAllTextMenu" ) + { + fillPopupMenu(m_xPopupMenu, MODE_SetLanguageAllTextMenu ); + } +} + +// XInitialization +void SAL_CALL LanguageSelectionMenuController::initialize( const Sequence< Any >& aArguments ) +{ + osl::MutexGuard aLock( m_aMutex ); + + bool bInitialized( m_bInitialized ); + if ( !bInitialized ) + { + svt::PopupMenuControllerBase::initialize(aArguments); + + if ( m_bInitialized ) + { + m_aLangStatusCommandURL = ".uno:LanguageStatus"; + m_aMenuCommandURL_Lang = m_aLangStatusCommandURL; + m_aMenuCommandURL_Font = ".uno:FontDialog"; + m_aMenuCommandURL_CharDlgForParagraph = ".uno:FontDialogForParagraph"; + } + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_LanguageSelectionMenuController_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::LanguageSelectionMenuController(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/langselectionstatusbarcontroller.cxx b/framework/source/uielement/langselectionstatusbarcontroller.cxx new file mode 100644 index 000000000..807806364 --- /dev/null +++ b/framework/source/uielement/langselectionstatusbarcontroller.cxx @@ -0,0 +1,359 @@ +/* -*- 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 <classes/fwkresid.hxx> +#include <services.h> +#include <strings.hrc> +#include <vcl/svapp.hxx> + +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/awt/PopupMenu.hpp> +#include <com/sun/star/awt/PopupMenuDirection.hpp> +#include <svtools/langtab.hxx> +#include <svtools/statusbarcontroller.hxx> +#include <sal/types.h> +#include <sal/log.hxx> +#include <com/sun/star/document/XDocumentLanguages.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/ui/XStatusbarItem.hpp> + +#include <com/sun/star/frame/XFrame.hpp> + +#include <com/sun/star/awt/Command.hpp> +#include <svl/languageoptions.hxx> + +#include <helper/mischelper.hxx> + +#include <rtl/ustrbuf.hxx> + +#include <map> +#include <set> + +using namespace ::cppu; +using namespace ::com::sun::star; +using namespace css::uno; +using namespace css::lang; +using namespace css::frame; +using namespace css::i18n; +using namespace css::document; +using namespace framework; + +namespace { + +class LangSelectionStatusbarController: + public svt::StatusbarController +{ +public: + explicit LangSelectionStatusbarController( const css::uno::Reference< css::uno::XComponentContext >& xContext ); + LangSelectionStatusbarController(const LangSelectionStatusbarController&) = delete; + LangSelectionStatusbarController& operator=(const LangSelectionStatusbarController&) = delete; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XStatusListener + virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& Event ) override; + + // XStatusbarController + virtual void SAL_CALL command( const css::awt::Point& aPos, + ::sal_Int32 nCommand, + sal_Bool bMouseEvent, + const css::uno::Any& aData ) override; + virtual void SAL_CALL click( const css::awt::Point& aPos ) override; + +private: + virtual ~LangSelectionStatusbarController() override {} + + bool m_bShowMenu; // if the menu is to be displayed or not (depending on the selected object/text) + SvtScriptType m_nScriptType; // the flags for the different script types available in the selection, LATIN = 0x0001, ASIAN = 0x0002, COMPLEX = 0x0004 + OUString m_aCurLang; // the language of the current selection, "*" if there are more than one languages + OUString m_aKeyboardLang; // the keyboard language + OUString m_aGuessedTextLang; // the 'guessed' language for the selection, "" if none could be guessed + LanguageGuessingHelper m_aLangGuessHelper; + + /// @throws css::uno::RuntimeException + void LangMenu( const css::awt::Point& aPos ); +}; + +LangSelectionStatusbarController::LangSelectionStatusbarController( const uno::Reference< uno::XComponentContext >& xContext ) : + svt::StatusbarController( xContext, uno::Reference< frame::XFrame >(), OUString(), 0 ), + m_bShowMenu( true ), + m_nScriptType( SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX ), + m_aLangGuessHelper( xContext ) +{ +} + +void SAL_CALL LangSelectionStatusbarController::initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) +{ + SolarMutexGuard aSolarMutexGuard; + + svt::StatusbarController::initialize( aArguments ); + + if ( m_xStatusbarItem.is() ) + { + m_xStatusbarItem->setText( FwkResId(STR_LANGSTATUS_MULTIPLE_LANGUAGES) ); + m_xStatusbarItem->setQuickHelpText(FwkResId(STR_LANGSTATUS_HINT)); + } +} + +void LangSelectionStatusbarController::LangMenu( + const css::awt::Point& aPos ) +{ + if (!m_bShowMenu) + return; + + const Reference<XServiceInfo> xService(m_xFrame->getController()->getModel(), UNO_QUERY); + bool bWriter = xService.is() && xService->supportsService("com.sun.star.text.GenericTextDocument"); + //add context menu + Reference< awt::XPopupMenu > xPopupMenu( awt::PopupMenu::create( m_xContext ) ); + //sub menu that contains all items except the last two items: Separator + Set Language for Paragraph + Reference< awt::XPopupMenu > subPopupMenu( awt::PopupMenu::create( m_xContext ) ); + + // get languages to be displayed in the menu + std::set< OUString > aLangItems; + FillLangItems( aLangItems, m_xFrame, m_aLangGuessHelper, + m_nScriptType, m_aCurLang, m_aKeyboardLang, m_aGuessedTextLang ); + + // add first few entries to main menu + sal_Int16 nItemId = static_cast< sal_Int16 >(MID_LANG_SEL_1); + static const OUStringLiteral sAsterisk(u"*"); // multiple languages in current selection + const OUString sNone( SvtLanguageTable::GetLanguageString( LANGUAGE_NONE )); + std::map< sal_Int16, OUString > aLangMap; + for (auto const& langItem : aLangItems) + { + if ( langItem != sNone && + langItem != sAsterisk && + !langItem.isEmpty()) // 'no language found' from language guessing + { + SAL_WARN_IF( MID_LANG_SEL_1 > nItemId || nItemId > MID_LANG_SEL_9, + "fwk.uielement", "nItemId outside of expected range!" ); + xPopupMenu->insertItem( nItemId, langItem, 0, nItemId ); + if ( langItem == m_aCurLang ) + { + //make a sign for the current language + xPopupMenu->checkItem( nItemId, true ); + } + aLangMap[ nItemId ] = langItem; + ++nItemId; + } + } + + if (bWriter) + { + xPopupMenu->insertItem( MID_LANG_SEL_NONE, FwkResId(STR_LANGSTATUS_NONE), 0, MID_LANG_SEL_NONE ); + if ( sNone == m_aCurLang ) + xPopupMenu->checkItem( MID_LANG_SEL_NONE, true ); + xPopupMenu->insertItem( MID_LANG_SEL_RESET, FwkResId(STR_RESET_TO_DEFAULT_LANGUAGE), 0, MID_LANG_SEL_RESET ); + xPopupMenu->insertItem( MID_LANG_SEL_MORE, FwkResId(STR_LANGSTATUS_MORE), 0, MID_LANG_SEL_MORE ); + + // add entries to submenu ('set language for paragraph') + nItemId = static_cast< sal_Int16 >(MID_LANG_PARA_1); + for (auto const& langItem : aLangItems) + { + if( langItem != sNone && + langItem != sAsterisk && + !langItem.isEmpty()) // 'no language found' from language guessing + { + SAL_WARN_IF( MID_LANG_PARA_1 > nItemId || nItemId > MID_LANG_PARA_9, + "fwk.uielement", "nItemId outside of expected range!" ); + subPopupMenu->insertItem( nItemId, langItem, 0, nItemId ); + aLangMap[nItemId] = langItem; + ++nItemId; + } + } + subPopupMenu->insertItem( MID_LANG_PARA_NONE, FwkResId(STR_LANGSTATUS_NONE), 0, MID_LANG_PARA_NONE ); + subPopupMenu->insertItem( MID_LANG_PARA_RESET, FwkResId(STR_RESET_TO_DEFAULT_LANGUAGE), 0, MID_LANG_PARA_RESET ); + subPopupMenu->insertItem( MID_LANG_PARA_MORE, FwkResId(STR_LANGSTATUS_MORE), 0, MID_LANG_PARA_MORE ); + + // add last two entries to main menu + xPopupMenu->insertSeparator( MID_LANG_PARA_SEPARATOR ); + xPopupMenu->insertItem( MID_LANG_PARA_STRING, FwkResId(STR_SET_LANGUAGE_FOR_PARAGRAPH), 0, MID_LANG_PARA_STRING ); + xPopupMenu->setPopupMenu( MID_LANG_PARA_STRING, subPopupMenu ); + } + else + { + xPopupMenu->insertItem( MID_LANG_DEF_NONE, FwkResId(STR_LANGSTATUS_NONE), 0, MID_LANG_DEF_NONE ); + if ( sNone == m_aCurLang ) + xPopupMenu->checkItem( MID_LANG_DEF_NONE, true ); + xPopupMenu->insertItem( MID_LANG_DEF_RESET, FwkResId(STR_RESET_TO_DEFAULT_LANGUAGE), 0, MID_LANG_DEF_RESET ); + xPopupMenu->insertItem( MID_LANG_DEF_MORE, FwkResId(STR_LANGSTATUS_MORE), 0, MID_LANG_DEF_MORE ); + } + + // now display the popup menu and execute every command ... + + Reference< awt::XWindowPeer > xParent( m_xParentWindow, UNO_QUERY ); + css::awt::Rectangle aRect( aPos.X, aPos.Y, 0, 0 ); + sal_Int16 nId = xPopupMenu->execute( xParent, aRect, css::awt::PopupMenuDirection::EXECUTE_UP+16 ); + //click "More..." + if ( !(nId && m_xFrame.is()) ) + return; + + OUStringBuffer aBuff; + //set selected language as current language for selection + const OUString aSelectedLang = aLangMap[nId]; + + if (MID_LANG_SEL_1 <= nId && nId <= MID_LANG_SEL_9) + { + if (bWriter) + aBuff.append( ".uno:LanguageStatus?Language:string=Current_" ); + else + aBuff.append( ".uno:LanguageStatus?Language:string=Default_" ); + + aBuff.append( aSelectedLang ); + } + else if (nId == MID_LANG_SEL_NONE) + { + //set None as current language for selection + aBuff.append( ".uno:LanguageStatus?Language:string=Current_LANGUAGE_NONE" ); + } + else if (nId == MID_LANG_SEL_RESET) + { + // reset language attributes for selection + aBuff.append( ".uno:LanguageStatus?Language:string=Current_RESET_LANGUAGES" ); + } + else if (nId == MID_LANG_SEL_MORE) + { + //open the dialog "format/character" for current selection + aBuff.append( ".uno:FontDialog?Page:string=font" ); + } + else if (nId == MID_LANG_DEF_NONE) + { + aBuff.append( ".uno:LanguageStatus?Language:string=Default_LANGUAGE_NONE" ); + } + else if (nId == MID_LANG_DEF_RESET) + { + aBuff.append( ".uno:LanguageStatus?Language:string=Default_RESET_LANGUAGES" ); + } + else if (nId == MID_LANG_DEF_MORE) + { + aBuff.append( ".uno:LanguageStatus?Language:string=*" ); + } + else if (MID_LANG_PARA_1 <= nId && nId <= MID_LANG_PARA_9) + { + aBuff.append( ".uno:LanguageStatus?Language:string=Paragraph_" ); + aBuff.append( aSelectedLang ); + } + else if (nId == MID_LANG_PARA_NONE) + { + //set None as language for current paragraph + aBuff.append( ".uno:LanguageStatus?Language:string=Paragraph_LANGUAGE_NONE" ); + } + else if (nId == MID_LANG_PARA_RESET) + { + // reset language attributes for paragraph + aBuff.append( ".uno:LanguageStatus?Language:string=Paragraph_RESET_LANGUAGES" ); + } + else if (nId == MID_LANG_PARA_MORE) + { + //open the dialog "format/character" for current paragraph + aBuff.append( ".uno:FontDialogForParagraph" ); + } + + const Sequence< beans::PropertyValue > aDummyArgs; + execute( aBuff.makeStringAndClear(), aDummyArgs ); +} + +void SAL_CALL LangSelectionStatusbarController::command( + const css::awt::Point& aPos, + ::sal_Int32 nCommand, + sal_Bool /*bMouseEvent*/, + const css::uno::Any& /*aData*/ ) +{ + if ( nCommand & ::awt::Command::CONTEXTMENU ) + { + LangMenu( aPos ); + } +} + +void SAL_CALL LangSelectionStatusbarController::click( + const css::awt::Point& aPos ) +{ + LangMenu( aPos ); +} + +// XStatusListener +void SAL_CALL LangSelectionStatusbarController::statusChanged( const FeatureStateEvent& Event ) +{ + // This function will be called when observed data changes, + // for example the selection or keyboard language. + // - It displays the language in use in the status bar + // - and it stores the relevant data for creating the menu + // at some later point in the member variables + // m_nScriptType, m_aCurLang, m_aKeyboardLang, m_aGuessedText + + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + return; + + m_bShowMenu = true; + m_nScriptType = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX; //set the default value + + if ( !m_xStatusbarItem.is() ) + return; + + OUString aStrValue; + Sequence< OUString > aSeq; + + if ( Event.State >>= aStrValue ) + { + m_xStatusbarItem->setText( aStrValue ); + m_xStatusbarItem->setQuickHelpText(FwkResId(STR_LANGSTATUS_HINT)); + m_aCurLang = aStrValue; + } + else if ( Event.State >>= aSeq ) + { + if ( aSeq.getLength() == 4 ) + { + OUString aStatusText = aSeq[0]; + if (aStatusText == "*") + { + aStatusText = FwkResId(STR_LANGSTATUS_MULTIPLE_LANGUAGES); + } + m_xStatusbarItem->setText( aStatusText ); + m_xStatusbarItem->setQuickHelpText(FwkResId(STR_LANGSTATUS_HINT)); + + // Retrieve all other values from the sequence and + // store it members! + m_aCurLang = aSeq[0]; + m_nScriptType = static_cast< SvtScriptType >( aSeq[1].toInt32() ); + m_aKeyboardLang = aSeq[2]; + m_aGuessedTextLang = aSeq[3]; + } + } + else if ( !Event.State.hasValue() ) + { + m_xStatusbarItem->setText( OUString() ); + m_xStatusbarItem->setQuickHelpText(u""); + m_bShowMenu = false; // no language -> no menu + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_LangSelectionStatusbarController_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new LangSelectionStatusbarController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/macrosmenucontroller.cxx b/framework/source/uielement/macrosmenucontroller.cxx new file mode 100644 index 000000000..a0745b1e4 --- /dev/null +++ b/framework/source/uielement/macrosmenucontroller.cxx @@ -0,0 +1,170 @@ +/* -*- 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 <uielement/macrosmenucontroller.hxx> +#include <services.h> +#include <com/sun/star/container/XContentEnumerationAccess.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <officecfg/Office/Common.hxx> +#include <vcl/svapp.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <osl/mutex.hxx> +#include <cppuhelper/supportsservice.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::frame; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace com::sun::star::style; +using namespace com::sun::star::container; + +namespace framework +{ + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL MacrosMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.MacrosMenuController"; +} + +sal_Bool SAL_CALL MacrosMenuController::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL MacrosMenuController::getSupportedServiceNames() +{ + return { SERVICENAME_POPUPMENUCONTROLLER }; +} + +MacrosMenuController::MacrosMenuController( const css::uno::Reference< css::uno::XComponentContext >& xContext ) : + svt::PopupMenuControllerBase( xContext ), + m_xContext( xContext) +{ +} + +MacrosMenuController::~MacrosMenuController() +{ +} + +// private function +void MacrosMenuController::fillPopupMenu( Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + bool bMacrosDisabled = officecfg::Office::Common::Security::Scripting::DisableMacrosExecution::get(); + if (bMacrosDisabled) + return; + + SolarMutexGuard aSolarMutexGuard; + + resetPopupMenu(rPopupMenu); + assert(rPopupMenu->getItemCount() == 0); + + // insert basic + OUString aCommand(".uno:MacroDialog"); + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommand, m_aModuleName); + OUString aDisplayName = vcl::CommandInfoProvider::GetMenuLabelForCommand(aProperties); + rPopupMenu->insertItem(2, aDisplayName, 0, 0); + rPopupMenu->setCommand(2, aCommand); + + // insert providers but not basic or java + addScriptItems(rPopupMenu, 4); +} + +// XEventListener +void SAL_CALL MacrosMenuController::disposing( const EventObject& ) +{ + Reference< css::awt::XMenuListener > xHolder(this); + + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + m_xContext.clear(); + + if ( m_xPopupMenu.is() ) + { + m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); + } + m_xPopupMenu.clear(); +} + +// XStatusListener +void SAL_CALL MacrosMenuController::statusChanged( const FeatureStateEvent& ) +{ + osl::MutexGuard aLock( m_aMutex ); + if ( m_xPopupMenu.is() ) + { + fillPopupMenu( m_xPopupMenu ); + } +} + +void MacrosMenuController::addScriptItems(const Reference<css::awt::XPopupMenu>& rPopupMenu, sal_uInt16 startItemId) +{ + static const OUStringLiteral aCmdBase(u".uno:ScriptOrganizer?ScriptOrganizer.Language:string="); + static const OUStringLiteral ellipsis( u"..." ); + static const OUStringLiteral providerKey(u"com.sun.star.script.provider.ScriptProviderFor"); + sal_uInt16 itemId = startItemId; + Reference< XContentEnumerationAccess > xEnumAccess( m_xContext->getServiceManager(), UNO_QUERY_THROW ); + Reference< XEnumeration > xEnum = xEnumAccess->createContentEnumeration ( "com.sun.star.script.provider.LanguageScriptProvider" ); + + sal_Int16 nPos = rPopupMenu->getItemCount(); + + while ( xEnum->hasMoreElements() ) + { + Reference< XServiceInfo > xServiceInfo; + if ( !( xEnum->nextElement() >>= xServiceInfo ) ) + { + break; + } + const Sequence< OUString > serviceNames = xServiceInfo->getSupportedServiceNames(); + + for ( OUString const & serviceName : serviceNames ) + { + if ( serviceName.startsWith( providerKey ) ) + { + OUString aCommand = aCmdBase; + OUString aDisplayName = serviceName.copy( providerKey.getLength() ); + if( aDisplayName == "Java" || aDisplayName == "Basic" ) + { + // no entries for Java & Basic added elsewhere + break; + } + aCommand += aDisplayName; + aDisplayName += ellipsis; + rPopupMenu->insertItem(itemId, aDisplayName, 0, nPos++); + rPopupMenu->setCommand(itemId, aCommand); + itemId++; + break; + } + } + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_MacrosMenuController_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::MacrosMenuController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/menubarmanager.cxx b/framework/source/uielement/menubarmanager.cxx new file mode 100644 index 000000000..c7efdb920 --- /dev/null +++ b/framework/source/uielement/menubarmanager.cxx @@ -0,0 +1,1572 @@ +/* -*- 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 <uielement/menubarmanager.hxx> +#include <uielement/styletoolbarcontroller.hxx> +#include <menuconfiguration.hxx> +#include <addonmenu.hxx> +#include <framework/addonsoptions.hxx> +#include <classes/fwkresid.hxx> +#include <strings.hrc> + +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/uno/XCurrentContext.hpp> +#include <com/sun/star/frame/XPopupMenuController.hpp> +#include <com/sun/star/frame/thePopupMenuControllerFactory.hpp> +#include <com/sun/star/lang/SystemDependent.hpp> +#include <com/sun/star/ui/GlobalAcceleratorConfiguration.hpp> +#include <com/sun/star/ui/ItemType.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/ItemStyle.hpp> +#include <com/sun/star/frame/status/Visibility.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <officecfg/Office/Common.hxx> +#include <svtools/javainteractionhandler.hxx> +#include <uno/current_context.hxx> +#include <unotools/cmdoptions.hxx> +#include <toolkit/awt/vclxmenu.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/menu.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <sal/log.hxx> +#include <svtools/acceleratorexecute.hxx> +#include <svtools/miscopt.hxx> +#include <uielement/menubarmerger.hxx> +#include <tools/urlobj.hxx> + +using namespace ::cppu; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::ui; + +const sal_uInt16 ADDONMENU_MERGE_ITEMID_START = 1500; +const sal_uInt16 ITEMID_ADDONLIST = 6678; // used to be a SID in sfx2, now just a unique id... + +namespace framework +{ + +constexpr OUStringLiteral aCmdHelpIndex = u".uno:HelpIndex"; +constexpr OUStringLiteral aCmdToolsMenu = u".uno:ToolsMenu"; +constexpr OUStringLiteral aCmdHelpMenu = u".uno:HelpMenu"; +constexpr OUStringLiteral aSpecialWindowCommand = u".uno:WindowList"; + +MenuBarManager::MenuBarManager( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + const Reference< XURLTransformer >& _xURLTransformer, + const Reference< XDispatchProvider >& rDispatchProvider, + const OUString& rModuleIdentifier, + Menu* pMenu, bool bDelete, bool bHasMenuBar ): + m_bRetrieveImages( false ) + , m_bAcceleratorCfg( false ) + , m_bHasMenuBar( bHasMenuBar ) + , m_xContext(rxContext) + , m_xURLTransformer(_xURLTransformer) + , m_sIconTheme( SvtMiscOptions().GetIconTheme() ) + , m_aAsyncSettingsTimer( "framework::MenuBarManager::Deactivate m_aAsyncSettingsTimer" ) +{ + m_xPopupMenuControllerFactory = frame::thePopupMenuControllerFactory::get(m_xContext); + FillMenuManager( pMenu, rFrame, rDispatchProvider, rModuleIdentifier, bDelete ); +} + +Any SAL_CALL MenuBarManager::getMenuHandle( const Sequence< sal_Int8 >& /*ProcessId*/, sal_Int16 SystemType ) +{ + SolarMutexGuard aSolarGuard; + + if ( m_bDisposed ) + throw css::lang::DisposedException(); + + Any a; + + if ( m_pVCLMenu ) + { + SystemMenuData aSystemMenuData; + + m_pVCLMenu->GetSystemMenuData( &aSystemMenuData ); +#ifdef _WIN32 + if( SystemType == SystemDependent::SYSTEM_WIN32 ) + { + a <<= sal_Int64( + reinterpret_cast<sal_IntPtr>(aSystemMenuData.hMenu)); + } +#else + (void) SystemType; +#endif + } + + return a; +} + +MenuBarManager::~MenuBarManager() +{ + // stop asynchronous settings timer + m_xDeferredItemContainer.clear(); + m_aAsyncSettingsTimer.Stop(); + + SAL_WARN_IF( OWeakObject::m_refCount != 0, "fwk.uielement", "Who wants to delete an object with refcount > 0!" ); +} + +// XComponent +void MenuBarManager::disposing(std::unique_lock<std::mutex>& ) +{ + Reference< XComponent > xThis( this ); + + SolarMutexGuard g; + + // stop asynchronous settings timer and + // release deferred item container reference + m_aAsyncSettingsTimer.Stop(); + m_xDeferredItemContainer.clear(); + RemoveListener(); + + m_aMenuItemHandlerVector.clear(); + + if ( m_bDeleteMenu ) + { + m_pVCLMenu.disposeAndClear(); + } + + if ( m_xDocImageManager.is() ) + { + try + { + m_xDocImageManager->removeConfigurationListener( + Reference< XUIConfigurationListener >(this) ); + } + catch ( const Exception& ) + { + } + } + if ( m_xModuleImageManager.is() ) + { + try + { + m_xModuleImageManager->removeConfigurationListener( + Reference< XUIConfigurationListener >(this) ); + } + catch ( const Exception& ) + { + } + } + m_xDocImageManager.clear(); + m_xModuleImageManager.clear(); + m_xGlobalAcceleratorManager.clear(); + m_xModuleAcceleratorManager.clear(); + m_xDocAcceleratorManager.clear(); + m_xPopupMenuControllerFactory.clear(); + m_xContext.clear(); +} + +void SAL_CALL MenuBarManager::elementInserted( const css::ui::ConfigurationEvent& Event ) +{ + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + return; + + sal_Int16 nImageType = sal_Int16(); + if (( Event.aInfo >>= nImageType ) && nImageType == 0 ) + RequestImages(); +} + +void SAL_CALL MenuBarManager::elementRemoved( const css::ui::ConfigurationEvent& Event ) +{ + elementInserted(Event); +} + +void SAL_CALL MenuBarManager::elementReplaced( const css::ui::ConfigurationEvent& Event ) +{ + elementInserted(Event); +} + +// XFrameActionListener +void SAL_CALL MenuBarManager::frameAction( const FrameActionEvent& Action ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw css::lang::DisposedException(); + + if ( Action.Action != FrameAction_CONTEXT_CHANGED ) + return; + + for (auto const& menuItemHandler : m_aMenuItemHandlerVector) + { + // Clear dispatch reference as we will requery it later + if ( menuItemHandler->xMenuItemDispatch.is() ) + { + URL aTargetURL; + aTargetURL.Complete = menuItemHandler->aMenuItemURL; + m_xURLTransformer->parseStrict( aTargetURL ); + + menuItemHandler->xMenuItemDispatch->removeStatusListener( this, aTargetURL ); + } + menuItemHandler->xMenuItemDispatch.clear(); + } +} + +// XStatusListener +void SAL_CALL MenuBarManager::statusChanged( const FeatureStateEvent& Event ) +{ + OUString aFeatureURL = Event.FeatureURL.Complete; + + SolarMutexGuard aSolarGuard; + { + if ( m_bDisposed ) + return; + + // We have to check all menu entries as there can be identical entries in a popup menu. + for (auto const& menuItemHandler : m_aMenuItemHandlerVector) + { + if ( menuItemHandler->aParsedItemURL == aFeatureURL ) + { + bool bCheckmark( false ); + bool bMenuItemEnabled( m_pVCLMenu->IsItemEnabled( menuItemHandler->nItemId )); + bool bEnabledItem( Event.IsEnabled ); + OUString aItemText; + status::Visibility aVisibilityStatus; + + #ifdef UNIX + //enable some slots hardly, because UNIX clipboard does not notify all changes + // Can be removed if follow up task will be fixed directly within applications. + // Note: PasteSpecial is handled specifically by calc + // Calc also disables Paste under some circumstances, do not override. + /* TODO: is this workaround even needed anymore? Was introduced + * in 2009 with commit 426ab2c0e8f6e3fe2b766f74f6b8da873d860260 + * as some "metropatch" and the other places it touched seem to + * be gone. */ + if ( (menuItemHandler->aMenuItemURL == ".uno:Paste" && + m_aModuleIdentifier != "com.sun.star.sheet.SpreadsheetDocument") + || menuItemHandler->aMenuItemURL == ".uno:PasteClipboard" ) // special for draw/impress + bEnabledItem = true; + #endif + + // Enable/disable item + if ( bEnabledItem != bMenuItemEnabled ) + { + m_pVCLMenu->EnableItem( menuItemHandler->nItemId, bEnabledItem ); + + // Remove "checked" mark for disabled menu items. + // Initially disabled but checkable menu items do not receive + // checked/unchecked state, so can appear inconsistently after + // enabling/disabling. Since we can not pass checked state for disabled + // items, we will just reset checked state for them, anyway correct state + // will be transferred from controller once item enabled. + if ( !bEnabledItem && m_pVCLMenu->IsItemChecked( menuItemHandler->nItemId ) ) + m_pVCLMenu->CheckItem( menuItemHandler->nItemId, false ); + } + + if ( Event.State >>= bCheckmark ) + { + // Checkmark or RadioButton + m_pVCLMenu->CheckItem( menuItemHandler->nItemId, bCheckmark ); + // If not already designated RadioButton set as CheckMark + MenuItemBits nBits = m_pVCLMenu->GetItemBits( menuItemHandler->nItemId ); + if (!(nBits & MenuItemBits::RADIOCHECK)) + m_pVCLMenu->SetItemBits( menuItemHandler->nItemId, nBits | MenuItemBits::CHECKABLE ); + + if ( menuItemHandler->bMadeInvisible ) + m_pVCLMenu->ShowItem( menuItemHandler->nItemId ); + } + else if ( Event.State >>= aItemText ) + { + INetURLObject aURL( aFeatureURL ); + OUString aEnumPart = aURL.GetURLPath().getToken( 1, '.' ); + if ( !aEnumPart.isEmpty() && aURL.GetProtocol() == INetProtocol::Uno ) + { + // Checkmark or RadioButton + m_pVCLMenu->CheckItem( menuItemHandler->nItemId, aItemText == aEnumPart ); + // If not already designated RadioButton set as CheckMark + MenuItemBits nBits = m_pVCLMenu->GetItemBits( menuItemHandler->nItemId ); + if (!(nBits & MenuItemBits::RADIOCHECK)) + m_pVCLMenu->SetItemBits( menuItemHandler->nItemId, nBits | MenuItemBits::CHECKABLE ); + } + else + { + // Replacement for place holders + if ( aItemText.startsWith("($1)") ) + { + aItemText = FwkResId(STR_UPDATEDOC) + " " + aItemText.subView( 4 ); + } + else if ( aItemText.startsWith("($2)") ) + { + aItemText = FwkResId(STR_CLOSEDOC_ANDRETURN) + aItemText.subView( 4 ); + } + else if ( aItemText.startsWith("($3)") ) + { + aItemText = FwkResId(STR_SAVECOPYDOC) + aItemText.subView( 4 ); + } + + m_pVCLMenu->SetItemText( menuItemHandler->nItemId, aItemText ); + } + + if ( menuItemHandler->bMadeInvisible ) + m_pVCLMenu->ShowItem( menuItemHandler->nItemId ); + } + else if ( Event.State >>= aVisibilityStatus ) + { + // Visibility + m_pVCLMenu->ShowItem( menuItemHandler->nItemId, aVisibilityStatus.bVisible ); + menuItemHandler->bMadeInvisible = !aVisibilityStatus.bVisible; + } + else if ( menuItemHandler->bMadeInvisible ) + m_pVCLMenu->ShowItem( menuItemHandler->nItemId ); + } + + if ( Event.Requery ) + { + // Release dispatch object - will be required on the next activate! + menuItemHandler->xMenuItemDispatch.clear(); + } + } + } +} + +// Helper to retrieve own structure from item ID +MenuBarManager::MenuItemHandler* MenuBarManager::GetMenuItemHandler( sal_uInt16 nItemId ) +{ + SolarMutexGuard g; + + for (auto const& menuItemHandler : m_aMenuItemHandlerVector) + { + if ( menuItemHandler->nItemId == nItemId ) + return menuItemHandler.get(); + } + + return nullptr; +} + +// Helper to set request images flag +void MenuBarManager::RequestImages() +{ + + m_bRetrieveImages = true; + for (auto const& menuItemHandler : m_aMenuItemHandlerVector) + { + if ( menuItemHandler->xSubMenuManager.is() ) + { + MenuBarManager* pMenuBarManager = static_cast<MenuBarManager*>(menuItemHandler->xSubMenuManager.get()); + pMenuBarManager->RequestImages(); + } + } +} + +// Helper to reset objects to prepare shutdown +void MenuBarManager::RemoveListener() +{ + SolarMutexGuard g; + + for (auto const& menuItemHandler : m_aMenuItemHandlerVector) + { + if ( menuItemHandler->xMenuItemDispatch.is() ) + { + URL aTargetURL; + aTargetURL.Complete = menuItemHandler->aMenuItemURL; + m_xURLTransformer->parseStrict( aTargetURL ); + + menuItemHandler->xMenuItemDispatch->removeStatusListener( + static_cast< XStatusListener* >( this ), aTargetURL ); + } + + menuItemHandler->xMenuItemDispatch.clear(); + + if ( menuItemHandler->xPopupMenu.is() ) + { + { + // Remove popup menu from menu structure + m_pVCLMenu->SetPopupMenu( menuItemHandler->nItemId, nullptr ); + } + + Reference< css::lang::XEventListener > xEventListener( menuItemHandler->xPopupMenuController, UNO_QUERY ); + if ( xEventListener.is() ) + { + EventObject aEventObject; + aEventObject.Source = static_cast<OWeakObject *>(this); + xEventListener->disposing( aEventObject ); + } + + // We now provide a popup menu controller to external code. + // Therefore the life-time must be explicitly handled via + // dispose!! + try + { + Reference< XComponent > xComponent( menuItemHandler->xPopupMenuController, UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + } + catch ( const RuntimeException& ) + { + throw; + } + catch ( const Exception& ) + { + } + + // Release references to controller and popup menu + menuItemHandler->xPopupMenuController.clear(); + menuItemHandler->xPopupMenu.clear(); + } + + Reference< XComponent > xComponent( menuItemHandler->xSubMenuManager, UNO_QUERY ); + if ( xComponent.is() ) + xComponent->dispose(); + } + + try + { + if ( m_xFrame.is() ) + m_xFrame->removeFrameActionListener( Reference< XFrameActionListener >(this) ); + } + catch ( const Exception& ) + { + } + + m_xFrame = nullptr; +} + +void SAL_CALL MenuBarManager::disposing( const EventObject& Source ) +{ + MenuItemHandler* pMenuItemDisposing = nullptr; + + SolarMutexGuard g; + + for (auto const& menuItemHandler : m_aMenuItemHandlerVector) + { + if ( menuItemHandler->xMenuItemDispatch.is() && + menuItemHandler->xMenuItemDispatch == Source.Source ) + { + // disposing called from menu item dispatcher, remove listener + pMenuItemDisposing = menuItemHandler.get(); + break; + } + } + + if ( pMenuItemDisposing ) + { + // Release references to the dispatch object + URL aTargetURL; + aTargetURL.Complete = pMenuItemDisposing->aMenuItemURL; + + m_xURLTransformer->parseStrict( aTargetURL ); + + pMenuItemDisposing->xMenuItemDispatch->removeStatusListener( + static_cast< XStatusListener* >( this ), aTargetURL ); + pMenuItemDisposing->xMenuItemDispatch.clear(); + if ( pMenuItemDisposing->xPopupMenu.is() ) + { + Reference< css::lang::XEventListener > xEventListener( pMenuItemDisposing->xPopupMenuController, UNO_QUERY ); + if ( xEventListener.is() ) + xEventListener->disposing( Source ); + + { + // Remove popup menu from menu structure as we release our reference to + // the controller. + m_pVCLMenu->SetPopupMenu( pMenuItemDisposing->nItemId, nullptr ); + } + + pMenuItemDisposing->xPopupMenuController.clear(); + pMenuItemDisposing->xPopupMenu.clear(); + } + return; + } + else if ( Source.Source == m_xFrame ) + { + // Our frame gets disposed. We have to remove all our listeners + RemoveListener(); + } + else if ( Source.Source == Reference< XInterface >( m_xDocImageManager, UNO_QUERY )) + m_xDocImageManager.clear(); + else if ( Source.Source == Reference< XInterface >( m_xModuleImageManager, UNO_QUERY )) + m_xModuleImageManager.clear(); +} + +static void lcl_CheckForChildren(Menu* pMenu, sal_uInt16 nItemId) +{ + if (PopupMenu* pThisPopup = pMenu->GetPopupMenu( nItemId )) + pMenu->EnableItem( nItemId, pThisPopup->GetItemCount() != 0 ); +} + +// vcl handler + +namespace { + +class QuietInteractionContext: + public cppu::WeakImplHelper< css::uno::XCurrentContext > +{ +public: + explicit QuietInteractionContext( + css::uno::Reference< css::uno::XCurrentContext > context): + context_(std::move(context)) {} + QuietInteractionContext(const QuietInteractionContext&) = delete; + QuietInteractionContext& operator=(const QuietInteractionContext&) = delete; + +private: + virtual ~QuietInteractionContext() override {} + + virtual css::uno::Any SAL_CALL getValueByName( + OUString const & Name) override + { + return Name != JAVA_INTERACTION_HANDLER_NAME && context_.is() + ? context_->getValueByName(Name) + : css::uno::Any(); + } + + css::uno::Reference< css::uno::XCurrentContext > + context_; +}; + +} + +IMPL_LINK( MenuBarManager, Activate, Menu *, pMenu, bool ) +{ + if ( pMenu != m_pVCLMenu ) + return true; + + css::uno::ContextLayer layer( + new QuietInteractionContext( + css::uno::getCurrentContext())); + + // set/unset hiding disabled menu entries + bool bDontHide = officecfg::Office::Common::View::Menu::DontHideDisabledEntry::get(); + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + bool bShowMenuImages = rSettings.GetUseImagesInMenus(); + bool bShowShortcuts = m_bHasMenuBar || rSettings.GetContextMenuShortcuts(); + bool bHasDisabledEntries = SvtCommandOptions().HasEntries( SvtCommandOptions::CMDOPTION_DISABLED ); + + SolarMutexGuard g; + + MenuFlags nFlag = pMenu->GetMenuFlags(); + if ( bDontHide ) + nFlag &= ~MenuFlags::HideDisabledEntries; + else + nFlag |= MenuFlags::HideDisabledEntries; + pMenu->SetMenuFlags( nFlag ); + + if ( m_bActive ) + return false; + + m_bActive = true; + + // Check if some modes have changed so we have to update our menu images + OUString sIconTheme = SvtMiscOptions().GetIconTheme(); + + if ( m_bRetrieveImages || + bShowMenuImages != m_bShowMenuImages || + sIconTheme != m_sIconTheme ) + { + m_bShowMenuImages = bShowMenuImages; + m_bRetrieveImages = false; + m_sIconTheme = sIconTheme; + FillMenuImages( m_xFrame, pMenu, bShowMenuImages ); + } + + // Try to map commands to labels + for ( sal_uInt16 nPos = 0; nPos < pMenu->GetItemCount(); nPos++ ) + { + sal_uInt16 nItemId = pMenu->GetItemId( nPos ); + if (( pMenu->GetItemType( nPos ) != MenuItemType::SEPARATOR ) && + ( pMenu->GetItemText( nItemId ).isEmpty() )) + { + OUString aCommand = pMenu->GetItemCommand( nItemId ); + if ( !aCommand.isEmpty() ) { + pMenu->SetItemText( nItemId, RetrieveLabelFromCommand( aCommand )); + } + } + } + + // Try to set accelerator keys + { + if ( bShowShortcuts ) + RetrieveShortcuts( m_aMenuItemHandlerVector ); + + for (auto const& menuItemHandler : m_aMenuItemHandlerVector) + { + if ( !bShowShortcuts ) + { + pMenu->SetAccelKey( menuItemHandler->nItemId, vcl::KeyCode() ); + } + else if ( menuItemHandler->aMenuItemURL == aCmdHelpIndex ) + { + // Set key code, workaround for hard-coded shortcut F1 mapped to .uno:HelpIndex + // Only non-popup menu items can have a short-cut + vcl::KeyCode aKeyCode( KEY_F1 ); + pMenu->SetAccelKey( menuItemHandler->nItemId, aKeyCode ); + } + else if ( pMenu->GetPopupMenu( menuItemHandler->nItemId ) == nullptr ) + pMenu->SetAccelKey( menuItemHandler->nItemId, menuItemHandler->aKeyCode ); + } + } + + URL aTargetURL; + + // Use provided dispatch provider => fallback to frame as dispatch provider + Reference< XDispatchProvider > xDispatchProvider; + if ( m_xDispatchProvider.is() ) + xDispatchProvider = m_xDispatchProvider; + else + xDispatchProvider.set( m_xFrame, UNO_QUERY ); + + if ( !xDispatchProvider.is() ) + return true; + + SvtCommandOptions aCmdOptions; + for (auto const& menuItemHandler : m_aMenuItemHandlerVector) + { + if (menuItemHandler) + { + if ( !menuItemHandler->xMenuItemDispatch.is() && + !menuItemHandler->xSubMenuManager.is() ) + { + Reference< XDispatch > xMenuItemDispatch; + + aTargetURL.Complete = menuItemHandler->aMenuItemURL; + + m_xURLTransformer->parseStrict( aTargetURL ); + + if ( bHasDisabledEntries ) + { + if ( aCmdOptions.Lookup( SvtCommandOptions::CMDOPTION_DISABLED, aTargetURL.Path )) + pMenu->HideItem( menuItemHandler->nItemId ); + } + + if ( aTargetURL.Complete.startsWith( ".uno:StyleApply?" ) ) + xMenuItemDispatch = new StyleDispatcher( m_xFrame, m_xURLTransformer, aTargetURL ); + else + xMenuItemDispatch = xDispatchProvider->queryDispatch( aTargetURL, menuItemHandler->aTargetFrame, 0 ); + + bool bPopupMenu( false ); + if ( !menuItemHandler->xPopupMenuController.is() && + m_xPopupMenuControllerFactory->hasController( menuItemHandler->aMenuItemURL, m_aModuleIdentifier ) ) + { + if( xMenuItemDispatch.is() || menuItemHandler->aMenuItemURL != ".uno:RecentFileList" ) + bPopupMenu = CreatePopupMenuController(menuItemHandler.get(), m_xDispatchProvider, m_aModuleIdentifier); + } + else if ( menuItemHandler->xPopupMenuController.is() ) + { + // Force update of popup menu + menuItemHandler->xPopupMenuController->updatePopupMenu(); + bPopupMenu = true; + if (PopupMenu* pThisPopup = pMenu->GetPopupMenu( menuItemHandler->nItemId )) + pMenu->EnableItem( menuItemHandler->nItemId, pThisPopup->GetItemCount() != 0 ); + } + lcl_CheckForChildren(pMenu, menuItemHandler->nItemId); + + if ( xMenuItemDispatch.is() ) + { + menuItemHandler->xMenuItemDispatch = xMenuItemDispatch; + menuItemHandler->aParsedItemURL = aTargetURL.Complete; + + if ( !bPopupMenu ) + { + xMenuItemDispatch->addStatusListener( static_cast< XStatusListener* >( this ), aTargetURL ); + // For the menubar, we have to keep status listening to support Ubuntu's HUD. + if ( !m_bHasMenuBar ) + xMenuItemDispatch->removeStatusListener( static_cast< XStatusListener* >( this ), aTargetURL ); + } + } + else if ( !bPopupMenu ) + pMenu->EnableItem( menuItemHandler->nItemId, false ); + } + else if ( menuItemHandler->xPopupMenuController.is() ) + { + // Force update of popup menu + menuItemHandler->xPopupMenuController->updatePopupMenu(); + lcl_CheckForChildren(pMenu, menuItemHandler->nItemId); + } + else if ( menuItemHandler->xMenuItemDispatch.is() ) + { + // We need an update to reflect the current state + try + { + aTargetURL.Complete = menuItemHandler->aMenuItemURL; + m_xURLTransformer->parseStrict( aTargetURL ); + + menuItemHandler->xMenuItemDispatch->addStatusListener( + static_cast< XStatusListener* >( this ), aTargetURL ); + menuItemHandler->xMenuItemDispatch->removeStatusListener( + static_cast< XStatusListener* >( this ), aTargetURL ); + } + catch ( const Exception& ) + { + } + } + else if ( menuItemHandler->xSubMenuManager.is() ) + lcl_CheckForChildren(pMenu, menuItemHandler->nItemId); + } + } + + return true; +} + +IMPL_LINK( MenuBarManager, Deactivate, Menu *, pMenu, bool ) +{ + if ( pMenu == m_pVCLMenu ) + { + m_bActive = false; + if ( pMenu->IsMenuBar() && m_xDeferredItemContainer.is() ) + { + // Start timer to handle settings asynchronous + // Changing the menu inside this handler leads to + // a crash under X! + m_aAsyncSettingsTimer.SetInvokeHandler(LINK(this, MenuBarManager, AsyncSettingsHdl)); + m_aAsyncSettingsTimer.SetTimeout(10); + m_aAsyncSettingsTimer.Start(); + } + } + + return true; +} + +IMPL_LINK_NOARG( MenuBarManager, AsyncSettingsHdl, Timer*, void) +{ + SolarMutexGuard g; + Reference< XInterface > xSelfHold( + static_cast< ::cppu::OWeakObject* >( this ), UNO_QUERY_THROW ); + + m_aAsyncSettingsTimer.Stop(); + if ( !m_bActive && m_xDeferredItemContainer.is() ) + { + SetItemContainer( m_xDeferredItemContainer ); + m_xDeferredItemContainer.clear(); + } +} + +IMPL_LINK( MenuBarManager, Select, Menu *, pMenu, bool ) +{ + URL aTargetURL; + Sequence<PropertyValue> aArgs; + Reference< XDispatch > xDispatch; + + { + SolarMutexGuard g; + + sal_uInt16 nCurItemId = pMenu->GetCurItemId(); + sal_uInt16 nCurPos = pMenu->GetItemPos( nCurItemId ); + if ( pMenu == m_pVCLMenu && + pMenu->GetItemType( nCurPos ) != MenuItemType::SEPARATOR ) + { + MenuItemHandler* pMenuItemHandler = GetMenuItemHandler( nCurItemId ); + if ( pMenuItemHandler && pMenuItemHandler->xMenuItemDispatch.is() ) + { + aTargetURL.Complete = pMenuItemHandler->aMenuItemURL; + m_xURLTransformer->parseStrict( aTargetURL ); + + if ( pMenu->GetUserValue( nCurItemId ) ) + { + // addon menu item selected + aArgs = { comphelper::makePropertyValue("Referer", OUString("private:user")) }; + } + + xDispatch = pMenuItemHandler->xMenuItemDispatch; + } + } + } + + // tdf#126054 don't let dispatch destroy this until after function completes + rtl::Reference<MenuBarManager> xKeepAlive(this); + if (xDispatch.is()) + { + SolarMutexReleaser aReleaser; + xDispatch->dispatch( aTargetURL, aArgs ); + } + + if ( !m_bHasMenuBar ) + // Standalone (non-native) popup menu doesn't fire deactivate event + // in this case, so we have to reset the active flag here. + m_bActive = false; + + return true; +} + +bool MenuBarManager::MustBeHidden( PopupMenu* pPopupMenu, const Reference< XURLTransformer >& rTransformer ) +{ + if ( !pPopupMenu ) + return true; + + URL aTargetURL; + SvtCommandOptions aCmdOptions; + + sal_uInt16 nCount = pPopupMenu->GetItemCount(); + sal_uInt16 nHideCount( 0 ); + + for ( sal_uInt16 i = 0; i < nCount; i++ ) + { + sal_uInt16 nId = pPopupMenu->GetItemId( i ); + if ( nId > 0 ) + { + PopupMenu* pSubPopupMenu = pPopupMenu->GetPopupMenu( nId ); + if ( pSubPopupMenu ) + { + if ( MustBeHidden( pSubPopupMenu, rTransformer )) + { + pPopupMenu->HideItem( nId ); + ++nHideCount; + } + } + else + { + aTargetURL.Complete = pPopupMenu->GetItemCommand( nId ); + rTransformer->parseStrict( aTargetURL ); + + if ( aCmdOptions.Lookup( SvtCommandOptions::CMDOPTION_DISABLED, aTargetURL.Path )) + ++nHideCount; + } + } + else + ++nHideCount; + } + + return ( nCount == nHideCount ); +} + +OUString MenuBarManager::RetrieveLabelFromCommand(const OUString& rCmdURL) +{ + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rCmdURL, m_aModuleIdentifier); + if ( !m_bHasMenuBar ) + { + // This is a context menu, prefer "PopupLabel" over "Label". + return vcl::CommandInfoProvider::GetPopupLabelForCommand(aProperties); + } + return vcl::CommandInfoProvider::GetMenuLabelForCommand(aProperties); +} + +bool MenuBarManager::CreatePopupMenuController( MenuItemHandler* pMenuItemHandler, + const css::uno::Reference< css::frame::XDispatchProvider >& rDispatchProvider, + const OUString& rModuleIdentifier ) +{ + OUString aItemCommand( pMenuItemHandler->aMenuItemURL ); + + // Try instantiate a popup menu controller. It is stored in the menu item handler. + if ( !m_xPopupMenuControllerFactory.is() ) + return false; + + auto aSeq( comphelper::InitAnyPropertySequence( { + { "DispatchProvider", Any(rDispatchProvider) }, + { "ModuleIdentifier", Any(rModuleIdentifier) }, + { "Frame", Any(m_xFrame) }, + { "InToolbar", Any(!m_bHasMenuBar) } + } ) ); + + Reference< XPopupMenuController > xPopupMenuController( + m_xPopupMenuControllerFactory->createInstanceWithArgumentsAndContext( + aItemCommand, + aSeq, + m_xContext ), + UNO_QUERY ); + + if ( xPopupMenuController.is() ) + { + // Provide our awt popup menu to the popup menu controller + pMenuItemHandler->xPopupMenuController = xPopupMenuController; + xPopupMenuController->setPopupMenu( pMenuItemHandler->xPopupMenu ); + return true; + } + + return false; +} + +void MenuBarManager::FillMenuManager( Menu* pMenu, const Reference< XFrame >& rFrame, + const Reference< XDispatchProvider >& rDispatchProvider, + const OUString& rModuleIdentifier, bool bDelete ) +{ + m_xFrame = rFrame; + m_bActive = false; + m_bDeleteMenu = bDelete; + m_pVCLMenu = pMenu; + m_xDispatchProvider = rDispatchProvider; + + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + m_bShowMenuImages = rSettings.GetUseImagesInMenus(); + m_bRetrieveImages = false; + + // Set module identifier when provided from outside + if (!rModuleIdentifier.isEmpty()) + m_aModuleIdentifier = rModuleIdentifier; + else + m_aModuleIdentifier = vcl::CommandInfoProvider::GetModuleIdentifier(m_xFrame); + + // Add root as ui configuration listener + RetrieveImageManagers(); + + if ( pMenu->IsMenuBar() && rFrame.is() ) + { + // First merge all addon popup menus into our structure + sal_uInt16 nPos = 0; + for ( nPos = 0; nPos < pMenu->GetItemCount(); nPos++ ) + { + sal_uInt16 nItemId = pMenu->GetItemId( nPos ); + OUString aCommand = pMenu->GetItemCommand( nItemId ); + if ( aCommand == aSpecialWindowCommand || aCommand == aCmdHelpMenu ) + { + // Retrieve addon popup menus and add them to our menu bar + framework::AddonMenuManager::MergeAddonPopupMenus( rFrame, nPos, static_cast<MenuBar *>(pMenu) ); + break; + } + } + + // Merge the Add-Ons help menu items into the Office help menu + framework::AddonMenuManager::MergeAddonHelpMenu( rFrame, static_cast<MenuBar *>(pMenu) ); + } + + bool bAccessibilityEnabled( Application::GetSettings().GetMiscSettings().GetEnableATToolSupport() ); + sal_uInt16 nItemCount = pMenu->GetItemCount(); + OUString aItemCommand; + m_aMenuItemHandlerVector.reserve(nItemCount); + for ( sal_uInt16 i = 0; i < nItemCount; i++ ) + { + sal_uInt16 nItemId = FillItemCommand(aItemCommand,pMenu, i ); + + if (( pMenu->IsMenuBar() || bAccessibilityEnabled ) && + ( pMenu->GetItemText( nItemId ).isEmpty() )) + { + if ( !aItemCommand.isEmpty() ) + pMenu->SetItemText( nItemId, RetrieveLabelFromCommand( aItemCommand )); + } + + // Command can be just an alias to another command. + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aItemCommand, m_aModuleIdentifier); + OUString aRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties); + if ( !aRealCommand.isEmpty() ) + aItemCommand = aRealCommand; + + Reference< XDispatch > xDispatch; + Reference< XStatusListener > xStatusListener; + VclPtr<PopupMenu> pPopup = pMenu->GetPopupMenu( nItemId ); + // overwrite the show icons on menu option? + MenuItemBits nBits = pMenu->GetItemBits( nItemId ) & ( MenuItemBits::ICON | MenuItemBits::TEXT ); + bool bItemShowMenuImages = ( m_bShowMenuImages && nBits != MenuItemBits::TEXT ) || nBits & MenuItemBits::ICON; + + if ( pPopup ) + { + // Retrieve module identifier from Help Command entry + OUString aModuleIdentifier( rModuleIdentifier ); + if (!pMenu->GetHelpCommand(nItemId).isEmpty()) + { + aModuleIdentifier = pMenu->GetHelpCommand( nItemId ); + pMenu->SetHelpCommand( nItemId, "" ); + } + + // Retrieve possible attributes struct + Reference< XDispatchProvider > xPopupMenuDispatchProvider( rDispatchProvider ); + MenuAttributes* pAttributes = static_cast<MenuAttributes *>(pMenu->GetUserValue( nItemId )); + if ( pAttributes ) + xPopupMenuDispatchProvider = pAttributes->xDispatchProvider; + + if ( m_xPopupMenuControllerFactory.is() && + m_xPopupMenuControllerFactory->hasController( aItemCommand, aModuleIdentifier ) + ) + { + // Check if we have to create a popup menu for a uno based popup menu controller. + // We have to set an empty popup menu into our menu structure so the controller also + // works with inplace OLE. + MenuItemHandler* pItemHandler = new MenuItemHandler( nItemId, xStatusListener, xDispatch ); + rtl::Reference<VCLXPopupMenu> pVCLXPopupMenu = new VCLXPopupMenu(pPopup); + pItemHandler->xPopupMenu = pVCLXPopupMenu; + pItemHandler->aMenuItemURL = aItemCommand; + m_aMenuItemHandlerVector.push_back( std::unique_ptr<MenuItemHandler>(pItemHandler) ); + + if ( bAccessibilityEnabled || pMenu->IsMenuBar()) + { + if ( CreatePopupMenuController( pItemHandler, xPopupMenuDispatchProvider, aModuleIdentifier )) + pItemHandler->xPopupMenuController->updatePopupMenu(); + } + lcl_CheckForChildren(pMenu, nItemId); + } + else + { + // Check if this is the tools menu. Add menu item if needed + if ( aItemCommand == aCmdToolsMenu && AddonMenuManager::HasAddonMenuElements() ) + { + // Create addon popup menu if there exist elements and this is the tools popup menu + VclPtr<PopupMenu> pSubMenu = AddonMenuManager::CreateAddonMenu(rFrame); + if ( pSubMenu && ( pSubMenu->GetItemCount() > 0 )) + { + if ( pPopup->GetItemType( pPopup->GetItemCount() - 1 ) != MenuItemType::SEPARATOR ) + pPopup->InsertSeparator(); + + pPopup->InsertItem( ITEMID_ADDONLIST, OUString() ); + pPopup->SetPopupMenu( ITEMID_ADDONLIST, pSubMenu ); + pPopup->SetItemCommand( ITEMID_ADDONLIST, ".uno:Addons" ); + } + else + pSubMenu.disposeAndClear(); + } + + rtl::Reference<MenuBarManager> pSubMenuManager = new MenuBarManager( m_xContext, rFrame, m_xURLTransformer, + xPopupMenuDispatchProvider, aModuleIdentifier, pPopup, false, m_bHasMenuBar ); + + AddMenu(pSubMenuManager.get(), aItemCommand, nItemId); + } + } + else if ( pMenu->GetItemType( i ) != MenuItemType::SEPARATOR ) + { + if ( bItemShowMenuImages ) + m_bRetrieveImages = true; + + std::unique_ptr<MenuItemHandler> pItemHandler(new MenuItemHandler( nItemId, xStatusListener, xDispatch )); + // Retrieve possible attributes struct + MenuAttributes* pAttributes = static_cast<MenuAttributes *>(pMenu->GetUserValue( nItemId )); + if ( pAttributes ) + pItemHandler->aTargetFrame = pAttributes->aTargetFrame; + pItemHandler->aMenuItemURL = aItemCommand; + + if ( m_xPopupMenuControllerFactory.is() && + m_xPopupMenuControllerFactory->hasController( aItemCommand, m_aModuleIdentifier ) ) + { + // Check if we have to create a popup menu for a uno based popup menu controller. + // We have to set an empty popup menu into our menu structure so the controller also + // works with inplace OLE. + rtl::Reference<VCLXPopupMenu> pVCLXPopupMenu = new VCLXPopupMenu; + PopupMenu* pPopupMenu = static_cast<PopupMenu *>(pVCLXPopupMenu->GetMenu()); + pMenu->SetPopupMenu( pItemHandler->nItemId, pPopupMenu ); + pItemHandler->xPopupMenu = pVCLXPopupMenu; + + if ( bAccessibilityEnabled && CreatePopupMenuController( pItemHandler.get(), m_xDispatchProvider, m_aModuleIdentifier ) ) + { + pItemHandler->xPopupMenuController->updatePopupMenu(); + } + + lcl_CheckForChildren(pMenu, pItemHandler->nItemId); + } + + m_aMenuItemHandlerVector.push_back( std::move(pItemHandler) ); + } + } + + if ( m_bHasMenuBar && bAccessibilityEnabled ) + { + RetrieveShortcuts( m_aMenuItemHandlerVector ); + for (auto const& menuItemHandler : m_aMenuItemHandlerVector) + { + // Set key code, workaround for hard-coded shortcut F1 mapped to .uno:HelpIndex + // Only non-popup menu items can have a short-cut + if ( menuItemHandler->aMenuItemURL == aCmdHelpIndex ) + { + vcl::KeyCode aKeyCode( KEY_F1 ); + pMenu->SetAccelKey( menuItemHandler->nItemId, aKeyCode ); + } + else if ( pMenu->GetPopupMenu( menuItemHandler->nItemId ) == nullptr ) + pMenu->SetAccelKey( menuItemHandler->nItemId, menuItemHandler->aKeyCode ); + } + } + + SetHdl(); +} + +void MenuBarManager::impl_RetrieveShortcutsFromConfiguration( + const Reference< XAcceleratorConfiguration >& rAccelCfg, + const Sequence< OUString >& rCommands, + std::vector< std::unique_ptr<MenuItemHandler> >& aMenuShortCuts ) +{ + if ( !rAccelCfg.is() ) + return; + + try + { + css::awt::KeyEvent aKeyEvent; + Sequence< Any > aSeqKeyCode = rAccelCfg->getPreferredKeyEventsForCommandList( rCommands ); + for ( sal_Int32 i = 0; i < aSeqKeyCode.getLength(); i++ ) + { + if ( aSeqKeyCode[i] >>= aKeyEvent ) + aMenuShortCuts[i]->aKeyCode = svt::AcceleratorExecute::st_AWTKey2VCLKey( aKeyEvent ); + } + } + catch ( const IllegalArgumentException& ) + { + } +} + +void MenuBarManager::RetrieveShortcuts( std::vector< std::unique_ptr<MenuItemHandler> >& aMenuShortCuts ) +{ + Reference< XAcceleratorConfiguration > xDocAccelCfg( m_xDocAcceleratorManager ); + Reference< XAcceleratorConfiguration > xModuleAccelCfg( m_xModuleAcceleratorManager ); + Reference< XAcceleratorConfiguration > xGlobalAccelCfg( m_xGlobalAcceleratorManager ); + + if ( !m_bAcceleratorCfg ) + { + // Retrieve references on demand + m_bAcceleratorCfg = true; + if ( !xDocAccelCfg.is() ) + { + Reference< XController > xController = m_xFrame->getController(); + Reference< XModel > xModel; + if ( xController.is() ) + { + xModel = xController->getModel(); + if ( xModel.is() ) + { + Reference< XUIConfigurationManagerSupplier > xSupplier( xModel, UNO_QUERY ); + if ( xSupplier.is() ) + { + Reference< XUIConfigurationManager > xDocUICfgMgr = xSupplier->getUIConfigurationManager(); + if ( xDocUICfgMgr.is() ) + { + xDocAccelCfg = xDocUICfgMgr->getShortCutManager(); + m_xDocAcceleratorManager = xDocAccelCfg; + } + } + } + } + } + + if ( !xModuleAccelCfg.is() ) + { + Reference< XModuleUIConfigurationManagerSupplier > xModuleCfgMgrSupplier = + theModuleUIConfigurationManagerSupplier::get( m_xContext ); + try + { + Reference< XUIConfigurationManager > xUICfgMgr = xModuleCfgMgrSupplier->getUIConfigurationManager( m_aModuleIdentifier ); + if ( xUICfgMgr.is() ) + { + xModuleAccelCfg = xUICfgMgr->getShortCutManager(); + m_xModuleAcceleratorManager = xModuleAccelCfg; + } + } + catch ( const RuntimeException& ) + { + throw; + } + catch ( const Exception& ) + { + } + } + + if ( !xGlobalAccelCfg.is() ) try + { + xGlobalAccelCfg = GlobalAcceleratorConfiguration::create( m_xContext ); + m_xGlobalAcceleratorManager = xGlobalAccelCfg; + } + catch ( const css::uno::DeploymentException& ) + { + SAL_WARN("fwk.uielement", "GlobalAcceleratorConfiguration" + " not available. This should happen only on mobile platforms."); + } + } + + vcl::KeyCode aEmptyKeyCode; + Sequence< OUString > aSeq( aMenuShortCuts.size() ); + auto aSeqRange = asNonConstRange(aSeq); + const sal_uInt32 nCount = aMenuShortCuts.size(); + for ( sal_uInt32 i = 0; i < nCount; ++i ) + { + aSeqRange[i] = aMenuShortCuts[i]->aMenuItemURL; + aMenuShortCuts[i]->aKeyCode = aEmptyKeyCode; + } + + if ( m_xGlobalAcceleratorManager.is() ) + impl_RetrieveShortcutsFromConfiguration( xGlobalAccelCfg, aSeq, aMenuShortCuts ); + if ( m_xModuleAcceleratorManager.is() ) + impl_RetrieveShortcutsFromConfiguration( xModuleAccelCfg, aSeq, aMenuShortCuts ); + if ( m_xDocAcceleratorManager.is() ) + impl_RetrieveShortcutsFromConfiguration( xDocAccelCfg, aSeq, aMenuShortCuts ); +} + +void MenuBarManager::RetrieveImageManagers() +{ + if ( !m_xDocImageManager.is() ) + { + Reference< XController > xController = m_xFrame->getController(); + Reference< XModel > xModel; + if ( xController.is() ) + { + xModel = xController->getModel(); + if ( xModel.is() ) + { + Reference< XUIConfigurationManagerSupplier > xSupplier( xModel, UNO_QUERY ); + if ( xSupplier.is() ) + { + Reference< XUIConfigurationManager > xDocUICfgMgr = xSupplier->getUIConfigurationManager(); + m_xDocImageManager.set( xDocUICfgMgr->getImageManager(), UNO_QUERY ); + m_xDocImageManager->addConfigurationListener( + Reference< XUIConfigurationListener >(this) ); + } + } + } + } + + if ( !m_xModuleImageManager.is() ) + { + Reference< XModuleUIConfigurationManagerSupplier > xModuleCfgMgrSupplier = + theModuleUIConfigurationManagerSupplier::get( m_xContext ); + Reference< XUIConfigurationManager > xUICfgMgr = xModuleCfgMgrSupplier->getUIConfigurationManager( m_aModuleIdentifier ); + m_xModuleImageManager.set( xUICfgMgr->getImageManager(), UNO_QUERY ); + m_xModuleImageManager->addConfigurationListener( Reference< XUIConfigurationListener >(this) ); + } +} + +void MenuBarManager::FillMenuWithConfiguration( + sal_uInt16& nId, + Menu* pMenu, + const OUString& rModuleIdentifier, + const Reference< XIndexAccess >& rItemContainer, + const Reference< XURLTransformer >& rTransformer ) +{ + Reference< XDispatchProvider > xEmptyDispatchProvider; + MenuBarManager::FillMenu( nId, pMenu, rModuleIdentifier, rItemContainer, xEmptyDispatchProvider ); + + // Merge add-on menu entries into the menu bar + MenuBarManager::MergeAddonMenus( pMenu, + AddonsOptions().GetMergeMenuInstructions(), + rModuleIdentifier ); + + bool bHasDisabledEntries = SvtCommandOptions().HasEntries( SvtCommandOptions::CMDOPTION_DISABLED ); + if ( !bHasDisabledEntries ) + return; + + sal_uInt16 nCount = pMenu->GetItemCount(); + for ( sal_uInt16 i = 0; i < nCount; i++ ) + { + sal_uInt16 nID = pMenu->GetItemId( i ); + if ( nID > 0 ) + { + PopupMenu* pPopupMenu = pMenu->GetPopupMenu( nID ); + if ( pPopupMenu ) + { + if ( MustBeHidden( pPopupMenu, rTransformer )) + pMenu->HideItem( nId ); + } + } + } +} + +void MenuBarManager::FillMenu( + sal_uInt16& nId, + Menu* pMenu, + const OUString& rModuleIdentifier, + const Reference< XIndexAccess >& rItemContainer, + const Reference< XDispatchProvider >& rDispatchProvider ) +{ + // Fill menu bar with container contents + for ( sal_Int32 n = 0; n < rItemContainer->getCount(); n++ ) + { + Sequence< PropertyValue > aProps; + OUString aCommandURL; + OUString aLabel; + OUString aModuleIdentifier( rModuleIdentifier ); + sal_uInt16 nType = 0; + Reference< XIndexAccess > xIndexContainer; + Reference< XDispatchProvider > xDispatchProvider( rDispatchProvider ); + sal_Int16 nStyle = 0; + try + { + if ( rItemContainer->getByIndex( n ) >>= aProps ) + { + bool bShow = true; + bool bEnabled = true; + + for ( beans::PropertyValue const & rProp : std::as_const(aProps) ) + { + OUString aPropName = rProp.Name; + if ( aPropName == "CommandURL" ) + rProp.Value >>= aCommandURL; + else if ( aPropName == "ItemDescriptorContainer" ) + rProp.Value >>= xIndexContainer; + else if ( aPropName == "Label" ) + rProp.Value >>= aLabel; + else if ( aPropName == "Type" ) + rProp.Value >>= nType; + else if ( aPropName == "ModuleIdentifier" ) + rProp.Value >>= aModuleIdentifier; + else if ( aPropName == "DispatchProvider" ) + rProp.Value >>= xDispatchProvider; + else if ( aPropName == "Style" ) + rProp.Value >>= nStyle; + else if ( aPropName == "IsVisible" ) + rProp.Value >>= bShow; + else if ( aPropName == "Enabled" ) + rProp.Value >>= bEnabled; + } + + if (!aCommandURL.isEmpty() && vcl::CommandInfoProvider::IsExperimental(aCommandURL, rModuleIdentifier) && + !officecfg::Office::Common::Misc::ExperimentalMode::get()) + { + continue; + } + + if ( nType == css::ui::ItemType::DEFAULT ) + { + pMenu->InsertItem( nId, aLabel ); + pMenu->SetItemCommand( nId, aCommandURL ); + + if ( nStyle ) + { + MenuItemBits nBits = pMenu->GetItemBits( nId ); + if ( nStyle & css::ui::ItemStyle::ICON ) + nBits |= MenuItemBits::ICON; + if ( nStyle & css::ui::ItemStyle::TEXT ) + nBits |= MenuItemBits::TEXT; + if ( nStyle & css::ui::ItemStyle::RADIO_CHECK ) + nBits |= MenuItemBits::RADIOCHECK; + pMenu->SetItemBits( nId, nBits ); + } + + if ( !bShow ) + pMenu->HideItem( nId ); + + if ( !bEnabled) + pMenu->EnableItem( nId, false ); + + if ( xIndexContainer.is() ) + { + VclPtr<PopupMenu> pNewPopupMenu = VclPtr<PopupMenu>::Create(); + pMenu->SetPopupMenu( nId, pNewPopupMenu ); + + if ( xDispatchProvider.is() ) + { + // Use attributes struct to transport special dispatch provider + void* nAttributePtr = MenuAttributes::CreateAttribute(xDispatchProvider); + pMenu->SetUserValue(nId, nAttributePtr, MenuAttributes::ReleaseAttribute); + } + + // Use help command to transport module identifier + if ( !aModuleIdentifier.isEmpty() ) + pMenu->SetHelpCommand( nId, aModuleIdentifier ); + + ++nId; + FillMenu( nId, pNewPopupMenu, aModuleIdentifier, xIndexContainer, xDispatchProvider ); + } + else + ++nId; + } + else + { + pMenu->InsertSeparator(); + ++nId; + } + } + } + catch ( const IndexOutOfBoundsException& ) + { + break; + } + } +} + +void MenuBarManager::MergeAddonMenus( + Menu* pMenuBar, + const MergeMenuInstructionContainer& aMergeInstructionContainer, + const OUString& rModuleIdentifier ) +{ + // set start value for the item ID for the new addon menu items + sal_uInt16 nItemId = ADDONMENU_MERGE_ITEMID_START; + + const sal_uInt32 nCount = aMergeInstructionContainer.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + const MergeMenuInstruction& rMergeInstruction = aMergeInstructionContainer[i]; + + if ( MenuBarMerger::IsCorrectContext( rMergeInstruction.aMergeContext, rModuleIdentifier )) + { + ::std::vector< OUString > aMergePath; + + // retrieve the merge path from the merge point string + MenuBarMerger::RetrieveReferencePath( rMergeInstruction.aMergePoint, aMergePath ); + + // convert the sequence/sequence property value to a more convenient vector<> + AddonMenuContainer aMergeMenuItems; + MenuBarMerger::GetSubMenu( rMergeInstruction.aMergeMenu, aMergeMenuItems ); + + // try to find the reference point for our merge operation + Menu* pMenu = pMenuBar; + ReferencePathInfo aResult = MenuBarMerger::FindReferencePath( aMergePath, pMenu ); + + if ( aResult.eResult == RP_OK ) + { + // normal merge operation + MenuBarMerger::ProcessMergeOperation( aResult.pPopupMenu, + aResult.nPos, + nItemId, + rMergeInstruction.aMergeCommand, + rMergeInstruction.aMergeCommandParameter, + rModuleIdentifier, + aMergeMenuItems ); + } + else + { + // fallback + MenuBarMerger::ProcessFallbackOperation( aResult, + nItemId, + rMergeInstruction.aMergeCommand, + rMergeInstruction.aMergeFallback, + aMergePath, + rModuleIdentifier, + aMergeMenuItems ); + } + } + } +} + +void MenuBarManager::SetItemContainer( const Reference< XIndexAccess >& rItemContainer ) +{ + SolarMutexGuard aSolarMutexGuard; + + Reference< XFrame > xFrame = m_xFrame; + + // Clear MenuBarManager structures + { + // Check active state as we cannot change our VCL menu during activation by the user + if ( m_bActive ) + { + m_xDeferredItemContainer = rItemContainer; + return; + } + + RemoveListener(); + m_aMenuItemHandlerVector.clear(); + m_pVCLMenu->Clear(); + + sal_uInt16 nId = 1; + + // Fill menu bar with container contents + FillMenuWithConfiguration( nId, m_pVCLMenu, m_aModuleIdentifier, rItemContainer, m_xURLTransformer ); + + // Refill menu manager again + Reference< XDispatchProvider > xDispatchProvider; + FillMenuManager( m_pVCLMenu, xFrame, xDispatchProvider, m_aModuleIdentifier, false ); + + // add itself as frame action listener + m_xFrame->addFrameActionListener( Reference< XFrameActionListener >(this) ); + } +} + +void MenuBarManager::GetPopupController( PopupControllerCache& rPopupController ) +{ + + SolarMutexGuard aSolarMutexGuard; + + for (auto const& menuItemHandler : m_aMenuItemHandlerVector) + { + if ( menuItemHandler->xPopupMenuController.is() ) + { + Reference< XDispatchProvider > xDispatchProvider( menuItemHandler->xPopupMenuController, UNO_QUERY ); + + PopupControllerEntry aPopupControllerEntry; + aPopupControllerEntry.m_xDispatchProvider = xDispatchProvider; + + // Just use the main part of the URL for popup menu controllers + sal_Int32 nSchemePart( 0 ); + OUString aMenuURL( menuItemHandler->aMenuItemURL ); + + nSchemePart = aMenuURL.indexOf( ':' ); + if (( nSchemePart > 0 ) && + ( aMenuURL.getLength() > ( nSchemePart+1 ))) + { + OUString aMainURL( "vnd.sun.star.popup:" ); + sal_Int32 nQueryPart = aMenuURL.indexOf( '?', nSchemePart ); + if ( nQueryPart > 0 ) + aMainURL += aMenuURL.subView( nSchemePart, nQueryPart-nSchemePart ); + else if ( nQueryPart == -1 ) + aMainURL += aMenuURL.subView( nSchemePart+1 ); + + rPopupController.emplace( aMainURL, aPopupControllerEntry ); + } + } + if ( menuItemHandler->xSubMenuManager.is() ) + { + MenuBarManager* pMenuBarManager = static_cast<MenuBarManager*>(menuItemHandler->xSubMenuManager.get()); + if ( pMenuBarManager ) + pMenuBarManager->GetPopupController( rPopupController ); + } + } +} + +void MenuBarManager::AddMenu(MenuBarManager* pSubMenuManager,const OUString& _sItemCommand,sal_uInt16 _nItemId) +{ + Reference< XStatusListener > xSubMenuManager( pSubMenuManager ); + m_xFrame->addFrameActionListener( Reference< XFrameActionListener >( xSubMenuManager, UNO_QUERY )); + + Reference< XDispatch > xDispatch; + std::unique_ptr<MenuItemHandler> pMenuItemHandler(new MenuItemHandler( + _nItemId, + xSubMenuManager, + xDispatch )); + pMenuItemHandler->aMenuItemURL = _sItemCommand; + m_aMenuItemHandlerVector.push_back( std::move(pMenuItemHandler) ); +} + +sal_uInt16 MenuBarManager::FillItemCommand(OUString& _rItemCommand, Menu* _pMenu,sal_uInt16 _nIndex) const +{ + sal_uInt16 nItemId = _pMenu->GetItemId( _nIndex ); + + _rItemCommand = _pMenu->GetItemCommand( nItemId ); + if ( _rItemCommand.isEmpty() ) + { + _rItemCommand = "slot:" + OUString::number( nItemId ); + _pMenu->SetItemCommand( nItemId, _rItemCommand ); + } + return nItemId; +} + +void MenuBarManager::SetHdl() +{ + m_pVCLMenu->SetActivateHdl( LINK( this, MenuBarManager, Activate )); + m_pVCLMenu->SetDeactivateHdl( LINK( this, MenuBarManager, Deactivate )); + m_pVCLMenu->SetSelectHdl( LINK( this, MenuBarManager, Select )); + + if ( !m_xURLTransformer.is() && m_xContext.is() ) + m_xURLTransformer.set( URLTransformer::create( m_xContext) ); +} + +void MenuBarManager::FillMenuImages(Reference< XFrame > const & _xFrame, Menu* _pMenu,bool bShowMenuImages) +{ + AddonsOptions aAddonOptions; + + for ( sal_uInt16 nPos = 0; nPos < _pMenu->GetItemCount(); nPos++ ) + { + sal_uInt16 nId = _pMenu->GetItemId( nPos ); + if ( _pMenu->GetItemType( nPos ) != MenuItemType::SEPARATOR ) + { + // overwrite the show icons on menu option? + MenuItemBits nBits = _pMenu->GetItemBits( nId ) & ( MenuItemBits::ICON | MenuItemBits::TEXT ); + bool bTmpShowMenuImages = ( bShowMenuImages && nBits != MenuItemBits::TEXT ) || nBits & MenuItemBits::ICON; + + if ( bTmpShowMenuImages ) + { + OUString aMenuItemCommand = _pMenu->GetItemCommand( nId ); + Image aImage = vcl::CommandInfoProvider::GetImageForCommand(aMenuItemCommand, _xFrame); + if ( !aImage ) + aImage = Image(aAddonOptions.GetImageFromURL(aMenuItemCommand, false)); + + _pMenu->SetItemImage( nId, aImage ); + } + else + _pMenu->SetItemImage( nId, Image() ); + } + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/menubarmerger.cxx b/framework/source/uielement/menubarmerger.cxx new file mode 100644 index 000000000..c5730d2ef --- /dev/null +++ b/framework/source/uielement/menubarmerger.cxx @@ -0,0 +1,430 @@ +/* -*- 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 <uielement/menubarmerger.hxx> +#include <framework/addonsoptions.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <o3tl/string_view.hxx> + +using namespace ::com::sun::star; + +const char SEPARATOR_STRING[] = "private:separator"; + +const char16_t MERGECOMMAND_ADDAFTER[] = u"AddAfter"; +const char16_t MERGECOMMAND_ADDBEFORE[] = u"AddBefore"; +const char16_t MERGECOMMAND_REPLACE[] = u"Replace"; +const char16_t MERGECOMMAND_REMOVE[] = u"Remove"; + +const char16_t MERGEFALLBACK_ADDPATH[] = u"AddPath"; +const char16_t MERGEFALLBACK_IGNORE[] = u"Ignore"; + +namespace framework +{ + +/** + Check whether a module identifier is part of a context + defined by a colon separated list of module identifier. + + @param + rContext + + Describes a context string list where all contexts + are delimited by a colon. For more information about + the module identifier used as context strings see the + IDL description of css::frame::XModuleManager + + @param + rModuleIdentifier + + A string describing a module identifier. See IDL + description of css::frame::XModuleManager. + +*/ +bool MenuBarMerger::IsCorrectContext( + std::u16string_view rContext, std::u16string_view rModuleIdentifier ) +{ + return ( rContext.empty() || ( rContext.find( rModuleIdentifier ) != std::u16string_view::npos )); +} + +void MenuBarMerger::RetrieveReferencePath( + std::u16string_view rReferencePathString, + ::std::vector< OUString >& rReferencePath ) +{ + const char aDelimiter = '\\'; + + rReferencePath.clear(); + sal_Int32 nIndex( 0 ); + do + { + OUString aToken( o3tl::getToken(rReferencePathString, 0, aDelimiter, nIndex ) ); + if ( !aToken.isEmpty() ) + rReferencePath.push_back( aToken ); + } + while ( nIndex >= 0 ); +} + +ReferencePathInfo MenuBarMerger::FindReferencePath( + const ::std::vector< OUString >& rReferencePath, + Menu* pMenu ) +{ + sal_uInt32 i( 0 ); + const sal_uInt32 nCount( rReferencePath.size() ); + + ReferencePathInfo aResult; + if ( !nCount ) + { + aResult.pPopupMenu = nullptr; + aResult.nPos = 0; + aResult.nLevel = -1; + aResult.eResult = RP_MENUITEM_NOT_FOUND; + return aResult; + } + + Menu* pCurrMenu( pMenu ); + RPResultInfo eResult( RP_OK ); + + sal_Int32 nLevel( - 1 ); + sal_uInt16 nPos( MENU_ITEM_NOTFOUND ); + do + { + ++nLevel; + OUString aCmd( rReferencePath[i] ); + + if ( i == nCount-1 ) + { + // Check last reference path element. Must be a leave (menu item). + sal_uInt16 nTmpPos = FindMenuItem( aCmd, pCurrMenu ); + if ( nTmpPos != MENU_ITEM_NOTFOUND ) + nPos = nTmpPos; + eResult = ( nTmpPos != MENU_ITEM_NOTFOUND ) ? RP_OK : RP_MENUITEM_NOT_FOUND; + } + else + { + // Check reference path element. Must be a node (popup menu)! + sal_uInt16 nTmpPos = FindMenuItem( aCmd, pCurrMenu ); + if ( nTmpPos != MENU_ITEM_NOTFOUND ) + { + sal_uInt16 nItemId = pCurrMenu->GetItemId( nTmpPos ); + Menu* pTmpMenu = pCurrMenu->GetPopupMenu( nItemId ); + if ( pTmpMenu != nullptr ) + pCurrMenu = pTmpMenu; + else + { + nPos = nTmpPos; + eResult = RP_MENUITEM_INSTEAD_OF_POPUPMENU_FOUND; + } + } + else + eResult = RP_POPUPMENU_NOT_FOUND; + } + i++; + } + while ((i < nCount) && (eResult == RP_OK)); + + aResult.pPopupMenu = pCurrMenu; + aResult.nPos = nPos; + aResult.nLevel = nLevel; + aResult.eResult = eResult; + + return aResult; +} + +sal_uInt16 MenuBarMerger::FindMenuItem( std::u16string_view rCmd, Menu const * pCurrMenu ) +{ + for ( sal_uInt16 i = 0; i < pCurrMenu->GetItemCount(); i++ ) + { + const sal_uInt16 nItemId = pCurrMenu->GetItemId( i ); + if ( nItemId > 0 ) + { + if ( rCmd == pCurrMenu->GetItemCommand( nItemId ) ) + return i; + } + } + + return MENU_ITEM_NOTFOUND; +} + +bool MenuBarMerger::CreateSubMenu( + Menu* pSubMenu, + sal_uInt16& nItemId, + const OUString& rModuleIdentifier, + const AddonMenuContainer& rAddonSubMenu ) +{ + const sal_uInt32 nSize = rAddonSubMenu.size(); + for ( sal_uInt32 i = 0; i < nSize; i++ ) + { + const AddonMenuItem& rMenuItem = rAddonSubMenu[i]; + + if ( IsCorrectContext( rMenuItem.aContext, rModuleIdentifier )) + { + if ( rMenuItem.aURL == SEPARATOR_STRING ) + { + pSubMenu->InsertSeparator(); + } + else + { + pSubMenu->InsertItem(nItemId, rMenuItem.aTitle); + pSubMenu->SetItemCommand( nItemId, rMenuItem.aURL ); + if ( !rMenuItem.aSubMenu.empty() ) + { + VclPtr<PopupMenu> pPopupMenu = VclPtr<PopupMenu>::Create(); + pSubMenu->SetPopupMenu( nItemId, pPopupMenu ); + ++nItemId; + + CreateSubMenu( pPopupMenu, nItemId, rModuleIdentifier, rMenuItem.aSubMenu ); + } + else + ++nItemId; + } + } + } + + return true; +} + +bool MenuBarMerger::MergeMenuItems( + Menu* pMenu, + sal_uInt16 nPos, + sal_uInt16 nModIndex, + sal_uInt16& nItemId, + const OUString& rModuleIdentifier, + const AddonMenuContainer& rAddonMenuItems ) +{ + sal_uInt16 nIndex( 0 ); + const sal_uInt32 nSize = rAddonMenuItems.size(); + for ( sal_uInt32 i = 0; i < nSize; i++ ) + { + const AddonMenuItem& rMenuItem = rAddonMenuItems[i]; + + if ( IsCorrectContext( rMenuItem.aContext, rModuleIdentifier )) + { + if ( rMenuItem.aURL == SEPARATOR_STRING ) + { + pMenu->InsertSeparator(OString(), nPos+nModIndex+nIndex); + } + else + { + pMenu->InsertItem(nItemId, rMenuItem.aTitle, MenuItemBits::NONE, OString(), nPos+nModIndex+nIndex); + pMenu->SetItemCommand( nItemId, rMenuItem.aURL ); + if ( !rMenuItem.aSubMenu.empty() ) + { + VclPtr<PopupMenu> pSubMenu = VclPtr<PopupMenu>::Create(); + pMenu->SetPopupMenu( nItemId, pSubMenu ); + ++nItemId; + + CreateSubMenu( pSubMenu, nItemId, rModuleIdentifier, rMenuItem.aSubMenu ); + } + else + ++nItemId; + } + ++nIndex; + } + } + + return true; +} + +bool MenuBarMerger::ReplaceMenuItem( + Menu* pMenu, + sal_uInt16 nPos, + sal_uInt16& rItemId, + const OUString& rModuleIdentifier, + const AddonMenuContainer& rAddonMenuItems ) +{ + // There is no replace available. Therefore we first have to + // remove the old menu entry, + pMenu->RemoveItem( nPos ); + + return MergeMenuItems( pMenu, nPos, 0, rItemId, rModuleIdentifier, rAddonMenuItems ); +} + +bool MenuBarMerger::RemoveMenuItems( + Menu* pMenu, + sal_uInt16 nPos, + std::u16string_view rMergeCommandParameter ) +{ + const sal_uInt16 nParam( sal_uInt16( o3tl::toInt32(rMergeCommandParameter) )); + sal_uInt16 nCount = std::max( nParam, sal_uInt16(1) ); + + sal_uInt16 i = 0; + while (( nPos < pMenu->GetItemCount() ) && ( i < nCount )) + { + pMenu->RemoveItem( nPos ); + ++i; + } + + return true; +} + +bool MenuBarMerger::ProcessMergeOperation( + Menu* pMenu, + sal_uInt16 nPos, + sal_uInt16& nItemId, + std::u16string_view rMergeCommand, + std::u16string_view rMergeCommandParameter, + const OUString& rModuleIdentifier, + const AddonMenuContainer& rAddonMenuItems ) +{ + sal_uInt16 nModIndex( 0 ); + + if ( rMergeCommand == MERGECOMMAND_ADDBEFORE ) + { + nModIndex = 0; + return MergeMenuItems( pMenu, nPos, nModIndex, nItemId, rModuleIdentifier, rAddonMenuItems ); + } + else if ( rMergeCommand == MERGECOMMAND_ADDAFTER ) + { + nModIndex = 1; + return MergeMenuItems( pMenu, nPos, nModIndex, nItemId, rModuleIdentifier, rAddonMenuItems ); + } + else if ( rMergeCommand == MERGECOMMAND_REPLACE ) + { + return ReplaceMenuItem( pMenu, nPos, nItemId, rModuleIdentifier, rAddonMenuItems ); + } + else if ( rMergeCommand == MERGECOMMAND_REMOVE ) + { + return RemoveMenuItems( pMenu, nPos, rMergeCommandParameter ); + } + + return false; +} + +bool MenuBarMerger::ProcessFallbackOperation( + const ReferencePathInfo& aRefPathInfo, + sal_uInt16& rItemId, + std::u16string_view rMergeCommand, + std::u16string_view rMergeFallback, + const ::std::vector< OUString >& rReferencePath, + const std::u16string_view rModuleIdentifier, + const AddonMenuContainer& rAddonMenuItems ) +{ + if (( rMergeFallback == MERGEFALLBACK_IGNORE ) || + ( rMergeCommand == MERGECOMMAND_REPLACE ) || + ( rMergeCommand == MERGECOMMAND_REMOVE ) ) + { + return true; + } + else if ( rMergeFallback == MERGEFALLBACK_ADDPATH ) + { + Menu* pCurrMenu( aRefPathInfo.pPopupMenu ); + sal_Int32 nLevel( aRefPathInfo.nLevel ); + const sal_Int32 nSize( rReferencePath.size() ); + bool bFirstLevel( true ); + + while ( nLevel < nSize ) + { + if ( nLevel == nSize-1 ) + { + const sal_uInt32 nCount = rAddonMenuItems.size(); + for ( sal_uInt32 i = 0; i < nCount; ++i ) + { + const AddonMenuItem& rMenuItem = rAddonMenuItems[i]; + if ( IsCorrectContext( rMenuItem.aContext, rModuleIdentifier )) + { + if ( rMenuItem.aURL == SEPARATOR_STRING ) + pCurrMenu->InsertSeparator(); + else + { + pCurrMenu->InsertItem(rItemId, rMenuItem.aTitle); + pCurrMenu->SetItemCommand( rItemId, rMenuItem.aURL ); + ++rItemId; + } + } + } + } + else + { + const OUString aCmd( rReferencePath[nLevel] ); + + VclPtr<PopupMenu> pPopupMenu = VclPtr<PopupMenu>::Create(); + + if ( bFirstLevel && ( aRefPathInfo.eResult == RP_MENUITEM_INSTEAD_OF_POPUPMENU_FOUND )) + { + // special case: menu item without popup + sal_uInt16 nInsPos = aRefPathInfo.nPos; + sal_uInt16 nSetItemId = pCurrMenu->GetItemId( nInsPos ); + pCurrMenu->SetItemCommand( nSetItemId, aCmd ); + pCurrMenu->SetPopupMenu( nSetItemId, pPopupMenu ); + } + else + { + // normal case: insert a new item with popup + pCurrMenu->InsertItem(rItemId, OUString()); + pCurrMenu->SetItemCommand( rItemId, aCmd ); + pCurrMenu->SetPopupMenu( rItemId, pPopupMenu ); + } + + pCurrMenu = pPopupMenu; + ++rItemId; + bFirstLevel = false; + } + ++nLevel; + } + return true; + } + + return false; +} + +void MenuBarMerger::GetMenuEntry( + const uno::Sequence< beans::PropertyValue >& rAddonMenuEntry, + AddonMenuItem& rAddonMenuItem ) +{ + // Reset submenu member + rAddonMenuItem.aSubMenu.clear(); + + for ( const beans::PropertyValue& rProp : rAddonMenuEntry ) + { + OUString aMenuEntryPropName = rProp.Name; + if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_URL ) + rProp.Value >>= rAddonMenuItem.aURL; + else if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_TITLE ) + rProp.Value >>= rAddonMenuItem.aTitle; + else if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_SUBMENU ) + { + uno::Sequence< uno::Sequence< beans::PropertyValue > > aSubMenu; + rProp.Value >>= aSubMenu; + GetSubMenu( aSubMenu, rAddonMenuItem.aSubMenu ); + } + else if ( aMenuEntryPropName == ADDONSMENUITEM_STRING_CONTEXT ) + rProp.Value >>= rAddonMenuItem.aContext; + } +} + +void MenuBarMerger::GetSubMenu( + const uno::Sequence< uno::Sequence< beans::PropertyValue > >& rSubMenuEntries, + AddonMenuContainer& rSubMenu ) +{ + rSubMenu.clear(); + + const sal_Int32 nCount = rSubMenuEntries.getLength(); + rSubMenu.reserve(rSubMenu.size() + nCount); + for ( sal_Int32 i = 0; i < nCount; i++ ) + { + const uno::Sequence< beans::PropertyValue >& rMenuEntry = rSubMenuEntries[ i ]; + + AddonMenuItem aMenuItem; + GetMenuEntry( rMenuEntry, aMenuItem ); + rSubMenu.push_back( aMenuItem ); + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/menubarwrapper.cxx b/framework/source/uielement/menubarwrapper.cxx new file mode 100644 index 000000000..3bbd71ed7 --- /dev/null +++ b/framework/source/uielement/menubarwrapper.cxx @@ -0,0 +1,352 @@ +/* -*- 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 <uielement/menubarwrapper.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/ui/UIElementType.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +#include <comphelper/sequence.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <toolkit/awt/vclxmenu.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::frame; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::awt; +using namespace com::sun::star::util; +using namespace ::com::sun::star::ui; + +namespace framework +{ + +void SAL_CALL MenuBarWrapper::acquire() noexcept \ +{ \ + /* Don't use mutex in methods of XInterface! */ \ + UIConfigElementWrapperBase::acquire(); \ +} \ + \ +void SAL_CALL MenuBarWrapper::release() noexcept \ +{ \ + /* Don't use mutex in methods of XInterface! */ \ + UIConfigElementWrapperBase::release(); \ +} + +css::uno::Any SAL_CALL MenuBarWrapper::queryInterface( const css::uno::Type& aType ) +{ + /* Attention: Don't use mutex or guard in this method!!! Is a method of XInterface. */ + /* Ask for my own supported interfaces ... */ + css::uno::Any aReturn = ::cppu::queryInterface( aType, + static_cast< css::lang::XTypeProvider* >( this ), + static_cast< css::ui::XUIElement* >( this ), + static_cast< css::ui::XUIElementSettings* >( this ), + static_cast< css::beans::XMultiPropertySet* >( this ), + static_cast< css::beans::XFastPropertySet* >( this ), + static_cast< css::beans::XPropertySet* >( this ), + static_cast< css::lang::XInitialization* >( this ), + static_cast< css::lang::XComponent* >( this ), + static_cast< css::util::XUpdatable* >( this ), + static_cast< css::ui::XUIConfigurationListener* >( this ), + static_cast< css::container::XNameAccess* >( static_cast< css::container::XElementAccess* >( this ) ) + ); + /* If searched interface not supported by this class ... */ + if ( !aReturn.hasValue() ) + { + /* ... ask baseclass for interfaces! */ + aReturn = UIConfigElementWrapperBase::queryInterface( aType ); + } + /* Return result of this search. */ + return aReturn; +} + +css::uno::Sequence< sal_Int8 > SAL_CALL MenuBarWrapper::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +css::uno::Sequence< css::uno::Type > SAL_CALL MenuBarWrapper::getTypes() +{ + /* Attention: "TYPES" will expand to "(...)"! */ + static cppu::OTypeCollection ourTypeCollection { + cppu::UnoType<css::lang::XTypeProvider>::get() , + cppu::UnoType<css::ui::XUIElement>::get() , + cppu::UnoType<css::ui::XUIElementSettings>::get() , + cppu::UnoType<css::beans::XMultiPropertySet>::get() , + cppu::UnoType<css::beans::XFastPropertySet>::get() , + cppu::UnoType<css::beans::XPropertySet>::get() , + cppu::UnoType<css::lang::XInitialization>::get() , + cppu::UnoType<css::lang::XComponent>::get() , + cppu::UnoType<css::util::XUpdatable>::get() , + cppu::UnoType<css::ui::XUIConfigurationListener>::get() , + cppu::UnoType<css::container::XNameAccess>::get() }; + return ourTypeCollection.getTypes(); +} + +MenuBarWrapper::MenuBarWrapper( + css::uno::Reference< css::uno::XComponentContext > xContext + ) +: UIConfigElementWrapperBase( UIElementType::MENUBAR ), + m_bRefreshPopupControllerCache( true ), + m_xContext(std::move( xContext )) +{ +} + +MenuBarWrapper::~MenuBarWrapper() +{ +} + +void SAL_CALL MenuBarWrapper::dispose() +{ + Reference< XComponent > xThis(this); + + css::lang::EventObject aEvent( xThis ); + m_aListenerContainer.disposeAndClear( aEvent ); + + SolarMutexGuard g; + + m_xMenuBarManager->dispose(); + m_xMenuBarManager.clear(); + m_xConfigSource.clear(); + m_xConfigData.clear(); + + m_xMenuBar.clear(); + m_bDisposed = true; +} + +// XInitialization +void SAL_CALL MenuBarWrapper::initialize( const Sequence< Any >& aArguments ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_bInitialized ) + return; + + OUString aModuleIdentifier; + UIConfigElementWrapperBase::initialize( aArguments ); + + Reference< XFrame > xFrame( m_xWeakFrame ); + if ( !(xFrame.is() && m_xConfigSource.is()) ) + return; + + // Create VCL menubar which will be filled with settings data + VclPtr<MenuBar> pVCLMenuBar; + { + SolarMutexGuard aSolarMutexGuard; + pVCLMenuBar = VclPtr<MenuBar>::Create(); + } + + Reference< XModuleManager2 > xModuleManager = ModuleManager::create( m_xContext ); + + try + { + aModuleIdentifier = xModuleManager->identify( xFrame ); + } + catch( const Exception& ) + { + } + + Reference< XURLTransformer > xTrans; + try + { + xTrans.set( URLTransformer::create(m_xContext) ); + m_xConfigData = m_xConfigSource->getSettings( m_aResourceURL, false ); + if ( m_xConfigData.is() ) + { + // Fill menubar with container contents + sal_uInt16 nId = 1; + MenuBarManager::FillMenuWithConfiguration( nId, pVCLMenuBar, aModuleIdentifier, m_xConfigData, xTrans ); + } + } + catch ( const NoSuchElementException& ) + { + } + + bool bMenuOnly( false ); + for ( const Any& rArg : aArguments ) + { + PropertyValue aPropValue; + if ( rArg >>= aPropValue ) + { + if ( aPropValue.Name == "MenuOnly" ) + aPropValue.Value >>= bMenuOnly; + } + } + + if ( !bMenuOnly ) + { + // Initialize menubar manager with our vcl menu bar. There are some situations where we only want to get the menu without any + // interaction which is done by the menu bar manager. This must be requested by a special property called "MenuOnly". Be careful + // a menu bar created with this property is not fully supported. It must be attached to a real menu bar manager to have full + // support. This feature is currently used for "Inplace editing"! + Reference< XDispatchProvider > xDispatchProvider; + + m_xMenuBarManager = new MenuBarManager( m_xContext, + xFrame, + xTrans, + xDispatchProvider, + aModuleIdentifier, + pVCLMenuBar, + false ); + } + + // Initialize toolkit menu bar implementation to have awt::XMenuBar for data exchange. + // Don't use this toolkit menu bar or one of its functions. It is only used as a data container! + m_xMenuBar = new VCLXMenuBar( pVCLMenuBar ); +} + +// XUIElementSettings +void SAL_CALL MenuBarWrapper::updateSettings() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_xMenuBarManager.is() ) + return; + + if ( m_xConfigSource.is() && m_bPersistent ) + { + try + { + MenuBarManager* pMenuBarManager = static_cast< MenuBarManager *>( m_xMenuBarManager.get() ); + + m_xConfigData = m_xConfigSource->getSettings( m_aResourceURL, false ); + if ( m_xConfigData.is() ) + pMenuBarManager->SetItemContainer( m_xConfigData ); + } + catch ( const NoSuchElementException& ) + { + } + } + else if ( !m_bPersistent ) + { + // Transient menubar: do nothing + } +} +void MenuBarWrapper::impl_fillNewData() +{ + // Transient menubar => Fill menubar with new data + MenuBarManager* pMenuBarManager = static_cast< MenuBarManager *>( m_xMenuBarManager.get() ); + + if ( pMenuBarManager ) + pMenuBarManager->SetItemContainer( m_xConfigData ); +} + +void MenuBarWrapper::fillPopupControllerCache() +{ + if ( m_bRefreshPopupControllerCache ) + { + MenuBarManager* pMenuBarManager = static_cast< MenuBarManager *>( m_xMenuBarManager.get() ); + if ( pMenuBarManager ) + pMenuBarManager->GetPopupController( m_aPopupControllerCache ); + if ( !m_aPopupControllerCache.empty() ) + m_bRefreshPopupControllerCache = false; + } +} + +// XElementAccess +Type SAL_CALL MenuBarWrapper::getElementType() +{ + return cppu::UnoType<XDispatchProvider>::get(); +} + +sal_Bool SAL_CALL MenuBarWrapper::hasElements() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + fillPopupControllerCache(); + return ( !m_aPopupControllerCache.empty() ); +} + +// XNameAccess +Any SAL_CALL MenuBarWrapper::getByName( + const OUString& aName ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + fillPopupControllerCache(); + + PopupControllerCache::const_iterator pIter = m_aPopupControllerCache.find( aName ); + if ( pIter == m_aPopupControllerCache.end() ) + throw container::NoSuchElementException(); + + uno::Reference< frame::XDispatchProvider > xDispatchProvider = pIter->second.m_xDispatchProvider; + return uno::Any( xDispatchProvider ); +} + +Sequence< OUString > SAL_CALL MenuBarWrapper::getElementNames() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + fillPopupControllerCache(); + + return comphelper::mapKeysToSequence( m_aPopupControllerCache ); +} + +sal_Bool SAL_CALL MenuBarWrapper::hasByName( + const OUString& aName ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + fillPopupControllerCache(); + + PopupControllerCache::const_iterator pIter = m_aPopupControllerCache.find( aName ); + if ( pIter != m_aPopupControllerCache.end() ) + return true; + else + return false; +} + +// XUIElement +Reference< XInterface > SAL_CALL MenuBarWrapper::getRealInterface() +{ + if ( m_bDisposed ) + throw DisposedException(); + + return Reference< XInterface >( m_xMenuBarManager, UNO_QUERY ); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/newmenucontroller.cxx b/framework/source/uielement/newmenucontroller.cxx new file mode 100644 index 000000000..d282509d9 --- /dev/null +++ b/framework/source/uielement/newmenucontroller.cxx @@ -0,0 +1,513 @@ +/* -*- 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 <uielement/newmenucontroller.hxx> +#include <menuconfiguration.hxx> + +#include <services.h> + +#include <com/sun/star/awt/MenuItemType.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/GlobalAcceleratorConfiguration.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <svtools/acceleratorexecute.hxx> +#include <svtools/imagemgr.hxx> +#include <toolkit/awt/vclxmenu.hxx> +#include <tools/urlobj.hxx> +#include <unotools/dynamicmenuoptions.hxx> +#include <unotools/moduleoptions.hxx> +#include <osl/mutex.hxx> +#include <cppuhelper/supportsservice.hxx> + +// Defines +constexpr OUStringLiteral aSlotNewDocDirect = u".uno:AddDirect"; +constexpr OUStringLiteral aSlotAutoPilot = u".uno:AutoPilotMenu"; + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::frame; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace com::sun::star::container; +using namespace com::sun::star::ui; + +namespace framework +{ + +OUString SAL_CALL NewMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.NewMenuController"; +} + +sal_Bool SAL_CALL NewMenuController::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL NewMenuController::getSupportedServiceNames() +{ + return { SERVICENAME_POPUPMENUCONTROLLER }; +} + +void NewMenuController::setMenuImages( PopupMenu* pPopupMenu, bool bSetImages ) +{ + sal_uInt16 nItemCount = pPopupMenu->GetItemCount(); + Reference< XFrame > xFrame( m_xFrame ); + + for ( sal_uInt16 i = 0; i < nItemCount; i++ ) + { + sal_uInt16 nItemId = pPopupMenu->GetItemId( i ); + if ( nItemId != 0 ) + { + if ( bSetImages ) + { + OUString aImageId; + OUString aCmd( pPopupMenu->GetItemCommand( nItemId ) ); + void* nAttributePtr = pPopupMenu->GetUserValue( nItemId ); + MenuAttributes* pAttributes = static_cast<MenuAttributes *>(nAttributePtr); + if (pAttributes) + aImageId = pAttributes->aImageId; + + INetURLObject aURLObj( aImageId.isEmpty() ? aCmd : aImageId ); + Image aImage = SvFileInformationManager::GetImageNoDefault( aURLObj ); + if ( !aImage ) + aImage = vcl::CommandInfoProvider::GetImageForCommand(aCmd, xFrame); + + if ( !!aImage ) + pPopupMenu->SetItemImage( nItemId, aImage ); + } + else + pPopupMenu->SetItemImage( nItemId, Image() ); + } + } +} + +void NewMenuController::determineAndSetNewDocAccel(const css::awt::KeyEvent& rKeyCode) +{ + sal_uInt16 nCount(m_xPopupMenu->getItemCount()); + sal_uInt16 nId( 0 ); + bool bFound( false ); + OUString aCommand; + + if ( !m_aEmptyDocURL.isEmpty() ) + { + // Search for the empty document URL + + for ( sal_uInt16 i = 0; i < nCount; i++ ) + { + if (m_xPopupMenu->getItemType(i) != css::awt::MenuItemType_SEPARATOR) + { + nId = m_xPopupMenu->getItemId(i); + aCommand = m_xPopupMenu->getCommand(nId); + if ( aCommand.startsWith( m_aEmptyDocURL ) ) + { + m_xPopupMenu->setAcceleratorKeyEvent(nId, rKeyCode); + bFound = true; + break; + } + } + } + } + + if ( bFound ) + return; + + // Search for the default module name + OUString aDefaultModuleName( SvtModuleOptions().GetDefaultModuleName() ); + if ( aDefaultModuleName.isEmpty() ) + return; + + for ( sal_uInt16 i = 0; i < nCount; i++ ) + { + if (m_xPopupMenu->getItemType(i) != css::awt::MenuItemType_SEPARATOR) + { + nId = m_xPopupMenu->getItemId(i); + aCommand = m_xPopupMenu->getCommand(nId); + if ( aCommand.indexOf( aDefaultModuleName ) >= 0 ) + { + m_xPopupMenu->setAcceleratorKeyEvent(nId, rKeyCode); + break; + } + } + } +} + +void NewMenuController::setAccelerators() +{ + if ( !m_bModuleIdentified ) + return; + + Reference< XAcceleratorConfiguration > xDocAccelCfg( m_xDocAcceleratorManager ); + Reference< XAcceleratorConfiguration > xModuleAccelCfg( m_xModuleAcceleratorManager ); + Reference< XAcceleratorConfiguration > xGlobalAccelCfg( m_xGlobalAcceleratorManager ); + + if ( !m_bAcceleratorCfg ) + { + // Retrieve references on demand + m_bAcceleratorCfg = true; + if ( !xDocAccelCfg.is() ) + { + Reference< XController > xController = m_xFrame->getController(); + Reference< XModel > xModel; + if ( xController.is() ) + { + xModel = xController->getModel(); + if ( xModel.is() ) + { + Reference< XUIConfigurationManagerSupplier > xSupplier( xModel, UNO_QUERY ); + if ( xSupplier.is() ) + { + Reference< XUIConfigurationManager > xDocUICfgMgr = xSupplier->getUIConfigurationManager(); + if ( xDocUICfgMgr.is() ) + { + xDocAccelCfg = xDocUICfgMgr->getShortCutManager(); + m_xDocAcceleratorManager = xDocAccelCfg; + } + } + } + } + } + + if ( !xModuleAccelCfg.is() ) + { + Reference< XModuleUIConfigurationManagerSupplier > xModuleCfgMgrSupplier = + theModuleUIConfigurationManagerSupplier::get( m_xContext ); + Reference< XUIConfigurationManager > xUICfgMgr = xModuleCfgMgrSupplier->getUIConfigurationManager( m_aModuleIdentifier ); + if ( xUICfgMgr.is() ) + { + xModuleAccelCfg = xUICfgMgr->getShortCutManager(); + m_xModuleAcceleratorManager = xModuleAccelCfg; + } + } + + if ( !xGlobalAccelCfg.is() ) + { + xGlobalAccelCfg = GlobalAcceleratorConfiguration::create( m_xContext ); + m_xGlobalAcceleratorManager = xGlobalAccelCfg; + } + } + + vcl::KeyCode aEmptyKeyCode; + sal_uInt16 nItemCount(m_xPopupMenu->getItemCount()); + std::vector< vcl::KeyCode > aMenuShortCuts; + std::vector< OUString > aCmds; + std::vector< sal_uInt16 > aIds; + for ( sal_uInt16 i = 0; i < nItemCount; i++ ) + { + if (m_xPopupMenu->getItemType(i) != css::awt::MenuItemType_SEPARATOR) + { + sal_uInt16 nId(m_xPopupMenu->getItemId(i)); + aIds.push_back( nId ); + aMenuShortCuts.push_back( aEmptyKeyCode ); + aCmds.push_back(m_xPopupMenu->getCommand(nId)); + } + } + + sal_uInt32 nSeqCount( aIds.size() ); + + if ( m_bNewMenu ) + nSeqCount+=1; + + Sequence< OUString > aSeq( nSeqCount ); + auto aSeqRange = asNonConstRange(aSeq); + + // Add a special command for our "New" menu. + if ( m_bNewMenu ) + { + aSeqRange[nSeqCount-1] = m_aCommandURL; + aMenuShortCuts.push_back( aEmptyKeyCode ); + } + + const sal_uInt32 nCount = aCmds.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + aSeqRange[i] = aCmds[i]; + + if ( m_xGlobalAcceleratorManager.is() ) + retrieveShortcutsFromConfiguration( xGlobalAccelCfg, aSeq, aMenuShortCuts ); + if ( m_xModuleAcceleratorManager.is() ) + retrieveShortcutsFromConfiguration( xModuleAccelCfg, aSeq, aMenuShortCuts ); + if ( m_xDocAcceleratorManager.is() ) + retrieveShortcutsFromConfiguration( xDocAccelCfg, aSeq, aMenuShortCuts ); + + const sal_uInt32 nCount2 = aIds.size(); + for ( sal_uInt32 i = 0; i < nCount2; i++ ) + m_xPopupMenu->setAcceleratorKeyEvent(aIds[i], svt::AcceleratorExecute::st_VCLKey2AWTKey(aMenuShortCuts[i])); + + // Special handling for "New" menu short-cut should be set at the + // document which will be opened using it. + if ( m_bNewMenu ) + { + if ( aMenuShortCuts[nSeqCount-1] != aEmptyKeyCode ) + determineAndSetNewDocAccel(svt::AcceleratorExecute::st_VCLKey2AWTKey(aMenuShortCuts[nSeqCount-1])); + } +} + +void NewMenuController::retrieveShortcutsFromConfiguration( + const Reference< XAcceleratorConfiguration >& rAccelCfg, + const Sequence< OUString >& rCommands, + std::vector< vcl::KeyCode >& aMenuShortCuts ) +{ + if ( !rAccelCfg.is() ) + return; + + try + { + css::awt::KeyEvent aKeyEvent; + Sequence< Any > aSeqKeyCode = rAccelCfg->getPreferredKeyEventsForCommandList( rCommands ); + for ( sal_Int32 i = 0; i < aSeqKeyCode.getLength(); i++ ) + { + if ( aSeqKeyCode[i] >>= aKeyEvent ) + aMenuShortCuts[i] = svt::AcceleratorExecute::st_AWTKey2VCLKey( aKeyEvent ); + } + } + catch ( const IllegalArgumentException& ) + { + } +} + +NewMenuController::NewMenuController( const css::uno::Reference< css::uno::XComponentContext >& xContext ) : + svt::PopupMenuControllerBase( xContext ), + m_bShowImages( true ), + m_bNewMenu( false ), + m_bModuleIdentified( false ), + m_bAcceleratorCfg( false ), + m_aTargetFrame( "_default" ), + m_xContext( xContext ) +{ +} + +NewMenuController::~NewMenuController() +{ +} + +// private function +void NewMenuController::fillPopupMenu( Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + VCLXPopupMenu* pPopupMenu = static_cast<VCLXPopupMenu *>(comphelper::getFromUnoTunnel<VCLXMenu>( rPopupMenu )); + PopupMenu* pVCLPopupMenu = nullptr; + + SolarMutexGuard aSolarMutexGuard; + + resetPopupMenu( rPopupMenu ); + if ( pPopupMenu ) + pVCLPopupMenu = static_cast<PopupMenu *>(pPopupMenu->GetMenu()); + + if ( !pVCLPopupMenu ) + return; + + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + URL aTargetURL; + aTargetURL.Complete = m_bNewMenu ? OUString(aSlotNewDocDirect) : OUString(aSlotAutoPilot); + m_xURLTransformer->parseStrict( aTargetURL ); + Reference< XDispatch > xMenuItemDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + if(xMenuItemDispatch == nullptr) + return; + + const std::vector< SvtDynMenuEntry > aDynamicMenuEntries = + SvtDynamicMenuOptions::GetMenu( m_bNewMenu ? EDynamicMenuType::NewMenu : EDynamicMenuType::WizardMenu ); + + sal_uInt16 nItemId = 1; + + for ( const auto& aDynamicMenuEntry : aDynamicMenuEntries ) + { + if ( aDynamicMenuEntry.sTitle.isEmpty() && aDynamicMenuEntry.sURL.isEmpty() ) + continue; + + if ( aDynamicMenuEntry.sURL == "private:separator" ) + rPopupMenu->insertSeparator(-1); + else + { + rPopupMenu->insertItem(nItemId, aDynamicMenuEntry.sTitle, 0, -1); + rPopupMenu->setCommand(nItemId, aDynamicMenuEntry.sURL); + + void* nAttributePtr = MenuAttributes::CreateAttribute( aDynamicMenuEntry.sTargetName, aDynamicMenuEntry.sImageIdentifier ); + pPopupMenu->setUserValue(nItemId, nAttributePtr, MenuAttributes::ReleaseAttribute); + + nItemId++; + } + } + + if ( m_bShowImages ) + setMenuImages( pVCLPopupMenu, m_bShowImages ); +} + +// XEventListener +void SAL_CALL NewMenuController::disposing( const EventObject& ) +{ + Reference< css::awt::XMenuListener > xHolder(this); + + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + m_xContext.clear(); + + if ( m_xPopupMenu.is() ) + m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); + m_xPopupMenu.clear(); +} + +// XStatusListener +void SAL_CALL NewMenuController::statusChanged( const FeatureStateEvent& ) +{ +} + +// XMenuListener +void SAL_CALL NewMenuController::itemSelected( const css::awt::MenuEvent& rEvent ) +{ + Reference< css::awt::XPopupMenu > xPopupMenu; + Reference< XComponentContext > xContext; + + { + osl::MutexGuard aLock(m_aMutex); + xPopupMenu = m_xPopupMenu; + xContext = m_xContext; + } + + if ( !xPopupMenu.is() ) + return; + + VCLXPopupMenu* pPopupMenu = static_cast<VCLXPopupMenu *>(comphelper::getFromUnoTunnel<VCLXMenu>( xPopupMenu )); + if ( !pPopupMenu ) + return; + + OUString aURL; + OUString aTargetFrame( m_aTargetFrame ); + + { + SolarMutexGuard aSolarMutexGuard; + aURL = pPopupMenu->getCommand(rEvent.MenuId); + void* nAttributePtr = pPopupMenu->getUserValue(rEvent.MenuId); + MenuAttributes* pAttributes = static_cast<MenuAttributes *>(nAttributePtr); + if (pAttributes) + aTargetFrame = pAttributes->aTargetFrame; + } + + Sequence< PropertyValue > aArgsList{ comphelper::makePropertyValue("Referer", + OUString( "private:user" )) }; + + dispatchCommand( aURL, aArgsList, aTargetFrame ); +} + +void SAL_CALL NewMenuController::itemActivated( const css::awt::MenuEvent& ) +{ + SolarMutexGuard aSolarMutexGuard; + if ( !(m_xFrame.is() && m_xPopupMenu.is()) ) + return; + + VCLXPopupMenu* pPopupMenu = static_cast<VCLXPopupMenu *>(comphelper::getFromUnoTunnel<VCLXMenu>( m_xPopupMenu )); + if ( !pPopupMenu ) + return; + + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + bool bShowImages( rSettings.GetUseImagesInMenus() ); + OUString aIconTheme( rSettings.DetermineIconTheme() ); + + PopupMenu* pVCLPopupMenu = static_cast<PopupMenu *>(pPopupMenu->GetMenu()); + + if ( m_bShowImages != bShowImages || m_aIconTheme != aIconTheme ) + { + m_bShowImages = bShowImages; + m_aIconTheme = aIconTheme; + setMenuImages( pVCLPopupMenu, m_bShowImages ); + } + + setAccelerators(); +} + +// XPopupMenuController +void NewMenuController::impl_setPopupMenu() +{ + + if ( m_xPopupMenu.is() ) + fillPopupMenu( m_xPopupMenu ); + + // Identify module that we are attach to. It's our context that we need to know. + Reference< XModuleManager2 > xModuleManager = ModuleManager::create( m_xContext ); + try + { + m_aModuleIdentifier = xModuleManager->identify( m_xFrame ); + m_bModuleIdentified = true; + + if ( !m_aModuleIdentifier.isEmpty() ) + { + Sequence< PropertyValue > aSeq; + + if ( xModuleManager->getByName( m_aModuleIdentifier ) >>= aSeq ) + { + for ( PropertyValue const & prop : std::as_const(aSeq) ) + { + if ( prop.Name == "ooSetupFactoryEmptyDocumentURL" ) + { + prop.Value >>= m_aEmptyDocURL; + break; + } + } + } + } + } + catch ( const RuntimeException& ) + { + throw; + } + catch ( const Exception& ) + { + } +} + +// XInitialization +void SAL_CALL NewMenuController::initialize( const Sequence< Any >& aArguments ) +{ + osl::MutexGuard aLock( m_aMutex ); + + bool bInitialized( m_bInitialized ); + if ( bInitialized ) + return; + + svt::PopupMenuControllerBase::initialize( aArguments ); + + if ( m_bInitialized ) + { + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + + m_bShowImages = rSettings.GetUseImagesInMenus(); + m_aIconTheme = rSettings.DetermineIconTheme(); + m_bNewMenu = m_aCommandURL == aSlotNewDocDirect; + } +} + +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_NewMenuController_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::NewMenuController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/objectmenucontroller.cxx b/framework/source/uielement/objectmenucontroller.cxx new file mode 100644 index 000000000..62b5586ee --- /dev/null +++ b/framework/source/uielement/objectmenucontroller.cxx @@ -0,0 +1,136 @@ +/* -*- 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 <stdtypes.h> + +#include <com/sun/star/embed/VerbAttributes.hpp> +#include <com/sun/star/embed/VerbDescriptor.hpp> + +#include <svtools/popupmenucontrollerbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weak.hxx> +#include <vcl/svapp.hxx> +#include <osl/mutex.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::frame; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace framework; + +namespace { + +class ObjectMenuController : public svt::PopupMenuControllerBase +{ + using svt::PopupMenuControllerBase::disposing; + +public: + explicit ObjectMenuController( const css::uno::Reference< css::uno::XComponentContext >& xContext ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.ObjectMenuController"; + } + + 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.frame.PopupMenuController"}; + } + + // XStatusListener + virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& Event ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + +private: + void fillPopupMenu( const css::uno::Sequence< css::embed::VerbDescriptor >& rVerbCommandSeq, css::uno::Reference< css::awt::XPopupMenu > const & rPopupMenu ); +}; + +ObjectMenuController::ObjectMenuController( const css::uno::Reference< css::uno::XComponentContext >& xContext ) : + svt::PopupMenuControllerBase( xContext ) +{ +} + +// private function +void ObjectMenuController::fillPopupMenu( const Sequence< css::embed::VerbDescriptor >& rVerbCommandSeq, Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + const css::embed::VerbDescriptor* pVerbCommandArray = rVerbCommandSeq.getConstArray(); + + SolarMutexGuard aSolarMutexGuard; + + resetPopupMenu( rPopupMenu ); + + static const OUStringLiteral aVerbCommand( u".uno:ObjectMenue?VerbID:short=" ); + for ( sal_Int32 i = 0; i < rVerbCommandSeq.getLength(); i++ ) + { + const css::embed::VerbDescriptor& rVerb = pVerbCommandArray[i]; + if ( rVerb.VerbAttributes & css::embed::VerbAttributes::MS_VERBATTR_ONCONTAINERMENU ) + { + m_xPopupMenu->insertItem( i+1, rVerb.VerbName, 0, i ); + OUString aCommand = aVerbCommand + OUString::number( rVerb.VerbID ); + m_xPopupMenu->setCommand( i+1, aCommand ); // Store verb command + } + } +} + +// XEventListener +void SAL_CALL ObjectMenuController::disposing( const EventObject& ) +{ + Reference< css::awt::XMenuListener > xHolder(this); + + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + + if ( m_xPopupMenu.is() ) + m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); + m_xPopupMenu.clear(); +} + +// XStatusListener +void SAL_CALL ObjectMenuController::statusChanged( const FeatureStateEvent& Event ) +{ + Sequence < css::embed::VerbDescriptor > aVerbCommandSeq; + if ( Event.State >>= aVerbCommandSeq ) + { + osl::MutexGuard aLock( m_aMutex ); + if ( m_xPopupMenu.is() ) + fillPopupMenu( aVerbCommandSeq, m_xPopupMenu ); + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ObjectMenuController_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new ObjectMenuController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/popuptoolbarcontroller.cxx b/framework/source/uielement/popuptoolbarcontroller.cxx new file mode 100644 index 000000000..6cf02e5e4 --- /dev/null +++ b/framework/source/uielement/popuptoolbarcontroller.cxx @@ -0,0 +1,799 @@ +/* -*- 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 <bitmaps.hlst> + +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/propertyvalue.hxx> +#include <menuconfiguration.hxx> +#include <svtools/imagemgr.hxx> +#include <svtools/toolboxcontroller.hxx> +#include <toolkit/awt/vclxmenu.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/diagnose_ex.h> +#include <tools/urlobj.hxx> +#include <utility> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/menu.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> + +#include <com/sun/star/awt/PopupMenuDirection.hpp> +#include <com/sun/star/awt/XPopupMenu.hpp> +#include <com/sun/star/frame/thePopupMenuControllerFactory.hpp> +#include <com/sun/star/frame/XPopupMenuController.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/frame/XSubToolbarController.hpp> +#include <com/sun/star/frame/XUIControllerFactory.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/util/XModifiable.hpp> + +using namespace framework; + +namespace +{ + +typedef cppu::ImplInheritanceHelper< svt::ToolboxController, + css::lang::XServiceInfo > + ToolBarBase; + +class PopupMenuToolbarController : public ToolBarBase +{ +public: + // XComponent + virtual void SAL_CALL dispose() override; + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + // XToolbarController + virtual css::uno::Reference< css::awt::XWindow > SAL_CALL createPopupWindow() override; + // XStatusListener + virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& rEvent ) override; + +protected: + PopupMenuToolbarController( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + OUString aPopupCommand = OUString() ); + virtual void functionExecuted( const OUString &rCommand ); + virtual ToolBoxItemBits getDropDownStyle() const; + void createPopupMenuController(); + + bool m_bHasController; + bool m_bResourceURL; + OUString m_aPopupCommand; + css::uno::Reference< css::awt::XPopupMenu > m_xPopupMenu; + +private: + css::uno::Reference< css::frame::XUIControllerFactory > m_xPopupMenuFactory; + css::uno::Reference< css::frame::XPopupMenuController > m_xPopupMenuController; +}; + +PopupMenuToolbarController::PopupMenuToolbarController( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + OUString aPopupCommand ) + : ToolBarBase( xContext, css::uno::Reference< css::frame::XFrame >(), /*aCommandURL*/OUString() ) + , m_bHasController( false ) + , m_bResourceURL( false ) + , m_aPopupCommand(std::move( aPopupCommand )) +{ +} + +void SAL_CALL PopupMenuToolbarController::dispose() +{ + svt::ToolboxController::dispose(); + + osl::MutexGuard aGuard( m_aMutex ); + if( m_xPopupMenuController.is() ) + { + css::uno::Reference< css::lang::XComponent > xComponent( + m_xPopupMenuController, css::uno::UNO_QUERY ); + if( xComponent.is() ) + { + try + { + xComponent->dispose(); + } + catch (...) + {} + } + m_xPopupMenuController.clear(); + } + + m_xContext.clear(); + m_xPopupMenuFactory.clear(); + m_xPopupMenu.clear(); +} + +void SAL_CALL PopupMenuToolbarController::initialize( + const css::uno::Sequence< css::uno::Any >& aArguments ) +{ + ToolboxController::initialize( aArguments ); + + osl::MutexGuard aGuard( m_aMutex ); + if ( !m_aPopupCommand.getLength() ) + m_aPopupCommand = m_aCommandURL; + + try + { + m_xPopupMenuFactory.set( + css::frame::thePopupMenuControllerFactory::get( m_xContext ) ); + m_bHasController = m_xPopupMenuFactory->hasController( + m_aPopupCommand, getModuleName() ); + } + catch (const css::uno::Exception&) + { + TOOLS_INFO_EXCEPTION( "fwk.uielement", "" ); + } + + if ( !m_bHasController && m_aPopupCommand.startsWith( "private:resource/" ) ) + { + m_bResourceURL = true; + m_bHasController = true; + } + + SolarMutexGuard aSolarLock; + ToolBox* pToolBox = nullptr; + ToolBoxItemId nItemId; + if ( getToolboxId( nItemId, &pToolBox ) ) + { + ToolBoxItemBits nCurStyle( pToolBox->GetItemBits( nItemId ) ); + ToolBoxItemBits nSetStyle( getDropDownStyle() ); + pToolBox->SetItemBits( nItemId, + m_bHasController ? + nCurStyle | nSetStyle : + nCurStyle & ~nSetStyle ); + } + +} + +void SAL_CALL PopupMenuToolbarController::statusChanged( const css::frame::FeatureStateEvent& rEvent ) +{ + if ( m_bResourceURL ) + return; + + ToolBox* pToolBox = nullptr; + ToolBoxItemId nItemId; + if ( getToolboxId( nItemId, &pToolBox ) ) + { + SolarMutexGuard aSolarLock; + pToolBox->EnableItem( nItemId, rEvent.IsEnabled ); + bool bValue; + if ( rEvent.State >>= bValue ) + pToolBox->CheckItem( nItemId, bValue ); + } +} + +css::uno::Reference< css::awt::XWindow > SAL_CALL +PopupMenuToolbarController::createPopupWindow() +{ + css::uno::Reference< css::awt::XWindow > xRet; + + osl::MutexGuard aGuard( m_aMutex ); + if ( !m_bHasController ) + return xRet; + + createPopupMenuController(); + + SolarMutexGuard aSolarLock; + VclPtr< ToolBox > pToolBox = static_cast< ToolBox* >( VCLUnoHelper::GetWindow( getParent() ) ); + if ( !pToolBox ) + return xRet; + + pToolBox->SetItemDown( m_nToolBoxId, true ); + WindowAlign eAlign( pToolBox->GetAlign() ); + + // If the parent ToolBox is in popup mode (e.g. sub toolbar, overflow popup), + // its ToolBarManager can be disposed along with our controller, destroying + // m_xPopupMenu, while the latter still in execute. This should be fixed at a + // different level, for now just hold it here so it won't crash. + css::uno::Reference< css::awt::XPopupMenu > xPopupMenu ( m_xPopupMenu ); + sal_uInt16 nId = xPopupMenu->execute( + css::uno::Reference< css::awt::XWindowPeer >( getParent(), css::uno::UNO_QUERY ), + VCLUnoHelper::ConvertToAWTRect( pToolBox->GetItemRect( m_nToolBoxId ) ), + ( eAlign == WindowAlign::Top || eAlign == WindowAlign::Bottom ) ? + css::awt::PopupMenuDirection::EXECUTE_DOWN : + css::awt::PopupMenuDirection::EXECUTE_RIGHT ); + pToolBox->SetItemDown( m_nToolBoxId, false ); + + if ( nId ) + functionExecuted( xPopupMenu->getCommand( nId ) ); + + return xRet; +} + +void PopupMenuToolbarController::functionExecuted( const OUString &/*rCommand*/) +{ +} + +ToolBoxItemBits PopupMenuToolbarController::getDropDownStyle() const +{ + return ToolBoxItemBits::DROPDOWN; +} + +void PopupMenuToolbarController::createPopupMenuController() +{ + if( !m_bHasController ) + return; + + if ( m_xPopupMenuController.is() ) + { + m_xPopupMenuController->updatePopupMenu(); + } + else + { + css::uno::Sequence<css::uno::Any> aArgs { + css::uno::Any(comphelper::makePropertyValue("Frame", m_xFrame)), + css::uno::Any(comphelper::makePropertyValue("ModuleIdentifier", m_sModuleName)), + css::uno::Any(comphelper::makePropertyValue("InToolbar", true)) + }; + + try + { + m_xPopupMenu.set( + m_xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.awt.PopupMenu", m_xContext ), + css::uno::UNO_QUERY_THROW ); + + if (m_bResourceURL) + { + sal_Int32 nAppendIndex = aArgs.getLength(); + aArgs.realloc(nAppendIndex + 1); + aArgs.getArray()[nAppendIndex] <<= comphelper::makePropertyValue("ResourceURL", m_aPopupCommand); + + m_xPopupMenuController.set( m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.comp.framework.ResourceMenuController", aArgs, m_xContext), css::uno::UNO_QUERY_THROW ); + } + else + { + m_xPopupMenuController.set( m_xPopupMenuFactory->createInstanceWithArgumentsAndContext( + m_aPopupCommand, aArgs, m_xContext), css::uno::UNO_QUERY_THROW ); + } + + m_xPopupMenuController->setPopupMenu( m_xPopupMenu ); + } + catch ( const css::uno::Exception & ) + { + TOOLS_INFO_EXCEPTION( "fwk.uielement", "" ); + m_xPopupMenu.clear(); + } + } +} + +class GenericPopupToolbarController : public PopupMenuToolbarController +{ +public: + GenericPopupToolbarController( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Sequence< css::uno::Any >& rxArgs ); + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& rxArgs ) override; + + // XStatusListener + virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& rEvent ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + + virtual sal_Bool SAL_CALL supportsService(OUString const & rServiceName) override; + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + +private: + bool m_bSplitButton, m_bReplaceWithLast; + void functionExecuted(const OUString &rCommand) override; + ToolBoxItemBits getDropDownStyle() const override; +}; + +GenericPopupToolbarController::GenericPopupToolbarController( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + const css::uno::Sequence< css::uno::Any >& rxArgs ) + : PopupMenuToolbarController( xContext ) + , m_bReplaceWithLast( false ) +{ + css::beans::PropertyValue aPropValue; + for ( const auto& arg: rxArgs ) + { + if ( ( arg >>= aPropValue ) && aPropValue.Name == "Value" ) + { + sal_Int32 nIdx{ 0 }; + OUString aValue; + aPropValue.Value >>= aValue; + m_aPopupCommand = aValue.getToken(0, ';', nIdx); + m_bReplaceWithLast = aValue.getToken(0, ';', nIdx).toBoolean(); + break; + } + } + m_bSplitButton = m_bReplaceWithLast || !m_aPopupCommand.isEmpty(); +} + +OUString GenericPopupToolbarController::getImplementationName() +{ + return "com.sun.star.comp.framework.GenericPopupToolbarController"; +} + +sal_Bool GenericPopupToolbarController::supportsService(OUString const & rServiceName) +{ + return cppu::supportsService( this, rServiceName ); +} + +css::uno::Sequence<OUString> GenericPopupToolbarController::getSupportedServiceNames() +{ + return {"com.sun.star.frame.ToolbarController"}; +} + +void GenericPopupToolbarController::initialize( const css::uno::Sequence< css::uno::Any >& rxArgs ) +{ + PopupMenuToolbarController::initialize( rxArgs ); + if ( m_bReplaceWithLast ) + // Create early, so we can use the menu is statusChanged method. + createPopupMenuController(); +} + +void GenericPopupToolbarController::statusChanged( const css::frame::FeatureStateEvent& rEvent ) +{ + SolarMutexGuard aGuard; + + if ( m_bReplaceWithLast && !rEvent.IsEnabled && m_xPopupMenu.is() ) + { + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if ( getToolboxId( nId, &pToolBox ) && pToolBox->IsItemEnabled( nId ) ) + { + Menu* pVclMenu = comphelper::getFromUnoTunnel<VCLXMenu>( m_xPopupMenu )->GetMenu(); + pVclMenu->Activate(); + pVclMenu->Deactivate(); + } + + for (sal_uInt16 i = 0, nCount = m_xPopupMenu->getItemCount(); i < nCount; ++i ) + { + sal_uInt16 nItemId = m_xPopupMenu->getItemId(i); + if (nItemId && m_xPopupMenu->isItemEnabled(nItemId) && !m_xPopupMenu->getPopupMenu(nItemId).is()) + { + functionExecuted(m_xPopupMenu->getCommand(nItemId)); + return; + } + } + } + + PopupMenuToolbarController::statusChanged( rEvent ); +} + +void GenericPopupToolbarController::functionExecuted( const OUString& rCommand ) +{ + if ( !m_bReplaceWithLast ) + return; + + removeStatusListener( m_aCommandURL ); + + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rCommand, m_sModuleName); + OUString aRealCommand( vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties) ); + m_aCommandURL = aRealCommand.isEmpty() ? rCommand : aRealCommand; + addStatusListener( m_aCommandURL ); + + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if ( getToolboxId( nId, &pToolBox ) ) + { + pToolBox->SetItemCommand( nId, rCommand ); + pToolBox->SetHelpText( nId, OUString() ); // Will retrieve the new one from help. + pToolBox->SetItemText(nId, vcl::CommandInfoProvider::GetLabelForCommand(aProperties)); + pToolBox->SetQuickHelpText(nId, vcl::CommandInfoProvider::GetTooltipForCommand(rCommand, aProperties, m_xFrame)); + + Image aImage = vcl::CommandInfoProvider::GetImageForCommand(rCommand, m_xFrame, pToolBox->GetImageSize()); + if ( !!aImage ) + pToolBox->SetItemImage( nId, aImage ); + } +} + +ToolBoxItemBits GenericPopupToolbarController::getDropDownStyle() const +{ + return m_bSplitButton ? ToolBoxItemBits::DROPDOWN : ToolBoxItemBits::DROPDOWNONLY; +} + +class SaveToolbarController : public cppu::ImplInheritanceHelper< PopupMenuToolbarController, + css::frame::XSubToolbarController, + css::util::XModifyListener > +{ +public: + explicit SaveToolbarController( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XSubToolbarController + // Make ToolBarManager ask our controller for updated image, in case of icon theme change. + virtual sal_Bool SAL_CALL opensSubToolbar() override; + virtual OUString SAL_CALL getSubToolbarName() override; + virtual void SAL_CALL functionSelected( const OUString& aCommand ) override; + virtual void SAL_CALL updateImage() override; + + // XStatusListener + virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& rEvent ) override; + + // XModifyListener + virtual void SAL_CALL modified( const css::lang::EventObject& rEvent ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& rEvent ) override; + + // XComponent + virtual void SAL_CALL dispose() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( OUString const & rServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + +private: + bool m_bReadOnly; + bool m_bModified; + css::uno::Reference< css::frame::XStorable > m_xStorable; + css::uno::Reference< css::util::XModifiable > m_xModifiable; +}; + +SaveToolbarController::SaveToolbarController( const css::uno::Reference< css::uno::XComponentContext >& rxContext ) + : ImplInheritanceHelper( rxContext, ".uno:SaveAsMenu" ) + , m_bReadOnly( false ) + , m_bModified( false ) +{ +} + +void SaveToolbarController::initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) +{ + PopupMenuToolbarController::initialize( aArguments ); + + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if ( !getToolboxId( nId, &pToolBox ) ) + return; + + css::uno::Reference< css::frame::XController > xController = m_xFrame->getController(); + if ( xController.is() ) + m_xModifiable.set( xController->getModel(), css::uno::UNO_QUERY ); + + if ( m_xModifiable.is() && pToolBox->GetItemCommand( nId ) == m_aCommandURL ) + // Will also enable the save as only mode. + m_xStorable.set( m_xModifiable, css::uno::UNO_QUERY ); + else if ( !m_xModifiable.is() ) + // Can be in table/query design. + m_xModifiable.set( xController, css::uno::UNO_QUERY ); + else + // Simple save button, without the dropdown. + pToolBox->SetItemBits( nId, pToolBox->GetItemBits( nId ) & ~ ToolBoxItemBits::DROPDOWN ); + + if ( m_xModifiable.is() ) + { + m_xModifiable->addModifyListener( this ); + modified( css::lang::EventObject() ); + } +} + +sal_Bool SaveToolbarController::opensSubToolbar() +{ + return true; +} + +OUString SaveToolbarController::getSubToolbarName() +{ + return OUString(); +} + +void SaveToolbarController::functionSelected( const OUString& /*aCommand*/ ) +{ +} + +void SaveToolbarController::updateImage() +{ + SolarMutexGuard aGuard; + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if ( !getToolboxId( nId, &pToolBox ) ) + return; + + vcl::ImageType eImageType = pToolBox->GetImageSize(); + + Image aImage; + + if ( m_bReadOnly ) + { + aImage = vcl::CommandInfoProvider::GetImageForCommand(".uno:SaveAs", m_xFrame, eImageType); + } + else if ( m_bModified ) + { + if (eImageType == vcl::ImageType::Size26) + aImage = Image(StockImage::Yes, BMP_SAVEMODIFIED_LARGE); + else if (eImageType == vcl::ImageType::Size32) + aImage = Image(StockImage::Yes, BMP_SAVEMODIFIED_EXTRALARGE); + else + aImage = Image(StockImage::Yes, BMP_SAVEMODIFIED_SMALL); + } + + if ( !aImage ) + aImage = vcl::CommandInfoProvider::GetImageForCommand(m_aCommandURL, m_xFrame, eImageType); + + if ( !!aImage ) + pToolBox->SetItemImage( nId, aImage ); +} + +void SaveToolbarController::statusChanged( const css::frame::FeatureStateEvent& rEvent ) +{ + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if ( !getToolboxId( nId, &pToolBox ) ) + return; + + bool bLastReadOnly = m_bReadOnly; + m_bReadOnly = m_xStorable.is() && m_xStorable->isReadonly(); + if ( bLastReadOnly != m_bReadOnly ) + { + OUString sCommand = m_bReadOnly ? OUString( ".uno:SaveAs" ) : m_aCommandURL; + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(sCommand, + vcl::CommandInfoProvider::GetModuleIdentifier(m_xFrame)); + pToolBox->SetQuickHelpText( nId, + vcl::CommandInfoProvider::GetTooltipForCommand(sCommand, aProperties, m_xFrame) ); + pToolBox->SetItemBits( nId, pToolBox->GetItemBits( nId ) & ~( m_bReadOnly ? ToolBoxItemBits::DROPDOWN : ToolBoxItemBits::DROPDOWNONLY ) ); + pToolBox->SetItemBits( nId, pToolBox->GetItemBits( nId ) | ( m_bReadOnly ? ToolBoxItemBits::DROPDOWNONLY : ToolBoxItemBits::DROPDOWN ) ); + updateImage(); + } + + if ( !m_bReadOnly ) + pToolBox->EnableItem( nId, rEvent.IsEnabled ); +} + +void SaveToolbarController::modified( const css::lang::EventObject& /*rEvent*/ ) +{ + bool bLastModified = m_bModified; + m_bModified = m_xModifiable->isModified(); + if ( bLastModified != m_bModified ) + updateImage(); +} + +void SaveToolbarController::disposing( const css::lang::EventObject& rEvent ) +{ + if ( rEvent.Source == m_xModifiable ) + { + m_xModifiable.clear(); + m_xStorable.clear(); + } + else + PopupMenuToolbarController::disposing( rEvent ); +} + +void SaveToolbarController::dispose() +{ + PopupMenuToolbarController::dispose(); + if ( m_xModifiable.is() ) + { + m_xModifiable->removeModifyListener( this ); + m_xModifiable.clear(); + } + m_xStorable.clear(); +} + +OUString SaveToolbarController::getImplementationName() +{ + return "com.sun.star.comp.framework.SaveToolbarController"; +} + +sal_Bool SaveToolbarController::supportsService( OUString const & rServiceName ) +{ + return cppu::supportsService( this, rServiceName ); +} + +css::uno::Sequence< OUString > SaveToolbarController::getSupportedServiceNames() +{ + return {"com.sun.star.frame.ToolbarController"}; +} + +class NewToolbarController : public cppu::ImplInheritanceHelper<PopupMenuToolbarController, css::frame::XSubToolbarController> +{ +public: + explicit NewToolbarController( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + // XServiceInfo + OUString SAL_CALL getImplementationName() override; + + virtual sal_Bool SAL_CALL supportsService(OUString const & rServiceName) override; + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XSubToolbarController + // Make ToolBarManager ask our controller for updated image, in case of icon theme change. + sal_Bool SAL_CALL opensSubToolbar() override { return true; } + OUString SAL_CALL getSubToolbarName() override { return OUString(); } + void SAL_CALL functionSelected( const OUString& ) override {} + void SAL_CALL updateImage() override; + +private: + void functionExecuted( const OUString &rCommand ) override; + void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& rEvent ) override; + void SAL_CALL execute( sal_Int16 KeyModifier ) override; + sal_uInt16 getMenuIdForCommand( std::u16string_view rCommand ); + + sal_uInt16 m_nMenuId; +}; + +NewToolbarController::NewToolbarController( + const css::uno::Reference< css::uno::XComponentContext >& xContext ) + : ImplInheritanceHelper( xContext ) + , m_nMenuId( 0 ) +{ +} + +OUString NewToolbarController::getImplementationName() +{ + return "org.apache.openoffice.comp.framework.NewToolbarController"; +} + +sal_Bool NewToolbarController::supportsService(OUString const & rServiceName) +{ + return cppu::supportsService( this, rServiceName ); +} + +css::uno::Sequence<OUString> NewToolbarController::getSupportedServiceNames() +{ + return {"com.sun.star.frame.ToolbarController"}; +} + +void SAL_CALL NewToolbarController::initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) +{ + PopupMenuToolbarController::initialize( aArguments ); + + osl::MutexGuard aGuard( m_aMutex ); + createPopupMenuController(); +} + +void SAL_CALL NewToolbarController::statusChanged( const css::frame::FeatureStateEvent& rEvent ) +{ + if ( rEvent.IsEnabled ) + { + OUString aState; + rEvent.State >>= aState; + try + { + // set the image even if the state is not a string + // the toolbar item command will be used as a fallback + functionExecuted( aState ); + } + catch (const css::ucb::CommandFailedException&) + { + } + catch (const css::ucb::ContentCreationException&) + { + } + } + + enable( rEvent.IsEnabled ); +} + +void SAL_CALL NewToolbarController::execute( sal_Int16 /*KeyModifier*/ ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + OUString aURL, aTarget; + if ( m_xPopupMenu.is() && m_nMenuId ) + { + SolarMutexGuard aSolarMutexGuard; + aURL = m_xPopupMenu->getCommand(m_nMenuId); + + // TODO investigate how to wrap Get/SetUserValue in css::awt::XMenu + VCLXMenu* pMenu = comphelper::getFromUnoTunnel<VCLXMenu>(m_xPopupMenu); + MenuAttributes* pMenuAttributes(static_cast<MenuAttributes*>(pMenu->getUserValue(m_nMenuId))); + if ( pMenuAttributes ) + aTarget = pMenuAttributes->aTargetFrame; + else + aTarget = "_default"; + } + else + aURL = m_aCommandURL; + + css::uno::Sequence< css::beans::PropertyValue > aArgs{ comphelper::makePropertyValue( + "Referer", OUString( "private:user" )) }; + + dispatchCommand( aURL, aArgs, aTarget ); +} + +void NewToolbarController::functionExecuted( const OUString &rCommand ) +{ + m_nMenuId = getMenuIdForCommand( rCommand ); + updateImage(); +} + +sal_uInt16 NewToolbarController::getMenuIdForCommand( std::u16string_view rCommand ) +{ + if ( m_xPopupMenu.is() && !rCommand.empty() ) + { + sal_uInt16 nCount = m_xPopupMenu->getItemCount(); + for ( sal_uInt16 n = 0; n < nCount; ++n ) + { + sal_uInt16 nId = m_xPopupMenu->getItemId(n); + OUString aCmd(m_xPopupMenu->getCommand(nId)); + + // match even if the menu command is more detailed + // (maybe an additional query) #i28667# + if ( aCmd.match( rCommand ) ) + return nId; + } + } + + return 0; +} + +void SAL_CALL NewToolbarController::updateImage() +{ + SolarMutexGuard aSolarLock; + VclPtr< ToolBox> pToolBox = static_cast< ToolBox* >( VCLUnoHelper::GetWindow( getParent() ) ); + if ( !pToolBox ) + return; + + OUString aURL, aImageId; + if ( m_xPopupMenu.is() && m_nMenuId ) + { + aURL = m_xPopupMenu->getCommand(m_nMenuId); + VCLXMenu* pMenu = comphelper::getFromUnoTunnel<VCLXMenu>(m_xPopupMenu); + MenuAttributes* pMenuAttributes(static_cast<MenuAttributes*>(pMenu->getUserValue(m_nMenuId))); + if ( pMenuAttributes ) + aImageId = pMenuAttributes->aImageId; + } + else + aURL = m_aCommandURL; + + INetURLObject aURLObj( aImageId.isEmpty() ? aURL : aImageId ); + vcl::ImageType eImageType( pToolBox->GetImageSize() ); + Image aImage = SvFileInformationManager::GetImageNoDefault( aURLObj, eImageType ); + if ( !aImage ) + aImage = vcl::CommandInfoProvider::GetImageForCommand( aURL, m_xFrame, eImageType ); + + if ( !aImage ) + return; + + pToolBox->SetItemImage( m_nToolBoxId, aImage ); +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_GenericPopupToolbarController_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &args) +{ + return cppu::acquire(new GenericPopupToolbarController(context, args)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_SaveToolbarController_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SaveToolbarController(context)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +org_apache_openoffice_comp_framework_NewToolbarController_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new NewToolbarController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/progressbarwrapper.cxx b/framework/source/uielement/progressbarwrapper.cxx new file mode 100644 index 000000000..ad147111f --- /dev/null +++ b/framework/source/uielement/progressbarwrapper.cxx @@ -0,0 +1,310 @@ +/* -*- 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 <uielement/progressbarwrapper.hxx> + +#include <uielement/statusindicatorinterfacewrapper.hxx> + +#include <com/sun/star/ui/UIElementType.hpp> +#include <com/sun/star/lang/DisposedException.hpp> + +#include <vcl/status.hxx> +#include <vcl/svapp.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +using namespace ::com::sun::star; + +namespace framework{ + +ProgressBarWrapper::ProgressBarWrapper() : +UIElementWrapperBase( css::ui::UIElementType::PROGRESSBAR ) + , m_bOwnsInstance( false ) + , m_nRange( 100 ) + , m_nValue( 0 ) +{ +} + +ProgressBarWrapper::~ProgressBarWrapper() +{ +} + +// public interfaces +void ProgressBarWrapper::setStatusBar( const uno::Reference< awt::XWindow >& rStatusBar, bool bOwnsInstance ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + if ( m_bOwnsInstance ) + { + // dispose XWindow reference of our status bar + try + { + if ( m_xStatusBar.is() ) + m_xStatusBar->dispose(); + } + catch ( const uno::Exception& ) + { + } + m_xStatusBar.clear(); + } + + m_bOwnsInstance = bOwnsInstance; + m_xStatusBar = rStatusBar; +} + +uno::Reference< awt::XWindow > ProgressBarWrapper::getStatusBar() const +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return uno::Reference< awt::XWindow >(); + + return m_xStatusBar; +} + +// wrapped methods of css::task::XStatusIndicator +void ProgressBarWrapper::start( const OUString& Text, ::sal_Int32 Range ) +{ + uno::Reference< awt::XWindow > xWindow; + sal_Int32 nValue( 0 ); + + { + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + xWindow = m_xStatusBar; + m_nValue = 0; + m_nRange = Range; + nValue = m_nValue; + } + + if ( !xWindow.is() ) + return; + + SolarMutexGuard aSolarMutexGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( !(pWindow && pWindow->GetType() == WindowType::STATUSBAR) ) + return; + + StatusBar* pStatusBar = static_cast<StatusBar *>(pWindow.get()); + if ( !pStatusBar->IsProgressMode() ) + pStatusBar->StartProgressMode( Text ); + else + { + pStatusBar->SetUpdateMode( false ); + pStatusBar->EndProgressMode(); + pStatusBar->StartProgressMode( Text ); + pStatusBar->SetProgressValue( sal_uInt16( nValue )); + pStatusBar->SetUpdateMode( true ); + } + pStatusBar->Show( true, ShowFlags::NoFocusChange | ShowFlags::NoActivate ); +} + +void ProgressBarWrapper::end() +{ + uno::Reference< awt::XWindow > xWindow; + + { + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + xWindow = m_xStatusBar; + m_nRange = 100; + m_nValue = 0; + } + + if ( xWindow.is() ) + { + SolarMutexGuard aSolarMutexGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->GetType() == WindowType::STATUSBAR ) + { + StatusBar* pStatusBar = static_cast<StatusBar *>(pWindow.get()); + if ( pStatusBar->IsProgressMode() ) + pStatusBar->EndProgressMode(); + } + } +} + +void ProgressBarWrapper::setText( const OUString& Text ) +{ + uno::Reference< awt::XWindow > xWindow; + sal_Int32 nValue( 0 ); + + { + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + xWindow = m_xStatusBar; + m_aText = Text; + nValue = m_nValue; + } + + if ( !xWindow.is() ) + return; + + SolarMutexGuard aSolarMutexGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( !(pWindow && pWindow->GetType() == WindowType::STATUSBAR) ) + return; + + StatusBar* pStatusBar = static_cast<StatusBar *>(pWindow.get()); + if( pStatusBar->IsProgressMode() ) + { + pStatusBar->SetUpdateMode( false ); + pStatusBar->EndProgressMode(); + pStatusBar->StartProgressMode( Text ); + pStatusBar->SetProgressValue( sal_uInt16( nValue )); + pStatusBar->SetUpdateMode( true ); + } + else + pStatusBar->SetText( Text ); +} + +void ProgressBarWrapper::setValue( ::sal_Int32 nValue ) +{ + uno::Reference< awt::XWindow > xWindow; + OUString aText; + bool bSetValue( false ); + + { + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + xWindow = m_xStatusBar; + + double fVal( 0 ); + if ( m_nRange > 0 ) + { + fVal = ( double( nValue ) / double( m_nRange )) * 100; + fVal = std::clamp( fVal, 0.0, 100.0 ); + } + + if ( m_nValue != sal_Int32( fVal )) + { + m_nValue = sal_Int32( fVal ); + bSetValue = true; + } + + nValue = m_nValue; + aText = m_aText; + } + + if ( xWindow.is() && bSetValue ) + { + SolarMutexGuard aSolarMutexGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow && pWindow->GetType() == WindowType::STATUSBAR ) + { + StatusBar* pStatusBar = static_cast<StatusBar *>(pWindow.get()); + if ( !pStatusBar->IsProgressMode() ) + pStatusBar->StartProgressMode( aText ); + pStatusBar->SetProgressValue( sal_uInt16( nValue )); + } + } +} + +void ProgressBarWrapper::reset() +{ + setText( OUString() ); + setValue( 0 ); +} + +// XInitialization +void SAL_CALL ProgressBarWrapper::initialize( const uno::Sequence< uno::Any >& ) +{ + // dummy - do nothing +} + +// XUpdatable +void SAL_CALL ProgressBarWrapper::update() +{ + // dummy - do nothing +} + +// XComponent +void SAL_CALL ProgressBarWrapper::dispose() +{ + uno::Reference< lang::XComponent > xThis(this); + + { + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + } + + { + lang::EventObject aEvent( xThis ); + m_aListenerContainer.disposeAndClear( aEvent ); + + SolarMutexGuard g; + if ( m_bOwnsInstance ) + { + try + { + if ( m_xStatusBar.is() ) + m_xStatusBar->dispose(); + } + catch ( const lang::DisposedException& ) + { + } + } + + m_xStatusBar.clear(); + m_bDisposed = true; + } +} + +// XUIElement +uno::Reference< uno::XInterface > SAL_CALL ProgressBarWrapper::getRealInterface() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return uno::Reference< uno::XInterface >(); + else + { + uno::Reference< uno::XInterface > xComp( m_xProgressBarIfacWrapper ); + if ( !xComp.is() ) + { + rtl::Reference<StatusIndicatorInterfaceWrapper> pWrapper = + new StatusIndicatorInterfaceWrapper( uno::Reference< lang::XComponent >(this) ); + xComp.set(static_cast< cppu::OWeakObject* >( pWrapper.get() ), + uno::UNO_QUERY ); + m_xProgressBarIfacWrapper = xComp; + } + + return xComp; + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/recentfilesmenucontroller.cxx b/framework/source/uielement/recentfilesmenucontroller.cxx new file mode 100644 index 000000000..dc4001a1f --- /dev/null +++ b/framework/source/uielement/recentfilesmenucontroller.cxx @@ -0,0 +1,411 @@ +/* -*- 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 <strings.hrc> +#include <classes/fwkresid.hxx> + +#include <comphelper/propertyvalue.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <osl/mutex.hxx> +#include <svtools/imagemgr.hxx> +#include <svtools/popupmenucontrollerbase.hxx> +#include <tools/urlobj.hxx> +#include <unotools/historyoptions.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/graph.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <o3tl/string_view.hxx> + +using namespace css; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::frame; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; + +#define MAX_MENU_ITEMS 99 + +namespace { + +constexpr OUStringLiteral CMD_CLEAR_LIST = u".uno:ClearRecentFileList"; +constexpr OUStringLiteral CMD_OPEN_AS_TEMPLATE = u".uno:OpenTemplate"; +constexpr OUStringLiteral CMD_OPEN_REMOTE = u".uno:OpenRemote"; + +class RecentFilesMenuController : public svt::PopupMenuControllerBase +{ + using svt::PopupMenuControllerBase::disposing; + +public: + RecentFilesMenuController( const uno::Reference< uno::XComponentContext >& xContext, + const uno::Sequence< uno::Any >& args ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.RecentFilesMenuController"; + } + + 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.frame.PopupMenuController"}; + } + + // XStatusListener + virtual void SAL_CALL statusChanged( const frame::FeatureStateEvent& Event ) override; + + // XMenuListener + virtual void SAL_CALL itemSelected( const awt::MenuEvent& rEvent ) override; + virtual void SAL_CALL itemActivated( const awt::MenuEvent& rEvent ) override; + + // XDispatchProvider + virtual uno::Reference< frame::XDispatch > SAL_CALL queryDispatch( const util::URL& aURL, const OUString& sTarget, sal_Int32 nFlags ) override; + + // XDispatch + virtual void SAL_CALL dispatch( const util::URL& aURL, const uno::Sequence< beans::PropertyValue >& seqProperties ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + +private: + virtual void impl_setPopupMenu() override; + void fillPopupMenu( css::uno::Reference< css::awt::XPopupMenu > const & rPopupMenu ); + void executeEntry( sal_Int32 nIndex ); + + std::vector<std::pair<OUString, bool>> m_aRecentFilesItems; + bool m_bDisabled : 1; + bool m_bShowToolbarEntries; +}; + +RecentFilesMenuController::RecentFilesMenuController( const uno::Reference< uno::XComponentContext >& xContext, + const uno::Sequence< uno::Any >& args ) : + svt::PopupMenuControllerBase( xContext ), + m_bDisabled( false ), + m_bShowToolbarEntries( false ) +{ + css::beans::PropertyValue aPropValue; + for ( uno::Any const & arg : args ) + { + arg >>= aPropValue; + if ( aPropValue.Name == "InToolbar" ) + { + aPropValue.Value >>= m_bShowToolbarEntries; + break; + } + } +} + +void InsertItem(const css::uno::Reference<css::awt::XPopupMenu>& rPopupMenu, + const OUString& rCommand, + const css::uno::Reference<css::frame::XFrame>& rFrame) +{ + sal_uInt16 nItemId = rPopupMenu->getItemCount() + 1; + + if (rFrame.is()) + { + OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(rFrame)); + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rCommand, aModuleName); + OUString aLabel(vcl::CommandInfoProvider::GetPopupLabelForCommand(aProperties)); + OUString aTooltip(vcl::CommandInfoProvider::GetTooltipForCommand(rCommand, aProperties, rFrame)); + css::uno::Reference<css::graphic::XGraphic> xGraphic(vcl::CommandInfoProvider::GetXGraphicForCommand(rCommand, rFrame)); + + rPopupMenu->insertItem(nItemId, aLabel, 0, -1); + rPopupMenu->setItemImage(nItemId, xGraphic, false); + rPopupMenu->setHelpText(nItemId, aTooltip); + } + else + rPopupMenu->insertItem(nItemId, OUString(), 0, -1); + + rPopupMenu->setCommand(nItemId, rCommand); +} + + +// private function +void RecentFilesMenuController::fillPopupMenu( Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + SolarMutexGuard aSolarMutexGuard; + + resetPopupMenu( rPopupMenu ); + + std::vector< SvtHistoryOptions::HistoryItem > aHistoryList = SvtHistoryOptions::GetList( EHistoryType::PickList ); + + int nPickListMenuItems = std::min<sal_Int32>( aHistoryList.size(), MAX_MENU_ITEMS ); + m_aRecentFilesItems.clear(); + + if (( nPickListMenuItems > 0 ) && !m_bDisabled ) + { + for ( int i = 0; i < nPickListMenuItems; i++ ) + { + const SvtHistoryOptions::HistoryItem& rPickListEntry = aHistoryList[i]; + m_aRecentFilesItems.emplace_back(rPickListEntry.sURL, rPickListEntry.isReadOnly); + } + } + + if ( !m_aRecentFilesItems.empty() ) + { + const sal_uInt32 nCount = m_aRecentFilesItems.size(); + StyleSettings aIconSettings; + bool bIsIconsAllowed = aIconSettings.GetUseImagesInMenus(); + + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + + OUStringBuffer aMenuShortCut; + if ( i <= 9 ) + { + if ( i == 9 ) + aMenuShortCut.append( "1~0. " ); + else + { + aMenuShortCut.append( "~N. " ); + aMenuShortCut[ 1 ] = sal_Unicode( i + '1' ); + } + } + else + { + aMenuShortCut.append( sal_Int32( i + 1 ) ); + aMenuShortCut.append( ". " ); + } + + OUString aURLString = "vnd.sun.star.popup:RecentFileList?entry=" + OUString::number(i); + + // Abbreviate URL + OUString aMenuTitle; + INetURLObject const aURL(m_aRecentFilesItems[i].first); + OUString aTipHelpText( aURL.getFSysPath( FSysStyle::Detect ) ); + + if ( aURL.GetProtocol() == INetProtocol::File ) + { + // Do handle file URL differently: don't show the protocol, just the file name + aMenuTitle = aURL.GetLastName(INetURLObject::DecodeMechanism::WithCharset); + } + else + { + // In all other URLs show the protocol name before the file name + aMenuTitle = INetURLObject::GetSchemeName(aURL.GetProtocol()) + ": " + aURL.getName(); + } + + aMenuShortCut.append( aMenuTitle ); + + rPopupMenu->insertItem(sal_uInt16( i+1 ), aMenuShortCut.makeStringAndClear(), 0, -1); + + if ( bIsIconsAllowed ) { + // tdf#146219: don't use SvFileInformationManager::GetImageId, + // which needs to access the URL to detect if it's a directory + BitmapEx aThumbnail(SvFileInformationManager::GetFileImageId(aURL)); + rPopupMenu->setItemImage(sal_uInt16(i + 1), Graphic(aThumbnail).GetXGraphic(), false); + } + + rPopupMenu->setTipHelpText(sal_uInt16(i + 1), aTipHelpText); + rPopupMenu->setCommand(sal_uInt16(i + 1), aURLString); + } + + rPopupMenu->insertSeparator(-1); + // Clear List menu entry + rPopupMenu->insertItem(sal_uInt16(nCount + 1), FwkResId(STR_CLEAR_RECENT_FILES), 0, -1); + rPopupMenu->setCommand(sal_uInt16(nCount + 1), CMD_CLEAR_LIST); + rPopupMenu->setHelpText(sal_uInt16(nCount + 1), FwkResId(STR_CLEAR_RECENT_FILES_HELP)); + + // Open remote menu entry + if ( m_bShowToolbarEntries ) + { + rPopupMenu->insertSeparator(-1); + InsertItem(rPopupMenu, CMD_OPEN_AS_TEMPLATE, m_xFrame); + InsertItem(rPopupMenu, CMD_OPEN_REMOTE, m_xFrame); + } + } + else + { + if ( m_bShowToolbarEntries ) + { + InsertItem(rPopupMenu, CMD_OPEN_AS_TEMPLATE, m_xFrame); + InsertItem(rPopupMenu, CMD_OPEN_REMOTE, m_xFrame); + } + else + { + // Add InsertSeparator(), otherwise it will display + // the first item icon of recent files instead of displaying no icon. + rPopupMenu->insertSeparator(-1); + // No recent documents => insert "no documents" string + // Do not disable it, otherwise the Toolbar controller and MenuButton + // will display SV_RESID_STRING_NOSELECTIONPOSSIBLE instead of STR_NODOCUMENT + rPopupMenu->insertItem(1, FwkResId(STR_NODOCUMENT), static_cast<sal_Int16>(MenuItemBits::NOSELECT), -1); + } + } +} + +void RecentFilesMenuController::executeEntry( sal_Int32 nIndex ) +{ + if (( nIndex < 0 ) || + ( nIndex >= sal::static_int_cast<sal_Int32>( m_aRecentFilesItems.size() ))) + return; + + Sequence< PropertyValue > aArgsList{ + comphelper::makePropertyValue("Referer", OUString( "private:user" )), + + // documents in the picklist will never be opened as templates + comphelper::makePropertyValue("AsTemplate", false), + + // Type detection needs to know which app we are opening it from. + comphelper::makePropertyValue("DocumentService", m_aModuleName) + }; + if (m_aRecentFilesItems[nIndex].second) // tdf#149170 only add if true + { + aArgsList.realloc(aArgsList.size()+1); + aArgsList.getArray()[aArgsList.size()-1] = comphelper::makePropertyValue("ReadOnly", true); + } + dispatchCommand(m_aRecentFilesItems[nIndex].first, aArgsList, "_default"); +} + +// XEventListener +void SAL_CALL RecentFilesMenuController::disposing( const EventObject& ) +{ + Reference< css::awt::XMenuListener > xHolder(this); + + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + + if ( m_xPopupMenu.is() ) + m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); + m_xPopupMenu.clear(); +} + +// XStatusListener +void SAL_CALL RecentFilesMenuController::statusChanged( const FeatureStateEvent& Event ) +{ + osl::MutexGuard aLock( m_aMutex ); + m_bDisabled = !Event.IsEnabled; +} + +void SAL_CALL RecentFilesMenuController::itemSelected( const css::awt::MenuEvent& rEvent ) +{ + Reference< css::awt::XPopupMenu > xPopupMenu; + + { + osl::MutexGuard aLock(m_aMutex); + xPopupMenu = m_xPopupMenu; + } + + if ( !xPopupMenu.is() ) + return; + + const OUString aCommand( xPopupMenu->getCommand( rEvent.MenuId ) ); + + if ( aCommand == CMD_CLEAR_LIST ) + { + SvtHistoryOptions::Clear( EHistoryType::PickList ); + dispatchCommand( + "vnd.org.libreoffice.recentdocs:ClearRecentFileList", + css::uno::Sequence< css::beans::PropertyValue >() ); + } + else if ( aCommand == CMD_OPEN_REMOTE ) + { + Sequence< PropertyValue > aArgsList( 0 ); + dispatchCommand( CMD_OPEN_REMOTE, aArgsList ); + } + else if ( aCommand == CMD_OPEN_AS_TEMPLATE ) + { + Sequence< PropertyValue > aArgsList( 0 ); + dispatchCommand( CMD_OPEN_AS_TEMPLATE, aArgsList ); + } + else + executeEntry( rEvent.MenuId-1 ); +} + +void SAL_CALL RecentFilesMenuController::itemActivated( const css::awt::MenuEvent& ) +{ + osl::MutexGuard aLock( m_aMutex ); + impl_setPopupMenu(); +} + +// XPopupMenuController +void RecentFilesMenuController::impl_setPopupMenu() +{ + if ( m_xPopupMenu.is() ) + fillPopupMenu( m_xPopupMenu ); +} + +// XDispatchProvider +Reference< XDispatch > SAL_CALL RecentFilesMenuController::queryDispatch( + const URL& aURL, + const OUString& /*sTarget*/, + sal_Int32 /*nFlags*/ ) +{ + osl::MutexGuard aLock( m_aMutex ); + + throwIfDisposed(); + + if ( aURL.Complete.startsWith( m_aBaseURL ) ) + return Reference< XDispatch >( this ); + else + return Reference< XDispatch >(); +} + +// XDispatch +void SAL_CALL RecentFilesMenuController::dispatch( + const URL& aURL, + const Sequence< PropertyValue >& /*seqProperties*/ ) +{ + osl::MutexGuard aLock( m_aMutex ); + + throwIfDisposed(); + + if ( !aURL.Complete.startsWith( m_aBaseURL ) ) + return; + + // Parse URL to retrieve entry argument and its value + sal_Int32 nQueryPart = aURL.Complete.indexOf( '?', m_aBaseURL.getLength() ); + if ( nQueryPart <= 0 ) + return; + + static const OUStringLiteral aEntryArgStr( u"entry=" ); + sal_Int32 nEntryArg = aURL.Complete.indexOf( aEntryArgStr, nQueryPart ); + sal_Int32 nEntryPos = nEntryArg + aEntryArgStr.getLength(); + if (( nEntryArg <= 0 ) || ( nEntryPos >= aURL.Complete.getLength() )) + return; + + sal_Int32 nAddArgs = aURL.Complete.indexOf( '&', nEntryPos ); + std::u16string_view aEntryArg; + + if ( nAddArgs < 0 ) + aEntryArg = aURL.Complete.subView( nEntryPos ); + else + aEntryArg = aURL.Complete.subView( nEntryPos, nAddArgs-nEntryPos ); + + sal_Int32 nEntry = o3tl::toInt32(aEntryArg); + executeEntry( nEntry ); +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_RecentFilesMenuController_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &args) +{ + return cppu::acquire(new RecentFilesMenuController(context, args)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/resourcemenucontroller.cxx b/framework/source/uielement/resourcemenucontroller.cxx new file mode 100644 index 000000000..7a1364b41 --- /dev/null +++ b/framework/source/uielement/resourcemenucontroller.cxx @@ -0,0 +1,584 @@ +/* -*- 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 <uielement/menubarmanager.hxx> + +#include <cppuhelper/implbase.hxx> +#include <svtools/popupmenucontrollerbase.hxx> +#include <toolkit/awt/vclxmenu.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <sal/log.hxx> + +#include <com/sun/star/embed/VerbAttributes.hpp> +#include <com/sun/star/embed/VerbDescriptor.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/util/URL.hpp> + +namespace { + +class ResourceMenuController : public cppu::ImplInheritanceHelper< svt::PopupMenuControllerBase, css::ui::XUIConfigurationListener > +{ +public: + ResourceMenuController( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Sequence< css::uno::Any >& rxArgs, bool bToolbarContainer ); + + // XPopupMenuController + virtual void SAL_CALL updatePopupMenu() override; + + // XStatusListener + virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& rEvent ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& rEvent ) override; + + // XUIConfigurationListener + virtual void SAL_CALL elementInserted( const css::ui::ConfigurationEvent& rEvent ) override; + virtual void SAL_CALL elementRemoved( const css::ui::ConfigurationEvent& rEvent ) override; + virtual void SAL_CALL elementReplaced( const css::ui::ConfigurationEvent& rEvent ) override; + + // XMenuListener + virtual void SAL_CALL itemActivated( const css::awt::MenuEvent& rEvent ) override; + virtual void SAL_CALL itemSelected( const css::awt::MenuEvent& rEvent ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + +private: + OUString m_aMenuURL; + bool m_bContextMenu; + bool m_bInToolbar; + bool m_bToolbarContainer; + sal_uInt16 m_nNewMenuId; + rtl::Reference< framework::MenuBarManager > m_xMenuBarManager; + css::uno::Reference< css::frame::XDispatchProvider > m_xDispatchProvider; + css::uno::Reference< css::container::XIndexAccess > m_xMenuContainer; + css::uno::Reference< css::ui::XUIConfigurationManager > m_xConfigManager, m_xModuleConfigManager; + void addVerbs( const css::uno::Sequence< css::embed::VerbDescriptor >& rVerbs ); + virtual void SAL_CALL disposing() override; + +protected: + css::uno::Reference< css::uno::XComponentContext > m_xContext; +}; + +ResourceMenuController::ResourceMenuController( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Sequence< css::uno::Any >& rxArgs, bool bToolbarContainer ) : + ImplInheritanceHelper( rxContext ), + m_bContextMenu( false ), + m_bInToolbar( false ), + m_bToolbarContainer( bToolbarContainer ), + m_nNewMenuId( 1 ), + m_xContext( rxContext ) +{ + for ( const auto& arg: rxArgs ) + { + css::beans::PropertyValue aPropValue; + if ( arg >>= aPropValue ) + { + if ( aPropValue.Name == "Value" ) + { + OUString aMenuName; + aPropValue.Value >>= aMenuName; + if ( aMenuName.isEmpty() ) + continue; + + if ( m_bToolbarContainer ) + m_aMenuURL = "private:resource/toolbar/" + aMenuName; + else + m_aMenuURL = "private:resource/popupmenu/" + aMenuName; + } + else if ( aPropValue.Name == "ResourceURL" ) + aPropValue.Value >>= m_aMenuURL; + else if ( aPropValue.Name == "Frame" ) + aPropValue.Value >>= m_xFrame; + else if ( aPropValue.Name == "ModuleIdentifier" ) + aPropValue.Value >>= m_aModuleName; + else if ( aPropValue.Name == "DispatchProvider" ) + aPropValue.Value >>= m_xDispatchProvider; + else if ( aPropValue.Name == "IsContextMenu" ) + aPropValue.Value >>= m_bContextMenu; + else if ( aPropValue.Name == "InToolbar" ) + aPropValue.Value >>= m_bInToolbar; + } + } + if ( m_xFrame.is() ) + // No need to initialize again through initialize method. + m_bInitialized = true; +} + +void ResourceMenuController::updatePopupMenu() +{ + if ( ( m_xMenuContainer.is() && !m_bContextMenu ) || m_aMenuURL.isEmpty() ) + return; + + if ( m_aModuleName.isEmpty() ) + { + try + { + css::uno::Reference< css::frame::XModuleManager > xModuleManager( css::frame::ModuleManager::create( m_xContext ) ); + m_aModuleName = xModuleManager->identify( m_xFrame ); + } + catch( const css::uno::Exception& ) + {} + } + + if ( !m_xConfigManager.is() ) + { + try + { + css::uno::Reference< css::frame::XController > xController( m_xFrame->getController() ); + css::uno::Reference< css::frame::XModel > xModel( xController->getModel() ); + css::uno::Reference< css::ui::XUIConfigurationManagerSupplier > xSupplier( xModel, css::uno::UNO_QUERY_THROW ); + m_xConfigManager.set( xSupplier->getUIConfigurationManager() ); + css::uno::Reference< css::ui::XUIConfiguration > xConfig( m_xConfigManager, css::uno::UNO_QUERY_THROW ); + xConfig->addConfigurationListener( this ); + } + catch( const css::uno::RuntimeException& ) + {} + } + + if ( !m_xModuleConfigManager.is() ) + { + try + { + css::uno::Reference< css::ui::XModuleUIConfigurationManagerSupplier > xModuleCfgMgrSupplier( + css::ui::theModuleUIConfigurationManagerSupplier::get( m_xContext ) ); + m_xModuleConfigManager.set( xModuleCfgMgrSupplier->getUIConfigurationManager( m_aModuleName ) ); + css::uno::Reference< css::ui::XUIConfiguration > xConfig( m_xModuleConfigManager, css::uno::UNO_QUERY_THROW ); + xConfig->addConfigurationListener( this ); + } + catch ( const css::container::NoSuchElementException& ) + { + SAL_WARN( "fwk.uielement", "Invalid module identifier: " << m_aModuleName ); + } + catch( const css::uno::RuntimeException& ) + {} + } + + if ( !m_xMenuContainer.is() && m_xConfigManager.is() ) + { + try + { + m_xMenuContainer.set( m_xConfigManager->getSettings( m_aMenuURL, false ) ); + } + catch ( const css::container::NoSuchElementException& ) + { + // Not an error - element may exist only in the module. + } + catch ( const css::lang::IllegalArgumentException& ) + { + SAL_WARN( "fwk.uielement", "The given URL is not valid: " << m_aMenuURL ); + return; + } + } + + if ( !m_xMenuContainer.is() && m_xModuleConfigManager.is() ) + { + try + { + m_xMenuContainer.set( m_xModuleConfigManager->getSettings( m_aMenuURL, false ) ); + } + catch ( const css::container::NoSuchElementException& ) + { + SAL_WARN( "fwk.uielement", "Can not find settings for " << m_aMenuURL ); + return; + } + catch ( const css::lang::IllegalArgumentException& ) + { + SAL_WARN( "fwk.uielement", "The given URL is not valid: " << m_aMenuURL ); + return; + } + } + + if ( !m_xMenuContainer.is() ) + return; + + // Clear previous content. + if ( m_xMenuBarManager.is() ) + { + m_xMenuBarManager->dispose(); + m_xMenuBarManager.clear(); + } + resetPopupMenu( m_xPopupMenu ); + m_nNewMenuId = 1; + + // Now fill the menu with the configuration data. + framework::MenuBarManager::FillMenu( m_nNewMenuId, comphelper::getFromUnoTunnel<VCLXMenu>( m_xPopupMenu )->GetMenu(), m_aModuleName, m_xMenuContainer, m_xDispatchProvider ); + + // For context menus, add object verbs. + if ( !m_bContextMenu ) + return; + + css::util::URL aObjectMenuURL; + aObjectMenuURL.Complete = ".uno:ObjectMenue"; + m_xURLTransformer->parseStrict( aObjectMenuURL ); + css::uno::Reference< css::frame::XDispatchProvider > xDispatchProvider( m_xFrame, css::uno::UNO_QUERY ); + css::uno::Reference< css::frame::XDispatch > xDispatch( xDispatchProvider->queryDispatch( aObjectMenuURL, OUString(), 0 ) ); + if ( xDispatch.is() ) + { + xDispatch->addStatusListener( this, aObjectMenuURL ); + xDispatch->removeStatusListener( this, aObjectMenuURL ); + } +} + +void ResourceMenuController::statusChanged( const css::frame::FeatureStateEvent& rEvent ) +{ + css::uno::Sequence< css::embed::VerbDescriptor > aVerbs; + if ( rEvent.IsEnabled && ( rEvent.State >>= aVerbs ) ) + addVerbs( aVerbs ); +} + +void ResourceMenuController::addVerbs( const css::uno::Sequence< css::embed::VerbDescriptor >& rVerbs ) +{ + // Check if the document is read-only. + css::uno::Reference< css::frame::XController > xController( m_xFrame->getController() ); + css::uno::Reference< css::frame::XStorable > xStorable; + if ( xController.is() ) + xStorable.set( xController->getModel(), css::uno::UNO_QUERY ); + + bool bReadOnly = xStorable.is() && xStorable->isReadonly(); + VCLXMenu* pAwtMenu = comphelper::getFromUnoTunnel<VCLXMenu>( m_xPopupMenu ); + Menu* pVCLMenu = pAwtMenu->GetMenu(); + + for ( const auto& rVerb : rVerbs ) + { + if ( !( rVerb.VerbAttributes & css::embed::VerbAttributes::MS_VERBATTR_ONCONTAINERMENU ) || + ( bReadOnly && !( rVerb.VerbAttributes & css::embed::VerbAttributes::MS_VERBATTR_NEVERDIRTIES ) ) ) + continue; + + pVCLMenu->InsertItem( m_nNewMenuId, rVerb.VerbName ); + pVCLMenu->SetItemCommand( m_nNewMenuId, ".uno:ObjectMenue?VerbID:short=" + OUString::number( rVerb.VerbID ) ); + ++m_nNewMenuId; + } +} + +void ResourceMenuController::itemActivated( const css::awt::MenuEvent& /*rEvent*/ ) +{ + // Must initialize MenuBarManager here, because we want to let the app do context menu interception before. + if ( !m_xMenuBarManager.is() ) + { + VCLXMenu* pAwtMenu = comphelper::getFromUnoTunnel<VCLXMenu>( m_xPopupMenu ); + m_xMenuBarManager.set( new framework::MenuBarManager( + m_xContext, m_xFrame, m_xURLTransformer, m_xDispatchProvider, m_aModuleName, pAwtMenu->GetMenu(), false, !m_bContextMenu && !m_bInToolbar ) ); + m_xFrame->addFrameActionListener( m_xMenuBarManager ); + } +} + +void ResourceMenuController::itemSelected( const css::awt::MenuEvent& /*rEvent*/ ) +{ + // Must override this, because we are managed by MenuBarManager, so don't want the handler found in the base class. +} + +void ResourceMenuController::elementInserted( const css::ui::ConfigurationEvent& rEvent ) +{ + if ( rEvent.ResourceURL == m_aMenuURL ) + m_xMenuContainer.clear(); +} + +void ResourceMenuController::elementRemoved( const css::ui::ConfigurationEvent& rEvent ) +{ + elementInserted( rEvent ); +} + +void ResourceMenuController::elementReplaced( const css::ui::ConfigurationEvent& rEvent ) +{ + elementInserted( rEvent ); +} + +void ResourceMenuController::disposing( const css::lang::EventObject& rEvent ) +{ + if ( rEvent.Source == m_xConfigManager ) + m_xConfigManager.clear(); + else if ( rEvent.Source == m_xModuleConfigManager ) + m_xModuleConfigManager.clear(); + else + { + if ( m_xMenuBarManager.is() ) + { + if (m_xFrame.is()) + m_xFrame->removeFrameActionListener( m_xMenuBarManager ); + + m_xMenuBarManager->dispose(); + m_xMenuBarManager.clear(); + } + svt::PopupMenuControllerBase::disposing( rEvent ); + } +} + +void ResourceMenuController::disposing() +{ + css::uno::Reference< css::ui::XUIConfiguration > xConfig( m_xConfigManager, css::uno::UNO_QUERY ); + if ( xConfig.is() ) + xConfig->removeConfigurationListener( this ); + + css::uno::Reference< css::ui::XUIConfiguration > xModuleConfig( m_xModuleConfigManager, css::uno::UNO_QUERY ); + if ( xModuleConfig.is() ) + xModuleConfig->removeConfigurationListener( this ); + + m_xConfigManager.clear(); + m_xModuleConfigManager.clear(); + m_xMenuContainer.clear(); + m_xDispatchProvider.clear(); + if ( m_xMenuBarManager.is() ) + { + if (m_xFrame.is()) + m_xFrame->removeFrameActionListener( m_xMenuBarManager ); + + m_xMenuBarManager->dispose(); + m_xMenuBarManager.clear(); + } + + svt::PopupMenuControllerBase::disposing(); +} + +OUString ResourceMenuController::getImplementationName() +{ + if ( m_bToolbarContainer ) + return "com.sun.star.comp.framework.ToolbarAsMenuController"; + + return "com.sun.star.comp.framework.ResourceMenuController"; +} + +css::uno::Sequence< OUString > ResourceMenuController::getSupportedServiceNames() +{ + return { "com.sun.star.frame.PopupMenuController" }; +} + +class SaveAsMenuController : public ResourceMenuController +{ +public: + SaveAsMenuController( const css::uno::Reference< css::uno::XComponentContext >& rContext, + const css::uno::Sequence< css::uno::Any >& rArgs ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + +private: + virtual void impl_setPopupMenu() override; +}; + +SaveAsMenuController::SaveAsMenuController( const css::uno::Reference< css::uno::XComponentContext >& rContext, + const css::uno::Sequence< css::uno::Any >& rArgs ) + : ResourceMenuController( rContext, rArgs, false ) +{ +} + +void InsertItem(const css::uno::Reference<css::awt::XPopupMenu>& rPopupMenu, + const OUString& rCommand) +{ + sal_uInt16 nItemId = rPopupMenu->getItemCount() + 1; + rPopupMenu->insertItem(nItemId, OUString(), 0, -1); + rPopupMenu->setCommand(nItemId, rCommand); +} + +void SaveAsMenuController::impl_setPopupMenu() +{ + SolarMutexGuard aGuard; + + InsertItem(m_xPopupMenu, ".uno:SaveAs"); + InsertItem(m_xPopupMenu, ".uno:ExportTo"); + InsertItem(m_xPopupMenu, ".uno:SaveACopy"); + InsertItem(m_xPopupMenu, ".uno:SaveAsTemplate"); + m_xPopupMenu->insertSeparator(-1); + InsertItem(m_xPopupMenu, ".uno:SaveAsRemote"); +} + +OUString SaveAsMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.SaveAsMenuController"; +} + +class WindowListMenuController : public ResourceMenuController +{ +public: + WindowListMenuController( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Sequence< css::uno::Any >& rxArgs ) + : ResourceMenuController(rxContext, rxArgs, false) {} + + // XMenuListener + void SAL_CALL itemActivated( const css::awt::MenuEvent& rEvent ) override; + void SAL_CALL itemSelected( const css::awt::MenuEvent& rEvent ) override; + + // XServiceInfo + OUString SAL_CALL getImplementationName() override; + +private: + void impl_setPopupMenu() override; +}; + +constexpr sal_uInt16 START_ITEMID_WINDOWLIST = 4600; +constexpr sal_uInt16 END_ITEMID_WINDOWLIST = 4699; + +void WindowListMenuController::itemActivated( const css::awt::MenuEvent& rEvent ) +{ + ResourceMenuController::itemActivated( rEvent ); + + // update window list + ::std::vector< OUString > aNewWindowListVector; + + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( m_xContext ); + + sal_uInt16 nActiveItemId = 0; + sal_uInt16 nItemId = START_ITEMID_WINDOWLIST; + + css::uno::Reference< css::frame::XFrame > xCurrentFrame = xDesktop->getCurrentFrame(); + css::uno::Reference< css::container::XIndexAccess > xList = xDesktop->getFrames(); + sal_Int32 nFrameCount = xList->getCount(); + aNewWindowListVector.reserve(nFrameCount); + for (sal_Int32 i=0; i<nFrameCount; ++i ) + { + css::uno::Reference< css::frame::XFrame > xFrame; + xList->getByIndex(i) >>= xFrame; + + if (xFrame.is()) + { + if ( xFrame == xCurrentFrame ) + nActiveItemId = nItemId; + + VclPtr<vcl::Window> pWin = VCLUnoHelper::GetWindow( xFrame->getContainerWindow() ); + OUString sWindowTitle; + if ( pWin && pWin->IsVisible() ) + sWindowTitle = pWin->GetText(); + + // tdf#101658 In case the frame is embedded somewhere, LO has no control over it. + // So we just skip it. + if ( sWindowTitle.isEmpty() ) + continue; + + aNewWindowListVector.push_back( sWindowTitle ); + ++nItemId; + } + } + + { + SolarMutexGuard g; + + VCLXMenu* pAwtMenu = comphelper::getFromUnoTunnel<VCLXMenu>( m_xPopupMenu ); + Menu* pVCLMenu = pAwtMenu->GetMenu(); + int nItemCount = pVCLMenu->GetItemCount(); + + if ( nItemCount > 0 ) + { + // remove all old window list entries from menu + sal_uInt16 nPos = pVCLMenu->GetItemPos( START_ITEMID_WINDOWLIST ); + for ( sal_uInt16 n = nPos; n < pVCLMenu->GetItemCount(); ) + pVCLMenu->RemoveItem( n ); + + if ( pVCLMenu->GetItemType( pVCLMenu->GetItemCount()-1 ) == MenuItemType::SEPARATOR ) + pVCLMenu->RemoveItem( pVCLMenu->GetItemCount()-1 ); + } + + if ( !aNewWindowListVector.empty() ) + { + // append new window list entries to menu + pVCLMenu->InsertSeparator(); + nItemId = START_ITEMID_WINDOWLIST; + const sal_uInt32 nCount = aNewWindowListVector.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + pVCLMenu->InsertItem( nItemId, aNewWindowListVector.at( i ), MenuItemBits::RADIOCHECK ); + if ( nItemId == nActiveItemId ) + pVCLMenu->CheckItem( nItemId ); + ++nItemId; + } + } + } +} + +void WindowListMenuController::itemSelected( const css::awt::MenuEvent& rEvent ) +{ + if ( rEvent.MenuId < START_ITEMID_WINDOWLIST || rEvent.MenuId > END_ITEMID_WINDOWLIST ) + return; + + // window list menu item selected + css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( m_xContext ); + + sal_uInt16 nTaskId = START_ITEMID_WINDOWLIST; + css::uno::Reference< css::container::XIndexAccess > xList = xDesktop->getFrames(); + sal_Int32 nCount = xList->getCount(); + for ( sal_Int32 i=0; i<nCount; ++i ) + { + css::uno::Reference< css::frame::XFrame > xFrame; + xList->getByIndex(i) >>= xFrame; + if ( xFrame.is() && nTaskId == rEvent.MenuId ) + { + VclPtr<vcl::Window> pWin = VCLUnoHelper::GetWindow( xFrame->getContainerWindow() ); + pWin->GrabFocus(); + pWin->ToTop( ToTopFlags::RestoreWhenMin ); + break; + } + + nTaskId++; + } +} + +void WindowListMenuController::impl_setPopupMenu() +{ + // Make this controller work also with initially empty + // menu, which PopupMenu::ImplExecute doesn't allow. + if (m_xPopupMenu.is() && !m_xPopupMenu->getItemCount()) + m_xPopupMenu->insertSeparator(0); +} + +OUString WindowListMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.WindowListMenuController"; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ResourceMenuController_get_implementation( + css::uno::XComponentContext* context, + css::uno::Sequence< css::uno::Any > const & args ) +{ + return cppu::acquire( new ResourceMenuController( context, args, false ) ); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ToolbarAsMenuController_get_implementation( + css::uno::XComponentContext* context, + css::uno::Sequence< css::uno::Any > const & args ) +{ + return cppu::acquire( new ResourceMenuController( context, args, true ) ); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_WindowListMenuController_get_implementation( + css::uno::XComponentContext* context, + css::uno::Sequence< css::uno::Any > const & args ) +{ + return cppu::acquire( new WindowListMenuController( context, args ) ); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_SaveAsMenuController_get_implementation( + css::uno::XComponentContext* context, + css::uno::Sequence< css::uno::Any > const & args ) +{ + return cppu::acquire( new SaveAsMenuController( context, args ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/spinfieldtoolbarcontroller.cxx b/framework/source/uielement/spinfieldtoolbarcontroller.cxx new file mode 100644 index 000000000..fdcac34fd --- /dev/null +++ b/framework/source/uielement/spinfieldtoolbarcontroller.cxx @@ -0,0 +1,452 @@ +/* -*- 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 <stdio.h> + +#include <uielement/spinfieldtoolbarcontroller.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <svtools/toolboxcontroller.hxx> +#include <vcl/InterimItemWindow.hxx> +#include <vcl/event.hxx> +#include <vcl/formatter.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> +#include <o3tl/char16_t2wchar_t.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::util; + +namespace framework +{ + +// Wrapper class to notify controller about events from combobox. +// Unfortunaltly the events are notified through virtual methods instead +// of Listeners. + +class SpinfieldControl final : public InterimItemWindow +{ +public: + SpinfieldControl(vcl::Window* pParent, SpinfieldToolbarController* pSpinfieldToolbarController); + virtual ~SpinfieldControl() override; + virtual void dispose() override; + + Formatter& GetFormatter() + { + return m_xWidget->GetFormatter(); + } + + OUString get_entry_text() const { return m_xWidget->get_text(); } + + DECL_LINK(ValueChangedHdl, weld::FormattedSpinButton&, void); + DECL_LINK(FormatOutputHdl, LinkParamNone*, bool); + DECL_LINK(ParseInputHdl, sal_Int64*, TriState); + DECL_LINK(ModifyHdl, weld::Entry&, void); + DECL_LINK(ActivateHdl, weld::Entry&, bool); + DECL_LINK(FocusInHdl, weld::Widget&, void); + DECL_LINK(FocusOutHdl, weld::Widget&, void); + DECL_LINK(KeyInputHdl, const ::KeyEvent&, bool); + +private: + std::unique_ptr<weld::FormattedSpinButton> m_xWidget; + SpinfieldToolbarController* m_pSpinfieldToolbarController; +}; + +SpinfieldControl::SpinfieldControl(vcl::Window* pParent, SpinfieldToolbarController* pSpinfieldToolbarController) + : InterimItemWindow(pParent, "svt/ui/spinfieldcontrol.ui", "SpinFieldControl") + , m_xWidget(m_xBuilder->weld_formatted_spin_button("spinbutton")) + , m_pSpinfieldToolbarController(pSpinfieldToolbarController) +{ + InitControlBase(m_xWidget.get()); + + m_xWidget->connect_focus_in(LINK(this, SpinfieldControl, FocusInHdl)); + m_xWidget->connect_focus_out(LINK(this, SpinfieldControl, FocusOutHdl)); + Formatter& rFormatter = m_xWidget->GetFormatter(); + rFormatter.SetOutputHdl(LINK(this, SpinfieldControl, FormatOutputHdl)); + rFormatter.SetInputHdl(LINK(this, SpinfieldControl, ParseInputHdl)); + m_xWidget->connect_value_changed(LINK(this, SpinfieldControl, ValueChangedHdl)); + m_xWidget->connect_changed(LINK(this, SpinfieldControl, ModifyHdl)); + m_xWidget->connect_activate(LINK(this, SpinfieldControl, ActivateHdl)); + m_xWidget->connect_key_press(LINK(this, SpinfieldControl, KeyInputHdl)); + + // so a later narrow size request can stick + m_xWidget->set_width_chars(3); + m_xWidget->set_size_request(42, -1); + + SetSizePixel(get_preferred_size()); +} + +IMPL_LINK(SpinfieldControl, KeyInputHdl, const ::KeyEvent&, rKEvt, bool) +{ + return ChildKeyInput(rKEvt); +} + +IMPL_LINK(SpinfieldControl, ParseInputHdl, sal_Int64*, result, TriState) +{ + *result = m_xWidget->get_text().toDouble() * weld::SpinButton::Power10(m_xWidget->GetFormatter().GetDecimalDigits()); + return TRISTATE_TRUE; +} + +SpinfieldControl::~SpinfieldControl() +{ + disposeOnce(); +} + +void SpinfieldControl::dispose() +{ + m_pSpinfieldToolbarController = nullptr; + m_xWidget.reset(); + InterimItemWindow::dispose(); +} + +IMPL_LINK_NOARG(SpinfieldControl, ValueChangedHdl, weld::FormattedSpinButton&, void) +{ + if (m_pSpinfieldToolbarController) + m_pSpinfieldToolbarController->execute(0); +} + +IMPL_LINK_NOARG(SpinfieldControl, ModifyHdl, weld::Entry&, void) +{ + if (m_pSpinfieldToolbarController) + m_pSpinfieldToolbarController->Modify(); +} + +IMPL_LINK_NOARG(SpinfieldControl, FocusInHdl, weld::Widget&, void) +{ + if (m_pSpinfieldToolbarController) + m_pSpinfieldToolbarController->GetFocus(); +} + +IMPL_LINK_NOARG(SpinfieldControl, FocusOutHdl, weld::Widget&, void) +{ + if (m_pSpinfieldToolbarController) + m_pSpinfieldToolbarController->LoseFocus(); +} + +IMPL_LINK_NOARG(SpinfieldControl, ActivateHdl, weld::Entry&, bool) +{ + bool bConsumed = false; + if (m_pSpinfieldToolbarController) + { + m_pSpinfieldToolbarController->Activate(); + bConsumed = true; + } + return bConsumed; +} + +IMPL_LINK_NOARG(SpinfieldControl, FormatOutputHdl, LinkParamNone*, bool) +{ + OUString aText = m_pSpinfieldToolbarController->FormatOutputString(m_xWidget->GetFormatter().GetValue()); + m_xWidget->set_text(aText); + return true; +} + +SpinfieldToolbarController::SpinfieldToolbarController( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + ToolBox* pToolbar, + ToolBoxItemId nID, + sal_Int32 nWidth, + const OUString& aCommand ) : + ComplexToolbarController( rxContext, rFrame, pToolbar, nID, aCommand ) + , m_bFloat( false ) + , m_nMax( 0.0 ) + , m_nMin( 0.0 ) + , m_nValue( 0.0 ) + , m_nStep( 0.0 ) + , m_pSpinfieldControl( nullptr ) +{ + m_pSpinfieldControl = VclPtr<SpinfieldControl>::Create(m_xToolbar, this); + if ( nWidth == 0 ) + nWidth = 100; + + // SpinFieldControl ctor has set a suitable height already + auto nHeight = m_pSpinfieldControl->GetSizePixel().Height(); + + m_pSpinfieldControl->SetSizePixel( ::Size( nWidth, nHeight )); + m_xToolbar->SetItemWindow( m_nID, m_pSpinfieldControl ); +} + +SpinfieldToolbarController::~SpinfieldToolbarController() +{ +} + +void SAL_CALL SpinfieldToolbarController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + + m_xToolbar->SetItemWindow( m_nID, nullptr ); + m_pSpinfieldControl.disposeAndClear(); + + ComplexToolbarController::dispose(); +} + +Sequence<PropertyValue> SpinfieldToolbarController::getExecuteArgs(sal_Int16 KeyModifier) const +{ + OUString aSpinfieldText = m_pSpinfieldControl->get_entry_text(); + + // Add key modifier to argument list + auto aArgs0 = comphelper::makePropertyValue("KeyModifier", KeyModifier); + auto aArgs1 = comphelper::makePropertyValue("Value", m_bFloat ? Any(aSpinfieldText.toDouble()) + : Any(aSpinfieldText.toInt32())); + return { aArgs0, aArgs1 }; +} + +void SpinfieldToolbarController::Modify() +{ + notifyTextChanged(m_pSpinfieldControl->get_entry_text()); +} + +void SpinfieldToolbarController::GetFocus() +{ + notifyFocusGet(); +} + +void SpinfieldToolbarController::LoseFocus() +{ + notifyFocusLost(); +} + +void SpinfieldToolbarController::Activate() +{ + // Call execute only with non-empty text + if (!m_pSpinfieldControl->get_entry_text().isEmpty()) + execute(0); +} + +void SpinfieldToolbarController::executeControlCommand( const css::frame::ControlCommand& rControlCommand ) +{ + OUString aValue; + OUString aMax; + OUString aMin; + OUString aStep; + bool bFloatValue( false ); + + if ( rControlCommand.Command == "SetStep" ) + { + for ( auto const & arg : rControlCommand.Arguments ) + { + if ( arg.Name == "Step" ) + { + sal_Int32 nValue; + double fValue; + bool bFloat( false ); + if ( impl_getValue( arg.Value, nValue, fValue, bFloat )) + aStep = bFloat ? OUString( OUString::number( fValue )) : + OUString( OUString::number( nValue )); + break; + } + } + } + else if ( rControlCommand.Command == "SetValue" ) + { + for ( auto const & arg : rControlCommand.Arguments ) + { + if ( arg.Name == "Value" ) + { + sal_Int32 nValue; + double fValue; + bool bFloat( false ); + + if ( impl_getValue( arg.Value, nValue, fValue, bFloat )) + { + aValue = bFloat ? OUString( OUString::number( fValue )) : + OUString( OUString::number( nValue )); + bFloatValue = bFloat; + } + break; + } + } + } + else if ( rControlCommand.Command == "SetValues" ) + { + for ( auto const & arg : rControlCommand.Arguments ) + { + sal_Int32 nValue; + double fValue; + bool bFloat( false ); + + OUString aName = arg.Name; + if ( impl_getValue( arg.Value, nValue, fValue, bFloat )) + { + if ( aName == "Value" ) + { + aValue = bFloat ? OUString( OUString::number( fValue )) : + OUString( OUString::number( nValue )); + bFloatValue = bFloat; + } + else if ( aName == "Step" ) + aStep = bFloat ? OUString( OUString::number( fValue )) : + OUString( OUString::number( nValue )); + else if ( aName == "LowerLimit" ) + aMin = bFloat ? OUString( OUString::number( fValue )) : + OUString( OUString::number( nValue )); + else if ( aName == "UpperLimit" ) + aMax = bFloat ? OUString( OUString::number( fValue )) : + OUString( OUString::number( nValue )); + } + else if ( aName == "OutputFormat" ) + arg.Value >>= m_aOutFormat; + } + } + else if ( rControlCommand.Command == "SetLowerLimit" ) + { + for ( auto const & arg : rControlCommand.Arguments ) + { + if ( arg.Name == "LowerLimit" ) + { + sal_Int32 nValue; + double fValue; + bool bFloat( false ); + if ( impl_getValue( arg.Value, nValue, fValue, bFloat )) + aMin = bFloat ? OUString( OUString::number( fValue )) : + OUString( OUString::number( nValue )); + break; + } + } + } + else if ( rControlCommand.Command == "SetUpperLimit" ) + { + for ( auto const & arg : rControlCommand.Arguments ) + { + if ( arg.Name == "UpperLimit" ) + { + sal_Int32 nValue; + double fValue; + bool bFloat( false ); + if ( impl_getValue( arg.Value, nValue, fValue, bFloat )) + aMax = bFloat ? OUString( OUString::number( fValue )) : + OUString( OUString::number( nValue )); + break; + } + } + } + else if ( rControlCommand.Command == "SetOutputFormat" ) + { + for ( auto const & arg : rControlCommand.Arguments ) + { + if ( arg.Name == "OutputFormat" ) + { + arg.Value >>= m_aOutFormat; + break; + } + } + } + + Formatter& rFormatter = m_pSpinfieldControl->GetFormatter(); + + // Check values and set members + if (bFloatValue) + rFormatter.SetDecimalDigits(2); + if ( !aValue.isEmpty() ) + { + m_bFloat = bFloatValue; + m_nValue = aValue.toDouble(); + rFormatter.SetValue(m_nValue); + } + if ( !aMax.isEmpty() ) + { + m_nMax = aMax.toDouble(); + rFormatter.SetMaxValue(m_nMax); + } + if ( !aMin.isEmpty() ) + { + m_nMin = aMin.toDouble(); + rFormatter.SetMinValue(m_nMin); + } + if ( !aStep.isEmpty() ) + { + m_nStep = aStep.toDouble(); + rFormatter.SetSpinSize(m_nStep); + } +} + +bool SpinfieldToolbarController::impl_getValue( + const Any& rAny, sal_Int32& nValue, double& fValue, bool& bFloat ) +{ + using ::com::sun::star::uno::TypeClass; + + bool bValueValid( false ); + + bFloat = false; + TypeClass aTypeClass = rAny.getValueTypeClass(); + if (( aTypeClass == TypeClass( typelib_TypeClass_LONG )) || + ( aTypeClass == TypeClass( typelib_TypeClass_SHORT )) || + ( aTypeClass == TypeClass( typelib_TypeClass_BYTE ))) + bValueValid = rAny >>= nValue; + else if (( aTypeClass == TypeClass( typelib_TypeClass_FLOAT )) || + ( aTypeClass == TypeClass( typelib_TypeClass_DOUBLE ))) + { + bValueValid = rAny >>= fValue; + bFloat = true; + } + + return bValueValid; +} + +OUString SpinfieldToolbarController::FormatOutputString( double fValue ) +{ + if ( m_aOutFormat.isEmpty() ) + { + if ( m_bFloat ) + return OUString::number( fValue ); + else + return OUString::number( sal_Int32( fValue )); + } + else + { +#ifdef _WIN32 + sal_Unicode aBuffer[128]; + + aBuffer[0] = 0; + if ( m_bFloat ) + _snwprintf( o3tl::toW(aBuffer), SAL_N_ELEMENTS(aBuffer), o3tl::toW(m_aOutFormat.getStr()), fValue ); + else + _snwprintf( o3tl::toW(aBuffer), SAL_N_ELEMENTS(aBuffer), o3tl::toW(m_aOutFormat.getStr()), sal_Int32( fValue )); + + return OUString(aBuffer); +#else + // Currently we have no support for a format string using sal_Unicode. wchar_t + // is 32 bit on Unix platform! + char aBuffer[128]; + + OString aFormat = OUStringToOString( m_aOutFormat, osl_getThreadTextEncoding() ); + if ( m_bFloat ) + snprintf( aBuffer, 128, aFormat.getStr(), fValue ); + else + snprintf( aBuffer, 128, aFormat.getStr(), static_cast<tools::Long>( fValue )); + + sal_Int32 nSize = strlen( aBuffer ); + OString aTmp( aBuffer, nSize ); + return OStringToOUString( aTmp, osl_getThreadTextEncoding() ); +#endif + } +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/statusbar.cxx b/framework/source/uielement/statusbar.cxx new file mode 100644 index 000000000..7189e6615 --- /dev/null +++ b/framework/source/uielement/statusbar.cxx @@ -0,0 +1,89 @@ +/* -*- 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 <uielement/statusbar.hxx> + +#include <vcl/svapp.hxx> + +namespace framework +{ + +FrameworkStatusBar::FrameworkStatusBar( + vcl::Window* pParent, + WinBits nWinBits ) : + StatusBar( pParent, nWinBits ), + m_pMgr( nullptr ) +{ + // set optimal size + SetOutputSizePixel( CalcWindowSizePixel() ); +} + +void FrameworkStatusBar::SetStatusBarManager( StatusBarManager* pStatusBarManager ) +{ + SolarMutexGuard aSolarMutexGuard; + m_pMgr = pStatusBarManager; +} + +void FrameworkStatusBar::UserDraw(const UserDrawEvent& rUDEvt) +{ + if ( m_pMgr ) + m_pMgr->UserDraw( rUDEvt ); +} + +void FrameworkStatusBar::Command( const CommandEvent& rEvt ) +{ + if ( m_pMgr ) + m_pMgr->Command( rEvt ); +} + +void FrameworkStatusBar::StateChanged( StateChangedType ) +{ +} + +void FrameworkStatusBar::DataChanged( const DataChangedEvent& rDCEvt ) +{ + StatusBar::DataChanged( rDCEvt ); + if ( m_pMgr ) + m_pMgr->DataChanged( rDCEvt ); +} + +void FrameworkStatusBar::MouseMove( const MouseEvent& rMEvt ) +{ + StatusBar::MouseMove( rMEvt ); + if ( m_pMgr ) + m_pMgr->MouseMove( rMEvt ); +} + +void FrameworkStatusBar::MouseButtonDown( const MouseEvent& rMEvt ) +{ + StatusBar::MouseButtonDown( rMEvt ); + if ( m_pMgr ) + m_pMgr->MouseButtonDown( rMEvt ); +} + +void FrameworkStatusBar::MouseButtonUp( const MouseEvent& rMEvt ) +{ + StatusBar::MouseButtonUp( rMEvt ); + if ( m_pMgr ) + m_pMgr->MouseButtonUp( rMEvt ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/statusbaritem.cxx b/framework/source/uielement/statusbaritem.cxx new file mode 100644 index 000000000..baf321fd6 --- /dev/null +++ b/framework/source/uielement/statusbaritem.cxx @@ -0,0 +1,236 @@ +/* -*- 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 <uielement/statusbaritem.hxx> +#include <utility> +#include <vcl/status.hxx> +#include <vcl/svapp.hxx> + +#include <com/sun/star/ui/ItemStyle.hpp> + +using namespace com::sun::star::ui; + +namespace framework +{ + +namespace +{ +sal_uInt16 impl_convertItemBitsToItemStyle( StatusBarItemBits nItemBits ) +{ + sal_uInt16 nStyle( 0 ); + + if ( nItemBits & StatusBarItemBits::Right ) + nStyle |= ItemStyle::ALIGN_RIGHT; + else if ( nItemBits & StatusBarItemBits::Left ) + nStyle |= ItemStyle::ALIGN_LEFT; + else + nStyle |= ItemStyle::ALIGN_CENTER; + + if ( nItemBits & StatusBarItemBits::Flat ) + nStyle |= ItemStyle::DRAW_FLAT; + else if ( nItemBits & StatusBarItemBits::Out ) + nStyle |= ItemStyle::DRAW_OUT3D; + else + nStyle |= ItemStyle::DRAW_IN3D; + + if ( nItemBits & StatusBarItemBits::AutoSize ) + nStyle |= ItemStyle::AUTO_SIZE; + + if ( nItemBits & StatusBarItemBits::UserDraw ) + nStyle |= ItemStyle::OWNER_DRAW; + + return nStyle; +} +} + +StatusbarItem::StatusbarItem( + StatusBar *pStatusBar, + sal_uInt16 nId, + OUString aCommand ) + : m_pStatusBar( pStatusBar ) + , m_nId( nId ) + , m_nStyle( 0 ) + , m_aCommand(std::move( aCommand )) +{ + if ( m_pStatusBar ) + m_nStyle = impl_convertItemBitsToItemStyle( + m_pStatusBar->GetItemBits( m_nId ) ); +} + +StatusbarItem::~StatusbarItem() +{ +} + +void StatusbarItem::disposing(std::unique_lock<std::mutex>&) +{ + m_pStatusBar = nullptr; +} + +OUString SAL_CALL StatusbarItem::getCommand() +{ + std::unique_lock aGuard( m_aMutex ); + return m_aCommand; +} + +::sal_uInt16 SAL_CALL StatusbarItem::getItemId() +{ + std::unique_lock aGuard( m_aMutex ); + return m_nId; +} + +::sal_uInt32 SAL_CALL StatusbarItem::getWidth() +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + return m_pStatusBar->GetItemWidth( m_nId ); + + return ::sal_uInt32(0); +} + +::sal_uInt16 SAL_CALL StatusbarItem::getStyle() +{ + std::unique_lock aGuard( m_aMutex ); + return m_nStyle; +} + +::sal_Int32 SAL_CALL StatusbarItem::getOffset() +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + return m_pStatusBar->GetItemOffset( m_nId ); + + return 0; +} + +css::awt::Rectangle SAL_CALL StatusbarItem::getItemRect() +{ + SolarMutexGuard aGuard; + css::awt::Rectangle aAWTRect; + if ( m_pStatusBar ) + { + tools::Rectangle aRect = m_pStatusBar->GetItemRect( m_nId ); + return css::awt::Rectangle( aRect.Left(), + aRect.Top(), + aRect.GetWidth(), + aRect.GetHeight() ); + } + + return aAWTRect; +} + +OUString SAL_CALL StatusbarItem::getText() +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + return m_pStatusBar->GetItemText( m_nId ); + + return OUString(); +} + +void SAL_CALL StatusbarItem::setText( const OUString& rText ) +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + m_pStatusBar->SetItemText( m_nId, rText ); +} + +OUString SAL_CALL StatusbarItem::getHelpText() +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + return m_pStatusBar->GetHelpText( m_nId ); + + return OUString(); +} + +void SAL_CALL StatusbarItem::setHelpText( const OUString& rHelpText ) +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + m_pStatusBar->SetHelpText( m_nId, rHelpText ); +} + +OUString SAL_CALL StatusbarItem::getQuickHelpText() +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + return m_pStatusBar->GetHelpText( m_nId ); + + return OUString(); +} + +void SAL_CALL StatusbarItem::setQuickHelpText( const OUString& rQuickHelpText ) +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + m_pStatusBar->SetQuickHelpText( m_nId, rQuickHelpText ); +} + +OUString SAL_CALL StatusbarItem::getAccessibleName() +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + return m_pStatusBar->GetAccessibleName( m_nId ); + + return OUString(); +} + +void SAL_CALL StatusbarItem::setAccessibleName( const OUString& rAccessibleName ) +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + m_pStatusBar->SetAccessibleName( m_nId, rAccessibleName ); +} + +sal_Bool SAL_CALL StatusbarItem::getVisible() +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + return m_pStatusBar->IsItemVisible( m_nId ); + + return false; +} + +void SAL_CALL StatusbarItem::setVisible( sal_Bool bVisible ) +{ + SolarMutexGuard aGuard; + if ( !m_pStatusBar ) + return; + + if ( bool(bVisible) != m_pStatusBar->IsItemVisible( m_nId ) ) + { + if ( bVisible ) + m_pStatusBar->ShowItem( m_nId ); + else + m_pStatusBar->HideItem( m_nId ); + } +} + +void SAL_CALL StatusbarItem::repaint( ) +{ + SolarMutexGuard aGuard; + if ( m_pStatusBar ) + { + m_pStatusBar->RedrawItem( m_nId ); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/statusbarmanager.cxx b/framework/source/uielement/statusbarmanager.cxx new file mode 100644 index 000000000..dbc305c69 --- /dev/null +++ b/framework/source/uielement/statusbarmanager.cxx @@ -0,0 +1,656 @@ +/* -*- 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 <uielement/statusbarmanager.hxx> +#include <uielement/genericstatusbarcontroller.hxx> + +#include <framework/sfxhelperfunctions.hxx> +#include <framework/addonsoptions.hxx> +#include <uielement/statusbarmerger.hxx> +#include <uielement/statusbaritem.hxx> +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <com/sun/star/frame/theStatusbarControllerFactory.hpp> +#include <com/sun/star/ui/ItemStyle.hpp> +#include <com/sun/star/ui/ItemType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/awt/Command.hpp> +#include <com/sun/star/ui/XStatusbarItem.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <svtools/statusbarcontroller.hxx> +#include <tools/debug.hxx> + +#include <utility> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/status.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandinfoprovider.hxx> + +#include <cassert> + +using namespace ::com::sun::star; + +namespace framework +{ + +namespace +{ + +template< class MAP > +struct lcl_UpdateController +{ + void operator()( typename MAP::value_type &rElement ) const + { + try + { + if ( rElement.second.is() ) + rElement.second->update(); + } + catch ( uno::Exception& ) + { + } + } +}; + +template< class MAP > +struct lcl_RemoveController +{ + void operator()( typename MAP::value_type &rElement ) const + { + try + { + if ( rElement.second.is() ) + rElement.second->dispose(); + } + catch ( uno::Exception& ) + { + } + } +}; + +StatusBarItemBits impl_convertItemStyleToItemBits( sal_Int16 nStyle ) +{ + StatusBarItemBits nItemBits( StatusBarItemBits::NONE ); + + if (( nStyle & css::ui::ItemStyle::ALIGN_RIGHT ) == css::ui::ItemStyle::ALIGN_RIGHT ) + nItemBits |= StatusBarItemBits::Right; + else if ( nStyle & css::ui::ItemStyle::ALIGN_LEFT ) + nItemBits |= StatusBarItemBits::Left; + else + nItemBits |= StatusBarItemBits::Center; + + if (( nStyle & css::ui::ItemStyle::DRAW_FLAT ) == css::ui::ItemStyle::DRAW_FLAT ) + nItemBits |= StatusBarItemBits::Flat; + else if ( nStyle & css::ui::ItemStyle::DRAW_OUT3D ) + nItemBits |= StatusBarItemBits::Out; + else + nItemBits |= StatusBarItemBits::In; + + if (( nStyle & css::ui::ItemStyle::AUTO_SIZE ) == css::ui::ItemStyle::AUTO_SIZE ) + nItemBits |= StatusBarItemBits::AutoSize; + if ( nStyle & css::ui::ItemStyle::OWNER_DRAW ) + nItemBits |= StatusBarItemBits::UserDraw; + + if ( nStyle & css::ui::ItemStyle::MANDATORY ) + nItemBits |= StatusBarItemBits::Mandatory; + + return nItemBits; +} + +} + +StatusBarManager::StatusBarManager( + uno::Reference< uno::XComponentContext > xContext, + uno::Reference< frame::XFrame > rFrame, + StatusBar* pStatusBar ) : + m_bDisposed( false ), + m_bFrameActionRegistered( false ), + m_bUpdateControllers( false ), + m_pStatusBar( pStatusBar ), + m_xFrame(std::move( rFrame )), + m_xContext(std::move( xContext )) +{ + + m_xStatusbarControllerFactory = frame::theStatusbarControllerFactory::get( + ::comphelper::getProcessComponentContext()); + + m_pStatusBar->AdjustItemWidthsForHiDPI(); + m_pStatusBar->SetClickHdl( LINK( this, StatusBarManager, Click ) ); + m_pStatusBar->SetDoubleClickHdl( LINK( this, StatusBarManager, DoubleClick ) ); +} + +StatusBarManager::~StatusBarManager() +{ +} + +StatusBar* StatusBarManager::GetStatusBar() const +{ + SolarMutexGuard g; + return m_pStatusBar; +} + +void StatusBarManager::frameAction( const frame::FrameActionEvent& Action ) +{ + SolarMutexGuard g; + if ( Action.Action == frame::FrameAction_CONTEXT_CHANGED ) + UpdateControllers(); +} + +void SAL_CALL StatusBarManager::disposing( const lang::EventObject& Source ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + RemoveControllers(); + + if ( Source.Source == uno::Reference< uno::XInterface >( m_xFrame, uno::UNO_QUERY )) + m_xFrame.clear(); + + m_xContext.clear(); +} + +// XComponent +void SAL_CALL StatusBarManager::dispose() +{ + uno::Reference< lang::XComponent > xThis(this ); + + { + lang::EventObject aEvent( xThis ); + std::unique_lock aGuard(m_mutex); + m_aListenerContainer.disposeAndClear( aGuard, aEvent ); + } + { + SolarMutexGuard g; + if ( m_bDisposed ) + return; + + RemoveControllers(); + + // destroy the item data + for ( sal_uInt16 n = 0; n < m_pStatusBar->GetItemCount(); n++ ) + { + AddonStatusbarItemData *pUserData = static_cast< AddonStatusbarItemData *>( + m_pStatusBar->GetItemData( m_pStatusBar->GetItemId( n ) ) ); + delete pUserData; + } + + m_pStatusBar.disposeAndClear(); + + if ( m_bFrameActionRegistered && m_xFrame.is() ) + { + try + { + m_xFrame->removeFrameActionListener( uno::Reference< frame::XFrameActionListener >(this) ); + } + catch ( const uno::Exception& ) + { + } + } + + m_xFrame.clear(); + m_xContext.clear(); + + m_bDisposed = true; + } +} + +void SAL_CALL StatusBarManager::addEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw lang::DisposedException(); + + std::unique_lock aGuard(m_mutex); + m_aListenerContainer.addInterface( aGuard, xListener ); +} + +void SAL_CALL StatusBarManager::removeEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + std::unique_lock aGuard(m_mutex); + m_aListenerContainer.removeInterface( aGuard, xListener ); +} + +// XUIConfigurationListener +void SAL_CALL StatusBarManager::elementInserted( const css::ui::ConfigurationEvent& ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; +} + +void SAL_CALL StatusBarManager::elementRemoved( const css::ui::ConfigurationEvent& ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; +} + +void SAL_CALL StatusBarManager::elementReplaced( const css::ui::ConfigurationEvent& ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; +} + +void StatusBarManager::UpdateControllers() +{ + if ( !m_bUpdateControllers ) + { + m_bUpdateControllers = true; + std::for_each( m_aControllerMap.begin(), + m_aControllerMap.end(), + lcl_UpdateController< StatusBarControllerMap >() ); + } + m_bUpdateControllers = false; +} + +void StatusBarManager::RemoveControllers() +{ + DBG_TESTSOLARMUTEX(); + assert(!m_bDisposed); + + std::for_each( m_aControllerMap.begin(), + m_aControllerMap.end(), + lcl_RemoveController< StatusBarControllerMap >() ); + m_aControllerMap.clear(); +} + +void StatusBarManager::CreateControllers() +{ + uno::Reference< awt::XWindow > xStatusbarWindow = VCLUnoHelper::GetInterface( m_pStatusBar ); + + for ( sal_uInt16 i = 0; i < m_pStatusBar->GetItemCount(); i++ ) + { + sal_uInt16 nId = m_pStatusBar->GetItemId( i ); + if ( nId == 0 ) + continue; + + OUString aCommandURL( m_pStatusBar->GetItemCommand( nId )); + bool bInit( true ); + uno::Reference< frame::XStatusbarController > xController; + AddonStatusbarItemData *pItemData = static_cast< AddonStatusbarItemData *>( m_pStatusBar->GetItemData( nId ) ); + uno::Reference< ui::XStatusbarItem > xStatusbarItem = new StatusbarItem( m_pStatusBar, nId, aCommandURL ); + + beans::PropertyValue aPropValue; + std::vector< uno::Any > aPropVector; + + aPropValue.Name = "CommandURL"; + aPropValue.Value <<= aCommandURL; + aPropVector.push_back( uno::Any( aPropValue ) ); + + aPropValue.Name = "ModuleIdentifier"; + aPropValue.Value <<= OUString(); + aPropVector.push_back( uno::Any( aPropValue ) ); + + aPropValue.Name = "Frame"; + aPropValue.Value <<= m_xFrame; + aPropVector.push_back( uno::Any( aPropValue ) ); + + // TODO remove this + aPropValue.Name = "ServiceManager"; + aPropValue.Value <<= uno::Reference<lang::XMultiServiceFactory>(m_xContext->getServiceManager(), uno::UNO_QUERY_THROW); + aPropVector.push_back( uno::Any( aPropValue ) ); + + aPropValue.Name = "ParentWindow"; + aPropValue.Value <<= xStatusbarWindow; + aPropVector.push_back( uno::Any( aPropValue ) ); + + // TODO still needing with the css::ui::XStatusbarItem? + aPropValue.Name = "Identifier"; + aPropValue.Value <<= nId; + aPropVector.push_back( uno::Any( aPropValue ) ); + + aPropValue.Name = "StatusbarItem"; + aPropValue.Value <<= xStatusbarItem; + aPropVector.push_back( uno::Any( aPropValue ) ); + + uno::Sequence< uno::Any > aArgs( comphelper::containerToSequence( aPropVector ) ); + + // 1) UNO Statusbar controllers, registered in Controllers.xcu + if ( m_xStatusbarControllerFactory.is() && + m_xStatusbarControllerFactory->hasController( aCommandURL, "" )) + { + xController.set(m_xStatusbarControllerFactory->createInstanceWithArgumentsAndContext( + aCommandURL, aArgs, m_xContext ), + uno::UNO_QUERY ); + bInit = false; // Initialization is done through the factory service + } + + if ( !xController.is() ) + { + // 2) Old SFX2 Statusbar controllers + xController = CreateStatusBarController( m_xFrame, m_pStatusBar, nId, aCommandURL ); + if ( !xController ) + { + // 3) Is Add-on? Generic statusbar controller + if ( pItemData ) + { + xController = new GenericStatusbarController( m_xContext, + m_xFrame, + xStatusbarItem, + pItemData ); + } + else + { + // 4) Default Statusbar controller + xController = new svt::StatusbarController( m_xContext, m_xFrame, aCommandURL, nId ); + } + } + } + + m_aControllerMap[nId] = xController; + if ( bInit ) + { + xController->initialize( aArgs ); + } + } + + // add frame action listeners + if ( !m_bFrameActionRegistered && m_xFrame.is() ) + { + m_bFrameActionRegistered = true; + m_xFrame->addFrameActionListener( uno::Reference< frame::XFrameActionListener >(this) ); + } +} + +void StatusBarManager::FillStatusBar( const uno::Reference< container::XIndexAccess >& rItemContainer ) +{ + SolarMutexGuard g; + + if ( m_bDisposed || !m_pStatusBar ) + return; + + sal_uInt16 nId( 1 ); + + RemoveControllers(); + + // reset and fill command map + m_pStatusBar->Clear(); + m_aControllerMap.clear();// TODO already done in RemoveControllers + + for ( sal_Int32 n = 0; n < rItemContainer->getCount(); n++ ) + { + uno::Sequence< beans::PropertyValue > aProps; + OUString aCommandURL; + sal_Int16 nOffset( 0 ); + sal_Int16 nStyle( 0 ); + sal_Int16 nWidth( 0 ); + sal_uInt16 nType( css::ui::ItemType::DEFAULT ); + + try + { + if ( rItemContainer->getByIndex( n ) >>= aProps ) + { + for ( beans::PropertyValue const & prop : std::as_const(aProps) ) + { + if ( prop.Name == "CommandURL" ) + { + prop.Value >>= aCommandURL; + } + else if ( prop.Name == "Style" ) + { + prop.Value >>= nStyle; + } + else if ( prop.Name == "Type" ) + { + prop.Value >>= nType; + } + else if ( prop.Name == "Width" ) + { + prop.Value >>= nWidth; + } + else if ( prop.Name == "Offset" ) + { + prop.Value >>= nOffset; + } + } + + if (( nType == css::ui::ItemType::DEFAULT ) && !aCommandURL.isEmpty() ) + { + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, ""); + OUString aString(vcl::CommandInfoProvider::GetLabelForCommand(aProperties)); + StatusBarItemBits nItemBits( impl_convertItemStyleToItemBits( nStyle )); + + m_pStatusBar->InsertItem( nId, nWidth, nItemBits, nOffset ); + m_pStatusBar->SetItemCommand( nId, aCommandURL ); + m_pStatusBar->SetAccessibleName( nId, aString ); + ++nId; + } + } + } + catch ( const css::lang::IndexOutOfBoundsException& ) + { + break; + } + } + + // Statusbar Merging + constexpr sal_uInt16 STATUSBAR_ITEM_STARTID = 1000; + MergeStatusbarInstructionContainer aMergeInstructions = AddonsOptions().GetMergeStatusbarInstructions(); + if ( !aMergeInstructions.empty() ) + { + const sal_uInt32 nCount = aMergeInstructions.size(); + sal_uInt16 nItemId( STATUSBAR_ITEM_STARTID ); + + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + MergeStatusbarInstruction &rInstruction = aMergeInstructions[i]; + if ( !StatusbarMerger::IsCorrectContext( rInstruction.aMergeContext ) ) + continue; + + AddonStatusbarItemContainer aItems; + StatusbarMerger::ConvertSeqSeqToVector( rInstruction.aMergeStatusbarItems, aItems ); + + sal_uInt16 nRefPos = StatusbarMerger::FindReferencePos( m_pStatusBar, rInstruction.aMergePoint ); + if ( nRefPos != STATUSBAR_ITEM_NOTFOUND ) + { + StatusbarMerger::ProcessMergeOperation( m_pStatusBar, + nRefPos, + nItemId, + rInstruction.aMergeCommand, + rInstruction.aMergeCommandParameter, + aItems ); + } + else + { + StatusbarMerger::ProcessMergeFallback( m_pStatusBar, + nItemId, + rInstruction.aMergeCommand, + rInstruction.aMergeCommandParameter, + aItems ); + } + } + } + + // Create controllers + CreateControllers(); + + // Notify controllers that they are now correctly initialized and can start listening + UpdateControllers(); +} + +void StatusBarManager::DataChanged( const DataChangedEvent& rDCEvt ) +{ + SolarMutexClearableGuard aGuard; + + if ((( rDCEvt.GetType() == DataChangedEventType::SETTINGS ) || + ( rDCEvt.GetType() == DataChangedEventType::FONTS ) || + ( rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION ) || + ( rDCEvt.GetType() == DataChangedEventType::DISPLAY )) && + ( rDCEvt.GetFlags() & AllSettingsFlags::STYLE )) + { + css::uno::Reference< css::frame::XLayoutManager > xLayoutManager; + css::uno::Reference< css::beans::XPropertySet > xPropSet( m_xFrame, css::uno::UNO_QUERY ); + if ( xPropSet.is() ) + xPropSet->getPropertyValue("LayoutManager") >>= xLayoutManager; + if ( xLayoutManager.is() ) + { + aGuard.clear(); + xLayoutManager->doLayout(); + } + } +} + +void StatusBarManager::UserDraw( const UserDrawEvent& rUDEvt ) +{ + SolarMutexClearableGuard aGuard; + + if ( m_bDisposed ) + return; + + sal_uInt16 nId( rUDEvt.GetItemId() ); + StatusBarControllerMap::const_iterator it = m_aControllerMap.find( nId ); + if (( nId <= 0 ) || ( it == m_aControllerMap.end() )) + return; + + uno::Reference< frame::XStatusbarController > xController( it->second ); + if (xController.is() && rUDEvt.GetRenderContext()) + { + uno::Reference< awt::XGraphics > xGraphics = rUDEvt.GetRenderContext()->CreateUnoGraphics(); + + awt::Rectangle aRect( rUDEvt.GetRect().Left(), + rUDEvt.GetRect().Top(), + rUDEvt.GetRect().GetWidth(), + rUDEvt.GetRect().GetHeight() ); + aGuard.clear(); + xController->paint(xGraphics, aRect, 0); + } +} + +void StatusBarManager::Command( const CommandEvent& rEvt ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + if ( rEvt.GetCommand() != CommandEventId::ContextMenu ) + return; + + sal_uInt16 nId = m_pStatusBar->GetItemId( rEvt.GetMousePosPixel() ); + StatusBarControllerMap::const_iterator it = m_aControllerMap.find( nId ); + if (( nId > 0 ) && ( it != m_aControllerMap.end() )) + { + uno::Reference< frame::XStatusbarController > xController( it->second ); + if ( xController.is() ) + { + awt::Point aPos; + aPos.X = rEvt.GetMousePosPixel().X(); + aPos.Y = rEvt.GetMousePosPixel().Y(); + xController->command( aPos, awt::Command::CONTEXTMENU, true, uno::Any() ); + } + } +} + +void StatusBarManager::MouseMove( const MouseEvent& rMEvt ) +{ + MouseButton(rMEvt,&frame::XStatusbarController::mouseMove); +} + +void StatusBarManager::MouseButton( const MouseEvent& rMEvt ,sal_Bool ( SAL_CALL frame::XStatusbarController::*_pMethod )(const css::awt::MouseEvent&)) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + sal_uInt16 nId = m_pStatusBar->GetItemId( rMEvt.GetPosPixel() ); + StatusBarControllerMap::const_iterator it = m_aControllerMap.find( nId ); + if (( nId <= 0 ) || ( it == m_aControllerMap.end() )) + return; + + uno::Reference< frame::XStatusbarController > xController( it->second ); + if ( xController.is() ) + { + css::awt::MouseEvent aMouseEvent; + aMouseEvent.Buttons = rMEvt.GetButtons(); + aMouseEvent.X = rMEvt.GetPosPixel().X(); + aMouseEvent.Y = rMEvt.GetPosPixel().Y(); + aMouseEvent.ClickCount = rMEvt.GetClicks(); + (xController.get()->*_pMethod)( aMouseEvent); + } +} + +void StatusBarManager::MouseButtonDown( const MouseEvent& rMEvt ) +{ + MouseButton(rMEvt,&frame::XStatusbarController::mouseButtonDown); +} + +void StatusBarManager::MouseButtonUp( const MouseEvent& rMEvt ) +{ + MouseButton(rMEvt,&frame::XStatusbarController::mouseButtonUp); +} + +IMPL_LINK_NOARG(StatusBarManager, Click, StatusBar*, void) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + sal_uInt16 nId = m_pStatusBar->GetCurItemId(); + StatusBarControllerMap::const_iterator it = m_aControllerMap.find( nId ); + if (( nId > 0 ) && ( it != m_aControllerMap.end() )) + { + uno::Reference< frame::XStatusbarController > xController( it->second ); + if ( xController.is() ) + { + const Point aVCLPos = m_pStatusBar->GetPointerPosPixel(); + const awt::Point aAWTPoint( aVCLPos.X(), aVCLPos.Y() ); + xController->click( aAWTPoint ); + } + } +} + +IMPL_LINK_NOARG(StatusBarManager, DoubleClick, StatusBar*, void) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + sal_uInt16 nId = m_pStatusBar->GetCurItemId(); + StatusBarControllerMap::const_iterator it = m_aControllerMap.find( nId ); + if (( nId > 0 ) && ( it != m_aControllerMap.end() )) + { + uno::Reference< frame::XStatusbarController > xController( it->second ); + if ( xController.is() ) + { + const Point aVCLPos = m_pStatusBar->GetPointerPosPixel(); + const awt::Point aAWTPoint( aVCLPos.X(), aVCLPos.Y() ); + xController->doubleClick( aAWTPoint ); + } + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/statusbarmerger.cxx b/framework/source/uielement/statusbarmerger.cxx new file mode 100644 index 000000000..c8e6633be --- /dev/null +++ b/framework/source/uielement/statusbarmerger.cxx @@ -0,0 +1,237 @@ +/* -*- 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 <uielement/statusbarmerger.hxx> +#include <o3tl/string_view.hxx> + +using com::sun::star::beans::PropertyValue; +using com::sun::star::uno::Sequence; + +namespace framework +{ +namespace { + +const char16_t MERGECOMMAND_ADDAFTER[] = u"AddAfter"; +const char16_t MERGECOMMAND_ADDBEFORE[] = u"AddBefore"; +const char16_t MERGECOMMAND_REPLACE[] = u"Replace"; +const char16_t MERGECOMMAND_REMOVE[] = u"Remove"; + +void lcl_ConvertSequenceToValues( + const Sequence< PropertyValue > &rSequence, + AddonStatusbarItem &rItem ) +{ + OUString sAlignment; + bool bAutoSize = false; + bool bOwnerDraw = false; + bool bMandatory = true; + + for ( PropertyValue const & aPropVal : rSequence ) + { + if ( aPropVal.Name == "URL" ) + aPropVal.Value >>= rItem.aCommandURL; + else if ( aPropVal.Name == "Title" ) + aPropVal.Value >>= rItem.aLabel; + else if ( aPropVal.Name == "Context" ) + aPropVal.Value >>= rItem.aContext; + else if ( aPropVal.Name == "Alignment" ) + aPropVal.Value >>= sAlignment; + else if ( aPropVal.Name == "AutoSize" ) + aPropVal.Value >>= bAutoSize; + else if ( aPropVal.Name == "OwnerDraw" ) + aPropVal.Value >>= bOwnerDraw; + else if ( aPropVal.Name == "Mandatory" ) + aPropVal.Value >>= bMandatory; + else if ( aPropVal.Name == "Width" ) + { + sal_Int32 aWidth = 0; + aPropVal.Value >>= aWidth; + rItem.nWidth = sal_uInt16( aWidth ); + } + } + + StatusBarItemBits nItemBits(StatusBarItemBits::NONE); + if ( bAutoSize ) + nItemBits |= StatusBarItemBits::AutoSize; + if ( bOwnerDraw ) + nItemBits |= StatusBarItemBits::UserDraw; + if ( bMandatory ) + nItemBits |= StatusBarItemBits::Mandatory; + if ( sAlignment == "center" ) + nItemBits |= StatusBarItemBits::Center; + else if ( sAlignment == "right" ) + nItemBits |= StatusBarItemBits::Right; + else + // if unset, defaults to left alignment + nItemBits |= StatusBarItemBits::Left; + rItem.nItemBits = nItemBits; +} + +void lcl_CreateStatusbarItem( StatusBar* pStatusbar, + sal_uInt16 nPos, + sal_uInt16 nItemId, + const AddonStatusbarItem& rAddonItem ) +{ + pStatusbar->InsertItem( nItemId, + rAddonItem.nWidth, + rAddonItem.nItemBits, + STATUSBAR_OFFSET, + nPos ); + pStatusbar->SetItemCommand( nItemId, rAddonItem.aCommandURL ); + pStatusbar->SetQuickHelpText( nItemId, rAddonItem.aLabel ); + pStatusbar->SetAccessibleName( nItemId, rAddonItem.aLabel ); + + // add-on specific data + AddonStatusbarItemData *pUserData = new AddonStatusbarItemData; + pUserData->aLabel = rAddonItem.aLabel; + pStatusbar->SetItemData( nItemId, pUserData ); +} + +bool lcl_MergeItems( StatusBar* pStatusbar, + sal_uInt16 nPos, + sal_uInt16 nModIndex, + sal_uInt16& rItemId, + const AddonStatusbarItemContainer& rAddonItems ) +{ + const sal_uInt16 nSize( rAddonItems.size() ); + for ( sal_Int32 i = 0; i < nSize; i++ ) + { + const AddonStatusbarItem& rItem = rAddonItems[i]; + if ( !StatusbarMerger::IsCorrectContext( rItem.aContext ) ) + continue; + + sal_uInt16 nInsPos = nPos + nModIndex + i; + if ( nInsPos > pStatusbar->GetItemCount() ) + nInsPos = STATUSBAR_APPEND; + + lcl_CreateStatusbarItem( pStatusbar, nInsPos, rItemId, rItem ); + ++rItemId; + } + + return true; +} + +bool lcl_ReplaceItem( StatusBar* pStatusbar, + sal_uInt16 nPos, + sal_uInt16& rItemId, + const AddonStatusbarItemContainer& rAddonToolbarItems ) +{ + pStatusbar->RemoveItem( pStatusbar->GetItemId( nPos ) ); + return lcl_MergeItems( pStatusbar, nPos, 0, rItemId, rAddonToolbarItems ); +} + +bool lcl_RemoveItems( StatusBar* pStatusbar, + sal_uInt16 nPos, + std::u16string_view rMergeCommandParameter ) +{ + sal_Int32 nCount = o3tl::toInt32(rMergeCommandParameter); + if ( nCount > 0 ) + { + for ( sal_Int32 i = 0; i < nCount; i++ ) + { + if ( nPos < pStatusbar->GetItemCount() ) + pStatusbar->RemoveItem( nPos ); + } + } + return true; +} + +} + +bool StatusbarMerger::IsCorrectContext( + std::u16string_view rContext ) +{ + return rContext.empty(); +} + +bool StatusbarMerger::ConvertSeqSeqToVector( + const Sequence< Sequence< PropertyValue > > &rSequence, + AddonStatusbarItemContainer& rContainer ) +{ + for ( auto const & i : rSequence ) + { + AddonStatusbarItem aStatusBarItem; + lcl_ConvertSequenceToValues( i, aStatusBarItem ); + rContainer.push_back( aStatusBarItem ); + } + + return true; +} + +sal_uInt16 StatusbarMerger::FindReferencePos( + StatusBar* pStatusbar, + std::u16string_view rReferencePoint ) +{ + for ( sal_uInt16 nPos = 0; nPos < pStatusbar->GetItemCount(); nPos++ ) + { + const OUString rCmd = pStatusbar->GetItemCommand( pStatusbar->GetItemId( nPos ) ); + if ( rReferencePoint == rCmd ) + return nPos; + } + + return STATUSBAR_ITEM_NOTFOUND; +} + +bool StatusbarMerger::ProcessMergeOperation( + StatusBar* pStatusbar, + sal_uInt16 nPos, + sal_uInt16& rItemId, + std::u16string_view rMergeCommand, + std::u16string_view rMergeCommandParameter, + const AddonStatusbarItemContainer& rItems ) +{ + if ( rMergeCommand == MERGECOMMAND_ADDAFTER ) + return lcl_MergeItems( pStatusbar, nPos, 1, rItemId, rItems ); + else if ( rMergeCommand == MERGECOMMAND_ADDBEFORE ) + return lcl_MergeItems( pStatusbar, nPos, 0, rItemId, rItems ); + else if ( rMergeCommand == MERGECOMMAND_REPLACE ) + return lcl_ReplaceItem( pStatusbar, nPos, rItemId, rItems ); + else if ( rMergeCommand == MERGECOMMAND_REMOVE ) + return lcl_RemoveItems( pStatusbar, nPos, rMergeCommandParameter ); + + return false; +} + +bool StatusbarMerger::ProcessMergeFallback( + StatusBar* pStatusbar, + sal_uInt16& rItemId, + std::u16string_view rMergeCommand, + std::u16string_view rMergeFallback, + const AddonStatusbarItemContainer& rItems ) +{ + // fallback IGNORE or REPLACE/REMOVE item not found + if (( rMergeFallback == u"Ignore" ) || + ( rMergeCommand == MERGECOMMAND_REPLACE ) || + ( rMergeCommand == MERGECOMMAND_REMOVE ) ) + { + return true; + } + else if (( rMergeCommand == MERGECOMMAND_ADDBEFORE ) || + ( rMergeCommand == MERGECOMMAND_ADDAFTER ) ) + { + if ( rMergeFallback == u"AddFirst" ) + return lcl_MergeItems( pStatusbar, 0, 0, rItemId, rItems ); + else if ( rMergeFallback == u"AddLast" ) + return lcl_MergeItems( pStatusbar, STATUSBAR_APPEND, 0, rItemId, rItems ); + } + + return false; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/statusbarwrapper.cxx b/framework/source/uielement/statusbarwrapper.cxx new file mode 100644 index 000000000..7627b2f0a --- /dev/null +++ b/framework/source/uielement/statusbarwrapper.cxx @@ -0,0 +1,171 @@ +/* -*- 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 <uielement/statusbarwrapper.hxx> + +#include <uielement/statusbar.hxx> + +#include <com/sun/star/ui/UIElementType.hpp> + +#include <toolkit/helper/vclunohelper.hxx> + +#include <tools/solar.h> +#include <utility> +#include <vcl/svapp.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::frame; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::awt; +using namespace ::com::sun::star::ui; + +namespace framework +{ + +StatusBarWrapper::StatusBarWrapper( + css::uno::Reference< css::uno::XComponentContext > xContext + ) + : UIConfigElementWrapperBase( UIElementType::STATUSBAR ), + m_xContext(std::move( xContext )) +{ +} + +StatusBarWrapper::~StatusBarWrapper() +{ +} + +void SAL_CALL StatusBarWrapper::dispose() +{ + Reference< XComponent > xThis(this); + + css::lang::EventObject aEvent( xThis ); + m_aListenerContainer.disposeAndClear( aEvent ); + + SolarMutexGuard g; + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_xStatusBarManager.is() ) + m_xStatusBarManager->dispose(); + m_xStatusBarManager.clear(); + m_xConfigSource.clear(); + m_xConfigData.clear(); + m_xContext.clear(); + + m_bDisposed = true; + +} + +// XInitialization +void SAL_CALL StatusBarWrapper::initialize( const Sequence< Any >& aArguments ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_bInitialized ) + return; + + UIConfigElementWrapperBase::initialize( aArguments ); + + Reference< XFrame > xFrame( m_xWeakFrame ); + if ( !(xFrame.is() && m_xConfigSource.is()) ) + return; + + // Create VCL based toolbar which will be filled with settings data + StatusBar* pStatusBar( nullptr ); + rtl::Reference<StatusBarManager> pStatusBarManager; + { + SolarMutexGuard aSolarMutexGuard; + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xFrame->getContainerWindow() ); + if ( pWindow ) + { + sal_uLong nStyles = WinBits( WB_LEFT | WB_3DLOOK ); + + pStatusBar = VclPtr<FrameworkStatusBar>::Create( pWindow, nStyles ); + pStatusBarManager = new StatusBarManager( m_xContext, xFrame, pStatusBar ); + static_cast<FrameworkStatusBar*>(pStatusBar)->SetStatusBarManager( pStatusBarManager.get() ); + m_xStatusBarManager = pStatusBarManager; + } + } + + try + { + m_xConfigData = m_xConfigSource->getSettings( m_aResourceURL, false ); + if ( m_xConfigData.is() && pStatusBar && pStatusBarManager ) + { + // Fill statusbar with container contents + pStatusBarManager->FillStatusBar( m_xConfigData ); + } + } + catch ( const NoSuchElementException& ) + { + } +} + +// XUIElementSettings +void SAL_CALL StatusBarWrapper::updateSettings() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !(m_bPersistent && + m_xConfigSource.is() && + m_xStatusBarManager.is()) ) + return; + + try + { + StatusBarManager* pStatusBarManager = static_cast< StatusBarManager *>( m_xStatusBarManager.get() ); + + m_xConfigData = m_xConfigSource->getSettings( m_aResourceURL, false ); + if ( m_xConfigData.is() ) + pStatusBarManager->FillStatusBar( m_xConfigData ); + } + catch ( const NoSuchElementException& ) + { + } +} + +Reference< XInterface > SAL_CALL StatusBarWrapper::getRealInterface() +{ + SolarMutexGuard g; + + if ( m_xStatusBarManager.is() ) + { + StatusBarManager* pStatusBarManager = static_cast< StatusBarManager *>( m_xStatusBarManager.get() ); + if ( pStatusBarManager ) + { + vcl::Window* pWindow = pStatusBarManager->GetStatusBar(); + if ( pWindow ) + return Reference< XInterface >( VCLUnoHelper::GetInterface( pWindow ), UNO_QUERY ); + } + } + + return Reference< XInterface >(); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/statusindicatorinterfacewrapper.cxx b/framework/source/uielement/statusindicatorinterfacewrapper.cxx new file mode 100644 index 000000000..ba796036b --- /dev/null +++ b/framework/source/uielement/statusindicatorinterfacewrapper.cxx @@ -0,0 +1,102 @@ +/* -*- 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 <uielement/statusindicatorinterfacewrapper.hxx> +#include <uielement/progressbarwrapper.hxx> + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; + +namespace framework +{ + +StatusIndicatorInterfaceWrapper::StatusIndicatorInterfaceWrapper( + const css::uno::Reference< css::lang::XComponent >& rStatusIndicatorImpl ) : + m_xStatusIndicatorImpl( rStatusIndicatorImpl ) +{ +} + +StatusIndicatorInterfaceWrapper::~StatusIndicatorInterfaceWrapper() +{ +} + +void SAL_CALL StatusIndicatorInterfaceWrapper::start( + const OUString& sText, + sal_Int32 nRange ) +{ + Reference< XComponent > xComp( m_xStatusIndicatorImpl ); + if ( xComp.is() ) + { + ProgressBarWrapper* pProgressBar = static_cast<ProgressBarWrapper*>(xComp.get()); + if ( pProgressBar ) + pProgressBar->start( sText, nRange ); + } +} + +void SAL_CALL StatusIndicatorInterfaceWrapper::end() +{ + Reference< XComponent > xComp( m_xStatusIndicatorImpl ); + if ( xComp.is() ) + { + ProgressBarWrapper* pProgressBar = static_cast<ProgressBarWrapper*>(xComp.get()); + if ( pProgressBar ) + pProgressBar->end(); + } +} + +void SAL_CALL StatusIndicatorInterfaceWrapper::reset() +{ + Reference< XComponent > xComp( m_xStatusIndicatorImpl ); + if ( xComp.is() ) + { + ProgressBarWrapper* pProgressBar = static_cast<ProgressBarWrapper*>(xComp.get()); + if ( pProgressBar ) + pProgressBar->reset(); + } +} + +void SAL_CALL StatusIndicatorInterfaceWrapper::setText( + const OUString& sText ) +{ + Reference< XComponent > xComp( m_xStatusIndicatorImpl ); + if ( xComp.is() ) + { + ProgressBarWrapper* pProgressBar = static_cast<ProgressBarWrapper*>(xComp.get()); + if ( pProgressBar ) + pProgressBar->setText( sText ); + } +} + +void SAL_CALL StatusIndicatorInterfaceWrapper::setValue( + sal_Int32 nValue ) +{ + Reference< XComponent > xComp( m_xStatusIndicatorImpl ); + if ( xComp.is() ) + { + ProgressBarWrapper* pProgressBar = static_cast<ProgressBarWrapper*>(xComp.get()); + if ( pProgressBar ) + pProgressBar->setValue( nValue ); + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/styletoolbarcontroller.cxx b/framework/source/uielement/styletoolbarcontroller.cxx new file mode 100644 index 000000000..31aed5336 --- /dev/null +++ b/framework/source/uielement/styletoolbarcontroller.cxx @@ -0,0 +1,243 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <uielement/styletoolbarcontroller.hxx> + +#include <tools/urlobj.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> +#include <sal/log.hxx> +#include <o3tl/string_view.hxx> + +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/status/Template.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> + +namespace { + +OUString MapFamilyToCommand( std::u16string_view rFamily ) +{ + if ( rFamily == u"ParagraphStyles" || + rFamily == u"CellStyles" || // In sc + rFamily == u"graphics" ) // In sd + return ".uno:ParaStyle"; + else if ( rFamily == u"CharacterStyles" ) + return ".uno:CharStyle"; + else if ( rFamily == u"PageStyles" ) + return ".uno:PageStyle"; + else if ( rFamily == u"FrameStyles" ) + return ".uno:FrameStyle"; + else if ( rFamily == u"NumberingStyles" ) + return ".uno:ListStyle"; + else if ( rFamily == u"TableStyles" ) + return ".uno:TableStyle"; + + return OUString(); +} + +OUString GetDisplayFromInternalName( const css::uno::Reference< css::frame::XFrame >& rFrame, + const OUString& rStyleName, + const OUString& rFamilyName ) +{ + try + { + css::uno::Reference< css::frame::XController > xController( + rFrame->getController(), css::uno::UNO_SET_THROW ); + css::uno::Reference< css::style::XStyleFamiliesSupplier > xStylesSupplier( + xController->getModel(), css::uno::UNO_QUERY_THROW ); + css::uno::Reference< css::container::XNameAccess > xFamilies( + xStylesSupplier->getStyleFamilies(), css::uno::UNO_SET_THROW ); + + css::uno::Reference< css::container::XNameAccess > xStyleSet; + xFamilies->getByName( rFamilyName ) >>= xStyleSet; + css::uno::Reference< css::beans::XPropertySet > xStyle; + xStyleSet->getByName( rStyleName ) >>= xStyle; + + OUString aDisplayName; + if ( xStyle.is() ) + xStyle->getPropertyValue( "DisplayName" ) >>= aDisplayName; + return aDisplayName; + } + catch ( const css::uno::Exception& ) + { + // We couldn't get the display name. As a last resort we'll + // try to use the internal name, as was specified in the URL. + } + + return rStyleName; +} + +} + +namespace framework { + +StyleDispatcher::StyleDispatcher( const css::uno::Reference< css::frame::XFrame >& rFrame, + css::uno::Reference< css::util::XURLTransformer > xUrlTransformer, + const css::util::URL& rURL ) + : m_aCommand( rURL.Complete ) + , m_xUrlTransformer(std::move( xUrlTransformer )) + , m_xFrame( rFrame, css::uno::UNO_QUERY ) +{ + SAL_WARN_IF( !m_aCommand.startsWith( ".uno:StyleApply?" ), "fwk.uielement", "Wrong dispatcher!" ); + + OUString aParams = rURL.Arguments; + OUString aStyleName, aFamilyName; + sal_Int32 nIndex = 0; + do + { + std::u16string_view aParam = o3tl::getToken(aParams, 0, '&', nIndex ); + + sal_Int32 nParamIndex = 0; + std::u16string_view aParamName = o3tl::getToken(aParam, 0, '=', nParamIndex ); + if ( nParamIndex < 0 ) + break; + + if ( aParamName == u"Style:string" ) + { + std::u16string_view aValue = o3tl::getToken(aParam, 0, '=', nParamIndex ); + aStyleName = INetURLObject::decode( aValue, INetURLObject::DecodeMechanism::WithCharset ); + } + else if ( aParamName == u"FamilyName:string" ) + { + aFamilyName = o3tl::getToken(aParam, 0, '=', nParamIndex ); + } + + } while ( nIndex >= 0 ); + + m_aStatusCommand = MapFamilyToCommand( aFamilyName ); + if ( m_aStatusCommand.isEmpty() || aStyleName.isEmpty() ) + { + // We can't provide status updates for this command, but just executing + // the command should still work (given that the command is valid). + SAL_WARN( "fwk.uielement", "Unable to parse as a style command: " << m_aCommand ); + return; + } + + m_aStyleName = GetDisplayFromInternalName( rFrame, aStyleName, aFamilyName ); + if ( m_xFrame.is() ) + { + css::util::URL aStatusURL; + aStatusURL.Complete = m_aStatusCommand; + m_xUrlTransformer->parseStrict( aStatusURL ); + m_xStatusDispatch = m_xFrame->queryDispatch( aStatusURL, OUString(), 0 ); + } +} + +void StyleDispatcher::dispatch( const css::util::URL& rURL, + const css::uno::Sequence< css::beans::PropertyValue >& rArguments ) +{ + if ( !m_xFrame.is() ) + return; + + css::uno::Reference< css::frame::XDispatch > xDispatch( m_xFrame->queryDispatch( rURL, OUString(), 0 ) ); + if ( xDispatch.is() ) + xDispatch->dispatch( rURL, rArguments ); +} + +void StyleDispatcher::addStatusListener( const css::uno::Reference< css::frame::XStatusListener >& rListener, + const css::util::URL& /*rURL*/ ) +{ + if ( m_xStatusDispatch.is() ) + { + if ( !m_xOwner.is() ) + m_xOwner.set( rListener ); + + css::util::URL aStatusURL; + aStatusURL.Complete = m_aStatusCommand; + m_xUrlTransformer->parseStrict( aStatusURL ); + m_xStatusDispatch->addStatusListener( this, aStatusURL ); + } +} + +void StyleDispatcher::removeStatusListener( const css::uno::Reference< css::frame::XStatusListener >& /*rListener*/, + const css::util::URL& /*rURL*/ ) +{ + if ( m_xStatusDispatch.is() ) + { + css::util::URL aStatusURL; + aStatusURL.Complete = m_aStatusCommand; + m_xUrlTransformer->parseStrict( aStatusURL ); + m_xStatusDispatch->removeStatusListener( this, aStatusURL ); + } +} + +void StyleDispatcher::statusChanged( const css::frame::FeatureStateEvent& rEvent ) +{ + css::frame::status::Template aTemplate; + rEvent.State >>= aTemplate; + + css::frame::FeatureStateEvent aEvent; + aEvent.FeatureURL.Complete = m_aCommand; + m_xUrlTransformer->parseStrict( aEvent.FeatureURL ); + + aEvent.IsEnabled = rEvent.IsEnabled; + aEvent.Requery = rEvent.Requery; + aEvent.State <<= m_aStyleName == aTemplate.StyleName; + m_xOwner->statusChanged( aEvent ); +} + +void StyleDispatcher::disposing( const css::lang::EventObject& /*rSource*/ ) +{ + m_xStatusDispatch.clear(); +} + +StyleToolbarController::StyleToolbarController( const css::uno::Reference< css::uno::XComponentContext >& rContext, + const css::uno::Reference< css::frame::XFrame >& rFrame, + const OUString& rCommand ) + : ToolboxController( rContext, rFrame, rCommand ) +{ +} + +void StyleToolbarController::update() +{ + if ( m_bDisposed ) + throw css::lang::DisposedException(); + + css::util::URL aURL; + aURL.Complete = m_aCommandURL; + m_xUrlTransformer->parseStrict( aURL ); + + auto& xDispatcher = m_aListenerMap[m_aCommandURL]; + if ( xDispatcher.is() ) + xDispatcher->removeStatusListener( this, aURL ); + + xDispatcher.set( new StyleDispatcher( m_xFrame, m_xUrlTransformer, aURL ) ); + xDispatcher->addStatusListener( this, aURL ); +} + +void StyleToolbarController::statusChanged( const css::frame::FeatureStateEvent& rEvent ) +{ + SolarMutexGuard aGuard; + + if ( m_bDisposed ) + throw css::lang::DisposedException(); + + ToolBox* pToolBox = nullptr; + ToolBoxItemId nItemId; + if ( getToolboxId( nItemId, &pToolBox ) ) + { + bool bChecked = false; + rEvent.State >>= bChecked; + pToolBox->CheckItem( nItemId, bChecked ); + pToolBox->EnableItem( nItemId, rEvent.IsEnabled ); + } +} + +void StyleToolbarController::dispose() +{ + ToolboxController::dispose(); + m_aListenerMap.clear(); // Break the cycle with StyleDispatcher. +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/framework/source/uielement/subtoolbarcontroller.cxx b/framework/source/uielement/subtoolbarcontroller.cxx new file mode 100644 index 000000000..de345f545 --- /dev/null +++ b/framework/source/uielement/subtoolbarcontroller.cxx @@ -0,0 +1,547 @@ +/* -*- 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 <comphelper/propertysequence.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/weakref.hxx> +#include <svtools/popupwindowcontroller.hxx> +#include <svtools/toolbarmenu.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/gen.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/weldutils.hxx> + +#include <com/sun/star/awt/XDockableWindow.hpp> +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <com/sun/star/frame/XSubToolbarController.hpp> +#include <com/sun/star/frame/status/Visibility.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/ui/theUIElementFactoryManager.hpp> +#include <com/sun/star/container/NoSuchElementException.hpp> + +typedef cppu::ImplInheritanceHelper< svt::PopupWindowController, + css::frame::XSubToolbarController, + css::awt::XDockableWindowListener> ToolBarBase; + +namespace { + +class SubToolBarController : public ToolBarBase +{ + OUString m_aSubTbName; + OUString m_aLastCommand; + css::uno::Reference< css::ui::XUIElement > m_xUIElement; + void disposeUIElement(); +public: + explicit SubToolBarController( const rtl::Reference< com::sun::star::uno::XComponentContext >& rxContext, + const css::uno::Sequence< css::uno::Any >& rxArgs ); + virtual ~SubToolBarController() override; + + void PopoverDestroyed(); + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& rxArgs ) override; + + // XStatusListener + virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& Event ) override; + + // XToolbarController + virtual void SAL_CALL execute( sal_Int16 nKeyModifier ) override; + + // PopupWindowController + virtual VclPtr<vcl::Window> createVclPopupWindow(vcl::Window* pParent) override; + virtual std::unique_ptr<WeldToolbarPopup> weldPopupWindow() override; + + // XSubToolbarController + virtual sal_Bool SAL_CALL opensSubToolbar() override; + virtual OUString SAL_CALL getSubToolbarName() override; + virtual void SAL_CALL functionSelected( const OUString& rCommand ) override; + virtual void SAL_CALL updateImage() override; + + // XDockableWindowListener + virtual void SAL_CALL startDocking( const css::awt::DockingEvent& e ) override; + virtual css::awt::DockingData SAL_CALL docking( const css::awt::DockingEvent& e ) override; + virtual void SAL_CALL endDocking( const css::awt::EndDockingEvent& e ) override; + virtual sal_Bool SAL_CALL prepareToggleFloatingMode( const css::lang::EventObject& e ) override; + virtual void SAL_CALL toggleFloatingMode( const css::lang::EventObject& e ) override; + virtual void SAL_CALL closed( const css::lang::EventObject& e ) override; + virtual void SAL_CALL endPopupMode( const css::awt::EndPopupModeEvent& e ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& e ) override; + + // XComponent + virtual void SAL_CALL dispose() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& rServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; +}; + +} + +SubToolBarController::SubToolBarController( + const rtl::Reference< com::sun::star::uno::XComponentContext >& rxContext, + const css::uno::Sequence< css::uno::Any >& rxArgs +) : ToolBarBase( + rxContext, + rtl::Reference< css::frame::XFrame >(), + "" + ) +{ + for ( css::uno::Any const & arg : rxArgs ) + { + css::beans::PropertyValue aPropValue; + arg >>= aPropValue; + if ( aPropValue.Name == "Value" ) + { + sal_Int32 nIdx{ 0 }; + OUString aValue; + aPropValue.Value >>= aValue; + m_aSubTbName = aValue.getToken(0, ';', nIdx); + m_aCommandURL = m_aSubTbName; + m_aLastCommand = aValue.getToken(0, ';', nIdx); + break; + } + } + if ( !m_aLastCommand.isEmpty() ) + addStatusListener( m_aLastCommand ); +} + +SubToolBarController::~SubToolBarController() +{ + disposeUIElement(); + m_xUIElement = nullptr; +} + +void SubToolBarController::disposeUIElement() +{ + if ( m_xUIElement.is() ) + { + css::uno::Reference< css::lang::XComponent > xComponent( m_xUIElement, css::uno::UNO_QUERY ); + xComponent->dispose(); + } +} + +void SubToolBarController::statusChanged( const css::frame::FeatureStateEvent& Event ) +{ + SolarMutexGuard aSolarMutexGuard; + + if ( m_bDisposed ) + return; + + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if ( !getToolboxId( nId, &pToolBox ) ) + return; + + ToolBoxItemBits nItemBits = pToolBox->GetItemBits( nId ); + nItemBits &= ~ToolBoxItemBits::CHECKABLE; + TriState eTri = TRISTATE_FALSE; + + if ( Event.FeatureURL.Complete == m_aCommandURL ) + { + pToolBox->EnableItem( nId, Event.IsEnabled ); + + OUString aStrValue; + css::frame::status::Visibility aItemVisibility; + if ( Event.State >>= aStrValue ) + { + // Enum command, such as the current custom shape, + // toggle checked state. + if ( m_aLastCommand == OUStringConcatenation( m_aCommandURL + "." + aStrValue ) ) + { + eTri = TRISTATE_TRUE; + nItemBits |= ToolBoxItemBits::CHECKABLE; + } + } + else if ( Event.State >>= aItemVisibility ) + { + pToolBox->ShowItem( nId, aItemVisibility.bVisible ); + } + } + else + { + bool bValue; + if ( Event.State >>= bValue ) + { + // Boolean, treat it as checked/unchecked + if ( bValue ) + eTri = TRISTATE_TRUE; + nItemBits |= ToolBoxItemBits::CHECKABLE; + } + } + + pToolBox->SetItemState( nId, eTri ); + pToolBox->SetItemBits( nId, nItemBits ); +} + +void SubToolBarController::execute( sal_Int16 nKeyModifier ) +{ + if ( !m_aLastCommand.isEmpty() ) + { + auto aArgs( comphelper::InitPropertySequence( { + { "KeyModifier", css::uno::Any( nKeyModifier ) } + } ) ); + dispatchCommand( m_aLastCommand, aArgs ); + } +} + +namespace { +class SubToolbarControl final : public WeldToolbarPopup +{ +public: + explicit SubToolbarControl(SubToolBarController& rController, weld::Widget* pParent); + virtual ~SubToolbarControl() override; + + virtual void GrabFocus() override; + + weld::Container* GetContainer() { return m_xTargetContainer.get(); } + +private: + SubToolBarController& m_rController; + std::unique_ptr<weld::Container> m_xTargetContainer; +}; +} + +SubToolbarControl::SubToolbarControl(SubToolBarController& rController, + weld::Widget* pParent) + : WeldToolbarPopup(rController.getFrameInterface(), pParent, "svt/ui/subtoolbar.ui", "subtoolbar") + , m_rController(rController) + , m_xTargetContainer(m_xBuilder->weld_container("container")) +{ +} + +void SubToolbarControl::GrabFocus() +{ + // TODO +} + +SubToolbarControl::~SubToolbarControl() +{ + m_rController.PopoverDestroyed(); +} + +std::unique_ptr<WeldToolbarPopup> SubToolBarController::weldPopupWindow() +{ + SolarMutexGuard aGuard; + + auto pPopup = std::make_unique<SubToolbarControl>(*this, m_pToolbar); + + css::uno::Reference< css::frame::XFrame > xFrame ( getFrameInterface() ); + + // create element with factory + static css::uno::WeakReference< css::ui::XUIElementFactoryManager > xWeakUIElementFactory; + css::uno::Reference< css::ui::XUIElementFactoryManager > xUIElementFactory = xWeakUIElementFactory; + if ( !xUIElementFactory.is() ) + { + xUIElementFactory = css::ui::theUIElementFactoryManager::get( m_xContext ); + xWeakUIElementFactory = xUIElementFactory; + } + + css::uno::Reference< css::awt::XWindow > xParent = new weld::TransportAsXWindow(pPopup->GetContainer()); + + auto aPropSeq( comphelper::InitPropertySequence( { + { "Frame", css::uno::Any( xFrame ) }, + { "ParentWindow", css::uno::Any( xParent ) }, + { "Persistent", css::uno::Any( false ) }, + { "PopupMode", css::uno::Any( true ) } + } ) ); + + try + { + m_xUIElement = xUIElementFactory->createUIElement( "private:resource/toolbar/" + m_aSubTbName, aPropSeq ); + } + catch ( css::container::NoSuchElementException& ) + {} + catch ( css::lang::IllegalArgumentException& ) + {} + + return pPopup; +} + +VclPtr<vcl::Window> SubToolBarController::createVclPopupWindow(vcl::Window* /*pParent*/) +{ + SolarMutexGuard aGuard; + + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if ( getToolboxId( nId, &pToolBox ) ) + { + css::uno::Reference< css::frame::XFrame > xFrame ( getFrameInterface() ); + + // create element with factory + static css::uno::WeakReference< css::ui::XUIElementFactoryManager > xWeakUIElementFactory; + css::uno::Reference< css::ui::XUIElement > xUIElement; + css::uno::Reference< css::ui::XUIElementFactoryManager > xUIElementFactory = xWeakUIElementFactory; + if ( !xUIElementFactory.is() ) + { + xUIElementFactory = css::ui::theUIElementFactoryManager::get( m_xContext ); + xWeakUIElementFactory = xUIElementFactory; + } + + auto aPropSeq( comphelper::InitPropertySequence( { + { "Frame", css::uno::Any( xFrame ) }, + { "ParentWindow", css::uno::Any( m_xParentWindow ) }, + { "Persistent", css::uno::Any( false ) }, + { "PopupMode", css::uno::Any( true ) } + } ) ); + + try + { + xUIElement = xUIElementFactory->createUIElement( "private:resource/toolbar/" + m_aSubTbName, aPropSeq ); + } + catch ( css::container::NoSuchElementException& ) + {} + catch ( css::lang::IllegalArgumentException& ) + {} + + if ( xUIElement.is() ) + { + css::uno::Reference< css::awt::XWindow > xSubToolBar( xUIElement->getRealInterface(), css::uno::UNO_QUERY ); + if ( xSubToolBar.is() ) + { + css::uno::Reference< css::awt::XDockableWindow > xDockWindow( xSubToolBar, css::uno::UNO_QUERY ); + xDockWindow->addDockableWindowListener( css::uno::Reference< css::awt::XDockableWindowListener >(this) ); + xDockWindow->enableDocking( true ); + + // keep reference to UIElement to avoid its destruction + disposeUIElement(); + m_xUIElement = xUIElement; + + VclPtr<vcl::Window> pTbxWindow = VCLUnoHelper::GetWindow( xSubToolBar ); + if ( pTbxWindow && pTbxWindow->GetType() == WindowType::TOOLBOX ) + { + ToolBox* pToolBar = static_cast< ToolBox* >( pTbxWindow.get() ); + // calc and set size for popup mode + Size aSize = pToolBar->CalcPopupWindowSizePixel(); + pToolBar->SetSizePixel( aSize ); + // open subtoolbox in popup mode + vcl::Window::GetDockingManager()->StartPopupMode( pToolBox, pToolBar ); + } + } + } + } + return nullptr; +} + +sal_Bool SubToolBarController::opensSubToolbar() +{ + return !m_aLastCommand.isEmpty(); +} + +OUString SubToolBarController::getSubToolbarName() +{ + return m_aSubTbName; +} + +void SubToolBarController::functionSelected( const OUString& rCommand ) +{ + if ( !m_aLastCommand.isEmpty() && m_aLastCommand != rCommand ) + { + removeStatusListener( m_aLastCommand ); + m_aLastCommand = rCommand; + addStatusListener( m_aLastCommand ); + updateImage(); + } +} + +void SubToolBarController::updateImage() +{ + SolarMutexGuard aGuard; + if ( !m_aLastCommand.isEmpty() ) + { + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if ( getToolboxId( nId, &pToolBox ) ) + { + vcl::ImageType eImageType = pToolBox->GetImageSize(); + Image aImage = vcl::CommandInfoProvider::GetImageForCommand(m_aLastCommand, getFrameInterface(), eImageType); + if ( !!aImage ) + pToolBox->SetItemImage( nId, aImage ); + } + } +} + +void SubToolBarController::startDocking( const css::awt::DockingEvent& ) +{ +} + +css::awt::DockingData SubToolBarController::docking( const css::awt::DockingEvent& ) +{ + return css::awt::DockingData(); +} + +void SubToolBarController::endDocking( const css::awt::EndDockingEvent& ) +{ +} + +sal_Bool SubToolBarController::prepareToggleFloatingMode( const css::lang::EventObject& ) +{ + return false; +} + +void SubToolBarController::toggleFloatingMode( const css::lang::EventObject& ) +{ +} + +void SubToolBarController::closed( const css::lang::EventObject& ) +{ +} + +void SubToolBarController::endPopupMode( const css::awt::EndPopupModeEvent& e ) +{ + SolarMutexGuard aGuard; + + OUString aSubToolBarResName; + if ( m_xUIElement.is() ) + { + css::uno::Reference< css::beans::XPropertySet > xPropSet( m_xUIElement, css::uno::UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + xPropSet->getPropertyValue("ResourceURL") >>= aSubToolBarResName; + } + catch ( css::beans::UnknownPropertyException& ) + {} + catch ( css::lang::WrappedTargetException& ) + {} + } + disposeUIElement(); + } + m_xUIElement = nullptr; + + // if the toolbar was teared-off recreate it and place it at the given position + if( !e.bTearoff ) + return; + + css::uno::Reference< css::ui::XUIElement > xUIElement; + css::uno::Reference< css::frame::XLayoutManager > xLayoutManager = getLayoutManager(); + + if ( !xLayoutManager.is() ) + return; + + xLayoutManager->createElement( aSubToolBarResName ); + xUIElement = xLayoutManager->getElement( aSubToolBarResName ); + if ( !xUIElement.is() ) + return; + + css::uno::Reference< css::awt::XWindow > xSubToolBar( xUIElement->getRealInterface(), css::uno::UNO_QUERY ); + css::uno::Reference< css::beans::XPropertySet > xProp( xUIElement, css::uno::UNO_QUERY ); + if ( !(xSubToolBar.is() && xProp.is()) ) + return; + + try + { + VclPtr<vcl::Window> pTbxWindow = VCLUnoHelper::GetWindow( xSubToolBar ); + if ( pTbxWindow && pTbxWindow->GetType() == WindowType::TOOLBOX ) + { + OUString aPersistentString( "Persistent" ); + css::uno::Any a = xProp->getPropertyValue( aPersistentString ); + xProp->setPropertyValue( aPersistentString, css::uno::Any( false ) ); + + xLayoutManager->hideElement( aSubToolBarResName ); + xLayoutManager->floatWindow( aSubToolBarResName ); + + xLayoutManager->setElementPos( aSubToolBarResName, e.FloatingPosition ); + xLayoutManager->showElement( aSubToolBarResName ); + + xProp->setPropertyValue("Persistent", a ); + } + } + catch ( css::uno::RuntimeException& ) + { + throw; + } + catch ( css::uno::Exception& ) + {} +} + +void SubToolBarController::disposing( const css::lang::EventObject& e ) +{ + svt::ToolboxController::disposing( e ); +} + +void SubToolBarController::initialize( const css::uno::Sequence< css::uno::Any >& rxArgs ) +{ + svt::PopupWindowController::initialize( rxArgs ); + + ToolBox* pToolBox = nullptr; + ToolBoxItemId nId; + if ( getToolboxId( nId, &pToolBox ) ) + { + if ( m_aLastCommand.isEmpty() ) + pToolBox->SetItemBits( nId, pToolBox->GetItemBits( nId ) | ToolBoxItemBits::DROPDOWNONLY ); + else + pToolBox->SetItemBits( nId, pToolBox->GetItemBits( nId ) | ToolBoxItemBits::DROPDOWN ); + } + + if (m_pToolbar) + { + mxPopoverContainer.reset(new ToolbarPopupContainer(m_pToolbar)); + m_pToolbar->set_item_popover(m_aCommandURL.toUtf8(), mxPopoverContainer->getTopLevel()); + } + + updateImage(); +} + +void SubToolBarController::PopoverDestroyed() +{ + disposeUIElement(); + m_xUIElement = nullptr; +} + +void SubToolBarController::dispose() +{ + if ( m_bDisposed ) + return; + + svt::PopupWindowController::dispose(); + disposeUIElement(); + m_xUIElement = nullptr; +} + +OUString SubToolBarController::getImplementationName() +{ + return "com.sun.star.comp.framework.SubToolBarController"; +} + +sal_Bool SubToolBarController::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService( this, rServiceName ); +} + +css::uno::Sequence< OUString > SubToolBarController::getSupportedServiceNames() +{ + return {"com.sun.star.frame.ToolbarController"}; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_SubToolBarController_get_implementation( + css::uno::XComponentContext* rxContext, + css::uno::Sequence<css::uno::Any> const & rxArgs ) +{ + return cppu::acquire( new SubToolBarController( rxContext, rxArgs ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/thesaurusmenucontroller.cxx b/framework/source/uielement/thesaurusmenucontroller.cxx new file mode 100644 index 000000000..fb7f3175a --- /dev/null +++ b/framework/source/uielement/thesaurusmenucontroller.cxx @@ -0,0 +1,185 @@ +/* -*- 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 <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <sal/log.hxx> +#include <svl/lngmisc.hxx> +#include <svtools/popupmenucontrollerbase.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/lingucfg.hxx> +#include <vcl/commandinfoprovider.hxx> + +#include <com/sun/star/graphic/GraphicProvider.hpp> +#include <com/sun/star/linguistic2/LinguServiceManager.hpp> + +namespace { + +class ThesaurusMenuController : public svt::PopupMenuControllerBase +{ +public: + explicit ThesaurusMenuController( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + // XStatusListener + virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& rEvent ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + +private: + void fillPopupMenu(); + void getMeanings( std::vector< OUString >& rSynonyms, const OUString& rWord, const css::lang::Locale& rLocale, size_t nMaxSynonms ); + OUString getThesImplName( const css::lang::Locale& rLocale ) const; + css::uno::Reference< css::linguistic2::XLinguServiceManager2 > m_xLinguServiceManager; + css::uno::Reference< css::linguistic2::XThesaurus > m_xThesaurus; + OUString m_aLastWord; +}; + +} + +ThesaurusMenuController::ThesaurusMenuController( const css::uno::Reference< css::uno::XComponentContext >& rxContext ) : + svt::PopupMenuControllerBase( rxContext ), + m_xLinguServiceManager( css::linguistic2::LinguServiceManager::create( rxContext ) ), + m_xThesaurus( m_xLinguServiceManager->getThesaurus() ) +{ +} + +void ThesaurusMenuController::statusChanged( const css::frame::FeatureStateEvent& rEvent ) +{ + rEvent.State >>= m_aLastWord; + m_xPopupMenu->clear(); + if ( rEvent.IsEnabled ) + fillPopupMenu(); +} + +void ThesaurusMenuController::fillPopupMenu() +{ + sal_Int32 nIdx{ 0 }; + OUString aText = m_aLastWord.getToken(0, '#', nIdx); + OUString aIsoLang = m_aLastWord.getToken(0, '#', nIdx); + if ( aText.isEmpty() || aIsoLang.isEmpty() ) + return; + + std::vector< OUString > aSynonyms; + css::lang::Locale aLocale = LanguageTag::convertToLocale( aIsoLang ); + getMeanings( aSynonyms, aText, aLocale, 7 /*max number of synonyms to retrieve*/ ); + + m_xPopupMenu->enableAutoMnemonics(false); + if ( aSynonyms.empty() ) + return; + + SvtLinguConfig aCfg; + css::uno::Reference<css::graphic::XGraphic> xGraphic; + OUString aThesImplName( getThesImplName( aLocale ) ); + OUString aSynonymsImageUrl( aCfg.GetSynonymsContextImage( aThesImplName ) ); + if (!aThesImplName.isEmpty() && !aSynonymsImageUrl.isEmpty()) + { + try + { + css::uno::Reference<css::uno::XComponentContext> xContext(::comphelper::getProcessComponentContext()); + css::uno::Reference<css::graphic::XGraphicProvider> xProvider(css::graphic::GraphicProvider::create(xContext)); + xGraphic = xProvider->queryGraphic({ comphelper::makePropertyValue("URL", aSynonymsImageUrl) }); + } + catch (const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + } + + sal_uInt16 nId = 1; + for ( const auto& aSynonym : aSynonyms ) + { + OUString aItemText( linguistic::GetThesaurusReplaceText( aSynonym ) ); + m_xPopupMenu->insertItem(nId, aItemText, 0, -1); + m_xPopupMenu->setCommand(nId, ".uno:ThesaurusFromContext?WordReplace:string=" + aItemText); + + if (xGraphic.is()) + m_xPopupMenu->setItemImage(nId, xGraphic, false); + + nId++; + } + + m_xPopupMenu->insertSeparator(-1); + OUString aThesaurusDialogCmd( ".uno:ThesaurusDialog" ); + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aThesaurusDialogCmd, m_aModuleName); + m_xPopupMenu->insertItem(nId, vcl::CommandInfoProvider::GetPopupLabelForCommand(aProperties), 0, -1); + m_xPopupMenu->setCommand(nId, aThesaurusDialogCmd); +} + +void ThesaurusMenuController::getMeanings( std::vector< OUString >& rSynonyms, const OUString& rWord, + const css::lang::Locale& rLocale, size_t nMaxSynonms ) +{ + rSynonyms.clear(); + if ( !(m_xThesaurus.is() && m_xThesaurus->hasLocale( rLocale ) && !rWord.isEmpty() && nMaxSynonms > 0) ) + return; + + try + { + const css::uno::Sequence< css::uno::Reference< css::linguistic2::XMeaning > > aMeaningSeq( + m_xThesaurus->queryMeanings( rWord, rLocale, css::uno::Sequence< css::beans::PropertyValue >() ) ); + + for ( const auto& xMeaning : aMeaningSeq ) + { + const css::uno::Sequence< OUString > aSynonymSeq( xMeaning->querySynonyms() ); + for ( const auto& aSynonym : aSynonymSeq ) + { + rSynonyms.push_back( aSynonym ); + if ( rSynonyms.size() == nMaxSynonms ) + return; + } + } + } + catch ( const css::uno::Exception& ) + { + SAL_WARN( "fwk.uielement", "Failed to get synonyms" ); + } +} + +OUString ThesaurusMenuController::getThesImplName( const css::lang::Locale& rLocale ) const +{ + css::uno::Sequence< OUString > aServiceNames = + m_xLinguServiceManager->getConfiguredServices( "com.sun.star.linguistic2.Thesaurus", rLocale ); + SAL_WARN_IF( aServiceNames.getLength() > 1, "fwk.uielement", "Only one thesaurus is allowed per locale, but found more!" ); + if ( aServiceNames.getLength() == 1 ) + return aServiceNames[0]; + + return OUString(); +} + +OUString ThesaurusMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.ThesaurusMenuController"; +} + +css::uno::Sequence< OUString > ThesaurusMenuController::getSupportedServiceNames() +{ + return { "com.sun.star.frame.PopupMenuController" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ThesaurusMenuController_get_implementation( + css::uno::XComponentContext* xContext, + css::uno::Sequence< css::uno::Any > const & ) +{ + return cppu::acquire( new ThesaurusMenuController( xContext ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/togglebuttontoolbarcontroller.cxx b/framework/source/uielement/togglebuttontoolbarcontroller.cxx new file mode 100644 index 000000000..c17b08efe --- /dev/null +++ b/framework/source/uielement/togglebuttontoolbarcontroller.cxx @@ -0,0 +1,270 @@ +/* -*- 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 <uielement/togglebuttontoolbarcontroller.hxx> + +#include <comphelper/propertyvalue.hxx> +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/menu.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::util; + +namespace framework +{ + +ToggleButtonToolbarController::ToggleButtonToolbarController( + const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + ToolBox* pToolbar, + ToolBoxItemId nID, + Style eStyle, + const OUString& aCommand ) : + ComplexToolbarController( rxContext, rFrame, pToolbar, nID, aCommand ) +{ + if ( eStyle == Style::DropDownButton ) + m_xToolbar->SetItemBits( m_nID, ToolBoxItemBits::DROPDOWNONLY | m_xToolbar->GetItemBits( m_nID ) ); + else // Style::ToggleDropDownButton + m_xToolbar->SetItemBits( m_nID, ToolBoxItemBits::DROPDOWN | m_xToolbar->GetItemBits( m_nID ) ); +} + +ToggleButtonToolbarController::~ToggleButtonToolbarController() +{ +} + +void SAL_CALL ToggleButtonToolbarController::dispose() +{ + SolarMutexGuard aSolarMutexGuard; + ComplexToolbarController::dispose(); +} + +Sequence<PropertyValue> ToggleButtonToolbarController::getExecuteArgs(sal_Int16 KeyModifier) const +{ + Sequence<PropertyValue> aArgs{ // Add key modifier to argument list + comphelper::makePropertyValue("KeyModifier", KeyModifier), + comphelper::makePropertyValue("Text", m_aCurrentSelection) }; + return aArgs; +} + +uno::Reference< awt::XWindow > SAL_CALL ToggleButtonToolbarController::createPopupWindow() +{ + uno::Reference< awt::XWindow > xWindow; + + SolarMutexGuard aSolarMutexGuard; + + // create popup menu + ScopedVclPtrInstance<::PopupMenu> aPopup; + const sal_uInt32 nCount = m_aDropdownMenuList.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + const OUString & rLabel = m_aDropdownMenuList[i].mLabel; + aPopup->InsertItem( sal_uInt16( i+1 ), rLabel ); + if ( rLabel == m_aCurrentSelection ) + aPopup->CheckItem( sal_uInt16( i+1 ) ); + else + aPopup->CheckItem( sal_uInt16( i+1 ), false ); + + if ( !m_aDropdownMenuList[i].mTipHelpText.isEmpty() ) + aPopup->SetTipHelpText( sal_uInt16( i+1 ), m_aDropdownMenuList[i].mTipHelpText ); + } + + m_xToolbar->SetItemDown( m_nID, true ); + aPopup->SetSelectHdl( LINK( this, ToggleButtonToolbarController, MenuSelectHdl )); + aPopup->Execute( m_xToolbar, m_xToolbar->GetItemRect( m_nID )); + m_xToolbar->SetItemDown( m_nID, false ); + + return xWindow; +} + +void ToggleButtonToolbarController::executeControlCommand( const css::frame::ControlCommand& rControlCommand ) +{ + SolarMutexGuard aSolarMutexGuard; + + if ( rControlCommand.Command == "SetList" ) + { + for ( auto const & arg : rControlCommand.Arguments ) + { + if ( arg.Name == "List" ) + { + Sequence< OUString > aList; + m_aDropdownMenuList.clear(); + m_aCurrentSelection.clear(); + + arg.Value >>= aList; + for ( OUString const & label : std::as_const(aList) ) + { + m_aDropdownMenuList.push_back( DropdownMenuItem() ); + m_aDropdownMenuList.back().mLabel = label; + } + + // send notification + uno::Sequence< beans::NamedValue > aInfo { { "List", css::uno::Any(aList) } }; + addNotifyInfo( "ListChanged", + getDispatchFromCommand( m_aCommandURL ), + aInfo ); + + break; + } + } + } + else if ( rControlCommand.Command == "CheckItemPos" ) + { + for ( auto const & arg : rControlCommand.Arguments ) + { + if ( arg.Name == "Pos" ) + { + sal_Int32 nPos( -1 ); + + arg.Value >>= nPos; + if ( nPos >= 0 && + ( sal::static_int_cast< sal_uInt32 >(nPos) + < m_aDropdownMenuList.size() ) ) + { + m_aCurrentSelection = m_aDropdownMenuList[nPos].mLabel; + + // send notification + uno::Sequence< beans::NamedValue > aInfo { { "ItemChecked", css::uno::Any(nPos) } }; + addNotifyInfo( "Pos", + getDispatchFromCommand( m_aCommandURL ), + aInfo ); + } + break; + } + } + } + else if ( rControlCommand.Command == "AddEntry" ) + { + OUString aText; + OUString aTipHelpText; + + for ( auto const & arg : rControlCommand.Arguments ) + { + if ( arg.Name == "Text" ) + { + arg.Value >>= aText; + } + else if ( arg.Name == "TipHelpText" ) + { + arg.Value >>= aTipHelpText; + } + } + + if (!aText.isEmpty()) + { + m_aDropdownMenuList.push_back( DropdownMenuItem() ); + m_aDropdownMenuList.back().mLabel = aText; + m_aDropdownMenuList.back().mTipHelpText = aTipHelpText; + } + } + else if ( rControlCommand.Command == "InsertEntry" ) + { + sal_Int32 nPos(0); + sal_Int32 nSize = sal_Int32( m_aDropdownMenuList.size() ); + OUString aText; + for ( auto const & arg : rControlCommand.Arguments ) + { + if ( arg.Name == "Pos" ) + { + sal_Int32 nTmpPos = 0; + if ( arg.Value >>= nTmpPos ) + { + if (( nTmpPos >= 0 ) && ( nTmpPos < nSize )) + nPos = nTmpPos; + } + } + else if ( arg.Name == "Text" ) + arg.Value >>= aText; + } + + std::vector< DropdownMenuItem >::iterator aIter = m_aDropdownMenuList.begin(); + aIter += nPos; + aIter = m_aDropdownMenuList.insert(aIter, DropdownMenuItem()); + if (aIter != m_aDropdownMenuList.end()) + aIter->mLabel = aText; + } + else if ( rControlCommand.Command == "RemoveEntryPos" ) + { + for ( auto const & arg : rControlCommand.Arguments ) + { + if ( arg.Name == "Pos" ) + { + sal_Int32 nPos( -1 ); + if ( arg.Value >>= nPos ) + { + if ( nPos < sal_Int32( m_aDropdownMenuList.size() )) + { + m_aDropdownMenuList.erase(m_aDropdownMenuList.begin() + nPos); + } + } + break; + } + } + } + else if ( rControlCommand.Command == "RemoveEntryText" ) + { + for ( auto const & arg : rControlCommand.Arguments ) + { + if ( arg.Name == "Text" ) + { + OUString aText; + if ( arg.Value >>= aText ) + { + sal_Int32 nSize = sal_Int32( m_aDropdownMenuList.size() ); + for ( sal_Int32 j = 0; j < nSize; j++ ) + { + if ( m_aDropdownMenuList[j].mLabel == aText ) + { + m_aDropdownMenuList.erase(m_aDropdownMenuList.begin() + j); + break; + } + } + } + break; + } + } + } + else if ( rControlCommand.Command == "createPopupMenu" ) + { + createPopupWindow(); + } +} + +IMPL_LINK( ToggleButtonToolbarController, MenuSelectHdl, Menu *, pMenu, bool ) +{ + SolarMutexGuard aGuard; + + sal_uInt16 nItemId = pMenu->GetCurItemId(); + if ( nItemId > 0 && nItemId <= m_aDropdownMenuList.size() ) + { + m_aCurrentSelection = m_aDropdownMenuList[nItemId-1].mLabel; + + execute( 0 ); + } + return false; +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/toolbarmanager.cxx b/framework/source/uielement/toolbarmanager.cxx new file mode 100644 index 000000000..3cce6eef4 --- /dev/null +++ b/framework/source/uielement/toolbarmanager.cxx @@ -0,0 +1,2365 @@ +/* + * 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 <sal/log.hxx> + +#include <cassert> + +#include <uielement/toolbarmanager.hxx> + +#include <framework/generictoolbarcontroller.hxx> +#include <officecfg/Office/Common.hxx> +#include <uielement/styletoolbarcontroller.hxx> +#include <properties.h> +#include <framework/sfxhelperfunctions.hxx> +#include <classes/fwkresid.hxx> +#include <classes/resource.hxx> +#include <strings.hrc> +#include <uielement/toolbarmerger.hxx> + +#include <com/sun/star/ui/ItemType.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/awt/XDockableWindow.hpp> +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <com/sun/star/ui/DockingArea.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/theToolbarControllerFactory.hpp> +#include <com/sun/star/ui/ItemStyle.hpp> +#include <com/sun/star/ui/XUIElementSettings.hpp> +#include <com/sun/star/ui/XUIConfigurationPersistence.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/ImageType.hpp> +#include <com/sun/star/ui/UIElementType.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +#include <svtools/toolboxcontroller.hxx> +#include <unotools/cmdoptions.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <unotools/mediadescriptor.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include <svtools/miscopt.hxx> +#include <svtools/imgdef.hxx> +#include <utility> +#include <vcl/event.hxx> +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <vcl/menu.hxx> +#include <vcl/syswin.hxx> +#include <vcl/taskpanelist.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/weldutils.hxx> +#include <tools/debug.hxx> + +// namespaces + +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::graphic; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::ui; +using namespace ::com::sun::star; + +namespace framework +{ + +const char ITEM_DESCRIPTOR_COMMANDURL[] = "CommandURL"; +const char ITEM_DESCRIPTOR_VISIBLE[] = "IsVisible"; + +const sal_uInt16 STARTID_CUSTOMIZE_POPUPMENU = 1000; + +static css::uno::Reference< css::frame::XLayoutManager > getLayoutManagerFromFrame( + css::uno::Reference< css::frame::XFrame > const & rFrame ) +{ + css::uno::Reference< css::frame::XLayoutManager > xLayoutManager; + + Reference< XPropertySet > xPropSet( rFrame, UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + xPropSet->getPropertyValue("LayoutManager") >>= xLayoutManager; + } + catch (const RuntimeException&) + { + throw; + } + catch (const Exception&) + { + } + } + + return xLayoutManager; +} +namespace +{ + +sal_Int16 getCurrentImageType() +{ + SvtMiscOptions aMiscOptions; + sal_Int16 nImageType = css::ui::ImageType::SIZE_DEFAULT; + if (aMiscOptions.GetCurrentSymbolsSize() == SFX_SYMBOLS_SIZE_LARGE) + nImageType |= css::ui::ImageType::SIZE_LARGE; + else if (aMiscOptions.GetCurrentSymbolsSize() == SFX_SYMBOLS_SIZE_32) + nImageType |= css::ui::ImageType::SIZE_32; + return nImageType; +} + +class VclToolBarManager : public ToolBarManagerImpl +{ + DECL_LINK(Click, ToolBox*, void); + +public: + VclToolBarManager(VclPtr<ToolBox> pToolbar) + : m_pToolBar(std::move(pToolbar)) + , m_bAddedToTaskPaneList(true) + , m_pManager(nullptr) + {} + + ~VclToolBarManager() + { + OSL_ASSERT( !m_bAddedToTaskPaneList ); + } + + virtual void Init() override + { + vcl::Window* pWindow = m_pToolBar; + while ( pWindow && !pWindow->IsSystemWindow() ) + pWindow = pWindow->GetParent(); + + if ( pWindow ) + static_cast<SystemWindow *>(pWindow)->GetTaskPaneList()->AddWindow( m_pToolBar ); + } + + virtual void Destroy() override + { + OSL_ASSERT( m_pToolBar != nullptr ); + SolarMutexGuard g; + if ( m_bAddedToTaskPaneList ) + { + vcl::Window* pWindow = m_pToolBar; + while ( pWindow && !pWindow->IsSystemWindow() ) + pWindow = pWindow->GetParent(); + + if ( pWindow ) + static_cast<SystemWindow *>(pWindow)->GetTaskPaneList()->RemoveWindow( m_pToolBar ); + m_bAddedToTaskPaneList = false; + } + + // Delete the additional add-ons data + for ( ToolBox::ImplToolItems::size_type i = 0; i < m_pToolBar->GetItemCount(); i++ ) + { + ToolBoxItemId nItemId = m_pToolBar->GetItemId( i ); + if ( nItemId > ToolBoxItemId(0) ) + delete static_cast< AddonsParams* >( m_pToolBar->GetItemData( nItemId )); + } + + // #i93173# note we can still be in one of the toolbar's handlers + m_pToolBar->SetSelectHdl( Link<ToolBox *, void>() ); + m_pToolBar->SetActivateHdl( Link<ToolBox *, void>() ); + m_pToolBar->SetDeactivateHdl( Link<ToolBox *, void>() ); + m_pToolBar->SetClickHdl( Link<ToolBox *, void>() ); + m_pToolBar->SetDropdownClickHdl( Link<ToolBox *, void>() ); + m_pToolBar->SetDoubleClickHdl( Link<ToolBox *, void>() ); + m_pToolBar->SetStateChangedHdl( Link<StateChangedType const *, void>() ); + m_pToolBar->SetDataChangedHdl( Link<DataChangedEvent const *, void>() ); + + m_pToolBar.disposeAndClear(); + } + + virtual css::uno::Reference<css::awt::XWindow> GetInterface() override + { + return VCLUnoHelper::GetInterface(m_pToolBar); + } + + virtual css::uno::Reference<css::frame::XStatusListener> CreateToolBoxController( + const css::uno::Reference<css::frame::XFrame>& rFrame, + ToolBoxItemId nId, + const OUString& rCommandURL ) override + { + rtl::Reference<svt::ToolboxController> pController + = ::framework::CreateToolBoxController( rFrame, m_pToolBar, nId, rCommandURL ); + css::uno::Reference<css::frame::XStatusListener> xListener; + if (pController) + xListener = pController; + return xListener; + } + + virtual void ConnectCallbacks(ToolBarManager* pManager) override + { + m_pManager = pManager; + m_pToolBar->SetSelectHdl( LINK( pManager, ToolBarManager, Select) ); + m_pToolBar->SetClickHdl( LINK( this, VclToolBarManager, Click ) ); + m_pToolBar->SetDropdownClickHdl( LINK( pManager, ToolBarManager, DropdownClick ) ); + m_pToolBar->SetDoubleClickHdl( LINK( pManager, ToolBarManager, DoubleClick ) ); + m_pToolBar->SetStateChangedHdl( LINK( pManager, ToolBarManager, StateChanged ) ); + m_pToolBar->SetDataChangedHdl( LINK( pManager, ToolBarManager, DataChanged ) ); + + m_pToolBar->SetMenuButtonHdl( LINK( pManager, ToolBarManager, MenuButton ) ); + m_pToolBar->SetMenuExecuteHdl( LINK( pManager, ToolBarManager, MenuPreExecute ) ); + m_pToolBar->GetMenu()->SetSelectHdl( LINK( pManager, ToolBarManager, MenuSelect ) ); + } + + virtual void InsertItem(ToolBoxItemId nId, + const OUString& rString, + const OUString& rCommandURL, + const OUString& rTooltip, + const OUString& rLabel, + ToolBoxItemBits nItemBits) override + { + m_pToolBar->InsertItem( nId, rString, nItemBits ); + m_pToolBar->SetItemCommand( nId, rCommandURL ); + m_pToolBar->SetQuickHelpText(nId, rTooltip); + m_pToolBar->SetItemText( nId, rLabel ); + m_pToolBar->EnableItem( nId ); + m_pToolBar->SetItemState( nId, TRISTATE_FALSE ); + } + + virtual void InsertSeparator() override + { + m_pToolBar->InsertSeparator(); + } + + virtual void InsertSpace() override + { + m_pToolBar->InsertSpace(); + } + + virtual void InsertBreak() override + { + m_pToolBar->InsertBreak(); + } + + virtual ToolBoxItemId GetItemId(sal_uInt16 nPos) override + { + return m_pToolBar->GetItemId(nPos); + } + + virtual ToolBoxItemId GetCurItemId() override + { + return m_pToolBar->GetCurItemId(); + } + + virtual OUString GetItemCommand(ToolBoxItemId nId) override + { + return m_pToolBar->GetItemCommand(nId); + } + + virtual sal_uInt16 GetItemCount() override + { + return m_pToolBar->GetItemCount(); + } + + virtual void SetItemCheckable(ToolBoxItemId nId) override + { + m_pToolBar->SetItemBits( nId, m_pToolBar->GetItemBits( nId ) | ToolBoxItemBits::CHECKABLE ); + } + + virtual void HideItem(ToolBoxItemId nId, const OUString& /*rCommandURL*/) override + { + m_pToolBar->HideItem( nId ); + } + + virtual bool IsItemVisible(ToolBoxItemId nId, const OUString& /*rCommandURL*/) override + { + return m_pToolBar->IsItemVisible(nId); + } + + virtual void Clear() override + { + m_pToolBar->Clear(); + } + + virtual void SetName(const OUString& rName) override + { + m_pToolBar->SetText( rName ); + } + + virtual void SetHelpId(const OString& rHelpId) override + { + m_pToolBar->SetHelpId( rHelpId ); + } + + virtual bool WillUsePopupMode() override + { + return m_pToolBar->WillUsePopupMode(); + } + + virtual bool IsReallyVisible() override + { + return m_pToolBar->IsReallyVisible(); + } + + virtual void SetIconSize(ToolBoxButtonSize eSize) override + { + m_pToolBar->SetToolboxButtonSize(eSize); + } + + virtual vcl::ImageType GetImageSize() override + { + return m_pToolBar->GetImageSize(); + } + + virtual void SetMenuType(ToolBoxMenuType eType) override + { + m_pToolBar->SetMenuType( eType ); + } + + virtual void MergeToolbar(ToolBoxItemId & rItemId, + const OUString& rModuleIdentifier, + CommandToInfoMap& rCommandMap, + MergeToolbarInstruction& rInstruction) override + { + ReferenceToolbarPathInfo aRefPoint = ToolBarMerger::FindReferencePoint( m_pToolBar, rInstruction.aMergePoint ); + + // convert the sequence< sequence< propertyvalue > > structure to + // something we can better handle. A vector with item data + AddonToolbarItemContainer aItems; + ToolBarMerger::ConvertSeqSeqToVector( rInstruction.aMergeToolbarItems, aItems ); + + if ( aRefPoint.bResult ) + { + ToolBarMerger::ProcessMergeOperation( m_pToolBar, + aRefPoint.nPos, + rItemId, + rCommandMap, + rModuleIdentifier, + rInstruction.aMergeCommand, + rInstruction.aMergeCommandParameter, + aItems ); + } + else + { + ToolBarMerger::ProcessMergeFallback( m_pToolBar, + rItemId, + rCommandMap, + rModuleIdentifier, + rInstruction.aMergeCommand, + rInstruction.aMergeFallback, + aItems ); + } + } + + virtual void SetItemImage(ToolBoxItemId nId, + const OUString& /*rCommandURL*/, + const Image& rImage) override + { + m_pToolBar->SetItemImage(nId, rImage); + } + + virtual void UpdateSize() override + { + ::Size aSize = m_pToolBar->CalcWindowSizePixel(); + m_pToolBar->SetOutputSizePixel( aSize ); + } + + virtual void SetItemWindow(ToolBoxItemId nItemId, vcl::Window* pNewWindow) override + { + m_pToolBar->SetItemWindow( nItemId, pNewWindow ); + } + +private: + VclPtr<ToolBox> m_pToolBar; + bool m_bAddedToTaskPaneList; + ToolBarManager* m_pManager; +}; + +IMPL_LINK_NOARG(VclToolBarManager, Click, ToolBox*, void) +{ + m_pManager->OnClick(); +} + +class WeldToolBarManager : public ToolBarManagerImpl +{ + DECL_LINK(Click, const OString&, void); + DECL_LINK(ToggleMenuHdl, const OString&, void); + +public: + WeldToolBarManager(weld::Toolbar* pToolbar, + weld::Builder* pBuilder) + : m_pWeldedToolBar(pToolbar) + , m_pBuilder(pBuilder) + , m_pManager(nullptr) + , m_nCurrentId(0) + {} + + virtual void Init() override {} + + virtual void Destroy() override {} + + virtual css::uno::Reference<css::awt::XWindow> GetInterface() override + { + return new weld::TransportAsXWindow(m_pWeldedToolBar); + } + + virtual css::uno::Reference<css::frame::XStatusListener> CreateToolBoxController( + const css::uno::Reference<css::frame::XFrame>& rFrame, + ToolBoxItemId /*nId*/, + const OUString& rCommandURL ) override + { + css::uno::Reference<css::frame::XToolbarController> xController + = ::framework::CreateWeldToolBoxController(rFrame, m_pWeldedToolBar, m_pBuilder, rCommandURL); + css::uno::Reference<css::frame::XStatusListener> xListener; + if (xController.is()) + xListener = css::uno::Reference<css::frame::XStatusListener>( xController, UNO_QUERY ); + return xListener; + } + + virtual void ConnectCallbacks(ToolBarManager* pManager) override + { + m_pManager = pManager; + m_pWeldedToolBar->connect_clicked(LINK(this, WeldToolBarManager, Click)); + m_pWeldedToolBar->connect_menu_toggled(LINK(this, WeldToolBarManager, ToggleMenuHdl)); + } + + virtual void InsertItem(ToolBoxItemId nId, + const OUString& /*rString*/, + const OUString& rCommandURL, + const OUString& rTooltip, + const OUString& rLabel, + ToolBoxItemBits /*nItemBits*/) override + { + OString sId = OUStringToOString(rCommandURL, RTL_TEXTENCODING_UTF8); + m_aCommandToId[sId] = nId; + m_aIdToCommand[nId] = sId; + m_aCommandOrder.push_back(sId); + + m_pWeldedToolBar->insert_item(m_aCommandOrder.size(), rCommandURL); + m_pWeldedToolBar->set_item_tooltip_text(sId, rTooltip); + m_pWeldedToolBar->set_item_label( sId, rLabel ); + m_pWeldedToolBar->set_item_sensitive( sId, true ); + m_pWeldedToolBar->set_item_active( sId, false ); + } + + virtual void InsertSeparator() override + { + m_pWeldedToolBar->append_separator(""); + } + + virtual void InsertSpace() override {} + + virtual void InsertBreak() override {} + + virtual ToolBoxItemId GetItemId(sal_uInt16 nPos) override + { + return m_aCommandToId[m_aCommandOrder[nPos]]; + } + + virtual ToolBoxItemId GetCurItemId() override + { + return m_nCurrentId; + } + + virtual OUString GetItemCommand(ToolBoxItemId nId) override + { + return OStringToOUString(m_aIdToCommand[nId], RTL_TEXTENCODING_UTF8); + } + + virtual sal_uInt16 GetItemCount() override + { + return m_aCommandOrder.size(); + } + + virtual void SetItemCheckable(ToolBoxItemId /*nId*/) override {} + + virtual void HideItem(ToolBoxItemId /*nId*/, const OUString& rCommandURL) override + { + OString sId = OUStringToOString(rCommandURL, RTL_TEXTENCODING_UTF8); + m_pWeldedToolBar->set_item_visible(sId, false); + } + + virtual bool IsItemVisible(ToolBoxItemId /*nId*/, const OUString& rCommandURL) override + { + OString sId = OUStringToOString(rCommandURL, RTL_TEXTENCODING_UTF8); + return m_pWeldedToolBar->get_item_visible(sId); + } + + virtual void Clear() override {} + + virtual void SetName(const OUString& /*rName*/) override {} + + virtual void SetHelpId(const OString& /*rHelpId*/) override {} + + virtual bool WillUsePopupMode() override { return true; } + + virtual bool IsReallyVisible() override { return true; } + + virtual void SetIconSize(ToolBoxButtonSize /*eSize*/) override {} + + virtual vcl::ImageType GetImageSize() override + { + return vcl::ImageType::Size32; + } + + virtual void SetMenuType(ToolBoxMenuType /*eType*/) override {} + + virtual void MergeToolbar(ToolBoxItemId & /*rItemId*/, + const OUString& /*rModuleIdentifier*/, + CommandToInfoMap& /*rCommandMap*/, + MergeToolbarInstruction& /*rInstruction*/) override {} + + virtual void SetItemImage(ToolBoxItemId /*nId*/, + const OUString& rCommandURL, + const Image& rImage) override + { + OString sId = OUStringToOString(rCommandURL, RTL_TEXTENCODING_UTF8); + m_pWeldedToolBar->set_item_image(sId, Graphic(rImage).GetXGraphic()); + } + + virtual void UpdateSize() override {} + + virtual void SetItemWindow(ToolBoxItemId /*nItemId*/, vcl::Window* /*pNewWindow*/) override {} + +private: + weld::Toolbar* m_pWeldedToolBar; + weld::Builder* m_pBuilder; + ToolBarManager* m_pManager; + ToolBoxItemId m_nCurrentId; + std::map<const OString, ToolBoxItemId> m_aCommandToId; + std::map<ToolBoxItemId, OString> m_aIdToCommand; + std::vector<OString> m_aCommandOrder; +}; + +IMPL_LINK(WeldToolBarManager, Click, const OString&, rCommand, void) +{ + m_nCurrentId = m_aCommandToId[rCommand]; + m_pManager->OnClick(true); +} + +IMPL_LINK(WeldToolBarManager, ToggleMenuHdl, const OString&, rCommand, void) +{ + m_nCurrentId = m_aCommandToId[rCommand]; + m_pManager->OnDropdownClick(false); +} + +} // end anonymous namespace + +// XInterface, XTypeProvider, XServiceInfo + +ToolBarManager::ToolBarManager( const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + OUString aResourceName, + ToolBox* pToolBar ) : + m_bDisposed( false ), + m_bFrameActionRegistered( false ), + m_bUpdateControllers( false ), + m_eSymbolSize(SvtMiscOptions().GetCurrentSymbolsSize()), + m_pImpl( new VclToolBarManager( pToolBar ) ), + m_pToolBar( pToolBar ), + m_aResourceName(std::move( aResourceName )), + m_xFrame( rFrame ), + m_xContext( rxContext ), + m_aAsyncUpdateControllersTimer( "framework::ToolBarManager m_aAsyncUpdateControllersTimer" ), + m_sIconTheme( SvtMiscOptions().GetIconTheme() ) +{ + Init(); +} + +ToolBarManager::ToolBarManager( const Reference< XComponentContext >& rxContext, + const Reference< XFrame >& rFrame, + OUString aResourceName, + weld::Toolbar* pToolBar, + weld::Builder* pBuilder ) : + m_bDisposed( false ), + m_bFrameActionRegistered( false ), + m_bUpdateControllers( false ), + m_eSymbolSize( SvtMiscOptions().GetCurrentSymbolsSize() ), + m_pImpl( new WeldToolBarManager( pToolBar, pBuilder ) ), + m_pToolBar( nullptr ), + m_aResourceName(std::move( aResourceName )), + m_xFrame( rFrame ), + m_xContext( rxContext ), + m_aAsyncUpdateControllersTimer( "framework::ToolBarManager m_aAsyncUpdateControllersTimer" ), + m_sIconTheme( SvtMiscOptions().GetIconTheme() ) +{ + Init(); +} + +void ToolBarManager::Init() +{ + OSL_ASSERT( m_xContext.is() ); + + m_pImpl->Init(); + + m_xToolbarControllerFactory = frame::theToolbarControllerFactory::get( m_xContext ); + m_xURLTransformer = URLTransformer::create( m_xContext ); + + m_pImpl->ConnectCallbacks(this); + + if (m_eSymbolSize == SFX_SYMBOLS_SIZE_LARGE) + m_pImpl->SetIconSize(ToolBoxButtonSize::Large); + else if (m_eSymbolSize == SFX_SYMBOLS_SIZE_32) + m_pImpl->SetIconSize(ToolBoxButtonSize::Size32); + else + m_pImpl->SetIconSize(ToolBoxButtonSize::Small); + + // enables a menu for clipped items and customization + SvtCommandOptions aCmdOptions; + ToolBoxMenuType nMenuType = ToolBoxMenuType::ClippedItems; + if ( !aCmdOptions.Lookup( SvtCommandOptions::CMDOPTION_DISABLED, "CreateDialog")) + nMenuType |= ToolBoxMenuType::Customize; + + m_pImpl->SetMenuType( nMenuType ); + + // set name for testtool, the useful part is after the last '/' + sal_Int32 idx = m_aResourceName.lastIndexOf('/'); + idx++; // will become 0 if '/' not found: use full string + std::u16string_view aToolbarName = m_aResourceName.subView( idx ); + OString aHelpIdAsString = ".HelpId:" + OUStringToOString( aToolbarName, RTL_TEXTENCODING_UTF8 ); + m_pImpl->SetHelpId( aHelpIdAsString ); + + m_aAsyncUpdateControllersTimer.SetTimeout( 50 ); + m_aAsyncUpdateControllersTimer.SetInvokeHandler( LINK( this, ToolBarManager, AsyncUpdateControllersHdl ) ); + + SvtMiscOptions().AddListenerLink( LINK( this, ToolBarManager, MiscOptionsChanged ) ); +} + +ToolBarManager::~ToolBarManager() +{ + assert(!m_aAsyncUpdateControllersTimer.IsActive()); + assert(!m_pToolBar); // must be disposed by ToolbarLayoutManager +} + +void ToolBarManager::Destroy() +{ + m_pImpl->Destroy(); + + SvtMiscOptions().RemoveListenerLink( LINK( this, ToolBarManager, MiscOptionsChanged ) ); +} + +ToolBox* ToolBarManager::GetToolBar() const +{ + SolarMutexGuard g; + return m_pToolBar; +} + +void ToolBarManager::CheckAndUpdateImages() +{ + SolarMutexGuard g; + bool bRefreshImages = false; + + SvtMiscOptions aMiscOptions; + sal_Int16 eNewSymbolSize = aMiscOptions.GetCurrentSymbolsSize(); + + if (m_eSymbolSize != eNewSymbolSize ) + { + bRefreshImages = true; + m_eSymbolSize = eNewSymbolSize; + } + + const OUString& sCurrentIconTheme = aMiscOptions.GetIconTheme(); + if ( m_sIconTheme != sCurrentIconTheme ) + { + bRefreshImages = true; + m_sIconTheme = sCurrentIconTheme; + } + + // Refresh images if requested + if ( bRefreshImages ) + RefreshImages(); +} + +void ToolBarManager::RefreshImages() +{ + SolarMutexGuard g; + + if (m_eSymbolSize == SFX_SYMBOLS_SIZE_LARGE) + m_pImpl->SetIconSize(ToolBoxButtonSize::Large); + else if (m_eSymbolSize == SFX_SYMBOLS_SIZE_32) + m_pImpl->SetIconSize(ToolBoxButtonSize::Size32); + else + m_pImpl->SetIconSize(ToolBoxButtonSize::Small); + + for ( auto const& it : m_aControllerMap ) + { + Reference< XSubToolbarController > xController( it.second, UNO_QUERY ); + if ( xController.is() && xController->opensSubToolbar() ) + { + // The button should show the last function that was selected from the + // dropdown. The controller should know better than us what it was. + xController->updateImage(); + } + else + { + OUString aCommandURL = m_pImpl->GetItemCommand( it.first ); + vcl::ImageType eImageType = m_pImpl->GetImageSize(); + Image aImage = vcl::CommandInfoProvider::GetImageForCommand(aCommandURL, m_xFrame, eImageType); + // Try also to query for add-on images before giving up and use an + // empty image. + bool bBigImages = eImageType != vcl::ImageType::Size16; + if ( !aImage ) + aImage = Image(framework::AddonsOptions().GetImageFromURL(aCommandURL, bBigImages)); + m_pImpl->SetItemImage( it.first, aCommandURL, aImage ); + } + } + + m_pImpl->UpdateSize(); +} + +void ToolBarManager::UpdateControllers() +{ + + if( officecfg::Office::Common::Misc::DisableUICustomization::get() ) + { + Any a; + Reference< XLayoutManager > xLayoutManager; + Reference< XPropertySet > xFramePropSet( m_xFrame, UNO_QUERY ); + if ( xFramePropSet.is() ) + a = xFramePropSet->getPropertyValue("LayoutManager"); + a >>= xLayoutManager; + Reference< XDockableWindow > xDockable( m_pImpl->GetInterface(), UNO_QUERY ); + if ( xLayoutManager.is() && xDockable.is() ) + { + css::awt::Point aPoint; + aPoint.X = aPoint.Y = SAL_MAX_INT32; + xLayoutManager->dockWindow( m_aResourceName, DockingArea_DOCKINGAREA_DEFAULT, aPoint ); + xLayoutManager->lockWindow( m_aResourceName ); + } + } + + if ( !m_bUpdateControllers ) + { + m_bUpdateControllers = true; + for (auto const& controller : m_aControllerMap) + { + try + { + Reference< XUpdatable > xUpdatable( controller.second, UNO_QUERY ); + if ( xUpdatable.is() ) + xUpdatable->update(); + } + catch (const Exception&) + { + } + } + } + m_bUpdateControllers = false; +} + +//for update toolbar controller via Support Visible +void ToolBarManager::UpdateController( const css::uno::Reference< css::frame::XToolbarController >& xController) +{ + + if ( !m_bUpdateControllers ) + { + m_bUpdateControllers = true; + try + { if(xController.is()) + { + Reference< XUpdatable > xUpdatable( xController, UNO_QUERY ); + if ( xUpdatable.is() ) + xUpdatable->update(); + } + } + catch (const Exception&) + { + } + + } + m_bUpdateControllers = false; +} + +void ToolBarManager::frameAction( const FrameActionEvent& Action ) +{ + SolarMutexGuard g; + if ( Action.Action == FrameAction_CONTEXT_CHANGED && !m_bDisposed ) + { + m_aAsyncUpdateControllersTimer.Start(); + } +} + +void SAL_CALL ToolBarManager::disposing( const EventObject& Source ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + RemoveControllers(); + + if ( m_xDocImageManager.is() ) + { + try + { + m_xDocImageManager->removeConfigurationListener( + Reference< XUIConfigurationListener >(this) ); + } + catch (const Exception&) + { + } + } + + if ( m_xModuleImageManager.is() ) + { + try + { + m_xModuleImageManager->removeConfigurationListener( + Reference< XUIConfigurationListener >(this) ); + } + catch (const Exception&) + { + } + } + + m_xDocImageManager.clear(); + m_xModuleImageManager.clear(); + + if ( Source.Source == Reference< XInterface >( m_xFrame, UNO_QUERY )) + m_xFrame.clear(); + + m_xContext.clear(); +} + +// XComponent +void SAL_CALL ToolBarManager::dispose() +{ + Reference< XComponent > xThis(this); + + { + EventObject aEvent( xThis ); + std::unique_lock aGuard(m_mutex); + m_aListenerContainer.disposeAndClear( aGuard, aEvent ); + } + { + SolarMutexGuard g; + + if (m_bDisposed) + { + return; + } + + RemoveControllers(); + + if ( m_xDocImageManager.is() ) + { + try + { + m_xDocImageManager->removeConfigurationListener( + Reference< XUIConfigurationListener >(this) ); + } + catch (const Exception&) + { + } + } + m_xDocImageManager.clear(); + if ( m_xModuleImageManager.is() ) + { + try + { + m_xModuleImageManager->removeConfigurationListener( + Reference< XUIConfigurationListener >(this) ); + } + catch (const Exception&) + { + } + } + m_xModuleImageManager.clear(); + + if ( m_aOverflowManager.is() ) + { + m_aOverflowManager->dispose(); + m_aOverflowManager.clear(); + } + + // We have to destroy our toolbar instance now. + Destroy(); + m_pToolBar.clear(); + + if ( m_bFrameActionRegistered && m_xFrame.is() ) + { + try + { + m_xFrame->removeFrameActionListener( Reference< XFrameActionListener >(this) ); + } + catch (const Exception&) + { + } + } + + m_xFrame.clear(); + m_xContext.clear(); + + // stop timer to prevent timer events after dispose + // do it last because other calls could restart timer in StateChanged() + m_aAsyncUpdateControllersTimer.Stop(); + + m_bDisposed = true; + } +} + +void SAL_CALL ToolBarManager::addEventListener( const Reference< XEventListener >& xListener ) +{ + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + throw DisposedException(); + + std::unique_lock aGuard(m_mutex); + m_aListenerContainer.addInterface( aGuard, xListener ); +} + +void SAL_CALL ToolBarManager::removeEventListener( const Reference< XEventListener >& xListener ) +{ + std::unique_lock aGuard(m_mutex); + m_aListenerContainer.removeInterface( aGuard, xListener ); +} + +// XUIConfigurationListener +void SAL_CALL ToolBarManager::elementInserted( const css::ui::ConfigurationEvent& Event ) +{ + impl_elementChanged(false,Event); +} + +void SAL_CALL ToolBarManager::elementRemoved( const css::ui::ConfigurationEvent& Event ) +{ + impl_elementChanged(true,Event); +} +void ToolBarManager::impl_elementChanged(bool const isRemove, + const css::ui::ConfigurationEvent& Event) +{ + SolarMutexGuard g; + + /* SAFE AREA ----------------------------------------------------------------------------------------------- */ + if ( m_bDisposed ) + return; + + Reference< XNameAccess > xNameAccess; + sal_Int16 nImageType = sal_Int16(); + sal_Int16 nCurrentImageType = getCurrentImageType(); + + if (!(( Event.aInfo >>= nImageType ) && + ( nImageType == nCurrentImageType ) && + ( Event.Element >>= xNameAccess ))) + return; + + sal_Int16 nImageInfo( 1 ); + Reference< XInterface > xIfacDocImgMgr( m_xDocImageManager, UNO_QUERY ); + if ( xIfacDocImgMgr == Event.Source ) + nImageInfo = 0; + + const Sequence< OUString > aSeq = xNameAccess->getElementNames(); + for ( OUString const & commandName : aSeq ) + { + CommandToInfoMap::iterator pIter = m_aCommandMap.find( commandName ); + if ( pIter != m_aCommandMap.end() && ( pIter->second.nImageInfo >= nImageInfo )) + { + if (isRemove) + { + Image aImage; + if (( pIter->second.nImageInfo == 0 ) && ( pIter->second.nImageInfo == nImageInfo )) + { + // Special case: An image from the document image manager has been removed. + // It is possible that we have an image at our module image manager. Before + // we can remove our image we have to ask our module image manager. + Sequence< OUString > aCmdURLSeq{ pIter->first }; + Sequence< Reference< XGraphic > > aGraphicSeq; + aGraphicSeq = m_xModuleImageManager->getImages( nImageType, aCmdURLSeq ); + aImage = Image( aGraphicSeq[0] ); + } + + setToolBarImage(aImage,pIter); + } // if (isRemove) + else + { + Reference< XGraphic > xGraphic; + if ( xNameAccess->getByName( commandName ) >>= xGraphic ) + { + Image aImage( xGraphic ); + setToolBarImage(aImage,pIter); + } + pIter->second.nImageInfo = nImageInfo; + } + } + } +} +void ToolBarManager::setToolBarImage(const Image& rImage, + const CommandToInfoMap::const_iterator& rIter) +{ + const ::std::vector<ToolBoxItemId>& rIDs = rIter->second.aIds; + m_pImpl->SetItemImage( rIter->second.nId, rIter->first, rImage ); + for (auto const& it : rIDs) + { + m_pImpl->SetItemImage(it, rIter->first, rImage); + } +} + +void SAL_CALL ToolBarManager::elementReplaced( const css::ui::ConfigurationEvent& Event ) +{ + impl_elementChanged(false,Event); +} + +void ToolBarManager::RemoveControllers() +{ + DBG_TESTSOLARMUTEX(); + assert(!m_bDisposed); + + m_aSubToolBarControllerMap.clear(); + + // i90033 + // Remove item window pointers from the toolbar. They were + // destroyed by the dispose() at the XComponent. This is needed + // as VCL code later tries to access the item window data in certain + // dtors where the item window is already invalid! + for ( ToolBox::ImplToolItems::size_type i = 0; i < m_pImpl->GetItemCount(); i++ ) + { + ToolBoxItemId nItemId = m_pImpl->GetItemId( i ); + if ( nItemId > ToolBoxItemId(0) ) + { + Reference< XComponent > xComponent( m_aControllerMap[ nItemId ], UNO_QUERY ); + if ( xComponent.is() ) + { + try + { + xComponent->dispose(); + } + catch (const Exception&) + { + } + } + m_pImpl->SetItemWindow(nItemId, nullptr); + } + } + m_aControllerMap.clear(); +} + +void ToolBarManager::CreateControllers() +{ + Reference< XWindow > xToolbarWindow = m_pImpl->GetInterface(); + + css::util::URL aURL; + bool bHasDisabledEntries = SvtCommandOptions().HasEntries( SvtCommandOptions::CMDOPTION_DISABLED ); + SvtCommandOptions aCmdOptions; + + for ( ToolBox::ImplToolItems::size_type i = 0; i < m_pImpl->GetItemCount(); i++ ) + { + ToolBoxItemId nId = m_pImpl->GetItemId( i ); + if ( nId == ToolBoxItemId(0) ) + continue; + + bool bInit( true ); + bool bCreate( true ); + Reference< XStatusListener > xController; + + rtl::Reference<svt::ToolboxController> pController; + + OUString aCommandURL( m_pImpl->GetItemCommand( nId ) ); + // Command can be just an alias to another command. + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, m_aModuleIdentifier); + OUString aRealCommandURL( vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties) ); + if ( !aRealCommandURL.isEmpty() ) + aCommandURL = aRealCommandURL; + + if ( bHasDisabledEntries ) + { + aURL.Complete = aCommandURL; + m_xURLTransformer->parseStrict( aURL ); + if ( aCmdOptions.Lookup( SvtCommandOptions::CMDOPTION_DISABLED, aURL.Path )) + { + m_aControllerMap[ nId ] = xController; + m_pImpl->HideItem( nId, aCommandURL ); + continue; + } + } + + if ( m_xToolbarControllerFactory.is() && + m_xToolbarControllerFactory->hasController( aCommandURL, m_aModuleIdentifier )) + { + PropertyValue aPropValue; + std::vector< Any > aPropertyVector; + + aPropValue.Name = "ModuleIdentifier"; + aPropValue.Value <<= m_aModuleIdentifier; + aPropertyVector.push_back( Any( aPropValue )); + aPropValue.Name = "Frame"; + aPropValue.Value <<= m_xFrame; + aPropertyVector.push_back( Any( aPropValue )); + aPropValue.Name = "ServiceManager"; + Reference<XMultiServiceFactory> xMSF(m_xContext->getServiceManager(), UNO_QUERY_THROW); + aPropValue.Value <<= xMSF; + aPropertyVector.push_back( Any( aPropValue )); + aPropValue.Name = "ParentWindow"; + aPropValue.Value <<= xToolbarWindow; + aPropertyVector.push_back( Any( aPropValue )); + aPropValue.Name = "Identifier"; + aPropValue.Value <<= sal_uInt16(nId); + aPropertyVector.push_back( uno::Any( aPropValue ) ); + + Sequence< Any > aArgs( comphelper::containerToSequence( aPropertyVector )); + xController.set( m_xToolbarControllerFactory->createInstanceWithArgumentsAndContext( aCommandURL, aArgs, m_xContext ), + UNO_QUERY ); + bInit = false; // Initialization is done through the factory service + } + + if (( aCommandURL == ".uno:OpenUrl" ) && ( !m_pImpl->IsItemVisible(nId, aCommandURL))) + bCreate = false; + + if ( !xController.is() && bCreate ) + { + xController = m_pImpl->CreateToolBoxController( m_xFrame, nId, aCommandURL ); + if ( !xController ) + { + if ( aCommandURL.startsWith( ".uno:StyleApply?" ) ) + { + xController.set( new StyleToolbarController( m_xContext, m_xFrame, aCommandURL )); + m_pImpl->SetItemCheckable( nId ); + } + else if ( aCommandURL.startsWith( "private:resource/" ) ) + { + xController.set( m_xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.comp.framework.GenericPopupToolbarController", m_xContext ), UNO_QUERY ); + } + else if ( m_pToolBar && m_pToolBar->GetItemData( nId ) != nullptr ) + { + // retrieve additional parameters + OUString aControlType = static_cast< AddonsParams* >( m_pToolBar->GetItemData( nId ))->aControlType; + sal_uInt16 nWidth = static_cast< AddonsParams* >( m_pToolBar->GetItemData( nId ))->nWidth; + + Reference< XStatusListener > xStatusListener( + ToolBarMerger::CreateController( m_xContext, + m_xFrame, + m_pToolBar, + aCommandURL, + nId, + nWidth, + aControlType ).get(), UNO_QUERY ); + + xController = xStatusListener; + } + else if (m_pToolBar) + { + xController.set( + new GenericToolbarController( m_xContext, m_xFrame, m_pToolBar, nId, aCommandURL )); + + // Accessibility support: Set toggle button role for specific commands + sal_Int32 nProps = vcl::CommandInfoProvider::GetPropertiesForCommand(aCommandURL, m_aModuleIdentifier); + if ( nProps & UICOMMANDDESCRIPTION_PROPERTIES_TOGGLEBUTTON ) + m_pImpl->SetItemCheckable( nId ); + } + } + } + + // Associate ID and controller to be able to retrieve + // the controller from the ID later. + m_aControllerMap[ nId ] = xController; + + // Fill sub-toolbars into our hash-map + Reference< XSubToolbarController > xSubToolBar( xController, UNO_QUERY ); + if ( xSubToolBar.is() && xSubToolBar->opensSubToolbar() ) + { + OUString aSubToolBarName = xSubToolBar->getSubToolbarName(); + if ( !aSubToolBarName.isEmpty() ) + { + SubToolBarToSubToolBarControllerMap::iterator pIter = + m_aSubToolBarControllerMap.find( aSubToolBarName ); + if ( pIter == m_aSubToolBarControllerMap.end() ) + { + SubToolBarControllerVector aSubToolBarVector; + aSubToolBarVector.push_back( xSubToolBar ); + m_aSubToolBarControllerMap.emplace( + aSubToolBarName, aSubToolBarVector ); + } + else + pIter->second.push_back( xSubToolBar ); + } + } + + Reference< XInitialization > xInit( xController, UNO_QUERY ); + if ( xInit.is() ) + { + if ( bInit ) + { + Reference<XMultiServiceFactory> xMSF(m_xContext->getServiceManager(), UNO_QUERY_THROW); + Sequence< Any > aArgs { + Any( comphelper::makePropertyValue("Frame", m_xFrame) ), + Any( comphelper::makePropertyValue("CommandURL", aCommandURL) ), + Any( comphelper::makePropertyValue("ServiceManager", xMSF) ), + Any( comphelper::makePropertyValue("ParentWindow", xToolbarWindow) ), + Any( comphelper::makePropertyValue("ModuleIdentifier", m_aModuleIdentifier) ), + Any( comphelper::makePropertyValue("Identifier", sal_uInt16(nId)) ), + }; + + xInit->initialize( aArgs ); + + if (pController) + { + if (aCommandURL == ".uno:SwitchXFormsDesignMode" || aCommandURL == ".uno:ViewDataSourceBrowser") + pController->setFastPropertyValue_NoBroadcast(1, Any(true)); + } + } + + // Request an item window from the toolbar controller and set it at the VCL toolbar + Reference< XToolbarController > xTbxController( xController, UNO_QUERY ); + if ( xTbxController.is() && xToolbarWindow.is() ) + { + Reference< XWindow > xWindow = xTbxController->createItemWindow( xToolbarWindow ); + if ( xWindow.is() ) + { + VclPtr<vcl::Window> pItemWin = VCLUnoHelper::GetWindow( xWindow ); + if ( pItemWin ) + { + WindowType nType = pItemWin->GetType(); + if ( m_pToolBar && (nType == WindowType::LISTBOX || nType == WindowType::MULTILISTBOX || nType == WindowType::COMBOBOX) ) + pItemWin->SetAccessibleName( m_pToolBar->GetItemText( nId ) ); + m_pImpl->SetItemWindow( nId, pItemWin ); + } + } + } + } + + //for update Controller via support visible state + Reference< XPropertySet > xPropSet( xController, UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + bool bSupportVisible = true; + Any a( xPropSet->getPropertyValue("SupportsVisible") ); + a >>= bSupportVisible; + if (bSupportVisible) + { + Reference< XToolbarController > xTbxController( xController, UNO_QUERY ); + UpdateController(xTbxController); + } + } + catch (const RuntimeException&) + { + throw; + } + catch (const Exception&) + { + } + } + } + + AddFrameActionListener(); +} + +void ToolBarManager::AddFrameActionListener() +{ + if ( !m_bFrameActionRegistered && m_xFrame.is() ) + { + m_bFrameActionRegistered = true; + m_xFrame->addFrameActionListener( Reference< XFrameActionListener >(this) ); + } +} + +ToolBoxItemBits ToolBarManager::ConvertStyleToToolboxItemBits( sal_Int32 nStyle ) +{ + ToolBoxItemBits nItemBits( ToolBoxItemBits::NONE ); + if ( nStyle & css::ui::ItemStyle::RADIO_CHECK ) + nItemBits |= ToolBoxItemBits::RADIOCHECK; + if ( nStyle & css::ui::ItemStyle::ALIGN_LEFT ) + nItemBits |= ToolBoxItemBits::LEFT; + if ( nStyle & css::ui::ItemStyle::AUTO_SIZE ) + nItemBits |= ToolBoxItemBits::AUTOSIZE; + if ( nStyle & css::ui::ItemStyle::DROP_DOWN ) + nItemBits |= ToolBoxItemBits::DROPDOWN; + if ( nStyle & css::ui::ItemStyle::REPEAT ) + nItemBits |= ToolBoxItemBits::REPEAT; + if ( nStyle & css::ui::ItemStyle::DROPDOWN_ONLY ) + nItemBits |= ToolBoxItemBits::DROPDOWNONLY; + if ( nStyle & css::ui::ItemStyle::TEXT ) + nItemBits |= ToolBoxItemBits::TEXT_ONLY; + if ( nStyle & css::ui::ItemStyle::ICON ) + nItemBits |= ToolBoxItemBits::ICON_ONLY; + + return nItemBits; +} + +void ToolBarManager::InitImageManager() +{ + Reference< XModuleManager2 > xModuleManager = ModuleManager::create( m_xContext ); + if ( !m_xDocImageManager.is() ) + { + Reference< XModel > xModel( GetModelFromFrame() ); + if ( xModel.is() ) + { + Reference< XUIConfigurationManagerSupplier > xSupplier( xModel, UNO_QUERY ); + if ( xSupplier.is() ) + { + Reference< XUIConfigurationManager > xDocUICfgMgr = xSupplier->getUIConfigurationManager(); + m_xDocImageManager.set( xDocUICfgMgr->getImageManager(), UNO_QUERY ); + m_xDocImageManager->addConfigurationListener( + Reference< XUIConfigurationListener >(this) ); + } + } + } + + try + { + m_aModuleIdentifier = xModuleManager->identify( Reference< XInterface >( m_xFrame, UNO_QUERY ) ); + } + catch (const Exception&) + { + } + + if ( !m_xModuleImageManager.is() ) + { + Reference< XModuleUIConfigurationManagerSupplier > xModuleCfgMgrSupplier = + theModuleUIConfigurationManagerSupplier::get( m_xContext ); + Reference< XUIConfigurationManager > xUICfgMgr = xModuleCfgMgrSupplier->getUIConfigurationManager( m_aModuleIdentifier ); + m_xModuleImageManager.set( xUICfgMgr->getImageManager(), UNO_QUERY ); + m_xModuleImageManager->addConfigurationListener( Reference< XUIConfigurationListener >(this) ); + } +} + +void ToolBarManager::FillToolbar( const Reference< XIndexAccess >& rItemContainer ) +{ + OString aTbxName = OUStringToOString( m_aResourceName, RTL_TEXTENCODING_ASCII_US ); + SAL_INFO( "fwk.uielement", "framework (cd100003) ::ToolBarManager::FillToolbar " << aTbxName ); + + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + InitImageManager(); + + RemoveControllers(); + + // reset and fill command map + m_pImpl->Clear(); + m_aControllerMap.clear(); + m_aCommandMap.clear(); + + ToolBoxItemId nId( 1 ); + CommandInfo aCmdInfo; + for ( sal_Int32 n = 0; n < rItemContainer->getCount(); n++ ) + { + Sequence< PropertyValue > aProps; + OUString aCommandURL; + OUString aLabel; + OUString aTooltip; + sal_uInt16 nType( css::ui::ItemType::DEFAULT ); + sal_uInt32 nStyle( 0 ); + + try + { + if ( rItemContainer->getByIndex( n ) >>= aProps ) + { + bool bIsVisible( true ); + for ( PropertyValue const & prop : std::as_const(aProps) ) + { + if ( prop.Name == ITEM_DESCRIPTOR_COMMANDURL ) + prop.Value >>= aCommandURL; + else if ( prop.Name == "Label" ) + prop.Value >>= aLabel; + else if ( prop.Name == "Tooltip" ) + prop.Value >>= aTooltip; + else if ( prop.Name == "Type" ) + prop.Value >>= nType; + else if ( prop.Name == ITEM_DESCRIPTOR_VISIBLE ) + prop.Value >>= bIsVisible; + else if ( prop.Name == "Style" ) + prop.Value >>= nStyle; + } + + if (!aCommandURL.isEmpty() && vcl::CommandInfoProvider::IsExperimental(aCommandURL, m_aModuleIdentifier) && + !officecfg::Office::Common::Misc::ExperimentalMode::get()) + { + continue; + } + + if (( nType == css::ui::ItemType::DEFAULT ) && !aCommandURL.isEmpty() ) + { + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, m_aModuleIdentifier); + if (!aProperties.hasElements()) // E.g., user-provided macro command? + aProperties = aProps; // Use existing info, including user-provided Label + OUString aString(vcl::CommandInfoProvider::GetLabelForCommand(aProperties)); + + ToolBoxItemBits nItemBits = ConvertStyleToToolboxItemBits( nStyle ); + + if ( aTooltip.isEmpty() ) + aTooltip = vcl::CommandInfoProvider::GetTooltipForCommand(aCommandURL, aProperties, m_xFrame); + + if ( aLabel.isEmpty() ) + aLabel = aString; + + m_pImpl->InsertItem(nId, aString, aCommandURL, aTooltip, aLabel, nItemBits); + + // Fill command map. It stores all our commands and from what + // image manager we got our image. So we can decide if we have to use an + // image from a notification message. + auto pIter = m_aCommandMap.emplace( aCommandURL, aCmdInfo ); + if ( pIter.second ) + { + aCmdInfo.nId = nId; + pIter.first->second.nId = nId; + } + else + { + pIter.first->second.aIds.push_back( nId ); + } + + if ( !bIsVisible ) + m_pImpl->HideItem( nId, aCommandURL ); + + ++nId; + } + else if ( nType == css::ui::ItemType::SEPARATOR_LINE ) + { + m_pImpl->InsertSeparator(); + } + else if ( nType == css::ui::ItemType::SEPARATOR_SPACE ) + { + m_pImpl->InsertSpace(); + } + else if ( nType == css::ui::ItemType::SEPARATOR_LINEBREAK ) + { + m_pImpl->InsertBreak(); + } + } + } + catch (const css::lang::IndexOutOfBoundsException&) + { + break; + } + } + + // Support add-on toolbar merging here. Working directly on the toolbar object is much + // simpler and faster. + constexpr sal_uInt16 TOOLBAR_ITEM_STARTID = 1000; + + MergeToolbarInstructionContainer aMergeInstructionContainer; + + // Retrieve the toolbar name from the resource name + OUString aToolbarName( m_aResourceName ); + sal_Int32 nIndex = aToolbarName.lastIndexOf( '/' ); + if (( nIndex > 0 ) && ( nIndex < aToolbarName.getLength() )) + aToolbarName = aToolbarName.copy( nIndex+1 ); + + AddonsOptions().GetMergeToolbarInstructions( aToolbarName, aMergeInstructionContainer ); + + if ( !aMergeInstructionContainer.empty() ) + { + ToolBoxItemId nItemId( TOOLBAR_ITEM_STARTID ); + const sal_uInt32 nCount = aMergeInstructionContainer.size(); + for ( sal_uInt32 i=0; i < nCount; i++ ) + { + MergeToolbarInstruction& rInstruction = aMergeInstructionContainer[i]; + if ( ToolBarMerger::IsCorrectContext( rInstruction.aMergeContext, m_aModuleIdentifier )) + { + m_pImpl->MergeToolbar(nItemId, m_aModuleIdentifier, m_aCommandMap, rInstruction); + } + } + } + + // Request images for all toolbar items. Must be done before CreateControllers as + // some controllers need access to the image. + RequestImages(); + + // Create controllers after we set the images. There are controllers which needs + // an image at the toolbar at creation time! + CreateControllers(); + + // Notify controllers that they are now correctly initialized and can start listening + // toolbars that will open in popup mode will be updated immediately to avoid flickering + if( m_pImpl->WillUsePopupMode() ) + UpdateControllers(); + else if ( m_pImpl->IsReallyVisible() ) + { + m_aAsyncUpdateControllersTimer.Start(); + } + + // Try to retrieve UIName from the container property set and set it as the title + // if it is not empty. + Reference< XPropertySet > xPropSet( rItemContainer, UNO_QUERY ); + if ( !xPropSet.is() ) + return; + + try + { + OUString aUIName; + xPropSet->getPropertyValue("UIName") >>= aUIName; + if ( !aUIName.isEmpty() ) + m_pImpl->SetName( aUIName ); + } + catch (const Exception&) + { + } +} + +void ToolBarManager::FillAddonToolbar( const Sequence< Sequence< PropertyValue > >& rAddonToolbar ) +{ + if (!m_pToolBar) + return; + + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + InitImageManager(); + + RemoveControllers(); + + // reset and fill command map + m_pToolBar->Clear(); + m_aControllerMap.clear(); + m_aCommandMap.clear(); + + ToolBoxItemId nId( 1 ); + CommandInfo aCmdInfo; + for ( const Sequence< PropertyValue >& rSeq : rAddonToolbar ) + { + OUString aURL; + OUString aTitle; + OUString aContext; + OUString aTarget; + OUString aControlType; + sal_uInt16 nWidth( 0 ); + + ToolBarMerger::ConvertSequenceToValues( rSeq, aURL, aTitle, aTarget, aContext, aControlType, nWidth ); + + if ( ToolBarMerger::IsCorrectContext( aContext, m_aModuleIdentifier ) ) + { + if ( aURL == "private:separator" ) + { + ToolBox::ImplToolItems::size_type nCount = m_pToolBar->GetItemCount(); + if ( nCount > 0 && m_pToolBar->GetItemType( nCount-1 ) != ToolBoxItemType::SEPARATOR ) + m_pToolBar->InsertSeparator(); + } + else + { + m_pToolBar->InsertItem( nId, aTitle ); + + OUString aShortcut(vcl::CommandInfoProvider::GetCommandShortcut(aURL, m_xFrame)); + if (!aShortcut.isEmpty()) + m_pToolBar->SetQuickHelpText(nId, aTitle + " (" + aShortcut + ")"); + + // Create AddonsParams to hold additional information we will need in the future + AddonsParams* pRuntimeItemData = new AddonsParams; + pRuntimeItemData->aControlType = aControlType; + pRuntimeItemData->nWidth = nWidth; + m_pToolBar->SetItemData( nId, pRuntimeItemData ); + m_pToolBar->SetItemCommand( nId, aURL ); + + // Fill command map. It stores all our commands and from what + // image manager we got our image. So we can decide if we have to use an + // image from a notification message. + auto pIter = m_aCommandMap.emplace( aURL, aCmdInfo ); + if ( pIter.second ) + { + aCmdInfo.nId = nId; + pIter.first->second.nId = nId; + } + else + { + pIter.first->second.aIds.push_back( nId ); + } + ++nId; + } + } + } + + // Don't setup images yet, AddonsToolbarWrapper::populateImages does that. + // (But some controllers might need an image at the toolbar at creation time!) + CreateControllers(); + + // Notify controllers that they are now correctly initialized and can start listening. + UpdateControllers(); +} + +void ToolBarManager::FillOverflowToolbar( ToolBox const * pParent ) +{ + if (!m_pToolBar) + return; + + CommandInfo aCmdInfo; + bool bInsertSeparator = false; + for ( ToolBox::ImplToolItems::size_type i = 0; i < pParent->GetItemCount(); ++i ) + { + ToolBoxItemId nId = pParent->GetItemId( i ); + if ( pParent->IsItemClipped( nId ) ) + { + if ( bInsertSeparator ) + { + m_pToolBar->InsertSeparator(); + bInsertSeparator = false; + } + + const OUString aCommandURL( pParent->GetItemCommand( nId ) ); + m_pToolBar->InsertItem( nId, pParent->GetItemText( nId ) ); + m_pToolBar->SetItemCommand( nId, aCommandURL ); + m_pToolBar->SetQuickHelpText( nId, pParent->GetQuickHelpText( nId ) ); + + // Handle possible add-on controls. + AddonsParams* pAddonParams = static_cast< AddonsParams* >( pParent->GetItemData( nId ) ); + if ( pAddonParams ) + m_pToolBar->SetItemData( nId, new AddonsParams( *pAddonParams ) ); + + // Fill command map. It stores all our commands and from what + // image manager we got our image. So we can decide if we have to use an + // image from a notification message. + auto pIter = m_aCommandMap.emplace( aCommandURL, aCmdInfo ); + if ( pIter.second ) + { + aCmdInfo.nId = nId; + pIter.first->second.nId = nId; + } + else + { + pIter.first->second.aIds.push_back( nId ); + } + } + else + { + ToolBoxItemType eType = pParent->GetItemType( i ); + if ( m_pToolBar->GetItemCount() && + ( eType == ToolBoxItemType::SEPARATOR || eType == ToolBoxItemType::BREAK ) ) + bInsertSeparator = true; + } + } + + InitImageManager(); + + // Request images for all toolbar items. Must be done before CreateControllers as + // some controllers need access to the image. + RequestImages(); + + // Create controllers after we set the images. There are controllers which needs + // an image at the toolbar at creation time! + CreateControllers(); + + // Notify controllers that they are now correctly initialized and can start listening + // toolbars that will open in popup mode will be updated immediately to avoid flickering + UpdateControllers(); +} + +void ToolBarManager::RequestImages() +{ + + // Request images from image manager + Sequence< OUString > aCmdURLSeq( comphelper::mapKeysToSequence(m_aCommandMap) ); + Sequence< Reference< XGraphic > > aDocGraphicSeq; + Sequence< Reference< XGraphic > > aModGraphicSeq; + + SvtMiscOptions aMiscOptions; + + sal_Int16 nImageType = getCurrentImageType(); + + if ( m_xDocImageManager.is() ) + aDocGraphicSeq = m_xDocImageManager->getImages(nImageType, aCmdURLSeq); + aModGraphicSeq = m_xModuleImageManager->getImages(nImageType, aCmdURLSeq); + + sal_uInt32 i = 0; + CommandToInfoMap::iterator pIter = m_aCommandMap.begin(); + CommandToInfoMap::iterator pEnd = m_aCommandMap.end(); + while ( pIter != pEnd ) + { + Image aImage; + if ( aDocGraphicSeq.hasElements() ) + aImage = Image( aDocGraphicSeq[i] ); + if ( !aImage ) + { + aImage = Image( aModGraphicSeq[i] ); + // Try also to query for add-on images before giving up and use an + // empty image. + if ( !aImage ) + aImage = Image(framework::AddonsOptions().GetImageFromURL(aCmdURLSeq[i], aMiscOptions.AreCurrentSymbolsLarge())); + + pIter->second.nImageInfo = 1; // mark image as module based + } + else + { + pIter->second.nImageInfo = 0; // mark image as document based + } + setToolBarImage(aImage,pIter); + ++pIter; + ++i; + } +} + +void ToolBarManager::notifyRegisteredControllers( const OUString& aUIElementName, const OUString& aCommand ) +{ + SolarMutexClearableGuard aGuard; + if ( m_aSubToolBarControllerMap.empty() ) + return; + + SubToolBarToSubToolBarControllerMap::const_iterator pIter = + m_aSubToolBarControllerMap.find( aUIElementName ); + + if ( pIter == m_aSubToolBarControllerMap.end() ) + return; + + const SubToolBarControllerVector& rSubToolBarVector = pIter->second; + if ( rSubToolBarVector.empty() ) + return; + + SubToolBarControllerVector aNotifyVector = rSubToolBarVector; + aGuard.clear(); + + const sal_uInt32 nCount = aNotifyVector.size(); + for ( sal_uInt32 i=0; i < nCount; i++ ) + { + try + { + Reference< XSubToolbarController > xController = aNotifyVector[i]; + if ( xController.is() ) + xController->functionSelected( aCommand ); + } + catch (const RuntimeException&) + { + throw; + } + catch (const Exception&) + { + } + } +} + +void ToolBarManager::HandleClick(ClickAction eClickAction) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + ToolBoxItemId nId( m_pImpl->GetCurItemId() ); + ToolBarControllerMap::const_iterator pIter = m_aControllerMap.find( nId ); + if ( pIter == m_aControllerMap.end() ) + return; + + Reference< XToolbarController > xController( pIter->second, UNO_QUERY ); + + if ( xController.is() ) + { + switch (eClickAction) + { + case ClickAction::Click: + xController->click(); + break; + + case ClickAction::DblClick: + xController->doubleClick(); + break; + + case ClickAction::Execute: + xController->execute(0); + break; + } + } +} + +void ToolBarManager::OnClick(bool bUseExecute) +{ + if (bUseExecute) + HandleClick(ClickAction::Execute); + else + HandleClick(ClickAction::Click); +} + +IMPL_LINK_NOARG(ToolBarManager, DropdownClick, ToolBox *, void) +{ + OnDropdownClick(true); +} + +void ToolBarManager::OnDropdownClick(bool bCreatePopupWindow) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + ToolBoxItemId nId( m_pImpl->GetCurItemId() ); + ToolBarControllerMap::const_iterator pIter = m_aControllerMap.find( nId ); + if ( pIter == m_aControllerMap.end() ) + return; + + Reference< XToolbarController > xController( pIter->second, UNO_QUERY ); + + if ( xController.is() ) + { + if (bCreatePopupWindow) + { + Reference< XWindow > xWin = xController->createPopupWindow(); + if ( xWin.is() ) + xWin->setFocus(); + } + else + { + xController->click(); + } + } +} + +IMPL_LINK_NOARG(ToolBarManager, DoubleClick, ToolBox *, void) +{ + HandleClick(ClickAction::DblClick); +} + +Reference< XModel > ToolBarManager::GetModelFromFrame() const +{ + Reference< XController > xController = m_xFrame->getController(); + Reference< XModel > xModel; + if ( xController.is() ) + xModel = xController->getModel(); + + return xModel; +} + +bool ToolBarManager::IsPluginMode() const +{ + bool bPluginMode( false ); + + if ( m_xFrame.is() ) + { + Reference< XModel > xModel = GetModelFromFrame(); + if ( xModel.is() ) + { + Sequence< PropertyValue > aSeq = xModel->getArgs(); + utl::MediaDescriptor aMediaDescriptor( aSeq ); + bPluginMode = aMediaDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_VIEWONLY, false ); + } + } + + return bPluginMode; +} + +void ToolBarManager::AddCustomizeMenuItems(ToolBox const * pToolBar) +{ + if (!m_pToolBar) + return; + + // No config menu entries if command ".uno:ConfigureDialog" is not enabled + Reference< XDispatch > xDisp; + css::util::URL aURL; + if ( m_xFrame.is() ) + { + Reference< XDispatchProvider > xProv( m_xFrame, UNO_QUERY ); + aURL.Complete = ".uno:ConfigureDialog"; + m_xURLTransformer->parseStrict( aURL ); + if ( xProv.is() ) + xDisp = xProv->queryDispatch( aURL, OUString(), 0 ); + + if ( !xDisp.is() || IsPluginMode() ) + return; + } + + // popup menu for quick customization + bool bHideDisabledEntries = !officecfg::Office::Common::View::Menu::DontHideDisabledEntry::get(); + + ::PopupMenu *pMenu = pToolBar->GetMenu(); + + // copy all menu items 'Visible buttons, Customize toolbar, Dock toolbar, + // Dock all Toolbars) from the loaded resource into the toolbar menu + sal_uInt16 nGroupLen = pMenu->GetItemCount(); + if (nGroupLen) + pMenu->InsertSeparator(); + + VclPtr<PopupMenu> xVisibleItemsPopupMenu; + + if (!m_aResourceName.startsWith("private:resource/toolbar/addon_")) + { + pMenu->InsertItem(MENUITEM_TOOLBAR_VISIBLEBUTTON, FwkResId(STR_TOOLBAR_VISIBLE_BUTTONS)); + xVisibleItemsPopupMenu = VclPtr<PopupMenu>::Create(); + pMenu->SetPopupMenu(MENUITEM_TOOLBAR_VISIBLEBUTTON, xVisibleItemsPopupMenu); + + if (m_pToolBar->IsCustomize()) + { + pMenu->InsertItem(MENUITEM_TOOLBAR_CUSTOMIZETOOLBAR, FwkResId(STR_TOOLBAR_CUSTOMIZE_TOOLBAR)); + pMenu->SetItemCommand(MENUITEM_TOOLBAR_CUSTOMIZETOOLBAR, ".uno:ConfigureToolboxVisible"); + } + pMenu->InsertSeparator(); + } + + if (pToolBar->IsFloatingMode()) + { + pMenu->InsertItem(MENUITEM_TOOLBAR_DOCKTOOLBAR, FwkResId(STR_TOOLBAR_DOCK_TOOLBAR)); + pMenu->SetAccelKey(MENUITEM_TOOLBAR_DOCKTOOLBAR, vcl::KeyCode(KEY_F10, true, true, false, false)); + } + else + { + pMenu->InsertItem(MENUITEM_TOOLBAR_UNDOCKTOOLBAR, FwkResId(STR_TOOLBAR_UNDOCK_TOOLBAR)); + pMenu->SetAccelKey(MENUITEM_TOOLBAR_UNDOCKTOOLBAR, vcl::KeyCode(KEY_F10, true, true, false, false)); + } + + pMenu->InsertItem(MENUITEM_TOOLBAR_DOCKALLTOOLBAR, FwkResId(STR_TOOLBAR_DOCK_ALL_TOOLBARS)); + pMenu->InsertSeparator(); + pMenu->InsertItem(MENUITEM_TOOLBAR_LOCKTOOLBARPOSITION, FwkResId(STR_TOOLBAR_LOCK_TOOLBAR), MenuItemBits::CHECKABLE); + pMenu->InsertItem(MENUITEM_TOOLBAR_CLOSE, FwkResId(STR_TOOLBAR_CLOSE_TOOLBAR)); + + if (m_pToolBar->IsCustomize()) + { + bool bIsFloating( false ); + + DockingManager* pDockMgr = vcl::Window::GetDockingManager(); + if ( pDockMgr ) + bIsFloating = pDockMgr->IsFloating( m_pToolBar ); + + if ( !bIsFloating ) + { + pMenu->EnableItem(MENUITEM_TOOLBAR_DOCKALLTOOLBAR, false); + Reference< XDockableWindow > xDockable( VCLUnoHelper::GetInterface( m_pToolBar ), UNO_QUERY ); + if( xDockable.is() ) + pMenu->CheckItem(MENUITEM_TOOLBAR_LOCKTOOLBARPOSITION, xDockable->isLocked()); + } + else + pMenu->EnableItem(MENUITEM_TOOLBAR_LOCKTOOLBARPOSITION, false); + + if (officecfg::Office::Common::Misc::DisableUICustomization::get()) + { + pMenu->EnableItem(MENUITEM_TOOLBAR_VISIBLEBUTTON, false); + pMenu->EnableItem(MENUITEM_TOOLBAR_CUSTOMIZETOOLBAR, false); + pMenu->EnableItem(MENUITEM_TOOLBAR_LOCKTOOLBARPOSITION, false); + } + + // Disable menu item CLOSE if the toolbar has no closer + if( !(pToolBar->GetFloatStyle() & WB_CLOSEABLE) ) + pMenu->EnableItem(MENUITEM_TOOLBAR_CLOSE, false); + + // Temporary stores a Command --> Url map to update contextual menu with the + // correct icons. The popup icons are by default the same as those in the + // toolbar. They are not correct for contextual popup menu. + std::map< OUString, Image > commandToImage; + + if (xVisibleItemsPopupMenu) + { + // Go through all toolbar items and add them to the context menu + for ( ToolBox::ImplToolItems::size_type nPos = 0; nPos < m_pToolBar->GetItemCount(); ++nPos ) + { + if ( m_pToolBar->GetItemType(nPos) == ToolBoxItemType::BUTTON ) + { + ToolBoxItemId nId = m_pToolBar->GetItemId(nPos); + OUString aCommandURL = m_pToolBar->GetItemCommand( nId ); + xVisibleItemsPopupMenu->InsertItem( STARTID_CUSTOMIZE_POPUPMENU+nPos, m_pToolBar->GetItemText( nId ), MenuItemBits::CHECKABLE ); + xVisibleItemsPopupMenu->CheckItem( STARTID_CUSTOMIZE_POPUPMENU+nPos, m_pToolBar->IsItemVisible( nId ) ); + xVisibleItemsPopupMenu->SetItemCommand( STARTID_CUSTOMIZE_POPUPMENU+nPos, aCommandURL ); + Image aImage(vcl::CommandInfoProvider::GetImageForCommand(aCommandURL, m_xFrame)); + commandToImage[aCommandURL] = aImage; + xVisibleItemsPopupMenu->SetItemImage( STARTID_CUSTOMIZE_POPUPMENU+nPos, aImage ); + vcl::KeyCode aKeyCodeShortCut = vcl::CommandInfoProvider::GetCommandKeyCodeShortcut( aCommandURL, m_xFrame ); + xVisibleItemsPopupMenu->SetAccelKey( STARTID_CUSTOMIZE_POPUPMENU+nPos, aKeyCodeShortCut ); + } + else + { + xVisibleItemsPopupMenu->InsertSeparator(); + } + } + } + + // Now we go through all the contextual menu to update the icons + // and accelerator key shortcuts + std::map< OUString, Image >::iterator it; + for ( sal_uInt16 nPos = 0; nPos < pMenu->GetItemCount(); ++nPos ) + { + sal_uInt16 nId = pMenu->GetItemId( nPos ); + OUString cmdUrl = pMenu->GetItemCommand( nId ); + it = commandToImage.find( cmdUrl ); + if (it != commandToImage.end()) { + pMenu->SetItemImage( nId, it->second ); + } + vcl::KeyCode aKeyCodeShortCut = vcl::CommandInfoProvider::GetCommandKeyCodeShortcut( cmdUrl, m_xFrame ); + if ( aKeyCodeShortCut.GetFullCode() != 0 ) + pMenu->SetAccelKey( nId, aKeyCodeShortCut ); + } + } + + // Set the title of the menu + pMenu->SetText( pToolBar->GetText() ); + + if ( bHideDisabledEntries ) + pMenu->RemoveDisabledEntries(); +} + +IMPL_LINK( ToolBarManager, MenuButton, ToolBox*, pToolBar, void ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + assert( !m_aOverflowManager.is() ); + + VclPtrInstance<ToolBox> pOverflowToolBar( pToolBar, WB_BORDER | WB_SCROLL ); + pOverflowToolBar->SetLineSpacing(true); + m_aOverflowManager.set( new ToolBarManager( m_xContext, m_xFrame, OUString(), pOverflowToolBar ) ); + m_aOverflowManager->FillOverflowToolbar( pToolBar ); + + ::Size aActSize( pOverflowToolBar->GetSizePixel() ); + ::Size aSize( pOverflowToolBar->CalcWindowSizePixel() ); + aSize.setWidth( aActSize.Width() ); + pOverflowToolBar->SetOutputSizePixel( aSize ); + + aSize = pOverflowToolBar->CalcPopupWindowSizePixel(); + pOverflowToolBar->SetSizePixel( aSize ); + + pOverflowToolBar->EnableDocking(); + pOverflowToolBar->AddEventListener( LINK( this, ToolBarManager, OverflowEventListener ) ); + vcl::Window::GetDockingManager()->StartPopupMode( pToolBar, pOverflowToolBar, FloatWinPopupFlags::AllMouseButtonClose ); + + // send HOME key to subtoolbar in order to select first item if keyboard activated + if(pToolBar->IsKeyEvent() ) + { + ::KeyEvent aEvent( 0, vcl::KeyCode( KEY_HOME ) ); + pOverflowToolBar->KeyInput(aEvent); + } +} + +IMPL_LINK( ToolBarManager, OverflowEventListener, VclWindowEvent&, rWindowEvent, void ) +{ + if ( rWindowEvent.GetId() != VclEventId::WindowEndPopupMode ) + return; + + if ( m_aOverflowManager.is() ) + { + m_aOverflowManager->dispose(); + m_aOverflowManager.clear(); + } +} + +IMPL_LINK( ToolBarManager, MenuPreExecute, ToolBox*, pToolBar, void ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + AddCustomizeMenuItems( pToolBar ); +} + +IMPL_LINK( ToolBarManager, MenuSelect, Menu*, pMenu, bool ) +{ + // We have to hold a reference to ourself as it is possible that we will be disposed and + // our refcount could be zero (destruction) otherwise. + Reference< XInterface > xKeepAlive( static_cast< OWeakObject* >( this ), UNO_QUERY ); + + { + // The guard must be in its own context as the we can get destroyed when our + // own xInterface reference get destroyed! + SolarMutexGuard g; + + if ( m_bDisposed ) + return true; + + switch ( pMenu->GetCurItemId() ) + { + case MENUITEM_TOOLBAR_CUSTOMIZETOOLBAR: + { + Reference< XDispatch > xDisp; + css::util::URL aURL; + if ( m_xFrame.is() ) + { + Reference< XDispatchProvider > xProv( m_xFrame, UNO_QUERY ); + aURL.Complete = ".uno:ConfigureDialog"; + m_xURLTransformer->parseStrict( aURL ); + if ( xProv.is() ) + xDisp = xProv->queryDispatch( aURL, OUString(), 0 ); + } + + if ( xDisp.is() ) + { + Sequence< PropertyValue > aPropSeq{ comphelper::makePropertyValue( + "ResourceURL", m_aResourceName) }; + + xDisp->dispatch( aURL, aPropSeq ); + } + break; + } + + case MENUITEM_TOOLBAR_UNDOCKTOOLBAR: + { + ExecuteInfo* pExecuteInfo = new ExecuteInfo; + + pExecuteInfo->aToolbarResName = m_aResourceName; + pExecuteInfo->nCmd = EXEC_CMD_UNDOCKTOOLBAR; + pExecuteInfo->xLayoutManager = getLayoutManagerFromFrame( m_xFrame ); + + Application::PostUserEvent( LINK(nullptr, ToolBarManager, ExecuteHdl_Impl), pExecuteInfo ); + break; + } + + case MENUITEM_TOOLBAR_DOCKTOOLBAR: + { + ExecuteInfo* pExecuteInfo = new ExecuteInfo; + + pExecuteInfo->aToolbarResName = m_aResourceName; + pExecuteInfo->nCmd = EXEC_CMD_DOCKTOOLBAR; + pExecuteInfo->xLayoutManager = getLayoutManagerFromFrame( m_xFrame ); + + Application::PostUserEvent( LINK(nullptr, ToolBarManager, ExecuteHdl_Impl), pExecuteInfo ); + break; + } + + case MENUITEM_TOOLBAR_DOCKALLTOOLBAR: + { + ExecuteInfo* pExecuteInfo = new ExecuteInfo; + + pExecuteInfo->aToolbarResName = m_aResourceName; + pExecuteInfo->nCmd = EXEC_CMD_DOCKALLTOOLBARS; + pExecuteInfo->xLayoutManager = getLayoutManagerFromFrame( m_xFrame ); + + Application::PostUserEvent( LINK(nullptr, ToolBarManager, ExecuteHdl_Impl), pExecuteInfo ); + break; + } + + case MENUITEM_TOOLBAR_LOCKTOOLBARPOSITION: + { + Reference< XLayoutManager > xLayoutManager = getLayoutManagerFromFrame( m_xFrame ); + if ( xLayoutManager.is() ) + { + Reference< XDockableWindow > xDockable( VCLUnoHelper::GetInterface( m_pToolBar ), UNO_QUERY ); + + if( xDockable->isLocked() ) + xLayoutManager->unlockWindow( m_aResourceName ); + else + xLayoutManager->lockWindow( m_aResourceName ); + } + break; + } + + case MENUITEM_TOOLBAR_CLOSE: + { + ExecuteInfo* pExecuteInfo = new ExecuteInfo; + + pExecuteInfo->aToolbarResName = m_aResourceName; + pExecuteInfo->nCmd = EXEC_CMD_CLOSETOOLBAR; + pExecuteInfo->xLayoutManager = getLayoutManagerFromFrame( m_xFrame ); + pExecuteInfo->xWindow = VCLUnoHelper::GetInterface( m_pToolBar ); + + Application::PostUserEvent( LINK(nullptr, ToolBarManager, ExecuteHdl_Impl), pExecuteInfo ); + break; + } + + default: + { + sal_uInt16 nId = pMenu->GetCurItemId(); + if(( nId > 0 ) && ( nId < TOOLBOX_MENUITEM_START )) + // Items in the "enable/disable" sub-menu + { + // toggle toolbar button visibility + OUString aCommand = pMenu->GetItemCommand( nId ); + + Reference< XLayoutManager > xLayoutManager = getLayoutManagerFromFrame( m_xFrame ); + if ( xLayoutManager.is() ) + { + Reference< XUIElementSettings > xUIElementSettings( xLayoutManager->getElement( m_aResourceName ), UNO_QUERY ); + if ( xUIElementSettings.is() ) + { + Reference< XIndexContainer > xItemContainer( xUIElementSettings->getSettings( true ), UNO_QUERY ); + sal_Int32 nCount = xItemContainer->getCount(); + for ( sal_Int32 i = 0; i < nCount; i++ ) + { + Sequence< PropertyValue > aProp; + sal_Int32 nVisibleIndex( -1 ); + OUString aCommandURL; + bool bVisible( false ); + + if ( xItemContainer->getByIndex( i ) >>= aProp ) + { + for ( sal_Int32 j = 0; j < aProp.getLength(); j++ ) + { + if ( aProp[j].Name == ITEM_DESCRIPTOR_COMMANDURL ) + { + aProp[j].Value >>= aCommandURL; + } + else if ( aProp[j].Name == ITEM_DESCRIPTOR_VISIBLE ) + { + aProp[j].Value >>= bVisible; + nVisibleIndex = j; + } + } + + if (( aCommandURL == aCommand ) && ( nVisibleIndex >= 0 )) + { + // We have found the requested item, toggle the visible flag + // and write back the configuration settings to the toolbar + aProp.getArray()[nVisibleIndex].Value <<= !bVisible; + try + { + xItemContainer->replaceByIndex( i, Any( aProp )); + xUIElementSettings->setSettings( xItemContainer ); + Reference< XPropertySet > xPropSet( xUIElementSettings, UNO_QUERY ); + if ( xPropSet.is() ) + { + Reference< XUIConfigurationPersistence > xUICfgMgr; + if (( xPropSet->getPropertyValue("ConfigurationSource") >>= xUICfgMgr ) && ( xUICfgMgr.is() )) + xUICfgMgr->store(); + } + } + catch (const Exception&) + { + } + + break; + } + } + } + } + } + } + break; + } + } + } + + return true; +} + +IMPL_LINK_NOARG(ToolBarManager, Select, ToolBox *, void) +{ + if ( m_bDisposed ) + return; + + sal_Int16 nKeyModifier( static_cast<sal_Int16>(m_pToolBar->GetModifier()) ); + ToolBoxItemId nId( m_pToolBar->GetCurItemId() ); + + ToolBarControllerMap::const_iterator pIter = m_aControllerMap.find( nId ); + if ( pIter != m_aControllerMap.end() ) + { + Reference< XToolbarController > xController( pIter->second, UNO_QUERY ); + + if ( xController.is() ) + xController->execute( nKeyModifier ); + } +} + +IMPL_LINK( ToolBarManager, StateChanged, StateChangedType const *, pStateChangedType, void ) +{ + if ( m_bDisposed ) + return; + + if ( *pStateChangedType == StateChangedType::ControlBackground ) + { + CheckAndUpdateImages(); + } + else if ( *pStateChangedType == StateChangedType::Visible ) + { + if ( m_pToolBar->IsReallyVisible() ) + { + m_aAsyncUpdateControllersTimer.Start(); + } + } + else if ( *pStateChangedType == StateChangedType::InitShow ) + { + m_aAsyncUpdateControllersTimer.Start(); + } +} + +IMPL_LINK( ToolBarManager, DataChanged, DataChangedEvent const *, pDataChangedEvent, void ) +{ + if ((( pDataChangedEvent->GetType() == DataChangedEventType::SETTINGS ) || + ( pDataChangedEvent->GetType() == DataChangedEventType::DISPLAY )) && + ( pDataChangedEvent->GetFlags() & AllSettingsFlags::STYLE )) + { + CheckAndUpdateImages(); + } + + for ( ToolBox::ImplToolItems::size_type nPos = 0; nPos < m_pToolBar->GetItemCount(); ++nPos ) + { + const ToolBoxItemId nId = m_pToolBar->GetItemId(nPos); + vcl::Window* pWindow = m_pToolBar->GetItemWindow( nId ); + if ( pWindow ) + { + const DataChangedEvent& rDCEvt( *pDataChangedEvent ); + pWindow->DataChanged( rDCEvt ); + } + } + + if ( !m_pToolBar->IsFloatingMode() && + m_pToolBar->IsVisible() ) + { + // Resize toolbar, layout manager is resize listener and will calc + // the layout automatically. + ::Size aSize( m_pToolBar->CalcWindowSizePixel() ); + m_pToolBar->SetOutputSizePixel( aSize ); + } +} + +IMPL_LINK_NOARG(ToolBarManager, MiscOptionsChanged, LinkParamNone*, void) +{ + CheckAndUpdateImages(); +} + +IMPL_LINK_NOARG(ToolBarManager, AsyncUpdateControllersHdl, Timer *, void) +{ + // The guard must be in its own context as the we can get destroyed when our + // own xInterface reference get destroyed! + Reference< XComponent > xThis(this); + + SolarMutexGuard g; + + if ( m_bDisposed ) + return; + + // Request to update our controllers + m_aAsyncUpdateControllersTimer.Stop(); + UpdateControllers(); +} + +IMPL_STATIC_LINK( ToolBarManager, ExecuteHdl_Impl, void*, p, void ) +{ + ExecuteInfo* pExecuteInfo = static_cast<ExecuteInfo*>(p); + try + { + // Asynchronous execution as this can lead to our own destruction! + if (( pExecuteInfo->nCmd == EXEC_CMD_CLOSETOOLBAR ) && + ( pExecuteInfo->xLayoutManager.is() ) && + ( pExecuteInfo->xWindow.is() )) + { + // Use docking window close to close the toolbar. The toolbar layout manager is + // listener and will react correctly according to the context sensitive + // flag of our toolbar. + VclPtr<vcl::Window> pWin = VCLUnoHelper::GetWindow( pExecuteInfo->xWindow ); + DockingWindow* pDockWin = dynamic_cast< DockingWindow* >( pWin.get() ); + if ( pDockWin ) + pDockWin->Close(); + } + else if (( pExecuteInfo->nCmd == EXEC_CMD_UNDOCKTOOLBAR ) && + ( pExecuteInfo->xLayoutManager.is() )) + { + pExecuteInfo->xLayoutManager->floatWindow( pExecuteInfo->aToolbarResName ); + } + else if (( pExecuteInfo->nCmd == EXEC_CMD_DOCKTOOLBAR ) && + ( pExecuteInfo->xLayoutManager.is() )) + { + css::awt::Point aPoint; + aPoint.X = aPoint.Y = SAL_MAX_INT32; + pExecuteInfo->xLayoutManager->dockWindow( pExecuteInfo->aToolbarResName, + DockingArea_DOCKINGAREA_DEFAULT, + aPoint ); + } + else if (( pExecuteInfo->nCmd == EXEC_CMD_DOCKALLTOOLBARS ) && + ( pExecuteInfo->xLayoutManager.is() )) + { + pExecuteInfo->xLayoutManager->dockAllWindows( UIElementType::TOOLBAR ); + } + } + catch (const Exception&) + { + } + + delete pExecuteInfo; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/toolbarmerger.cxx b/framework/source/uielement/toolbarmerger.cxx new file mode 100644 index 000000000..2657873c3 --- /dev/null +++ b/framework/source/uielement/toolbarmerger.cxx @@ -0,0 +1,645 @@ +/* -*- 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 <string_view> + +#include <uielement/toolbarmerger.hxx> +#include <framework/generictoolbarcontroller.hxx> + +#include <uielement/buttontoolbarcontroller.hxx> +#include <uielement/comboboxtoolbarcontroller.hxx> +#include <uielement/dropdownboxtoolbarcontroller.hxx> +#include <uielement/edittoolbarcontroller.hxx> +#include <uielement/imagebuttontoolbarcontroller.hxx> +#include <uielement/spinfieldtoolbarcontroller.hxx> +#include <uielement/togglebuttontoolbarcontroller.hxx> +#include <uielement/FixedTextToolbarController.hxx> +#include <uielement/FixedImageToolbarController.hxx> +#include <o3tl/string_view.hxx> + +namespace framework +{ + +const char MERGE_TOOLBAR_URL[] = "URL"; +const char MERGE_TOOLBAR_TITLE[] = "Title"; +const char MERGE_TOOLBAR_CONTEXT[] = "Context"; +const char MERGE_TOOLBAR_TARGET[] = "Target"; +const char MERGE_TOOLBAR_CONTROLTYPE[] = "ControlType"; +const char MERGE_TOOLBAR_WIDTH[] = "Width"; + +const char16_t MERGECOMMAND_ADDAFTER[] = u"AddAfter"; +const char16_t MERGECOMMAND_ADDBEFORE[] = u"AddBefore"; +const char16_t MERGECOMMAND_REPLACE[] = u"Replace"; +const char16_t MERGECOMMAND_REMOVE[] = u"Remove"; + +const char16_t MERGEFALLBACK_ADDLAST[] = u"AddLast"; +const char16_t MERGEFALLBACK_ADDFIRST[] = u"AddFirst"; +const char16_t MERGEFALLBACK_IGNORE[] = u"Ignore"; + +const char16_t TOOLBARCONTROLLER_BUTTON[] = u"Button"; +const char16_t TOOLBARCONTROLLER_COMBOBOX[] = u"Combobox"; +const char16_t TOOLBARCONTROLLER_EDIT[] = u"Editfield"; +const char16_t TOOLBARCONTROLLER_SPINFIELD[] = u"Spinfield"; +const char16_t TOOLBARCONTROLLER_IMGBUTTON[] = u"ImageButton"; +const char16_t TOOLBARCONTROLLER_DROPDOWNBOX[] = u"Dropdownbox"; +const char16_t TOOLBARCONTROLLER_DROPDOWNBTN[] = u"DropdownButton"; +const char16_t TOOLBARCONTROLLER_TOGGLEDDBTN[] = u"ToggleDropdownButton"; +const char16_t TOOLBARCONTROLLER_FIXEDIMAGE[] = u"FixedImage"; +const char16_t TOOLBARCONTROLLER_FIXEDTEXT[] = u"FixedText"; + +const char TOOLBOXITEM_SEPARATOR_STR[] = "private:separator"; + +using namespace ::com::sun::star; + +/** + Check whether a module identifier is part of a context + defined by a colon separated list of module identifier. + + @param + rContext + + Describes a context string list where all contexts + are delimited by a colon. For more information about + the module identifier used as context strings see the + IDL description of css::frame::XModuleManager + + @param + rModuleIdentifier + + A string describing a module identifier. See IDL + description of css::frame::XModuleManager. + + @result + The result is true if the rContext is an empty string + or rModuleIdentifier is part of the context string. + +*/ +bool ToolBarMerger::IsCorrectContext( + std::u16string_view rContext, + std::u16string_view rModuleIdentifier ) +{ + return ( rContext.empty() || ( rContext.find( rModuleIdentifier ) != std::u16string_view::npos )); +} + +/** + Converts a sequence, sequence of property values to + a vector of structs. + + @param + rSequence + + Provides a sequence, sequence of property values. + + @param + rContainer + + A vector of AddonToolbarItems which will hold the + conversion from the rSequence argument. +*/ +void ToolBarMerger::ConvertSeqSeqToVector( + const uno::Sequence< uno::Sequence< beans::PropertyValue > >& rSequence, + AddonToolbarItemContainer& rContainer ) +{ + sal_Int32 nLen( rSequence.getLength() ); + for ( sal_Int32 i = 0; i < nLen; i++ ) + { + AddonToolbarItem aAddonToolbarItem; + ConvertSequenceToValues( rSequence[i], + aAddonToolbarItem.aCommandURL, + aAddonToolbarItem.aLabel, + aAddonToolbarItem.aTarget, + aAddonToolbarItem.aContext, + aAddonToolbarItem.aControlType, + aAddonToolbarItem.nWidth ); + rContainer.push_back( aAddonToolbarItem ); + } +} + +/** + Converts a sequence of property values to single + values. + + @param + rSequence + + Provides a sequence of property values. + + @param + rCommandURL + + Contains the value of the property with + Name="CommandURL". + + @param + rLabel + + Contains the value of the property with + Name="Title" + + @param + rTarget + + Contains the value of the property with + Name="Target" + + @param + rContext + + Contains the value of the property with + Name="Context" + + @param + rControlType + + Contains the value of the property with + Name="ControlType" + + @result + All possible mapping between sequence of property + values and the single values are done. + +*/ +void ToolBarMerger::ConvertSequenceToValues( + const uno::Sequence< beans::PropertyValue >& rSequence, + OUString& rCommandURL, + OUString& rLabel, + OUString& rTarget, + OUString& rContext, + OUString& rControlType, + sal_uInt16& rWidth ) +{ + for ( beans::PropertyValue const & prop : rSequence ) + { + if ( prop.Name == MERGE_TOOLBAR_URL ) + prop.Value >>= rCommandURL; + else if ( prop.Name == MERGE_TOOLBAR_TITLE ) + prop.Value >>= rLabel; + else if ( prop.Name == MERGE_TOOLBAR_CONTEXT ) + prop.Value >>= rContext; + else if ( prop.Name == MERGE_TOOLBAR_TARGET ) + prop.Value >>= rTarget; + else if ( prop.Name == MERGE_TOOLBAR_CONTROLTYPE ) + prop.Value >>= rControlType; + else if ( prop.Name == MERGE_TOOLBAR_WIDTH ) + { + sal_Int32 aValue = 0; + prop.Value >>= aValue; + rWidth = sal_uInt16( aValue ); + } + } +} + +/** + Tries to find the reference point provided and delivers + position and result of the search process. + + @param + pToolbar + + Must be a valid pointer to a toolbar with items which + should be searched. + + @param + rReferencePoint + + A command URL which should be the reference point for + the coming merge operation. + + @result + Provides information about the search result, the + position of the reference point and the toolbar used. +*/ +ReferenceToolbarPathInfo ToolBarMerger::FindReferencePoint( + const ToolBox* pToolbar, + std::u16string_view rReferencePoint ) +{ + ReferenceToolbarPathInfo aResult; + aResult.bResult = false; + aResult.nPos = ToolBox::ITEM_NOTFOUND; + + const ToolBox::ImplToolItems::size_type nSize( pToolbar->GetItemCount() ); + + for ( ToolBox::ImplToolItems::size_type i = 0; i < nSize; i++ ) + { + const ToolBoxItemId nItemId = pToolbar->GetItemId( i ); + if ( nItemId > ToolBoxItemId(0) ) + { + const OUString rCmd = pToolbar->GetItemCommand( nItemId ); + if ( rCmd == rReferencePoint ) + { + aResult.bResult = true; + aResult.nPos = i; + return aResult; + } + } + } + + return aResult; +} + +/** + Processes a merge operation. + + @param + pToolbar + + A valid pointer to the toolbar where the merge + operation is applied to. + + @param + nPos + + The reference position of the toolbar item for + the merge operation. Value must be between + 0 and number of toolbar items - 1. + + @param + rItemId + + A unique item ID. + + @param + rModuleIdentifier + + The current application module context. + + @param + rMergeCommand + + A merge command. + + @param + rMergeCommandParameter. + + An optional argument for the merge command. + + @param + rItems + + Toolbar items which are associated to the merge + command. + + @result + Returns true for a successful operation otherwise + false. +*/ +bool ToolBarMerger::ProcessMergeOperation( + ToolBox* pToolbar, + ToolBox::ImplToolItems::size_type nPos, + ToolBoxItemId& rItemId, + CommandToInfoMap& rCommandMap, + std::u16string_view rModuleIdentifier, + std::u16string_view rMergeCommand, + std::u16string_view rMergeCommandParameter, + const AddonToolbarItemContainer& rItems ) +{ + if ( rMergeCommand == MERGECOMMAND_ADDAFTER ) + MergeItems( pToolbar, nPos, 1, rItemId, rCommandMap, rModuleIdentifier, rItems ); + else if ( rMergeCommand == MERGECOMMAND_ADDBEFORE ) + MergeItems( pToolbar, nPos, 0, rItemId, rCommandMap, rModuleIdentifier, rItems ); + else if ( rMergeCommand == MERGECOMMAND_REPLACE ) + ReplaceItem( pToolbar, nPos, rItemId, rCommandMap, rModuleIdentifier, rItems ); + else if ( rMergeCommand == MERGECOMMAND_REMOVE ) + RemoveItems( pToolbar, nPos, rMergeCommandParameter ); + else + return false; + return true; +} + +/** + Processes a merge fallback operation. + + @param + pToolbar + + A valid pointer to the toolbar where the merge + fall back operation is applied to. + + @param + nPos + + The reference position of the toolbar item for + the merge operation. Value must be between + 0 and number of toolbar items - 1. + + @param + rItemId + + A unique item ID. + + @param + rModuleIdentifier + + The current application module context. + + @param + rMergeCommand + + A merge command. + + @param + rItems + + Toolbar items which are associated to the merge + command. + + @result + Returns true for a successful operation otherwise + false. +*/ +bool ToolBarMerger::ProcessMergeFallback( + ToolBox* pToolbar, + ToolBoxItemId& rItemId, + CommandToInfoMap& rCommandMap, + std::u16string_view rModuleIdentifier, + std::u16string_view rMergeCommand, + std::u16string_view rMergeFallback, + const AddonToolbarItemContainer& rItems ) +{ + if (( rMergeFallback == MERGEFALLBACK_IGNORE ) || + ( rMergeCommand == MERGECOMMAND_REPLACE ) || + ( rMergeCommand == MERGECOMMAND_REMOVE ) ) + { + return true; + } + else if (( rMergeCommand == MERGECOMMAND_ADDBEFORE ) || + ( rMergeCommand == MERGECOMMAND_ADDAFTER ) ) + { + if ( rMergeFallback == MERGEFALLBACK_ADDFIRST ) + MergeItems( pToolbar, 0, 0, rItemId, rCommandMap, rModuleIdentifier, rItems ); + else if ( rMergeFallback == MERGEFALLBACK_ADDLAST ) + MergeItems( pToolbar, ToolBox::APPEND, 0, rItemId, rCommandMap, rModuleIdentifier, rItems ); + else + return false; + return true; + } + + return false; +} + +/** + Merges (adds) toolbar items into an existing toolbar. + + @param + pToolbar + + A valid pointer to the toolbar where the merge + fall back operation is applied to. + + @param + nPos + + The reference position of the toolbar item for + the merge operation. Value must be between + 0 and number of toolbar items - 1. + + @param + rItemId + + A unique item ID. + + @param + rModuleIdentifier + + The current application module context. + + @param + rItems + + Toolbar items which are associated to the merge + command. +*/ +void ToolBarMerger::MergeItems( + ToolBox* pToolbar, + ToolBox::ImplToolItems::size_type nPos, + sal_uInt16 nModIndex, + ToolBoxItemId& rItemId, + CommandToInfoMap& rCommandMap, + std::u16string_view rModuleIdentifier, + const AddonToolbarItemContainer& rAddonToolbarItems ) +{ + const sal_Int32 nSize( rAddonToolbarItems.size() ); + + for ( sal_Int32 i = 0; i < nSize; i++ ) + { + const AddonToolbarItem& rItem = rAddonToolbarItems[i]; + if ( IsCorrectContext( rItem.aContext, rModuleIdentifier )) + { + ToolBox::ImplToolItems::size_type nInsPos = nPos; + if (nInsPos != ToolBox::APPEND) + { + nInsPos += nModIndex+i; + if ( nInsPos > pToolbar->GetItemCount() ) + nInsPos = ToolBox::APPEND; + } + + if ( rItem.aCommandURL == TOOLBOXITEM_SEPARATOR_STR ) + pToolbar->InsertSeparator( nInsPos ); + else + { + CommandToInfoMap::iterator pIter = rCommandMap.find( rItem.aCommandURL ); + if ( pIter == rCommandMap.end()) + { + CommandInfo aCmdInfo; + aCmdInfo.nId = rItemId; + const CommandToInfoMap::value_type aValue( rItem.aCommandURL, aCmdInfo ); + rCommandMap.insert( aValue ); + } + else + { + pIter->second.aIds.push_back( rItemId ); + } + + ToolBarMerger::CreateToolbarItem( pToolbar, nInsPos, rItemId, rItem ); + } + + ++rItemId; + } + } +} + +/** + Replaces a toolbar item with new items for an + existing toolbar. + + @param + pToolbar + + A valid pointer to the toolbar where the merge + fall back operation is applied to. + + @param + nPos + + The reference position of the toolbar item for + the merge operation. Value must be between + 0 and number of toolbar items - 1. + + @param + rItemId + + A unique item ID. + + @param + rModuleIdentifier + + The current application module context. + + @param + rItems + + Toolbar items which are associated to the merge + command. +*/ +void ToolBarMerger::ReplaceItem( + ToolBox* pToolbar, + ToolBox::ImplToolItems::size_type nPos, + ToolBoxItemId& rItemId, + CommandToInfoMap& rCommandMap, + std::u16string_view rModuleIdentifier, + const AddonToolbarItemContainer& rAddonToolbarItems ) +{ + pToolbar->RemoveItem( nPos ); + MergeItems( pToolbar, nPos, 0, rItemId, rCommandMap, rModuleIdentifier, rAddonToolbarItems ); +} + +/** + Removes toolbar items from an existing toolbar. + + @param + pToolbar + + A valid pointer to the toolbar where the merge + fall back operation is applied to. + + @param + nPos + + The reference position of the toolbar item for + the merge operation. Value must be between + 0 and number of toolbar items - 1. + + @param + rMergeCommandParameter. + + An optional argument for the merge command. +*/ +void ToolBarMerger::RemoveItems( + ToolBox* pToolbar, + ToolBox::ImplToolItems::size_type nPos, + std::u16string_view rMergeCommandParameter ) +{ + sal_Int32 nCount = o3tl::toInt32(rMergeCommandParameter); + if ( nCount > 0 ) + { + for ( sal_Int32 i = 0; i < nCount; i++ ) + { + if ( nPos < pToolbar->GetItemCount() ) + pToolbar->RemoveItem( nPos ); + } + } +} + +/** + Removes toolbar items from an existing toolbar. + + @param + pToolbar + + A valid pointer to the toolbar where the merge + fall back operation is applied to. + + @param + nPos + + The reference position of the toolbar item for + the merge operation. Value must be between + 0 and number of toolbar items - 1. + + @param + rMergeCommandParameter. + + An optional argument for the merge command. + + @result + Returns true for a successful operation otherwise + false. +*/ +rtl::Reference<::cppu::OWeakObject> ToolBarMerger::CreateController( + const uno::Reference< uno::XComponentContext >& rxContext, + const uno::Reference< frame::XFrame > & xFrame, + ToolBox* pToolbar, + const OUString& rCommandURL, + ToolBoxItemId nId, + sal_uInt16 nWidth, + std::u16string_view rControlType ) +{ + rtl::Reference<::cppu::OWeakObject> pResult; + + if ( rControlType == TOOLBARCONTROLLER_BUTTON ) + pResult = new ButtonToolbarController( rxContext, pToolbar, rCommandURL ); + else if ( rControlType == TOOLBARCONTROLLER_COMBOBOX ) + pResult = new ComboboxToolbarController( rxContext, xFrame, pToolbar, nId, nWidth, rCommandURL ); + else if ( rControlType == TOOLBARCONTROLLER_EDIT ) + pResult = new EditToolbarController( rxContext, xFrame, pToolbar, nId, nWidth, rCommandURL ); + else if ( rControlType == TOOLBARCONTROLLER_SPINFIELD ) + pResult = new SpinfieldToolbarController( rxContext, xFrame, pToolbar, nId, nWidth, rCommandURL ); + else if ( rControlType == TOOLBARCONTROLLER_IMGBUTTON ) + pResult = new ImageButtonToolbarController( rxContext, xFrame, pToolbar, nId, rCommandURL ); + else if ( rControlType == TOOLBARCONTROLLER_DROPDOWNBOX ) + pResult = new DropdownToolbarController( rxContext, xFrame, pToolbar, nId, nWidth, rCommandURL ); + else if ( rControlType == TOOLBARCONTROLLER_DROPDOWNBTN ) + pResult = new ToggleButtonToolbarController( rxContext, xFrame, pToolbar, nId, + ToggleButtonToolbarController::Style::DropDownButton, rCommandURL ); + else if ( rControlType == TOOLBARCONTROLLER_FIXEDIMAGE ) + pResult = new FixedImageToolbarController( rxContext, xFrame, pToolbar, nId, rCommandURL ); + else if ( rControlType == TOOLBARCONTROLLER_FIXEDTEXT ) + pResult = new FixedTextToolbarController( rxContext, xFrame, pToolbar, nId, rCommandURL ); + else if ( rControlType == TOOLBARCONTROLLER_TOGGLEDDBTN ) + pResult = new ToggleButtonToolbarController( rxContext, xFrame, pToolbar, nId, + ToggleButtonToolbarController::Style::ToggleDropDownButton, rCommandURL ); + else + pResult = new GenericToolbarController( rxContext, xFrame, pToolbar, nId, rCommandURL ); + + return pResult; +} + +void ToolBarMerger::CreateToolbarItem( ToolBox* pToolbar, ToolBox::ImplToolItems::size_type nPos, ToolBoxItemId nItemId, const AddonToolbarItem& rItem ) +{ + assert(pToolbar->GetItemData(nItemId) == nullptr); // that future would contain a double free + + pToolbar->InsertItem( nItemId, rItem.aLabel, ToolBoxItemBits::NONE, nPos ); + pToolbar->SetItemCommand( nItemId, rItem.aCommandURL ); + pToolbar->SetQuickHelpText( nItemId, rItem.aLabel ); + pToolbar->SetItemText( nItemId, rItem.aLabel ); + pToolbar->EnableItem( nItemId ); + pToolbar->SetItemState( nItemId, TRISTATE_FALSE ); + + // Use the user data to store add-on specific data with the toolbar item + AddonsParams* pAddonParams = new AddonsParams; + pAddonParams->aControlType = rItem.aControlType; + pAddonParams->nWidth = rItem.nWidth; + pToolbar->SetItemData( nItemId, pAddonParams ); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/toolbarmodemenucontroller.cxx b/framework/source/uielement/toolbarmodemenucontroller.cxx new file mode 100644 index 000000000..1740bf1f1 --- /dev/null +++ b/framework/source/uielement/toolbarmodemenucontroller.cxx @@ -0,0 +1,294 @@ +/* -*- 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 <uielement/toolbarmodemenucontroller.hxx> +#include <services.h> + +#include <com/sun/star/awt/MenuItemStyle.hpp> +#include <com/sun/star/ui/UIElementType.hpp> +#include <com/sun/star/frame/XModuleManager.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> + + +#include <officecfg/Office/Common.hxx> +#include <vcl/svapp.hxx> +#include <vcl/EnumContext.hxx> +#include <rtl/ustrbuf.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/types.hxx> +#include <unotools/confignode.hxx> +#include <cppuhelper/supportsservice.hxx> + +// Defines + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::ui; + +namespace framework +{ + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL ToolbarModeMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.ToolbarModeMenuController"; +} + +sal_Bool SAL_CALL ToolbarModeMenuController::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL ToolbarModeMenuController::getSupportedServiceNames() +{ + return { SERVICENAME_POPUPMENUCONTROLLER }; +} + + +ToolbarModeMenuController::ToolbarModeMenuController( const css::uno::Reference< css::uno::XComponentContext >& xContext ) : + svt::PopupMenuControllerBase( xContext ), + m_xContext( xContext ) +{ +} + +ToolbarModeMenuController::~ToolbarModeMenuController() +{ +} + +void ToolbarModeMenuController::fillPopupMenu( Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + if ( officecfg::Office::Common::Misc::DisableUICustomization::get() ) + return; + + SolarMutexGuard aSolarMutexGuard; + resetPopupMenu( rPopupMenu ); + + const Reference<XComponentContext> xContext (::comphelper::getProcessComponentContext() ); + const Reference<frame::XModuleManager> xModuleManager = frame::ModuleManager::create( xContext ); + vcl::EnumContext::Application eApp = vcl::EnumContext::GetApplicationEnum(xModuleManager->identify(m_xFrame)); + + OUStringBuffer aPath("org.openoffice.Office.UI.ToolbarMode/Applications/"); + switch ( eApp ) + { + case vcl::EnumContext::Application::Writer: + aPath.append("Writer"); + break; + case vcl::EnumContext::Application::Calc: + aPath.append("Calc"); + break; + case vcl::EnumContext::Application::Impress: + aPath.append("Impress"); + break; + case vcl::EnumContext::Application::Draw: + aPath.append("Draw"); + break; + case vcl::EnumContext::Application::Formula: + aPath.append("Formula"); + break; + case vcl::EnumContext::Application::Base: + aPath.append("Base"); + break; + default: + break; + } + aPath.append("/Modes"); + + const utl::OConfigurationTreeRoot aModesNode( + m_xContext, + aPath.makeStringAndClear(), + false); + if ( !aModesNode.isValid() ) + return; + + const Sequence<OUString> aModeNodeNames (aModesNode.getNodeNames()); + const sal_Int32 nCount(aModeNodeNames.getLength()); + tools::Long nCountToolbar = 0; + + for ( sal_Int32 nReadIndex = 0; nReadIndex < nCount; ++nReadIndex ) + { + const utl::OConfigurationNode aModeNode(aModesNode.openNode(aModeNodeNames[nReadIndex])); + if ( !aModeNode.isValid() ) + continue; + + OUString aLabel = comphelper::getString( aModeNode.getNodeValue( "Label" ) ); + OUString aCommandArg = comphelper::getString( aModeNode.getNodeValue( "CommandArg" ) ); + tools::Long nPosition = comphelper::getINT32( aModeNode.getNodeValue( "MenuPosition" ) ); + bool isExperimental = comphelper::getBOOL( aModeNode.getNodeValue( "IsExperimental" ) ); + bool hasNotebookbar = comphelper::getBOOL( aModeNode.getNodeValue( "HasNotebookbar" ) ); + + // Allow Notebookbar only in experimental mode + if ( isExperimental && !officecfg::Office::Common::Misc::ExperimentalMode::get() ) + continue; + if (!hasNotebookbar) + nCountToolbar++; + + m_xPopupMenu->insertItem( nReadIndex+1, aLabel, css::awt::MenuItemStyle::RADIOCHECK, nPosition ); + rPopupMenu->setCommand( nReadIndex+1, aCommandArg ); + } + rPopupMenu->insertSeparator(nCountToolbar); +} + +// XEventListener +void SAL_CALL ToolbarModeMenuController::disposing( const EventObject& ) +{ + Reference< css::awt::XMenuListener > xHolder(this); + + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + + if ( m_xPopupMenu.is() ) + m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); + m_xPopupMenu.clear(); +} + +// XStatusListener +void SAL_CALL ToolbarModeMenuController::statusChanged( const FeatureStateEvent& Event ) +{ + OUString aFeatureURL( Event.FeatureURL.Complete ); + + // All other status events will be processed here + osl::ClearableMutexGuard aLock( m_aMutex ); + Reference< css::awt::XPopupMenu > xPopupMenu( m_xPopupMenu ); + aLock.clear(); + + if ( !xPopupMenu.is() ) + return; + + SolarMutexGuard aGuard; + + bool bSetCheckmark = false; + bool bCheckmark = false; + for (sal_Int16 i = 0, nCount = xPopupMenu->getItemCount(); i < nCount; ++i) + { + sal_Int16 nId = xPopupMenu->getItemId(i); + if ( nId == 0 ) + continue; + + OUString aCmd = xPopupMenu->getCommand(nId); + if ( aCmd == aFeatureURL ) + { + // Enable/disable item + xPopupMenu->enableItem(nId, Event.IsEnabled); + + // Checkmark + if ( Event.State >>= bCheckmark ) + bSetCheckmark = true; + + if ( bSetCheckmark ) + xPopupMenu->checkItem(nId, bCheckmark); + else + { + OUString aItemText; + + if ( Event.State >>= aItemText ) + xPopupMenu->setItemText(nId, aItemText); + } + } + } +} + +// XMenuListener +void SAL_CALL ToolbarModeMenuController::itemSelected( const css::awt::MenuEvent& rEvent ) +{ + auto aArgs(comphelper::InitPropertySequence({{"Mode", Any(m_xPopupMenu->getCommand(rEvent.MenuId))}})); + dispatchCommand(m_aCommandURL, aArgs); +} + +void SAL_CALL ToolbarModeMenuController::itemActivated( const css::awt::MenuEvent& ) +{ + const Reference<frame::XModuleManager> xModuleManager = frame::ModuleManager::create( m_xContext ); + vcl::EnumContext::Application eApp = vcl::EnumContext::GetApplicationEnum(xModuleManager->identify(m_xFrame)); + + OUStringBuffer aPath("org.openoffice.Office.UI.ToolbarMode/Applications/"); + switch ( eApp ) + { + case vcl::EnumContext::Application::Writer: + aPath.append("Writer"); + break; + case vcl::EnumContext::Application::Calc: + aPath.append("Calc"); + break; + case vcl::EnumContext::Application::Impress: + aPath.append("Impress"); + break; + case vcl::EnumContext::Application::Draw: + aPath.append("Draw"); + break; + case vcl::EnumContext::Application::Formula: + aPath.append("Formula"); + break; + case vcl::EnumContext::Application::Base: + aPath.append("Base"); + break; + default: + break; + } + + const utl::OConfigurationTreeRoot aModesNode( + m_xContext, + aPath.makeStringAndClear(), + false); + if ( !aModesNode.isValid() ) + return; + + OUString aMode = comphelper::getString( aModesNode.getNodeValue( "Active" ) ); + + for ( int i = 0; i < m_xPopupMenu->getItemCount(); ++i ) + { + sal_Int16 nItemId(m_xPopupMenu->getItemId(i)); + m_xPopupMenu->checkItem(nItemId, aMode == m_xPopupMenu->getCommand(nItemId)); + } +} + +// XPopupMenuController +void SAL_CALL ToolbarModeMenuController::setPopupMenu( const Reference< css::awt::XPopupMenu >& xPopupMenu ) +{ + osl::MutexGuard aLock( m_aMutex ); + + throwIfDisposed(); + + if ( m_xFrame.is() && !m_xPopupMenu.is() ) + { + // Create popup menu on demand + SolarMutexGuard aSolarMutexGuard; + + m_xPopupMenu = xPopupMenu; + m_xPopupMenu->addMenuListener( Reference< css::awt::XMenuListener >(this) ); + fillPopupMenu( m_xPopupMenu ); + } +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_ToolbarModeMenuController_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::ToolbarModeMenuController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/toolbarsmenucontroller.cxx b/framework/source/uielement/toolbarsmenucontroller.cxx new file mode 100644 index 000000000..730190240 --- /dev/null +++ b/framework/source/uielement/toolbarsmenucontroller.cxx @@ -0,0 +1,794 @@ +/* -*- 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 <uielement/toolbarsmenucontroller.hxx> + +#include <algorithm> +#include <string_view> +#include <unordered_map> + +#include <services.h> +#include <strings.hrc> +#include <classes/fwkresid.hxx> +#include <framework/sfxhelperfunctions.hxx> +#include <uiconfiguration/windowstateproperties.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/awt/MenuItemStyle.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/UIElementType.hpp> +#include <com/sun/star/ui/theWindowStateConfiguration.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <officecfg/Office/Common.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <rtl/ustrbuf.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/window.hxx> +#include <unotools/cmdoptions.hxx> +#include <unotools/collatorwrapper.hxx> +#include <unotools/syslocale.hxx> +#include <cppuhelper/supportsservice.hxx> + +// Defines + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::ui; + +constexpr OUStringLiteral CMD_RESTOREVISIBILITY = u".cmd:RestoreVisibility"; +constexpr OUStringLiteral CMD_LOCKTOOLBARS = u".uno:ToolbarLock"; + +constexpr OUStringLiteral STATIC_CMD_PART = u".uno:AvailableToolbars?Toolbar:string="; +const char STATIC_INTERNAL_CMD_PART[] = ".cmd:"; + +namespace framework +{ + +typedef std::unordered_map< OUString, OUString > ToolbarHashMap; + +namespace { + +struct ToolBarEntry +{ + OUString aUIName; + OUString aCommand; + bool bVisible; + const CollatorWrapper* pCollatorWrapper; +}; + +} + +static bool CompareToolBarEntry( const ToolBarEntry& aOne, const ToolBarEntry& aTwo ) +{ + sal_Int32 nComp = aOne.pCollatorWrapper->compareString( aOne.aUIName, aTwo.aUIName ); + + return nComp < 0; +} + +static Reference< XLayoutManager > getLayoutManagerFromFrame( const Reference< XFrame >& rFrame ) +{ + Reference< XPropertySet > xPropSet( rFrame, UNO_QUERY ); + Reference< XLayoutManager > xLayoutManager; + + try + { + xPropSet->getPropertyValue("LayoutManager") >>= xLayoutManager; + } + catch ( const UnknownPropertyException& ) + { + } + + return xLayoutManager; +} + +namespace { + +struct ToolBarInfo +{ + OUString aToolBarResName; + OUString aToolBarUIName; +}; + +} + +// XInterface, XTypeProvider, XServiceInfo + +OUString SAL_CALL ToolbarsMenuController::getImplementationName() +{ + return "com.sun.star.comp.framework.ToolBarsMenuController"; +} + +sal_Bool SAL_CALL ToolbarsMenuController::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL ToolbarsMenuController::getSupportedServiceNames() +{ + return { SERVICENAME_POPUPMENUCONTROLLER }; +} + +constexpr OUStringLiteral g_aPropUIName( u"UIName" ); +constexpr OUStringLiteral g_aPropResourceURL( u"ResourceURL" ); + +ToolbarsMenuController::ToolbarsMenuController( const css::uno::Reference< css::uno::XComponentContext >& xContext ) : + svt::PopupMenuControllerBase( xContext ), + m_xContext( xContext ), + m_bResetActive( false ), + m_aIntlWrapper(SvtSysLocale().GetUILanguageTag()) +{ +} + +ToolbarsMenuController::~ToolbarsMenuController() +{ +} + +void ToolbarsMenuController::addCommand( + Reference< css::awt::XPopupMenu > const & rPopupMenu, const OUString& rCommandURL, const OUString& rLabel ) +{ + sal_uInt16 nItemId = m_xPopupMenu->getItemCount()+1; + + OUString aLabel; + if ( rLabel.isEmpty() ) + { + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rCommandURL, m_aModuleName); + aLabel = vcl::CommandInfoProvider::GetMenuLabelForCommand(aProperties); + } + else + aLabel = rLabel; + + rPopupMenu->insertItem( nItemId, aLabel, 0, nItemId ); + rPopupMenu->setCommand( nItemId, rCommandURL ); + + bool bInternal = rCommandURL.startsWith( STATIC_INTERNAL_CMD_PART ); + if ( !bInternal ) + { + if ( !getDispatchFromCommandURL( rCommandURL ).is() ) + m_xPopupMenu->enableItem( nItemId, false ); + } + + SolarMutexGuard aSolarMutexGuard; + + css::uno::Reference<css::graphic::XGraphic> xGraphic; + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + + if ( rSettings.GetUseImagesInMenus() ) + xGraphic = vcl::CommandInfoProvider::GetXGraphicForCommand(rCommandURL, m_xFrame); + + if (xGraphic.is()) + rPopupMenu->setItemImage(nItemId, xGraphic, false); + + m_aCommandVector.push_back( rCommandURL ); +} + +Reference< XDispatch > ToolbarsMenuController::getDispatchFromCommandURL( const OUString& rCommandURL ) +{ + URL aTargetURL; + Reference< XURLTransformer > xURLTransformer; + Reference< XFrame > xFrame; + + { + SolarMutexGuard aSolarMutexGuard; + xURLTransformer = m_xURLTransformer; + xFrame = m_xFrame; + } + + aTargetURL.Complete = rCommandURL; + xURLTransformer->parseStrict( aTargetURL ); + Reference< XDispatchProvider > xDispatchProvider( xFrame, UNO_QUERY ); + if ( xDispatchProvider.is() ) + return xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + else + return Reference< XDispatch >(); +} + +static void fillHashMap( const Sequence< Sequence< css::beans::PropertyValue > >& rSeqToolBars, + ToolbarHashMap& rHashMap ) +{ + for ( Sequence< css::beans::PropertyValue > const & props : rSeqToolBars ) + { + OUString aResourceURL; + OUString aUIName; + for ( css::beans::PropertyValue const & prop : props ) + { + if ( prop.Name == "ResourceURL" ) + prop.Value >>= aResourceURL; + else if ( prop.Name == "UIName" ) + prop.Value >>= aUIName; + } + + if ( !aResourceURL.isEmpty() && + rHashMap.find( aResourceURL ) == rHashMap.end() ) + rHashMap.emplace( aResourceURL, aUIName ); + } +} + +// private function +Sequence< Sequence< css::beans::PropertyValue > > ToolbarsMenuController::getLayoutManagerToolbars( const Reference< css::frame::XLayoutManager >& rLayoutManager ) +{ + std::vector< ToolBarInfo > aToolBarArray; + const Sequence< Reference< XUIElement > > aUIElements = rLayoutManager->getElements(); + for ( Reference< XUIElement > const & xUIElement : aUIElements ) + { + Reference< XPropertySet > xPropSet( xUIElement, UNO_QUERY ); + if ( xPropSet.is() && xUIElement.is() ) + { + try + { + OUString aResName; + sal_Int16 nType( -1 ); + xPropSet->getPropertyValue("Type") >>= nType; + xPropSet->getPropertyValue("ResourceURL") >>= aResName; + + if (( nType == css::ui::UIElementType::TOOLBAR ) && + !aResName.isEmpty() ) + { + ToolBarInfo aToolBarInfo; + + aToolBarInfo.aToolBarResName = aResName; + + SolarMutexGuard aGuard; + Reference< css::awt::XWindow > xWindow( xUIElement->getRealInterface(), UNO_QUERY ); + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow ); + if ( pWindow ) + aToolBarInfo.aToolBarUIName = pWindow->GetText(); + + aToolBarArray.push_back( aToolBarInfo ); + } + } + catch ( const Exception& ) + { + } + } + } + + Sequence< Sequence< css::beans::PropertyValue > > aSeq( aToolBarArray.size() ); + auto pSeq = aSeq.getArray(); + const sal_uInt32 nCount = aToolBarArray.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + Sequence< css::beans::PropertyValue > aTbSeq{ + comphelper::makePropertyValue(g_aPropUIName, aToolBarArray[i].aToolBarUIName), + comphelper::makePropertyValue(g_aPropResourceURL, aToolBarArray[i].aToolBarResName) + }; + pSeq[i] = aTbSeq; + } + + return aSeq; +} + + +void ToolbarsMenuController::fillPopupMenu( Reference< css::awt::XPopupMenu > const & rPopupMenu ) +{ + if( officecfg::Office::Common::Misc::DisableUICustomization::get() ) + return; + + SolarMutexGuard aSolarMutexGuard; + resetPopupMenu( rPopupMenu ); + + m_aCommandVector.clear(); + + // Retrieve layout manager for additional information + Reference< XLayoutManager > xLayoutManager( getLayoutManagerFromFrame( m_xFrame )); + + m_bResetActive = false; + if ( !xLayoutManager.is() ) + return; + + ToolbarHashMap aToolbarHashMap; + + if ( m_xDocCfgMgr.is() ) + { + Sequence< Sequence< css::beans::PropertyValue > > aSeqDocToolBars = + m_xDocCfgMgr->getUIElementsInfo( UIElementType::TOOLBAR ); + fillHashMap( aSeqDocToolBars, aToolbarHashMap ); + } + + if ( m_xModuleCfgMgr.is() ) + { + Sequence< Sequence< css::beans::PropertyValue > > aSeqToolBars = + m_xModuleCfgMgr->getUIElementsInfo( UIElementType::TOOLBAR ); + fillHashMap( aSeqToolBars, aToolbarHashMap ); + } + + std::vector< ToolBarEntry > aSortedTbs; + OUString aStaticCmdPart( STATIC_CMD_PART ); + + Sequence< Sequence< css::beans::PropertyValue > > aSeqFrameToolBars = getLayoutManagerToolbars( xLayoutManager ); + fillHashMap( aSeqFrameToolBars, aToolbarHashMap ); + + for (auto const& toolbar : aToolbarHashMap) + { + OUString aUIName = toolbar.second; + bool bHideFromMenu( false ); + bool bContextSensitive( false ); + if ( aUIName.isEmpty() && + m_xPersistentWindowState.is() ) + { + bool bVisible( false ); + + try + { + Sequence< PropertyValue > aWindowState; + Any a( m_xPersistentWindowState->getByName( toolbar.first )); + + if ( a >>= aWindowState ) + { + for ( PropertyValue const & prop : std::as_const(aWindowState) ) + { + if ( prop.Name == WINDOWSTATE_PROPERTY_UINAME ) + prop.Value >>= aUIName; + else if ( prop.Name == WINDOWSTATE_PROPERTY_HIDEFROMENU ) + prop.Value >>= bHideFromMenu; + else if ( prop.Name == WINDOWSTATE_PROPERTY_CONTEXT ) + prop.Value >>= bContextSensitive; + else if ( prop.Name == WINDOWSTATE_PROPERTY_VISIBLE ) + prop.Value >>= bVisible; + } + } + } + catch ( const Exception& ) + { + } + + // Check if we have to enable/disable "Reset" menu item + if ( bContextSensitive && !bVisible ) + m_bResetActive = true; + + } + + if ( !aUIName.isEmpty() && !bHideFromMenu ) + { + ToolBarEntry aTbEntry; + aTbEntry.aUIName = aUIName; + aTbEntry.aCommand = toolbar.first; + aTbEntry.bVisible = xLayoutManager->isElementVisible( toolbar.first ); + aTbEntry.pCollatorWrapper = m_aIntlWrapper.getCaseCollator(); + aSortedTbs.push_back( aTbEntry ); + } + } + + // sort toolbars + std::sort( aSortedTbs.begin(), aSortedTbs.end(), CompareToolBarEntry ); + + sal_Int16 nIndex( 1 ); + const sal_uInt32 nCount = aSortedTbs.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + sal_uInt16 nItemCount = m_xPopupMenu->getItemCount(); + m_xPopupMenu->insertItem( nIndex, aSortedTbs[i].aUIName, css::awt::MenuItemStyle::CHECKABLE, nItemCount ); + if ( aSortedTbs[i].bVisible ) + m_xPopupMenu->checkItem( nIndex, true ); + + OUStringBuffer aStrBuf( aStaticCmdPart ); + + sal_Int32 n = aSortedTbs[i].aCommand.lastIndexOf( '/' ); + if (( n > 0 ) && (( n+1 ) < aSortedTbs[i].aCommand.getLength() )) + aStrBuf.append( aSortedTbs[i].aCommand.subView(n+1) ); + + OUString aCmd( aStrBuf.makeStringAndClear() ); + + // Store complete uno-command so it can also be dispatched. This is necessary to support + // the test tool! + rPopupMenu->setCommand( nIndex, aCmd ); + ++nIndex; + } + + // Create commands for non-toolbars + + bool bAddCommand( true ); + SvtCommandOptions aCmdOptions; + + if ( aCmdOptions.HasEntries( SvtCommandOptions::CMDOPTION_DISABLED )) + { + if ( aCmdOptions.Lookup( SvtCommandOptions::CMDOPTION_DISABLED, + "ConfigureDialog")) + bAddCommand = false; + } + + if ( bAddCommand ) + { + // Create command for configure + if ( m_xPopupMenu->getItemCount() > 0 ) + { + sal_uInt16 nItemCount = m_xPopupMenu->getItemCount(); + m_xPopupMenu->insertSeparator( nItemCount+1 ); + } + + addCommand( m_xPopupMenu, ".uno:ConfigureDialog", "" ); + } + + // Add separator if no configure has been added + if ( !bAddCommand ) + { + // Create command for configure + if ( m_xPopupMenu->getItemCount() > 0 ) + { + sal_uInt16 nItemCount = m_xPopupMenu->getItemCount(); + m_xPopupMenu->insertSeparator( nItemCount+1 ); + } + } + + OUString aLabelStr(FwkResId(STR_RESTORE_TOOLBARS)); + addCommand( m_xPopupMenu, CMD_RESTOREVISIBILITY, aLabelStr ); + aLabelStr = FwkResId(STR_LOCK_TOOLBARS); + addCommand( m_xPopupMenu, CMD_LOCKTOOLBARS, aLabelStr ); +} + +// XEventListener +void SAL_CALL ToolbarsMenuController::disposing( const EventObject& ) +{ + Reference< css::awt::XMenuListener > xHolder(this); + + osl::MutexGuard aLock( m_aMutex ); + m_xFrame.clear(); + m_xDispatch.clear(); + m_xDocCfgMgr.clear(); + m_xModuleCfgMgr.clear(); + m_xContext.clear(); + + if ( m_xPopupMenu.is() ) + m_xPopupMenu->removeMenuListener( Reference< css::awt::XMenuListener >(this) ); + m_xPopupMenu.clear(); +} + +// XStatusListener +void SAL_CALL ToolbarsMenuController::statusChanged( const FeatureStateEvent& Event ) +{ + OUString aFeatureURL( Event.FeatureURL.Complete ); + + // All other status events will be processed here + osl::ClearableMutexGuard aLock( m_aMutex ); + Reference< css::awt::XPopupMenu > xPopupMenu( m_xPopupMenu ); + aLock.clear(); + + if ( !xPopupMenu.is() ) + return; + + SolarMutexGuard aGuard; + + bool bSetCheckmark = false; + bool bCheckmark = false; + for (sal_Int16 i = 0, nCount = xPopupMenu->getItemCount(); i < nCount; ++i) + { + sal_Int16 nId = xPopupMenu->getItemId(i); + if ( nId == 0 ) + continue; + + OUString aCmd = xPopupMenu->getCommand(nId); + if ( aCmd == aFeatureURL ) + { + // Enable/disable item + xPopupMenu->enableItem(nId, Event.IsEnabled); + + // Checkmark + if ( Event.State >>= bCheckmark ) + bSetCheckmark = true; + + if ( bSetCheckmark ) + xPopupMenu->checkItem(nId, bCheckmark); + else + { + OUString aItemText; + + if ( Event.State >>= aItemText ) + xPopupMenu->setItemText(nId, aItemText); + } + } + } +} + +// XMenuListener +void SAL_CALL ToolbarsMenuController::itemSelected( const css::awt::MenuEvent& rEvent ) +{ + Reference< css::awt::XPopupMenu > xPopupMenu; + Reference< XComponentContext > xContext; + Reference< XURLTransformer > xURLTransformer; + Reference< XFrame > xFrame; + Reference< XNameAccess > xPersistentWindowState; + + { + osl::MutexGuard aLock(m_aMutex); + xPopupMenu = m_xPopupMenu; + xContext = m_xContext; + xURLTransformer = m_xURLTransformer; + xFrame = m_xFrame; + xPersistentWindowState = m_xPersistentWindowState; + } + + if ( !xPopupMenu.is() ) + return; + + SolarMutexGuard aSolarMutexGuard; + + OUString aCmd(xPopupMenu->getCommand(rEvent.MenuId)); + if ( aCmd.startsWith( STATIC_INTERNAL_CMD_PART ) ) + { + // Command to restore the visibility of all context sensitive toolbars + Reference< XNameReplace > xNameReplace( xPersistentWindowState, UNO_QUERY ); + if ( xPersistentWindowState.is() && xNameReplace.is() ) + { + try + { + Sequence< OUString > aElementNames = xPersistentWindowState->getElementNames(); + sal_Int32 nCount = aElementNames.getLength(); + bool bRefreshToolbars( false ); + + for ( sal_Int32 i = 0; i < nCount; i++ ) + { + try + { + OUString aElementName = aElementNames[i]; + Sequence< PropertyValue > aWindowState; + + if ( xPersistentWindowState->getByName( aElementName ) >>= aWindowState ) + { + bool bVisible( false ); + bool bContextSensitive( false ); + sal_Int32 nVisibleIndex( -1 ); + for ( sal_Int32 j = 0; j < aWindowState.getLength(); j++ ) + { + if ( aWindowState[j].Name == WINDOWSTATE_PROPERTY_VISIBLE ) + { + aWindowState[j].Value >>= bVisible; + nVisibleIndex = j; + } + else if ( aWindowState[j].Name == WINDOWSTATE_PROPERTY_CONTEXT ) + aWindowState[j].Value >>= bContextSensitive; + } + + if ( !bVisible && bContextSensitive && nVisibleIndex >= 0 ) + { + // Default is: Every context sensitive toolbar is visible + aWindowState.getArray()[nVisibleIndex].Value <<= true; + xNameReplace->replaceByName( aElementName, Any( aWindowState )); + bRefreshToolbars = true; + } + } + } + catch ( const NoSuchElementException& ) + { + } + } + + if ( bRefreshToolbars ) + { + Reference< XLayoutManager > xLayoutManager( getLayoutManagerFromFrame( xFrame )); + if ( xLayoutManager.is() ) + { + Reference< XPropertySet > xPropSet( xLayoutManager, UNO_QUERY ); + if ( xPropSet.is() ) + { + try + { + xPropSet->setPropertyValue("RefreshContextToolbarVisibility", Any( true )); + } + catch ( const RuntimeException& ) + { + } + catch ( const Exception& ) + { + } + } + } + RefreshToolbars( xFrame ); + } + } + catch ( const RuntimeException& ) + { + throw; + } + catch ( const Exception& ) + { + } + } + } + else if ( aCmd.indexOf( STATIC_CMD_PART ) < 0 ) + { + URL aTargetURL; + Sequence<PropertyValue> aArgs; + + aTargetURL.Complete = aCmd; + xURLTransformer->parseStrict( aTargetURL ); + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + if ( xDispatchProvider.is() ) + { + Reference< XDispatch > xDispatch = xDispatchProvider->queryDispatch( + aTargetURL, OUString(), 0 ); + + ExecuteInfo* pExecuteInfo = new ExecuteInfo; + pExecuteInfo->xDispatch = xDispatch; + pExecuteInfo->aTargetURL = aTargetURL; + pExecuteInfo->aArgs = aArgs; + Application::PostUserEvent( LINK(nullptr, ToolbarsMenuController, ExecuteHdl_Impl), pExecuteInfo ); + } + } + else + { + Reference< XLayoutManager > xLayoutManager( getLayoutManagerFromFrame( xFrame )); + if ( xLayoutManager.is() ) + { + // Extract toolbar name from the combined uno-command. + sal_Int32 nIndex = aCmd.indexOf( '=' ); + if (( nIndex > 0 ) && (( nIndex+1 ) < aCmd.getLength() )) + { + OUString aToolBarResName = OUString::Concat("private:resource/toolbar/") + aCmd.subView(nIndex+1); + + const bool bShow(!xPopupMenu->isItemChecked(rEvent.MenuId)); + if ( bShow ) + { + xLayoutManager->createElement( aToolBarResName ); + xLayoutManager->showElement( aToolBarResName ); + } + else + { + // closing means: + // hide and destroy element + xLayoutManager->hideElement( aToolBarResName ); + xLayoutManager->destroyElement( aToolBarResName ); + } + } + } + } +} + +void SAL_CALL ToolbarsMenuController::itemActivated( const css::awt::MenuEvent& ) +{ + std::vector< OUString > aCmdVector; + Reference< XDispatchProvider > xDispatchProvider( m_xFrame, UNO_QUERY ); + Reference< XURLTransformer > xURLTransformer( m_xURLTransformer ); + { + osl::MutexGuard aLock( m_aMutex ); + fillPopupMenu( m_xPopupMenu ); + aCmdVector = m_aCommandVector; + } + + // Update status for all commands inside our toolbars popup menu + const sal_uInt32 nCount = aCmdVector.size(); + for ( sal_uInt32 i = 0; i < nCount; i++ ) + { + bool bInternal = aCmdVector[i].startsWith( STATIC_INTERNAL_CMD_PART ); + + if ( !bInternal ) + { + URL aTargetURL; + aTargetURL.Complete = aCmdVector[i]; + xURLTransformer->parseStrict( aTargetURL ); + Reference< XDispatch > xDispatch = xDispatchProvider->queryDispatch( aTargetURL, OUString(), 0 ); + if ( xDispatch.is() ) + { + xDispatch->addStatusListener( static_cast< XStatusListener* >(this), aTargetURL ); + xDispatch->removeStatusListener( static_cast< XStatusListener* >(this), aTargetURL ); + } + } + else if ( aCmdVector[i] == CMD_RESTOREVISIBILITY ) + { + // Special code to determine the enable/disable state of this command + FeatureStateEvent aFeatureStateEvent; + aFeatureStateEvent.FeatureURL.Complete = aCmdVector[i]; + aFeatureStateEvent.IsEnabled = m_bResetActive; // is context sensitive toolbar non visible + statusChanged( aFeatureStateEvent ); + } + } +} + +// XPopupMenuController +void SAL_CALL ToolbarsMenuController::setPopupMenu( const Reference< css::awt::XPopupMenu >& xPopupMenu ) +{ + osl::MutexGuard aLock( m_aMutex ); + + throwIfDisposed(); + + if ( m_xFrame.is() && !m_xPopupMenu.is() ) + { + // Create popup menu on demand + SolarMutexGuard aSolarMutexGuard; + + m_xPopupMenu = xPopupMenu; + m_xPopupMenu->addMenuListener( Reference< css::awt::XMenuListener >(this) ); + fillPopupMenu( m_xPopupMenu ); + } +} + +// XInitialization +void SAL_CALL ToolbarsMenuController::initialize( const Sequence< Any >& aArguments ) +{ + osl::MutexGuard aLock( m_aMutex ); + bool bInitialized( m_bInitialized ); + if ( bInitialized ) + return; + + svt::PopupMenuControllerBase::initialize(aArguments); + + if ( !m_bInitialized ) + return; + + Reference< XModuleManager2 > xModuleManager = ModuleManager::create( m_xContext ); + Reference< XNameAccess > xPersistentWindowStateSupplier = css::ui::theWindowStateConfiguration::get( m_xContext ); + + // Retrieve persistent window state reference for our module + try + { + OUString aModuleIdentifier = xModuleManager->identify( m_xFrame ); + xPersistentWindowStateSupplier->getByName( aModuleIdentifier ) >>= m_xPersistentWindowState; + + Reference< XModuleUIConfigurationManagerSupplier > xModuleCfgSupplier = + theModuleUIConfigurationManagerSupplier::get( m_xContext ); + m_xModuleCfgMgr = xModuleCfgSupplier->getUIConfigurationManager( aModuleIdentifier ); + + Reference< XController > xController = m_xFrame->getController(); + Reference< XModel > xModel; + if ( xController.is() ) + xModel = xController->getModel(); + if ( xModel.is() ) + { + Reference< XUIConfigurationManagerSupplier > xUIConfigurationManagerSupplier( xModel, UNO_QUERY ); + if ( xUIConfigurationManagerSupplier.is() ) + m_xDocCfgMgr = xUIConfigurationManagerSupplier->getUIConfigurationManager(); + } + } + catch ( const Exception& ) + { + } +} + +IMPL_STATIC_LINK( ToolbarsMenuController, ExecuteHdl_Impl, void*, p, void ) +{ + ExecuteInfo* pExecuteInfo = static_cast<ExecuteInfo*>(p); + try + { + // Asynchronous execution as this can lead to our own destruction! + // Framework can recycle our current frame and the layout manager disposes all user interface + // elements if a component gets detached from its frame! + if ( pExecuteInfo->xDispatch.is() ) + { + pExecuteInfo->xDispatch->dispatch( pExecuteInfo->aTargetURL, pExecuteInfo->aArgs ); + } + } + catch ( const Exception& ) + { + } + + delete pExecuteInfo; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +framework_ToolbarsMenuController_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& ) +{ + return cppu::acquire(new framework::ToolbarsMenuController(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/toolbarwrapper.cxx b/framework/source/uielement/toolbarwrapper.cxx new file mode 100644 index 000000000..9bf9a4bd7 --- /dev/null +++ b/framework/source/uielement/toolbarwrapper.cxx @@ -0,0 +1,328 @@ +/* -*- 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 <uielement/toolbarwrapper.hxx> +#include <uielement/toolbarmanager.hxx> + +#include <com/sun/star/ui/UIElementType.hpp> +#include <com/sun/star/lang/DisposedException.hpp> + +#include <toolkit/helper/vclunohelper.hxx> +#include <cppuhelper/queryinterface.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/toolbox.hxx> +#include <vcl/weldutils.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::frame; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; +using namespace com::sun::star::awt; +using namespace ::com::sun::star::ui; + +namespace framework +{ + +ToolBarWrapper::ToolBarWrapper( const Reference< XComponentContext >& rxContext ) : + UIConfigElementWrapperBase( UIElementType::TOOLBAR ), + m_xContext( rxContext ) +{ +} + +ToolBarWrapper::~ToolBarWrapper() +{ + m_xWeldedToolbar.reset(nullptr); + m_xTopLevel.reset(nullptr); + m_xBuilder.reset(nullptr); +} + +// XInterface +void SAL_CALL ToolBarWrapper::acquire() noexcept +{ + UIConfigElementWrapperBase::acquire(); +} + +void SAL_CALL ToolBarWrapper::release() noexcept +{ + UIConfigElementWrapperBase::release(); +} + +uno::Any SAL_CALL ToolBarWrapper::queryInterface( const uno::Type & rType ) +{ + Any a = ::cppu::queryInterface( + rType , + static_cast< css::ui::XUIFunctionListener* >(this) ); + + if( a.hasValue() ) + return a; + + return UIConfigElementWrapperBase::queryInterface( rType ); +} + +// XComponent +void SAL_CALL ToolBarWrapper::dispose() +{ + Reference< XComponent > xThis(this); + + { + SolarMutexGuard g; + if ( m_bDisposed ) + return; + } + + css::lang::EventObject aEvent( xThis ); + m_aListenerContainer.disposeAndClear( aEvent ); + + SolarMutexGuard g; + + if ( m_xToolBarManager.is() ) + m_xToolBarManager->dispose(); + m_xToolBarManager.clear(); + m_xConfigSource.clear(); + m_xConfigData.clear(); + + m_bDisposed = true; +} + +// XInitialization +void SAL_CALL ToolBarWrapper::initialize( const Sequence< Any >& aArguments ) +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( m_bInitialized ) + return; + + UIConfigElementWrapperBase::initialize( aArguments ); + + bool bPopupMode( false ); + Reference< XWindow > xParentWindow; + for ( Any const & arg : aArguments ) + { + PropertyValue aPropValue; + if ( arg >>= aPropValue ) + { + if ( aPropValue.Name == "PopupMode" ) + aPropValue.Value >>= bPopupMode; + else if ( aPropValue.Name == "ParentWindow" ) + xParentWindow.set( aPropValue.Value, UNO_QUERY ); + } + } + + Reference< XFrame > xFrame( m_xWeakFrame ); + if ( !(xFrame.is() && m_xConfigSource.is()) ) + return; + + // Create VCL based toolbar which will be filled with settings data + VclPtr<ToolBox> pToolBar; + rtl::Reference<ToolBarManager> pToolBarManager; + { + SolarMutexGuard aSolarMutexGuard; + if ( !xParentWindow.is() ) + xParentWindow.set( xFrame->getContainerWindow() ); + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xParentWindow ); + if ( pWindow ) + { + sal_uLong nStyles = WB_BORDER | WB_SCROLL | WB_MOVEABLE | WB_3DLOOK | WB_DOCKABLE | WB_SIZEABLE | WB_CLOSEABLE; + + pToolBar = VclPtr<ToolBox>::Create( pWindow, nStyles ); + pToolBar->SetLineSpacing(true); + pToolBarManager = new ToolBarManager( m_xContext, xFrame, m_aResourceURL, pToolBar ); + m_xToolBarManager = pToolBarManager; + pToolBar->WillUsePopupMode( bPopupMode ); + } + else if (weld::TransportAsXWindow* pTunnel = dynamic_cast<weld::TransportAsXWindow*>(xParentWindow.get())) + { + m_xBuilder = Application::CreateBuilder(pTunnel->getWidget(), "svt/ui/managedtoolbar.ui"); + m_xTopLevel = m_xBuilder->weld_container("toolbarcontainer"); + m_xWeldedToolbar = m_xBuilder->weld_toolbar("managedtoolbar"); + if ( m_xWeldedToolbar ) + { + pToolBarManager = new ToolBarManager( m_xContext, xFrame, m_aResourceURL, m_xWeldedToolbar.get(), m_xBuilder.get() ); + m_xToolBarManager = pToolBarManager; + } + } + } + + try + { + m_xConfigData = m_xConfigSource->getSettings( m_aResourceURL, false ); + if ( m_xConfigData.is() && (pToolBar || m_xWeldedToolbar) && pToolBarManager ) + { + // Fill toolbar with container contents + pToolBarManager->FillToolbar( m_xConfigData ); + if (pToolBar) + { + pToolBar->EnableCustomize(); + ::Size aActSize( pToolBar->GetSizePixel() ); + ::Size aSize( pToolBar->CalcWindowSizePixel() ); + aSize.setWidth( aActSize.Width() ); + pToolBar->SetOutputSizePixel( aSize ); + } + } + } + catch ( const NoSuchElementException& ) + { + // No settings in our configuration manager. This means we are + // a transient toolbar which has no persistent settings. + m_bPersistent = false; + if ( pToolBar && pToolBarManager ) + { + pToolBar->EnableCustomize(); + ::Size aActSize( pToolBar->GetSizePixel() ); + ::Size aSize( pToolBar->CalcWindowSizePixel() ); + aSize.setWidth( aActSize.Width() ); + pToolBar->SetOutputSizePixel( aSize ); + } + } +} + +// XEventListener +void SAL_CALL ToolBarWrapper::disposing( const css::lang::EventObject& ) +{ + // nothing todo +} + +// XUpdatable +void SAL_CALL ToolBarWrapper::update() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + ToolBarManager* pToolBarManager = static_cast< ToolBarManager *>( m_xToolBarManager.get() ); + if ( pToolBarManager ) + pToolBarManager->CheckAndUpdateImages(); +} + +// XUIElementSettings +void SAL_CALL ToolBarWrapper::updateSettings() +{ + SolarMutexGuard g; + + if ( m_bDisposed ) + throw DisposedException(); + + if ( !m_xToolBarManager.is() ) + return; + + if ( m_xConfigSource.is() && m_bPersistent ) + { + try + { + ToolBarManager* pToolBarManager = static_cast< ToolBarManager *>( m_xToolBarManager.get() ); + + m_xConfigData = m_xConfigSource->getSettings( m_aResourceURL, false ); + if ( m_xConfigData.is() ) + pToolBarManager->FillToolbar( m_xConfigData ); + } + catch ( const NoSuchElementException& ) + { + } + } + else if ( !m_bPersistent ) + { + // Transient toolbar: do nothing + } +} + +void ToolBarWrapper::impl_fillNewData() +{ + // Transient toolbar => Fill toolbar with new data + ToolBarManager* pToolBarManager = static_cast< ToolBarManager *>( m_xToolBarManager.get() ); + if ( pToolBarManager ) + pToolBarManager->FillToolbar( m_xConfigData ); +} + +// XUIElement interface +Reference< XInterface > SAL_CALL ToolBarWrapper::getRealInterface( ) +{ + SolarMutexGuard g; + + if ( m_xToolBarManager.is() ) + { + ToolBarManager* pToolBarManager = static_cast< ToolBarManager *>( m_xToolBarManager.get() ); + if ( pToolBarManager ) + { + vcl::Window* pWindow = pToolBarManager->GetToolBar(); + return Reference< XInterface >( VCLUnoHelper::GetInterface( pWindow ), UNO_QUERY ); + } + } + + return Reference< XInterface >(); +} + +//XUIFunctionExecute +void SAL_CALL ToolBarWrapper::functionExecute( + const OUString& aUIElementName, + const OUString& aCommand ) +{ + SolarMutexGuard g; + + if ( m_xToolBarManager.is() ) + { + ToolBarManager* pToolBarManager = static_cast< ToolBarManager *>( m_xToolBarManager.get() ); + if ( pToolBarManager ) + pToolBarManager->notifyRegisteredControllers( aUIElementName, aCommand ); + } +} + +void SAL_CALL ToolBarWrapper::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, const css::uno::Any& aValue ) +{ + SolarMutexResettableGuard aLock; + bool bNoClose( m_bNoClose ); + aLock.clear(); + + UIConfigElementWrapperBase::setFastPropertyValue_NoBroadcast( nHandle, aValue ); + + aLock.reset(); + + bool bNewNoClose( m_bNoClose ); + if ( !(m_xToolBarManager.is() && !m_bDisposed && ( bNewNoClose != bNoClose ))) + return; + + ToolBarManager* pToolBarManager = static_cast< ToolBarManager *>( m_xToolBarManager.get() ); + if ( !pToolBarManager ) + return; + + ToolBox* pToolBox = pToolBarManager->GetToolBar(); + if ( !pToolBox ) + return; + + if ( bNewNoClose ) + { + pToolBox->SetStyle( pToolBox->GetStyle() & ~WB_CLOSEABLE ); + pToolBox->SetFloatStyle( pToolBox->GetFloatStyle() & ~WB_CLOSEABLE ); + } + else + { + pToolBox->SetStyle( pToolBox->GetStyle() | WB_CLOSEABLE ); + pToolBox->SetFloatStyle( pToolBox->GetFloatStyle() | WB_CLOSEABLE ); + } +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uielement/uicommanddescription.cxx b/framework/source/uielement/uicommanddescription.cxx new file mode 100644 index 000000000..8054cf4bf --- /dev/null +++ b/framework/source/uielement/uicommanddescription.cxx @@ -0,0 +1,725 @@ +/* -*- 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 <string_view> + +#include <uielement/uicommanddescription.hxx> + +#include <properties.h> + +#include <helper/mischelper.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XContainer.hpp> + +#include <cppuhelper/implbase.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/syslocale.hxx> + +#include <vcl/mnemonic.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/string.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::configuration; +using namespace com::sun::star::container; +using namespace ::com::sun::star::frame; + +// Namespace + +const char CONFIGURATION_ROOT_ACCESS[] = "/org.openoffice.Office.UI."; + +// Special resource URLs to retrieve additional information +constexpr OUStringLiteral PRIVATE_RESOURCE_URL = u"private:"; + +const sal_Int32 COMMAND_PROPERTY_IMAGE = 1; +const sal_Int32 COMMAND_PROPERTY_ROTATE = 2; +const sal_Int32 COMMAND_PROPERTY_MIRROR = 4; + +namespace framework +{ + +// Configuration access class for PopupMenuControllerFactory implementation + +namespace { + +class ConfigurationAccess_UICommand : // Order is necessary for right initialization! + public ::cppu::WeakImplHelper<XNameAccess,XContainerListener> +{ + osl::Mutex m_aMutex; + public: + ConfigurationAccess_UICommand( std::u16string_view aModuleName, const Reference< XNameAccess >& xGenericUICommands, const Reference< XComponentContext >& rxContext ); + virtual ~ConfigurationAccess_UICommand() override; + + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override; + + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType() override; + + virtual sal_Bool SAL_CALL hasElements() override; + + // container.XContainerListener + virtual void SAL_CALL elementInserted( const ContainerEvent& aEvent ) override; + virtual void SAL_CALL elementRemoved ( const ContainerEvent& aEvent ) override; + virtual void SAL_CALL elementReplaced( const ContainerEvent& aEvent ) override; + + // lang.XEventListener + virtual void SAL_CALL disposing( const EventObject& aEvent ) override; + + protected: + css::uno::Any getByNameImpl( const OUString& aName ); + + struct CmdToInfoMap + { + CmdToInfoMap() : bPopup( false ), + bCommandNameCreated( false ), + bIsExperimental( false ), + nProperties( 0 ) {} + + OUString aLabel; + OUString aContextLabel; + OUString aCommandName; + OUString aPopupLabel; + OUString aTooltipLabel; + OUString aTargetURL; + bool bPopup : 1, + bCommandNameCreated : 1; + bool bIsExperimental; + sal_Int32 nProperties; + }; + + Any getSequenceFromCache( const OUString& aCommandURL ); + Any getInfoFromCommand( const OUString& rCommandURL ); + void fillInfoFromResult( CmdToInfoMap& rCmdInfo, const OUString& aLabel ); + Sequence< OUString > getAllCommands(); + void fillCache(); + void addGenericInfoToCache(); + void impl_fill(const Reference< XNameAccess >& _xConfigAccess,bool _bPopup, + std::vector< OUString >& aImageCommandVector, + std::vector< OUString >& aImageRotateVector, + std::vector< OUString >& aImageMirrorVector); + + private: + typedef std::unordered_map< OUString, + CmdToInfoMap > CommandToInfoCache; + + void initializeConfigAccess(); + + OUString m_aConfigCmdAccess; + OUString m_aConfigPopupAccess; + OUString m_aPropProperties; + Reference< XNameAccess > m_xGenericUICommands; + Reference< XMultiServiceFactory > m_xConfigProvider; + Reference< XNameAccess > m_xConfigAccess; + Reference< XContainerListener > m_xConfigListener; + Reference< XNameAccess > m_xConfigAccessPopups; + Reference< XContainerListener > m_xConfigAccessListener; + Sequence< OUString > m_aCommandImageList; + Sequence< OUString > m_aCommandRotateImageList; + Sequence< OUString > m_aCommandMirrorImageList; + CommandToInfoCache m_aCmdInfoCache; + bool m_bConfigAccessInitialized; + bool m_bCacheFilled; + bool m_bGenericDataRetrieved; +}; + +} + + +// XInterface, XTypeProvider + +ConfigurationAccess_UICommand::ConfigurationAccess_UICommand( std::u16string_view aModuleName, const Reference< XNameAccess >& rGenericUICommands, const Reference< XComponentContext>& rxContext ) : + // Create configuration hierarchical access name + m_aConfigCmdAccess( + OUString::Concat(CONFIGURATION_ROOT_ACCESS) + aModuleName + "/UserInterface/Commands"), + m_aConfigPopupAccess( + OUString::Concat(CONFIGURATION_ROOT_ACCESS) + aModuleName + "/UserInterface/Popups"), + m_aPropProperties( "Properties" ), + m_xGenericUICommands( rGenericUICommands ), + m_xConfigProvider( theDefaultProvider::get( rxContext ) ), + m_bConfigAccessInitialized( false ), + m_bCacheFilled( false ), + m_bGenericDataRetrieved( false ) +{ +} + +ConfigurationAccess_UICommand::~ConfigurationAccess_UICommand() +{ + // SAFE + osl::MutexGuard g(m_aMutex); + Reference< XContainer > xContainer( m_xConfigAccess, UNO_QUERY ); + if ( xContainer.is() ) + xContainer->removeContainerListener(m_xConfigListener); + xContainer.set( m_xConfigAccessPopups, UNO_QUERY ); + if ( xContainer.is() ) + xContainer->removeContainerListener(m_xConfigAccessListener); +} + +// XNameAccess +Any ConfigurationAccess_UICommand::getByNameImpl( const OUString& rCommandURL ) +{ + osl::MutexGuard g(m_aMutex); + if ( !m_bConfigAccessInitialized ) + { + initializeConfigAccess(); + m_bConfigAccessInitialized = true; + fillCache(); + } + + if ( rCommandURL.startsWith( PRIVATE_RESOURCE_URL ) ) + { + // special keys to retrieve information about a set of commands + // SAFE + addGenericInfoToCache(); + + if ( rCommandURL.equalsIgnoreAsciiCase( UICOMMANDDESCRIPTION_NAMEACCESS_COMMANDIMAGELIST )) + return Any( m_aCommandImageList ); + else if ( rCommandURL.equalsIgnoreAsciiCase( UICOMMANDDESCRIPTION_NAMEACCESS_COMMANDROTATEIMAGELIST )) + return Any( m_aCommandRotateImageList ); + else if ( rCommandURL.equalsIgnoreAsciiCase( UICOMMANDDESCRIPTION_NAMEACCESS_COMMANDMIRRORIMAGELIST )) + return Any( m_aCommandMirrorImageList ); + else + return Any(); + } + else + { + // SAFE + return getInfoFromCommand( rCommandURL ); + } +} + +Any SAL_CALL ConfigurationAccess_UICommand::getByName( const OUString& rCommandURL ) +{ + Any aRet( getByNameImpl( rCommandURL ) ); + if( !aRet.hasValue() ) + throw NoSuchElementException(); + + return aRet; +} + +Sequence< OUString > SAL_CALL ConfigurationAccess_UICommand::getElementNames() +{ + return getAllCommands(); +} + +sal_Bool SAL_CALL ConfigurationAccess_UICommand::hasByName( const OUString& rCommandURL ) +{ + return getByNameImpl( rCommandURL ).hasValue(); +} + +// XElementAccess +Type SAL_CALL ConfigurationAccess_UICommand::getElementType() +{ + return cppu::UnoType<Sequence< PropertyValue >>::get(); +} + +sal_Bool SAL_CALL ConfigurationAccess_UICommand::hasElements() +{ + // There must are global commands! + return true; +} + +void ConfigurationAccess_UICommand::fillInfoFromResult( CmdToInfoMap& rCmdInfo, const OUString& aLabel ) +{ + OUString aStr(aLabel.replaceAll("%PRODUCTNAME", utl::ConfigManager::getProductName())); + rCmdInfo.aLabel = aStr; + aStr = comphelper::string::stripEnd(aStr, '.'); // Remove "..." from string + rCmdInfo.aCommandName = MnemonicGenerator::EraseAllMnemonicChars(aStr); + rCmdInfo.bCommandNameCreated = true; +} + +Any ConfigurationAccess_UICommand::getSequenceFromCache( const OUString& aCommandURL ) +{ + CommandToInfoCache::iterator pIter = m_aCmdInfoCache.find( aCommandURL ); + if ( pIter != m_aCmdInfoCache.end() ) + { + if ( !pIter->second.bCommandNameCreated ) + fillInfoFromResult( pIter->second, pIter->second.aLabel ); + + static constexpr OUStringLiteral sLabel = u"Label"; + static constexpr OUStringLiteral sName = u"Name"; + static constexpr OUStringLiteral sPopup = u"Popup"; + static constexpr OUStringLiteral sPopupLabel = u"PopupLabel"; + static constexpr OUStringLiteral sTooltipLabel = u"TooltipLabel"; + static constexpr OUStringLiteral sTargetURL = u"TargetURL"; + static constexpr OUStringLiteral sIsExperimental = u"IsExperimental"; + Sequence< PropertyValue > aPropSeq{ + comphelper::makePropertyValue(sLabel, !pIter->second.aContextLabel.isEmpty() + ? Any(pIter->second.aContextLabel) + : Any(pIter->second.aLabel)), + comphelper::makePropertyValue(sName, pIter->second.aCommandName), + comphelper::makePropertyValue(sPopup, pIter->second.bPopup), + comphelper::makePropertyValue(m_aPropProperties, pIter->second.nProperties), + comphelper::makePropertyValue(sPopupLabel, pIter->second.aPopupLabel), + comphelper::makePropertyValue(sTooltipLabel, pIter->second.aTooltipLabel), + comphelper::makePropertyValue(sTargetURL, pIter->second.aTargetURL), + comphelper::makePropertyValue(sIsExperimental, pIter->second.bIsExperimental) + }; + return Any( aPropSeq ); + } + + return Any(); +} +void ConfigurationAccess_UICommand::impl_fill(const Reference< XNameAccess >& _xConfigAccess,bool _bPopup, + std::vector< OUString >& aImageCommandVector, + std::vector< OUString >& aImageRotateVector, + std::vector< OUString >& aImageMirrorVector) +{ + if ( !_xConfigAccess.is() ) + return; + + Sequence< OUString> aNameSeq = _xConfigAccess->getElementNames(); + const sal_Int32 nCount = aNameSeq.getLength(); + for ( sal_Int32 i = 0; i < nCount; i++ ) + { + try + { + Reference< XNameAccess > xNameAccess(_xConfigAccess->getByName( aNameSeq[i] ),UNO_QUERY); + if ( xNameAccess.is() ) + { + CmdToInfoMap aCmdToInfo; + + aCmdToInfo.bPopup = _bPopup; + xNameAccess->getByName( "Label" ) >>= aCmdToInfo.aLabel; + xNameAccess->getByName( "ContextLabel" ) >>= aCmdToInfo.aContextLabel; + xNameAccess->getByName( "PopupLabel" ) >>= aCmdToInfo.aPopupLabel; + xNameAccess->getByName( "TooltipLabel" ) >>= aCmdToInfo.aTooltipLabel; + xNameAccess->getByName( "TargetURL" ) >>= aCmdToInfo.aTargetURL; + xNameAccess->getByName( "IsExperimental" ) >>= aCmdToInfo.bIsExperimental; + xNameAccess->getByName( m_aPropProperties ) >>= aCmdToInfo.nProperties; + + m_aCmdInfoCache.emplace( aNameSeq[i], aCmdToInfo ); + + if ( aCmdToInfo.nProperties & COMMAND_PROPERTY_IMAGE ) + aImageCommandVector.push_back( aNameSeq[i] ); + if ( aCmdToInfo.nProperties & COMMAND_PROPERTY_ROTATE ) + aImageRotateVector.push_back( aNameSeq[i] ); + if ( aCmdToInfo.nProperties & COMMAND_PROPERTY_MIRROR ) + aImageMirrorVector.push_back( aNameSeq[i] ); + } + } + catch (const css::lang::WrappedTargetException&) + { + } + catch (const css::container::NoSuchElementException&) + { + } + } +} +void ConfigurationAccess_UICommand::fillCache() +{ + + if ( m_bCacheFilled ) + return; + + std::vector< OUString > aImageCommandVector; + std::vector< OUString > aImageRotateVector; + std::vector< OUString > aImageMirrorVector; + + impl_fill(m_xConfigAccess,false,aImageCommandVector,aImageRotateVector,aImageMirrorVector); + impl_fill(m_xConfigAccessPopups,true,aImageCommandVector,aImageRotateVector,aImageMirrorVector); + // Create cached sequences for fast retrieving + m_aCommandImageList = comphelper::containerToSequence( aImageCommandVector ); + m_aCommandRotateImageList = comphelper::containerToSequence( aImageRotateVector ); + m_aCommandMirrorImageList = comphelper::containerToSequence( aImageMirrorVector ); + + m_bCacheFilled = true; +} + +void ConfigurationAccess_UICommand::addGenericInfoToCache() +{ + if ( !m_xGenericUICommands.is() || m_bGenericDataRetrieved ) + return; + + Sequence< OUString > aCommandNameSeq; + try + { + if ( m_xGenericUICommands->getByName( + UICOMMANDDESCRIPTION_NAMEACCESS_COMMANDROTATEIMAGELIST ) >>= aCommandNameSeq ) + m_aCommandRotateImageList = comphelper::concatSequences< OUString >( m_aCommandRotateImageList, aCommandNameSeq ); + } + catch (const RuntimeException&) + { + throw; + } + catch (const Exception&) + { + } + + try + { + if ( m_xGenericUICommands->getByName( + UICOMMANDDESCRIPTION_NAMEACCESS_COMMANDMIRRORIMAGELIST ) >>= aCommandNameSeq ) + m_aCommandMirrorImageList = comphelper::concatSequences< OUString >( m_aCommandMirrorImageList, aCommandNameSeq ); + } + catch (const RuntimeException&) + { + throw; + } + catch (const Exception&) + { + } + + m_bGenericDataRetrieved = true; +} + +Any ConfigurationAccess_UICommand::getInfoFromCommand( const OUString& rCommandURL ) +{ + Any a; + + try + { + a = getSequenceFromCache( rCommandURL ); + if ( !a.hasValue() ) + { + // First try to ask our global commands configuration access. It also caches maybe + // we find the entry in its cache first. + if ( m_xGenericUICommands.is() && m_xGenericUICommands->hasByName( rCommandURL ) ) + { + try + { + return m_xGenericUICommands->getByName( rCommandURL ); + } + catch (const css::lang::WrappedTargetException&) + { + } + catch (const css::container::NoSuchElementException&) + { + } + } + } + } + catch (const css::container::NoSuchElementException&) + { + } + catch (const css::lang::WrappedTargetException&) + { + } + + return a; +} + +Sequence< OUString > ConfigurationAccess_UICommand::getAllCommands() +{ + // SAFE + osl::MutexGuard g(m_aMutex); + + if ( !m_bConfigAccessInitialized ) + { + initializeConfigAccess(); + m_bConfigAccessInitialized = true; + fillCache(); + } + + if ( m_xConfigAccess.is() ) + { + try + { + Sequence< OUString > aNameSeq = m_xConfigAccess->getElementNames(); + + if ( m_xGenericUICommands.is() ) + { + // Create concat list of supported user interface commands of the module + Sequence< OUString > aGenericNameSeq = m_xGenericUICommands->getElementNames(); + sal_uInt32 nCount1 = aNameSeq.getLength(); + sal_uInt32 nCount2 = aGenericNameSeq.getLength(); + + aNameSeq.realloc( nCount1 + nCount2 ); + OUString* pNameSeq = aNameSeq.getArray(); + const OUString* pGenericSeq = aGenericNameSeq.getConstArray(); + for ( sal_uInt32 i = 0; i < nCount2; i++ ) + pNameSeq[nCount1+i] = pGenericSeq[i]; + } + + return aNameSeq; + } + catch (const css::container::NoSuchElementException&) + { + } + catch (const css::lang::WrappedTargetException&) + { + } + } + + return Sequence< OUString >(); +} + +void ConfigurationAccess_UICommand::initializeConfigAccess() +{ + try + { + Sequence<Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"nodepath", Any(m_aConfigCmdAccess)} + })); + m_xConfigAccess.set( m_xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", aArgs ),UNO_QUERY ); + if ( m_xConfigAccess.is() ) + { + // Add as container listener + Reference< XContainer > xContainer( m_xConfigAccess, UNO_QUERY ); + if ( xContainer.is() ) + { + m_xConfigListener = new WeakContainerListener(this); + xContainer->addContainerListener(m_xConfigListener); + } + } + + Sequence<Any> aArgs2(comphelper::InitAnyPropertySequence( + { + {"nodepath", Any(m_aConfigPopupAccess)} + })); + m_xConfigAccessPopups.set( m_xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", aArgs2 ),UNO_QUERY ); + if ( m_xConfigAccessPopups.is() ) + { + // Add as container listener + Reference< XContainer > xContainer( m_xConfigAccessPopups, UNO_QUERY ); + if ( xContainer.is() ) + { + m_xConfigAccessListener = new WeakContainerListener(this); + xContainer->addContainerListener(m_xConfigAccessListener); + } + } + } + catch (const WrappedTargetException&) + { + } + catch (const Exception&) + { + } +} + +// container.XContainerListener +void SAL_CALL ConfigurationAccess_UICommand::elementInserted( const ContainerEvent& ) +{ + osl::MutexGuard g(m_aMutex); + m_bCacheFilled = false; + fillCache(); +} + +void SAL_CALL ConfigurationAccess_UICommand::elementRemoved( const ContainerEvent& ) +{ + osl::MutexGuard g(m_aMutex); + m_bCacheFilled = false; + fillCache(); +} + +void SAL_CALL ConfigurationAccess_UICommand::elementReplaced( const ContainerEvent& ) +{ + osl::MutexGuard g(m_aMutex); + m_bCacheFilled = false; + fillCache(); +} + +// lang.XEventListener +void SAL_CALL ConfigurationAccess_UICommand::disposing( const EventObject& aEvent ) +{ + // SAFE + // remove our reference to the config access + osl::MutexGuard g(m_aMutex); + + Reference< XInterface > xIfac1( aEvent.Source, UNO_QUERY ); + Reference< XInterface > xIfac2( m_xConfigAccess, UNO_QUERY ); + if ( xIfac1 == xIfac2 ) + m_xConfigAccess.clear(); + else + { + xIfac1.set( m_xConfigAccessPopups, UNO_QUERY ); + if ( xIfac1 == xIfac2 ) + m_xConfigAccessPopups.clear(); + } +} + +void UICommandDescription::ensureGenericUICommandsForLanguage(const LanguageTag& rLanguage) +{ + auto xGenericUICommands = m_xGenericUICommands.find(rLanguage); + if (xGenericUICommands == m_xGenericUICommands.end()) + { + Reference< XNameAccess > xEmpty; + m_xGenericUICommands[rLanguage] = new ConfigurationAccess_UICommand( u"GenericCommands", xEmpty, m_xContext ); + } +} + +UICommandDescription::UICommandDescription(const Reference< XComponentContext >& rxContext) + : m_aPrivateResourceURL(PRIVATE_RESOURCE_URL) + , m_xContext(rxContext) +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + + ensureGenericUICommandsForLanguage(rCurrentLanguage); + + impl_fillElements("ooSetupFactoryCommandConfigRef"); + + // insert generic commands + auto& rMap = m_aUICommandsHashMap[rCurrentLanguage]; + UICommandsHashMap::iterator pIter = rMap.find( "GenericCommands" ); + if ( pIter != rMap.end() ) + pIter->second = m_xGenericUICommands[rCurrentLanguage]; +} + +UICommandDescription::UICommandDescription(const Reference< XComponentContext >& rxContext, bool) + : m_xContext(rxContext) +{ +} + +UICommandDescription::~UICommandDescription() +{ + std::unique_lock g(m_aMutex); + m_aModuleToCommandFileMap.clear(); + m_aUICommandsHashMap.clear(); + m_xGenericUICommands.clear(); +} +void UICommandDescription::impl_fillElements(const char* _pName) +{ + m_xModuleManager.set( ModuleManager::create( m_xContext ) ); + const Sequence< OUString > aElementNames = m_xModuleManager->getElementNames(); + + SvtSysLocale aSysLocale; + + for ( OUString const & aModuleIdentifier : aElementNames ) + { + Sequence< PropertyValue > aSeq; + if ( m_xModuleManager->getByName( aModuleIdentifier ) >>= aSeq ) + { + OUString aCommandStr; + for ( PropertyValue const & prop : std::as_const(aSeq) ) + { + if ( prop.Name.equalsAscii(_pName) ) + { + prop.Value >>= aCommandStr; + break; + } + } + + // Create first mapping ModuleIdentifier ==> Command File + m_aModuleToCommandFileMap.emplace( aModuleIdentifier, aCommandStr ); + + // Create second mapping Command File ==> commands instance + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + auto& rMap = m_aUICommandsHashMap[rCurrentLanguage]; + UICommandsHashMap::iterator pIter = rMap.find( aCommandStr ); + if ( pIter == rMap.end() ) + rMap.emplace( aCommandStr, Reference< XNameAccess >() ); + } + } // for ( sal_Int32 i = 0; i < aElementNames.(); i++ ) +} + +Any SAL_CALL UICommandDescription::getByName( const OUString& aName ) +{ + SvtSysLocale aSysLocale; + const LanguageTag& rCurrentLanguage = aSysLocale.GetUILanguageTag(); + Any a; + + std::unique_lock g(m_aMutex); + + ModuleToCommandFileMap::const_iterator pM2CIter = m_aModuleToCommandFileMap.find( aName ); + if ( pM2CIter != m_aModuleToCommandFileMap.end() ) + { + OUString aCommandFile( pM2CIter->second ); + auto pMapIter = m_aUICommandsHashMap.find( rCurrentLanguage ); + if ( pMapIter == m_aUICommandsHashMap.end() ) + impl_fillElements("ooSetupFactoryCommandConfigRef"); + + auto& rMap = m_aUICommandsHashMap[rCurrentLanguage]; + UICommandsHashMap::iterator pIter = rMap.find( aCommandFile ); + if ( pIter != rMap.end() ) + { + if ( pIter->second.is() ) + a <<= pIter->second; + else + { + ensureGenericUICommandsForLanguage(rCurrentLanguage); + + Reference< XNameAccess > xUICommands = new ConfigurationAccess_UICommand( aCommandFile, + m_xGenericUICommands[rCurrentLanguage], + m_xContext ); + pIter->second = xUICommands; + a <<= xUICommands; + } + } + } + else if ( !m_aPrivateResourceURL.isEmpty() && aName.startsWith( m_aPrivateResourceURL ) ) + { + ensureGenericUICommandsForLanguage(rCurrentLanguage); + + // special keys to retrieve information about a set of commands + return m_xGenericUICommands[rCurrentLanguage]->getByName( aName ); + } + else + { + throw NoSuchElementException(); + } + + return a; +} + +Sequence< OUString > SAL_CALL UICommandDescription::getElementNames() +{ + std::unique_lock g(m_aMutex); + + return comphelper::mapKeysToSequence( m_aModuleToCommandFileMap ); +} + +sal_Bool SAL_CALL UICommandDescription::hasByName( const OUString& aName ) +{ + std::unique_lock g(m_aMutex); + + ModuleToCommandFileMap::const_iterator pIter = m_aModuleToCommandFileMap.find( aName ); + return ( pIter != m_aModuleToCommandFileMap.end() ); +} + +// XElementAccess +Type SAL_CALL UICommandDescription::getElementType() +{ + return cppu::UnoType<XNameAccess>::get(); +} + +sal_Bool SAL_CALL UICommandDescription::hasElements() +{ + // generic UI commands are always available! + return true; +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_UICommandDescription_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new framework::UICommandDescription(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uifactory/addonstoolbarfactory.cxx b/framework/source/uifactory/addonstoolbarfactory.cxx new file mode 100644 index 000000000..b9a1c95c9 --- /dev/null +++ b/framework/source/uifactory/addonstoolbarfactory.cxx @@ -0,0 +1,206 @@ +/* -*- 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 <string_view> + +#include <uielement/addonstoolbarwrapper.hxx> + +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XModuleManager2.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/ui/XUIElementFactory.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <vcl/svapp.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::frame; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace ::com::sun::star::ui; +using namespace framework; + +namespace { + +class AddonsToolBarFactory : public ::cppu::WeakImplHelper< css::lang::XServiceInfo , + css::ui::XUIElementFactory > +{ +public: + explicit AddonsToolBarFactory( const css::uno::Reference< css::uno::XComponentContext >& xContext ); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.AddonsToolBarFactory"; + } + + 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.ToolBarFactory"}; + } + + // XUIElementFactory + virtual css::uno::Reference< css::ui::XUIElement > SAL_CALL createUIElement( const OUString& ResourceURL, const css::uno::Sequence< css::beans::PropertyValue >& Args ) override; + + bool hasButtonsInContext( const css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > >& rPropSeq, + const css::uno::Reference< css::frame::XFrame >& rFrame ); + +private: + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::frame::XModuleManager2 > m_xModuleManager; +}; + +AddonsToolBarFactory::AddonsToolBarFactory( + const css::uno::Reference< css::uno::XComponentContext >& xContext ) : + m_xContext( xContext ) + , m_xModuleManager( ModuleManager::create( xContext ) ) +{ +} + +bool IsCorrectContext( std::u16string_view rModuleIdentifier, std::u16string_view aContextList ) +{ + if ( aContextList.empty() ) + return true; + + if ( !rModuleIdentifier.empty() ) + { + return aContextList.find( rModuleIdentifier ) != std::u16string_view::npos; + } + + return false; +} + +bool AddonsToolBarFactory::hasButtonsInContext( + const Sequence< Sequence< PropertyValue > >& rPropSeqSeq, + const Reference< XFrame >& rFrame ) +{ + OUString aModuleIdentifier; + try + { + aModuleIdentifier = m_xModuleManager->identify( rFrame ); + } + catch ( const RuntimeException& ) + { + throw; + } + catch ( const Exception& ) + { + } + + // Check before we create a toolbar that we have at least one button in + // the current frame context. + for ( Sequence<PropertyValue> const & props : rPropSeqSeq ) + { + bool bIsButton( true ); + bool bIsCorrectContext( false ); + sal_uInt32 nPropChecked( 0 ); + + for ( PropertyValue const & prop : props ) + { + if ( prop.Name == "Context" ) + { + OUString aContextList; + if ( prop.Value >>= aContextList ) + bIsCorrectContext = IsCorrectContext( aModuleIdentifier, aContextList ); + nPropChecked++; + } + else if ( prop.Name == "URL" ) + { + OUString aURL; + prop.Value >>= aURL; + bIsButton = aURL != "private:separator"; + nPropChecked++; + } + + if ( nPropChecked == 2 ) + break; + } + + if ( bIsButton && bIsCorrectContext ) + return true; + } + + return false; +} + +// XUIElementFactory +Reference< XUIElement > SAL_CALL AddonsToolBarFactory::createUIElement( + const OUString& ResourceURL, + const Sequence< PropertyValue >& Args ) +{ + SolarMutexGuard g; + + Sequence< Sequence< PropertyValue > > aConfigData; + Reference< XFrame > xFrame; + OUString aResourceURL( ResourceURL ); + + for ( PropertyValue const & arg : Args ) + { + if ( arg.Name == "ConfigurationData" ) + arg.Value >>= aConfigData; + else if ( arg.Name == "Frame" ) + arg.Value >>= xFrame; + else if ( arg.Name == "ResourceURL" ) + arg.Value >>= aResourceURL; + } + + if ( !aResourceURL.startsWith("private:resource/toolbar/addon_") ) + throw IllegalArgumentException(); + + // Identify frame and determine module identifier to look for context based buttons + Reference< css::ui::XUIElement > xToolBar; + if ( xFrame.is() && + aConfigData.hasElements() && + hasButtonsInContext( aConfigData, xFrame )) + { + Sequence< Any > aPropSeq{ Any(comphelper::makePropertyValue("Frame", xFrame)), + Any(comphelper::makePropertyValue("ConfigurationData", + aConfigData)), + Any(comphelper::makePropertyValue("ResourceURL", aResourceURL)) }; + + SolarMutexGuard aGuard; + rtl::Reference<AddonsToolBarWrapper> pToolBarWrapper = new AddonsToolBarWrapper( m_xContext ); + xToolBar = pToolBarWrapper; + pToolBarWrapper->initialize( aPropSeq ); + } + + return xToolBar; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_AddonsToolBarFactory_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new AddonsToolBarFactory(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uifactory/factoryconfiguration.cxx b/framework/source/uifactory/factoryconfiguration.cxx new file mode 100644 index 000000000..047a6b6ed --- /dev/null +++ b/framework/source/uifactory/factoryconfiguration.cxx @@ -0,0 +1,289 @@ +/* -*- 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 <string_view> + +#include <uifactory/factoryconfiguration.hxx> +#include <services.h> + +#include <helper/mischelper.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XContainer.hpp> + +#include <comphelper/propertysequence.hxx> +#include <utility> + +// Defines + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::container; + +// Namespace + +namespace framework +{ +static OUString getHashKeyFromStrings( + std::u16string_view aCommandURL, std::u16string_view aModuleName ) +{ + return OUString::Concat(aCommandURL) + "-" + aModuleName; +} + +// XInterface, XTypeProvider + +ConfigurationAccess_ControllerFactory::ConfigurationAccess_ControllerFactory( const Reference< XComponentContext >& rxContext, OUString _sRoot ) : + m_aPropCommand( "Command" ), + m_aPropModule( "Module" ), + m_aPropController( "Controller" ), + m_aPropValue( "Value" ), + m_sRoot(std::move(_sRoot)), + m_bConfigAccessInitialized( false ) +{ + m_xConfigProvider = configuration::theDefaultProvider::get( rxContext ); +} + +ConfigurationAccess_ControllerFactory::~ConfigurationAccess_ControllerFactory() +{ + std::unique_lock g(m_mutex); + + Reference< XContainer > xContainer( m_xConfigAccess, UNO_QUERY ); + if ( xContainer.is() ) + xContainer->removeContainerListener(m_xConfigAccessListener); +} + +OUString ConfigurationAccess_ControllerFactory::getServiceFromCommandModule( std::u16string_view rCommandURL, std::u16string_view rModule ) const +{ + std::unique_lock g(m_mutex); + MenuControllerMap::const_iterator pIter = m_aMenuControllerMap.find( getHashKeyFromStrings( rCommandURL, rModule )); + + if ( pIter != m_aMenuControllerMap.end() ) + return pIter->second.m_aImplementationName; + else if ( !rModule.empty() ) + { + // Try to detect if we have a generic popup menu controller + pIter = m_aMenuControllerMap.find( + getHashKeyFromStrings( rCommandURL, std::u16string_view() )); + + if ( pIter != m_aMenuControllerMap.end() ) + return pIter->second.m_aImplementationName; + } + + return OUString(); +} +OUString ConfigurationAccess_ControllerFactory::getValueFromCommandModule( std::u16string_view rCommandURL, std::u16string_view rModule ) const +{ + std::unique_lock g(m_mutex); + + MenuControllerMap::const_iterator pIter = m_aMenuControllerMap.find( getHashKeyFromStrings( rCommandURL, rModule )); + + if ( pIter != m_aMenuControllerMap.end() ) + return pIter->second.m_aValue; + else if ( !rModule.empty() ) + { + // Try to detect if we have a generic popup menu controller + pIter = m_aMenuControllerMap.find( + getHashKeyFromStrings( rCommandURL, std::u16string_view() )); + + if ( pIter != m_aMenuControllerMap.end() ) + return pIter->second.m_aValue; + } + + return OUString(); +} + +void ConfigurationAccess_ControllerFactory::addServiceToCommandModule( + std::u16string_view rCommandURL, + std::u16string_view rModule, + const OUString& rServiceSpecifier ) +{ + std::unique_lock g(m_mutex); + + OUString aHashKey = getHashKeyFromStrings( rCommandURL, rModule ); + m_aMenuControllerMap.emplace( aHashKey,ControllerInfo(rServiceSpecifier,OUString()) ); +} + +void ConfigurationAccess_ControllerFactory::removeServiceFromCommandModule( + std::u16string_view rCommandURL, + std::u16string_view rModule ) +{ + std::unique_lock g(m_mutex); + + OUString aHashKey = getHashKeyFromStrings( rCommandURL, rModule ); + m_aMenuControllerMap.erase( aHashKey ); +} + +// container.XContainerListener +void SAL_CALL ConfigurationAccess_ControllerFactory::elementInserted( const ContainerEvent& aEvent ) +{ + OUString aCommand; + OUString aModule; + OUString aService; + OUString aValue; + + std::unique_lock g(m_mutex); + + if ( impl_getElementProps( aEvent.Element, aCommand, aModule, aService, aValue )) + { + // Create hash key from command and module as they are together a primary key to + // the UNO service that implements the popup menu controller. + OUString aHashKey( getHashKeyFromStrings( aCommand, aModule )); + ControllerInfo& rControllerInfo = m_aMenuControllerMap[ aHashKey ]; + rControllerInfo.m_aImplementationName = aService; + rControllerInfo.m_aValue = aValue; + } +} + +void SAL_CALL ConfigurationAccess_ControllerFactory::elementRemoved ( const ContainerEvent& aEvent ) +{ + OUString aCommand; + OUString aModule; + OUString aService; + OUString aValue; + + std::unique_lock g(m_mutex); + + if ( impl_getElementProps( aEvent.Element, aCommand, aModule, aService, aValue )) + { + // Create hash key from command and module as they are together a primary key to + // the UNO service that implements the popup menu controller. + OUString aHashKey( getHashKeyFromStrings( aCommand, aModule )); + m_aMenuControllerMap.erase( aHashKey ); + } +} + +void SAL_CALL ConfigurationAccess_ControllerFactory::elementReplaced( const ContainerEvent& aEvent ) +{ + elementInserted(aEvent); +} + +// lang.XEventListener +void SAL_CALL ConfigurationAccess_ControllerFactory::disposing( const EventObject& ) +{ + // remove our reference to the config access + std::unique_lock g(m_mutex); + m_xConfigAccess.clear(); +} + +void ConfigurationAccess_ControllerFactory::readConfigurationData() +{ + // SAFE + std::unique_lock aLock( m_mutex ); + + if ( !m_bConfigAccessInitialized ) + { + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(m_sRoot)} + })); + try + { + m_xConfigAccess.set( m_xConfigProvider->createInstanceWithArguments(SERVICENAME_CFGREADACCESS,aArgs ), UNO_QUERY ); + } + catch ( const WrappedTargetException& ) + { + } + + m_bConfigAccessInitialized = true; + } + + if ( !m_xConfigAccess.is() ) + return; + + // Read and update configuration data + updateConfigurationDataImpl(); + + uno::Reference< container::XContainer > xContainer( m_xConfigAccess, uno::UNO_QUERY ); + // UNSAFE + aLock.unlock(); + + if ( xContainer.is() ) + { + m_xConfigAccessListener = new WeakContainerListener(this); + xContainer->addContainerListener(m_xConfigAccessListener); + } +} + +void ConfigurationAccess_ControllerFactory::updateConfigurationDataImpl() +{ + const Sequence< OUString > aPopupMenuControllers = m_xConfigAccess->getElementNames(); + + OUString aCommand; + OUString aModule; + OUString aService; + OUString aHashKey; + OUString aValue; + + m_aMenuControllerMap.clear(); + for ( OUString const & name : aPopupMenuControllers ) + { + try + { + if ( impl_getElementProps( m_xConfigAccess->getByName( name ), aCommand, aModule, aService,aValue )) + { + // Create hash key from command and module as they are together a primary key to + // the UNO service that implements the popup menu controller. + aHashKey = getHashKeyFromStrings( aCommand, aModule ); + m_aMenuControllerMap.emplace( aHashKey, ControllerInfo(aService,aValue) ); + } + } + catch ( const NoSuchElementException& ) + { + } + catch ( const WrappedTargetException& ) + { + } + } +} + +bool ConfigurationAccess_ControllerFactory::impl_getElementProps( const Any& aElement, OUString& aCommand, OUString& aModule, OUString& aServiceSpecifier,OUString& aValue ) const +{ + Reference< XPropertySet > xPropertySet; + aElement >>= xPropertySet; + + if ( !xPropertySet.is() ) + return true; + + try + { + xPropertySet->getPropertyValue( m_aPropCommand ) >>= aCommand; + xPropertySet->getPropertyValue( m_aPropModule ) >>= aModule; + xPropertySet->getPropertyValue( m_aPropController ) >>= aServiceSpecifier; + xPropertySet->getPropertyValue( m_aPropValue ) >>= aValue; + } + catch ( const css::beans::UnknownPropertyException& ) + { + return false; + } + catch ( const css::lang::WrappedTargetException& ) + { + return false; + } + + return true; +} +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uifactory/menubarfactory.cxx b/framework/source/uifactory/menubarfactory.cxx new file mode 100644 index 000000000..febc33066 --- /dev/null +++ b/framework/source/uifactory/menubarfactory.cxx @@ -0,0 +1,172 @@ +/* -*- 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 <uifactory/menubarfactory.hxx> + +#include <uielement/menubarwrapper.hxx> + +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <utility> +#include <vcl/svapp.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::frame; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace ::com::sun::star::ui; + +namespace framework +{ + +MenuBarFactory::MenuBarFactory( css::uno::Reference< css::uno::XComponentContext > xContext ) + : m_xContext(std::move( xContext )) +{ +} + +MenuBarFactory::~MenuBarFactory() +{ +} + +// XUIElementFactory +Reference< XUIElement > SAL_CALL MenuBarFactory::createUIElement( + const OUString& ResourceURL, + const Sequence< PropertyValue >& Args ) +{ + Reference< css::ui::XUIElement > xMenuBar = new MenuBarWrapper(m_xContext); + CreateUIElement(ResourceURL, Args, u"private:resource/menubar/", xMenuBar, m_xContext); + return xMenuBar; +} + +void MenuBarFactory::CreateUIElement(const OUString& ResourceURL + ,const Sequence< PropertyValue >& Args + ,std::u16string_view ResourceType + ,const Reference< css::ui::XUIElement >& _xMenuBar + ,const css::uno::Reference< css::uno::XComponentContext >& _rxContext) +{ + sal_Int32 nConfigPropertyIndex( Args.getLength() ); + sal_Int32 nURLPropertyIndex( Args.getLength() ); + Reference< XUIConfigurationManager > xCfgMgr; + Reference< XFrame > xFrame; + OUString aResourceURL( ResourceURL ); + + for ( sal_Int32 n = 0; n < Args.getLength(); n++ ) + { + if ( Args[n].Name == "ConfigurationSource" ) + { + nConfigPropertyIndex = n; + Args[n].Value >>= xCfgMgr; + } + else if ( Args[n].Name == "ResourceURL" ) + { + nURLPropertyIndex = n; + Args[n].Value >>= aResourceURL; + } + else if ( Args[n].Name == "Frame" ) + Args[n].Value >>= xFrame; + } + if (!aResourceURL.startsWith(ResourceType)) + throw IllegalArgumentException(); + + // Identify frame and determine document based ui configuration manager/module ui configuration manager + if ( xFrame.is() && !xCfgMgr.is() ) + { + bool bHasSettings( false ); + Reference< XModel > xModel; + + Reference< XController > xController = xFrame->getController(); + if ( xController.is() ) + xModel = xController->getModel(); + + if ( xModel.is() ) + { + Reference< XUIConfigurationManagerSupplier > xUIConfigurationManagerSupplier( xModel, UNO_QUERY ); + if ( xUIConfigurationManagerSupplier.is() ) + { + xCfgMgr = xUIConfigurationManagerSupplier->getUIConfigurationManager(); + bHasSettings = xCfgMgr->hasSettings( aResourceURL ); + } + } + + if ( !bHasSettings ) + { + Reference< css::frame::XModuleManager2 > xModuleManager = + ModuleManager::create( _rxContext ); + OUString aModuleIdentifier = xModuleManager->identify( Reference<XInterface>( xFrame, UNO_QUERY ) ); + if ( !aModuleIdentifier.isEmpty() ) + { + Reference< XModuleUIConfigurationManagerSupplier > xModuleCfgSupplier = + theModuleUIConfigurationManagerSupplier::get( _rxContext ); + xCfgMgr = xModuleCfgSupplier->getUIConfigurationManager( aModuleIdentifier ); + } + } + } + + sal_Int32 nSeqLength( Args.getLength() ); + if ( Args.getLength() == nConfigPropertyIndex ) + nSeqLength++; + if ( Args.getLength() == nURLPropertyIndex ) + nSeqLength++; + if ( nConfigPropertyIndex == nURLPropertyIndex ) + nURLPropertyIndex++; + + Sequence< Any > aPropSeq( nSeqLength ); + auto aPropSeqRange = asNonConstRange(aPropSeq); + for ( sal_Int32 n = 0; n < aPropSeq.getLength(); n++ ) + { + PropertyValue aPropValue; + if ( n == nURLPropertyIndex ) + { + aPropValue.Name = "ResourceURL"; + aPropValue.Value <<= aResourceURL; + } + else if ( n == nConfigPropertyIndex ) + { + aPropValue.Name = "ConfigurationSource"; + aPropValue.Value <<= xCfgMgr; + } + else + aPropValue = Args[n]; + + aPropSeqRange[n] <<= aPropValue; + } + + SolarMutexGuard aGuard; + Reference< XInitialization > xInit( _xMenuBar, UNO_QUERY ); + xInit->initialize( aPropSeq ); +} + +} // namespace framework + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_MenuBarFactory_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new framework::MenuBarFactory(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uifactory/statusbarfactory.cxx b/framework/source/uifactory/statusbarfactory.cxx new file mode 100644 index 000000000..2914dc4cc --- /dev/null +++ b/framework/source/uifactory/statusbarfactory.cxx @@ -0,0 +1,84 @@ +/* -*- 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 <uifactory/menubarfactory.hxx> +#include <uielement/statusbarwrapper.hxx> + +#include <cppuhelper/supportsservice.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace ::com::sun::star::ui; + +using namespace framework; + +namespace { + +class StatusBarFactory : public MenuBarFactory +{ +public: + explicit StatusBarFactory( const css::uno::Reference< css::uno::XComponentContext >& xContext ); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.StatusBarFactory"; + } + + 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.StatusBarFactory"}; + } + + // XUIElementFactory + virtual css::uno::Reference< css::ui::XUIElement > SAL_CALL createUIElement( const OUString& ResourceURL, const css::uno::Sequence< css::beans::PropertyValue >& Args ) override; +}; + +StatusBarFactory::StatusBarFactory( const css::uno::Reference< css::uno::XComponentContext >& xContext ) : + MenuBarFactory( xContext ) +{ +} + +// XUIElementFactory +Reference< XUIElement > SAL_CALL StatusBarFactory::createUIElement( + const OUString& ResourceURL, + const Sequence< PropertyValue >& Args ) +{ + Reference< css::ui::XUIElement > xStatusBar = new StatusBarWrapper(m_xContext); + MenuBarFactory::CreateUIElement(ResourceURL, Args, u"private:resource/statusbar/", xStatusBar, m_xContext); + return xStatusBar; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_StatusBarFactory_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new StatusBarFactory(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uifactory/toolbarfactory.cxx b/framework/source/uifactory/toolbarfactory.cxx new file mode 100644 index 000000000..1f9bb6114 --- /dev/null +++ b/framework/source/uifactory/toolbarfactory.cxx @@ -0,0 +1,84 @@ +/* -*- 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 <cppuhelper/supportsservice.hxx> +#include <vcl/svapp.hxx> +#include <uielement/toolbarwrapper.hxx> +#include <uifactory/menubarfactory.hxx> + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace ::com::sun::star::ui; +using namespace framework; + +namespace { + +class ToolBarFactory : public MenuBarFactory +{ +public: + explicit ToolBarFactory( const css::uno::Reference< css::uno::XComponentContext >& xContext ); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.ToolBarFactory"; + } + + 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.ToolBarFactory"}; + } + + // XUIElementFactory + virtual css::uno::Reference< css::ui::XUIElement > SAL_CALL createUIElement( + const OUString& ResourceURL, const css::uno::Sequence< css::beans::PropertyValue >& Args ) override; +}; + +ToolBarFactory::ToolBarFactory( const css::uno::Reference< css::uno::XComponentContext >& xContext ) : + MenuBarFactory( xContext ) +{ +} + +// XUIElementFactory +Reference< XUIElement > SAL_CALL ToolBarFactory::createUIElement( + const OUString& ResourceURL, + const Sequence< PropertyValue >& Args ) +{ + Reference< css::ui::XUIElement > xToolBar = new ToolBarWrapper(m_xContext); + CreateUIElement(ResourceURL, Args, u"private:resource/toolbar/", xToolBar, m_xContext); + return xToolBar; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ToolBarFactory_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new ToolBarFactory(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uifactory/uicontrollerfactory.cxx b/framework/source/uifactory/uicontrollerfactory.cxx new file mode 100644 index 000000000..5f2390076 --- /dev/null +++ b/framework/source/uifactory/uicontrollerfactory.cxx @@ -0,0 +1,341 @@ +/* -*- 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 <string_view> + +#include <uifactory/factoryconfiguration.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/frame/XUIControllerFactory.hpp> + +#include <rtl/ref.hxx> +#include <comphelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +using namespace css::uno; +using namespace css::lang; +using namespace css::beans; +using namespace css::container; +using namespace css::frame; +using namespace framework; + +namespace { + +typedef comphelper::WeakComponentImplHelper< + css::lang::XServiceInfo, + css::frame::XUIControllerFactory > UIControllerFactory_BASE; + +class UIControllerFactory : public UIControllerFactory_BASE +{ +public: + virtual ~UIControllerFactory() override; + + // XMultiComponentFactory + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstanceWithContext( const OUString& aServiceSpecifier, const css::uno::Reference< css::uno::XComponentContext >& Context ) override; + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstanceWithArgumentsAndContext( const OUString& ServiceSpecifier, const css::uno::Sequence< css::uno::Any >& Arguments, const css::uno::Reference< css::uno::XComponentContext >& Context ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getAvailableServiceNames() override; + + // XUIControllerRegistration + virtual sal_Bool SAL_CALL hasController( const OUString& aCommandURL, const OUString& aModuleName ) override; + virtual void SAL_CALL registerController( const OUString& aCommandURL, const OUString& aModuleName, const OUString& aControllerImplementationName ) override; + virtual void SAL_CALL deregisterController( const OUString& aCommandURL, const OUString& aModuleName ) override; + +protected: + UIControllerFactory( const css::uno::Reference< css::uno::XComponentContext >& xContext, std::u16string_view rUINode ); + bool m_bConfigRead; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + rtl::Reference<ConfigurationAccess_ControllerFactory> m_pConfigAccess; + +private: + virtual void disposing(std::unique_lock<std::mutex>&) final override; +}; + +UIControllerFactory::UIControllerFactory( + const Reference< XComponentContext >& xContext, + std::u16string_view rConfigurationNode ) + : m_bConfigRead( false ) + , m_xContext( xContext ) +{ + m_pConfigAccess = new ConfigurationAccess_ControllerFactory(m_xContext, + OUString::Concat("/org.openoffice.Office.UI.Controller/Registered/") + + rConfigurationNode); +} + +UIControllerFactory::~UIControllerFactory() +{ + std::unique_lock g(m_aMutex); + disposing(g); +} + +void UIControllerFactory::disposing(std::unique_lock<std::mutex>&) +{ + m_pConfigAccess.clear(); +} + +// XMultiComponentFactory +Reference< XInterface > SAL_CALL UIControllerFactory::createInstanceWithContext( + const OUString& aServiceSpecifier, + const Reference< XComponentContext >& ) +{ + // SAFE + std::unique_lock g(m_aMutex); + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + m_pConfigAccess->readConfigurationData(); + } + + OUString aServiceName = m_pConfigAccess->getServiceFromCommandModule( aServiceSpecifier, std::u16string_view() ); + if ( !aServiceName.isEmpty() ) + return m_xContext->getServiceManager()->createInstanceWithContext( aServiceName, m_xContext ); + else + return Reference< XInterface >(); + // SAFE +} + +Reference< XInterface > SAL_CALL UIControllerFactory::createInstanceWithArgumentsAndContext( + const OUString& ServiceSpecifier, + const Sequence< Any >& Arguments, + const Reference< XComponentContext >& ) +{ + static const OUStringLiteral aPropModuleName( u"ModuleIdentifier" ); + + OUString aPropName; + PropertyValue aPropValue; + + // Retrieve the optional module name from the Arguments sequence. It is used as a part of + // the hash map key to support different controller implementation for the same URL but different + // module!! + for ( Any const & arg : Arguments ) + { + if (( arg >>= aPropValue ) && ( aPropValue.Name == aPropModuleName )) + { + aPropValue.Value >>= aPropName; + break; + } + } + + Sequence< Any > aNewArgs( Arguments ); + + sal_Int32 nAppendIndex = aNewArgs.getLength(); + aNewArgs.realloc( aNewArgs.getLength() + 2 ); + auto pNewArgs = aNewArgs.getArray(); + + // Append the command URL to the Arguments sequence so that one controller can be + // used for more than one command URL. + aPropValue.Name = "CommandURL"; + aPropValue.Value <<= ServiceSpecifier; + pNewArgs[nAppendIndex] <<= aPropValue; + + // Append the optional value argument. It's an empty string if no additional info + // is provided to the controller. + OUString aValue = m_pConfigAccess->getValueFromCommandModule( ServiceSpecifier, aPropName ); + aPropValue.Name = "Value"; + aPropValue.Value <<= aValue; + pNewArgs[nAppendIndex+1] <<= aPropValue; + + { + OUString aServiceName; + { // SAFE + std::unique_lock g(m_aMutex); + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + m_pConfigAccess->readConfigurationData(); + } + + aServiceName = m_pConfigAccess->getServiceFromCommandModule( ServiceSpecifier, aPropName ); + } // SAFE + + if ( !aServiceName.isEmpty() ) + return m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext( aServiceName, aNewArgs, m_xContext ); + else + return Reference< XInterface >(); + } +} + +Sequence< OUString > SAL_CALL UIControllerFactory::getAvailableServiceNames() +{ + return Sequence< OUString >(); +} + +// XUIControllerRegistration +sal_Bool SAL_CALL UIControllerFactory::hasController( + const OUString& aCommandURL, + const OUString& aModuleName ) +{ + std::unique_lock g(m_aMutex); + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + m_pConfigAccess->readConfigurationData(); + } + + return ( !m_pConfigAccess->getServiceFromCommandModule( aCommandURL, aModuleName ).isEmpty() ); +} + +void SAL_CALL UIControllerFactory::registerController( + const OUString& aCommandURL, + const OUString& aModuleName, + const OUString& aControllerImplementationName ) +{ + // SAFE + std::unique_lock g(m_aMutex); + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + m_pConfigAccess->readConfigurationData(); + } + + m_pConfigAccess->addServiceToCommandModule( aCommandURL, aModuleName, aControllerImplementationName ); + // SAFE +} + +void SAL_CALL UIControllerFactory::deregisterController( + const OUString& aCommandURL, + const OUString& aModuleName ) +{ + // SAFE + std::unique_lock g(m_aMutex); + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + m_pConfigAccess->readConfigurationData(); + } + + m_pConfigAccess->removeServiceFromCommandModule( aCommandURL, aModuleName ); + // SAFE +} + +class PopupMenuControllerFactory : public UIControllerFactory +{ +public: + explicit PopupMenuControllerFactory( const css::uno::Reference< css::uno::XComponentContext >& xContext ); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.PopupMenuControllerFactory"; + } + + 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.frame.PopupMenuControllerFactory"}; + } + +}; + +PopupMenuControllerFactory::PopupMenuControllerFactory( const Reference< XComponentContext >& xContext ) : + UIControllerFactory( xContext, u"PopupMenu" ) +{ +} + +class ToolbarControllerFactory : public UIControllerFactory +{ +public: + explicit ToolbarControllerFactory( const css::uno::Reference< css::uno::XComponentContext >& xContext ); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.ToolBarControllerFactory"; + } + + 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.frame.ToolbarControllerFactory"}; + } + +}; + +ToolbarControllerFactory::ToolbarControllerFactory( const Reference< XComponentContext >& xContext ) : + UIControllerFactory( xContext, u"ToolBar" ) +{ +} + +class StatusbarControllerFactory : public UIControllerFactory +{ +public: + explicit StatusbarControllerFactory( const css::uno::Reference< css::uno::XComponentContext >& xContext ); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.StatusBarControllerFactory"; + } + + 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.frame.StatusbarControllerFactory"}; + } + +}; + +StatusbarControllerFactory::StatusbarControllerFactory( const Reference< XComponentContext >& xContext ) : + UIControllerFactory( xContext, u"StatusBar" ) +{ +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_PopupMenuControllerFactory_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new PopupMenuControllerFactory(context)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_ToolBarControllerFactory_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new ToolbarControllerFactory(context)); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_StatusBarControllerFactory_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new StatusbarControllerFactory(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uifactory/uielementfactorymanager.cxx b/framework/source/uifactory/uielementfactorymanager.cxx new file mode 100644 index 000000000..b8a8ea46e --- /dev/null +++ b/framework/source/uifactory/uielementfactorymanager.cxx @@ -0,0 +1,553 @@ +/* -*- 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 <string_view> + +#include <uifactory/configurationaccessfactorymanager.hxx> +#include <helper/mischelper.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/ElementExistException.hpp> +#include <com/sun/star/container/XContainer.hpp> +#include <com/sun/star/container/XContainerListener.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/loader/CannotActivateFactoryException.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/UnknownModuleException.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XModuleManager2.hpp> +#include <com/sun/star/ui/XUIElementFactoryManager.hpp> + +#include <rtl/ref.hxx> +#include <sal/log.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <utility> + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::frame; +using namespace com::sun::star::configuration; +using namespace com::sun::star::container; +using namespace ::com::sun::star::ui; +using namespace framework; + +namespace framework +{ + +// global function needed by both implementations +static OUString getHashKeyFromStrings( std::u16string_view aType, std::u16string_view aName, std::u16string_view aModuleName ) +{ + return OUString::Concat(aType) + "^" + aName + "^" + aModuleName; +} + +ConfigurationAccess_FactoryManager::ConfigurationAccess_FactoryManager( const Reference< XComponentContext >& rxContext, OUString _sRoot ) : + m_aPropType( "Type" ), + m_aPropName( "Name" ), + m_aPropModule( "Module" ), + m_aPropFactory( "FactoryImplementation" ), + m_sRoot(std::move(_sRoot)), + m_bConfigAccessInitialized( false ) +{ + m_xConfigProvider = theDefaultProvider::get( rxContext ); +} + +ConfigurationAccess_FactoryManager::~ConfigurationAccess_FactoryManager() +{ + // SAFE + std::unique_lock g(m_aMutex); + + Reference< XContainer > xContainer( m_xConfigAccess, UNO_QUERY ); + if ( xContainer.is() ) + xContainer->removeContainerListener(m_xConfigListener); +} + +OUString ConfigurationAccess_FactoryManager::getFactorySpecifierFromTypeNameModule( std::u16string_view rType, std::u16string_view rName, std::u16string_view rModule ) const +{ + // SAFE + std::unique_lock g(m_aMutex); + + FactoryManagerMap::const_iterator pIter = + m_aFactoryManagerMap.find( getHashKeyFromStrings( rType, rName, rModule )); + if ( pIter != m_aFactoryManagerMap.end() ) + return pIter->second; + else + { + pIter = m_aFactoryManagerMap.find( + getHashKeyFromStrings( rType, rName, std::u16string_view() )); + if ( pIter != m_aFactoryManagerMap.end() ) + return pIter->second; + else + { + // Support factories which uses a defined prefix before the ui name. + size_t nIndex = rName.find( '_' ); + if ( nIndex > 0 && nIndex != std::u16string_view::npos) + { + std::u16string_view aName = rName.substr( 0, nIndex+1 ); + pIter = m_aFactoryManagerMap.find( getHashKeyFromStrings( rType, aName, std::u16string_view() )); + if ( pIter != m_aFactoryManagerMap.end() ) + return pIter->second; + } + + pIter = m_aFactoryManagerMap.find( getHashKeyFromStrings( rType, std::u16string_view(), std::u16string_view() )); + if ( pIter != m_aFactoryManagerMap.end() ) + return pIter->second; + } + } + + return OUString(); +} + +void ConfigurationAccess_FactoryManager::addFactorySpecifierToTypeNameModule( std::u16string_view rType, std::u16string_view rName, std::u16string_view rModule, const OUString& rServiceSpecifier ) +{ + // SAFE + std::unique_lock g(m_aMutex); + + OUString aHashKey = getHashKeyFromStrings( rType, rName, rModule ); + + FactoryManagerMap::const_iterator pIter = m_aFactoryManagerMap.find( aHashKey ); + + if ( pIter != m_aFactoryManagerMap.end() ) + throw ElementExistException(); + m_aFactoryManagerMap.emplace( aHashKey, rServiceSpecifier ); +} + +void ConfigurationAccess_FactoryManager::removeFactorySpecifierFromTypeNameModule( std::u16string_view rType, std::u16string_view rName, std::u16string_view rModule ) +{ + // SAFE + std::unique_lock g(m_aMutex); + + OUString aHashKey = getHashKeyFromStrings( rType, rName, rModule ); + + FactoryManagerMap::const_iterator pIter = m_aFactoryManagerMap.find( aHashKey ); + + if ( pIter == m_aFactoryManagerMap.end() ) + throw NoSuchElementException(); + m_aFactoryManagerMap.erase( aHashKey ); +} + +Sequence< Sequence< PropertyValue > > ConfigurationAccess_FactoryManager::getFactoriesDescription() const +{ + // SAFE + std::unique_lock g(m_aMutex); + + Sequence< Sequence< PropertyValue > > aSeqSeq; + + sal_Int32 nIndex( 0 ); + for ( const auto& rEntry : m_aFactoryManagerMap ) + { + OUString aFactory = rEntry.first; + if ( !aFactory.isEmpty() ) + { + sal_Int32 nToken = 0; + + aSeqSeq.realloc( aSeqSeq.getLength() + 1 ); + Sequence< PropertyValue > aSeq{ comphelper::makePropertyValue( + m_aPropType, aFactory.getToken( 0, '^', nToken )) }; + if ( nToken > 0 ) + { + aSeq.realloc( 2 ); + aSeq.getArray()[1] + = comphelper::makePropertyValue(m_aPropName, + aFactory.getToken( 0, '^', nToken )); + if ( nToken > 0 ) + { + aSeq.realloc( 3 ); + aSeq.getArray()[2] + = comphelper::makePropertyValue(m_aPropModule, + aFactory.getToken( 0, '^', nToken )); + } + } + + aSeqSeq.getArray()[nIndex++] = aSeq; + } + } + + return aSeqSeq; +} + +// container.XContainerListener +void SAL_CALL ConfigurationAccess_FactoryManager::elementInserted( const ContainerEvent& aEvent ) +{ + OUString aType; + OUString aName; + OUString aModule; + OUString aService; + + // SAFE + std::unique_lock g(m_aMutex); + + if ( impl_getElementProps( aEvent.Element, aType, aName, aModule, aService )) + { + // Create hash key from type, name and module as they are together a primary key to + // the UNO service that implements a user interface factory. + OUString aHashKey( getHashKeyFromStrings( aType, aName, aModule )); + m_aFactoryManagerMap.emplace( aHashKey, aService ); + } +} + +void SAL_CALL ConfigurationAccess_FactoryManager::elementRemoved ( const ContainerEvent& aEvent ) +{ + OUString aType; + OUString aName; + OUString aModule; + OUString aService; + + // SAFE + std::unique_lock g(m_aMutex); + + if ( impl_getElementProps( aEvent.Element, aType, aName, aModule, aService )) + { + // Create hash key from command and model as they are together a primary key to + // the UNO service that implements the popup menu controller. + OUString aHashKey( getHashKeyFromStrings( aType, aName, aModule )); + m_aFactoryManagerMap.erase( aHashKey ); + } +} + +void SAL_CALL ConfigurationAccess_FactoryManager::elementReplaced( const ContainerEvent& aEvent ) +{ + OUString aType; + OUString aName; + OUString aModule; + OUString aService; + + // SAFE + std::unique_lock g(m_aMutex); + + if ( impl_getElementProps( aEvent.Element, aType, aName, aModule, aService )) + { + // Create hash key from command and model as they are together a primary key to + // the UNO service that implements the popup menu controller. + OUString aHashKey( getHashKeyFromStrings( aType, aName, aModule )); + m_aFactoryManagerMap.erase( aHashKey ); + m_aFactoryManagerMap.emplace( aHashKey, aService ); + } +} + +// lang.XEventListener +void SAL_CALL ConfigurationAccess_FactoryManager::disposing( const EventObject& ) +{ + // SAFE + // remove our reference to the config access + std::unique_lock g(m_aMutex); + m_xConfigAccess.clear(); +} + +void ConfigurationAccess_FactoryManager::readConfigurationData() +{ + // SAFE + std::unique_lock g(m_aMutex); + + if ( !m_bConfigAccessInitialized ) + { + Sequence<Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"nodepath", Any(m_sRoot)} + })); + + try + { + m_xConfigAccess.set( m_xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", aArgs ), UNO_QUERY ); + } + catch ( const WrappedTargetException& ) + { + } + + m_bConfigAccessInitialized = true; + } + + if ( !m_xConfigAccess.is() ) + return; + + const Sequence< OUString > aUIElementFactories = m_xConfigAccess->getElementNames(); + + OUString aType; + OUString aName; + OUString aModule; + OUString aService; + OUString aHashKey; + for ( OUString const & factoryName : aUIElementFactories ) + { + if ( impl_getElementProps( m_xConfigAccess->getByName( factoryName ), aType, aName, aModule, aService )) + { + // Create hash key from type, name and module as they are together a primary key to + // the UNO service that implements the user interface element factory. + aHashKey = getHashKeyFromStrings( aType, aName, aModule ); + m_aFactoryManagerMap.emplace( aHashKey, aService ); + } + } + + Reference< XContainer > xContainer( m_xConfigAccess, UNO_QUERY ); + if ( xContainer.is() ) + { + m_xConfigListener = new WeakContainerListener(this); + xContainer->addContainerListener(m_xConfigListener); + } +} + +bool ConfigurationAccess_FactoryManager::impl_getElementProps( const Any& aElement, OUString& rType, OUString& rName, OUString& rModule, OUString& rServiceSpecifier ) const +{ + Reference< XPropertySet > xPropertySet; + aElement >>= xPropertySet; + + if ( !xPropertySet.is() ) + return true; + + try + { + xPropertySet->getPropertyValue( m_aPropType ) >>= rType; + xPropertySet->getPropertyValue( m_aPropName ) >>= rName; + xPropertySet->getPropertyValue( m_aPropModule ) >>= rModule; + xPropertySet->getPropertyValue( m_aPropFactory ) >>= rServiceSpecifier; + } + catch ( const css::beans::UnknownPropertyException& ) + { + return false; + } + catch ( const css::lang::WrappedTargetException& ) + { + return false; + } + + return true; +} + +} // framework + +namespace { + +typedef comphelper::WeakComponentImplHelper< + css::lang::XServiceInfo, + css::ui::XUIElementFactoryManager> UIElementFactoryManager_BASE; + +class UIElementFactoryManager : public UIElementFactoryManager_BASE +{ + virtual void disposing(std::unique_lock<std::mutex>&) override; +public: + explicit UIElementFactoryManager( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.UIElementFactoryManager"; + } + + 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.UIElementFactoryManager"}; + } + + // XUIElementFactory + virtual css::uno::Reference< css::ui::XUIElement > SAL_CALL createUIElement( const OUString& ResourceURL, const css::uno::Sequence< css::beans::PropertyValue >& Args ) override; + + // XUIElementFactoryRegistration + virtual css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > > SAL_CALL getRegisteredFactories( ) override; + virtual css::uno::Reference< css::ui::XUIElementFactory > SAL_CALL getFactory( const OUString& ResourceURL, const OUString& ModuleIdentifier ) override; + virtual void SAL_CALL registerFactory( const OUString& aType, const OUString& aName, const OUString& aModuleIdentifier, const OUString& aFactoryImplementationName ) override; + virtual void SAL_CALL deregisterFactory( const OUString& aType, const OUString& aName, const OUString& aModuleIdentifier ) override; + +private: + bool m_bConfigRead; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + rtl::Reference<ConfigurationAccess_FactoryManager> m_pConfigAccess; +}; + +UIElementFactoryManager::UIElementFactoryManager( const Reference< XComponentContext >& rxContext ) : + m_bConfigRead( false ), + m_xContext(rxContext), + m_pConfigAccess( + new ConfigurationAccess_FactoryManager( + rxContext, + "/org.openoffice.Office.UI.Factories/Registered/UIElementFactories")) +{} + +void UIElementFactoryManager::disposing(std::unique_lock<std::mutex>&) +{ + m_pConfigAccess.clear(); +} + +// XUIElementFactory +Reference< XUIElement > SAL_CALL UIElementFactoryManager::createUIElement( + const OUString& ResourceURL, + const Sequence< PropertyValue >& Args ) +{ + Reference< XFrame > xFrame; + OUString aModuleId; + { // SAFE + std::unique_lock g(m_aMutex); + if (m_bDisposed) { + throw css::lang::DisposedException( + "disposed", static_cast<OWeakObject *>(this)); + } + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + m_pConfigAccess->readConfigurationData(); + } + + // Retrieve the frame instance from the arguments to determine the module identifier. This must be provided + // to the search function. An empty module identifier is provided if the frame is missing or the module id cannot + // retrieve from it. + for ( auto const & arg : Args ) + { + if ( arg.Name == "Frame") + arg.Value >>= xFrame; + if (arg.Name == "Module") + arg.Value >>= aModuleId; + } + } // SAFE + + Reference< XModuleManager2 > xManager = ModuleManager::create( m_xContext ); + + // Determine the module identifier + try + { + if ( aModuleId.isEmpty() && xFrame.is() && xManager.is() ) + aModuleId = xManager->identify( Reference<XInterface>( xFrame, UNO_QUERY ) ); + + Reference< XUIElementFactory > xUIElementFactory = getFactory( ResourceURL, aModuleId ); + if ( xUIElementFactory.is() ) + return xUIElementFactory->createUIElement( ResourceURL, Args ); + } + catch ( const UnknownModuleException& ) + { + } + + throw NoSuchElementException(); +} + +// XUIElementFactoryRegistration +Sequence< Sequence< PropertyValue > > SAL_CALL UIElementFactoryManager::getRegisteredFactories() +{ + // SAFE + std::unique_lock g(m_aMutex); + if (m_bDisposed) { + throw css::lang::DisposedException( + "disposed", static_cast<OWeakObject *>(this)); + } + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + m_pConfigAccess->readConfigurationData(); + } + + return m_pConfigAccess->getFactoriesDescription(); +} + +Reference< XUIElementFactory > SAL_CALL UIElementFactoryManager::getFactory( const OUString& aResourceURL, const OUString& aModuleId ) +{ + OUString aServiceSpecifier; + { // SAFE + std::unique_lock g(m_aMutex); + if (m_bDisposed) { + throw css::lang::DisposedException( + "disposed", static_cast<OWeakObject *>(this)); + } + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + m_pConfigAccess->readConfigurationData(); + } + + OUString aType; + OUString aName; + RetrieveTypeNameFromResourceURL( aResourceURL, aType, aName ); + aServiceSpecifier = m_pConfigAccess->getFactorySpecifierFromTypeNameModule( aType, aName, aModuleId ); + } // SAFE + + if ( !aServiceSpecifier.isEmpty() ) try + { + Reference< XUIElementFactory > xFactory(m_xContext->getServiceManager()-> + createInstanceWithContext(aServiceSpecifier, m_xContext), UNO_QUERY); + SAL_WARN_IF(!xFactory.is(), "fwk.uielement", "could not create factory: " << aServiceSpecifier); + return xFactory; + } + catch ( const css::loader::CannotActivateFactoryException& ) + { + SAL_WARN("fwk.uielement", aServiceSpecifier << + " not available. This should happen only on mobile platforms."); + } + return Reference< XUIElementFactory >(); +} + +void SAL_CALL UIElementFactoryManager::registerFactory( const OUString& aType, const OUString& aName, const OUString& aModuleId, const OUString& aFactoryImplementationName ) +{ + // SAFE + std::unique_lock g(m_aMutex); + if (m_bDisposed) { + throw css::lang::DisposedException( + "disposed", static_cast<OWeakObject *>(this)); + } + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + m_pConfigAccess->readConfigurationData(); + } + + m_pConfigAccess->addFactorySpecifierToTypeNameModule( aType, aName, aModuleId, aFactoryImplementationName ); + // SAFE +} + +void SAL_CALL UIElementFactoryManager::deregisterFactory( const OUString& aType, const OUString& aName, const OUString& aModuleId ) +{ + // SAFE + std::unique_lock g(m_aMutex); + if (m_bDisposed) { + throw css::lang::DisposedException( + "disposed", static_cast<OWeakObject *>(this)); + } + + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + m_pConfigAccess->readConfigurationData(); + } + + m_pConfigAccess->removeFactorySpecifierFromTypeNameModule( aType, aName, aModuleId ); + // SAFE +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_UIElementFactoryManager_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new UIElementFactoryManager(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/uifactory/windowcontentfactorymanager.cxx b/framework/source/uifactory/windowcontentfactorymanager.cxx new file mode 100644 index 000000000..da52775cc --- /dev/null +++ b/framework/source/uifactory/windowcontentfactorymanager.cxx @@ -0,0 +1,204 @@ +/* -*- 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 <uifactory/configurationaccessfactorymanager.hxx> +#include <helper/mischelper.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/UnknownModuleException.hpp> +#include <com/sun/star/frame/XModuleManager2.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <comphelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/ref.hxx> +#include <utility> +#include <tools/diagnose_ex.h> + +using namespace ::com::sun::star; +using namespace framework; + +namespace { + +typedef comphelper::WeakComponentImplHelper< + css::lang::XServiceInfo, + css::lang::XSingleComponentFactory > WindowContentFactoryManager_BASE; + +class WindowContentFactoryManager : public WindowContentFactoryManager_BASE +{ +public: + explicit WindowContentFactoryManager( css::uno::Reference< css::uno::XComponentContext> xContext ); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.framework.WindowContentFactoryManager"; + } + + 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.WindowContentFactoryManager"}; + } + + // XSingleComponentFactory + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstanceWithContext( const css::uno::Reference< css::uno::XComponentContext >& Context ) override; + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstanceWithArgumentsAndContext( const css::uno::Sequence< css::uno::Any >& Arguments, const css::uno::Reference< css::uno::XComponentContext >& Context ) override; + +private: + virtual void disposing(std::unique_lock<std::mutex>&) override; + + css::uno::Reference< css::uno::XComponentContext > m_xContext; + bool m_bConfigRead; + rtl::Reference<ConfigurationAccess_FactoryManager> m_pConfigAccess; +}; + +WindowContentFactoryManager::WindowContentFactoryManager( uno::Reference< uno::XComponentContext > xContext ) : + m_xContext(std::move( xContext )), + m_bConfigRead( false ), + m_pConfigAccess( + new ConfigurationAccess_FactoryManager( + m_xContext, + "/org.openoffice.Office.UI.WindowContentFactories/Registered/ContentFactories")) +{} + +void WindowContentFactoryManager::disposing(std::unique_lock<std::mutex>&) +{ + m_pConfigAccess.clear(); +} + +// XSingleComponentFactory +uno::Reference< uno::XInterface > SAL_CALL WindowContentFactoryManager::createInstanceWithContext( + const uno::Reference< uno::XComponentContext >& /*xContext*/ ) +{ + uno::Reference< uno::XInterface > xWindow; + return xWindow; +} + +uno::Reference< uno::XInterface > SAL_CALL WindowContentFactoryManager::createInstanceWithArgumentsAndContext( + const uno::Sequence< uno::Any >& Arguments, const uno::Reference< uno::XComponentContext >& Context ) +{ + uno::Reference< uno::XInterface > xWindow; + uno::Reference< frame::XFrame > xFrame; + OUString aResourceURL; + + for (auto const & arg : Arguments ) + { + beans::PropertyValue aPropValue; + if ( arg >>= aPropValue ) + { + if ( aPropValue.Name == "Frame" ) + aPropValue.Value >>= xFrame; + else if ( aPropValue.Name == "ResourceURL" ) + aPropValue.Value >>= aResourceURL; + } + } + + // Determine the module identifier + OUString aType; + OUString aName; + OUString aModuleId; + uno::Reference< frame::XModuleManager2 > xModuleManager = + frame::ModuleManager::create( m_xContext ); + try + { + if ( xFrame.is() && xModuleManager.is() ) + aModuleId = xModuleManager->identify( uno::Reference< uno::XInterface >( xFrame, uno::UNO_QUERY ) ); + } + catch ( const frame::UnknownModuleException& ) + { + } + + RetrieveTypeNameFromResourceURL( aResourceURL, aType, aName ); + if ( !aType.isEmpty() && + !aName.isEmpty() && + !aModuleId.isEmpty() ) + { + OUString aImplementationName; + uno::Reference< uno::XInterface > xHolder( static_cast<cppu::OWeakObject*>(this), uno::UNO_QUERY ); + + // Determine the implementation name of the window content factory dependent on the + // module identifier, user interface element type and name + { // SAFE + std::unique_lock g(m_aMutex); + if (m_bDisposed) { + throw css::lang::DisposedException( + "disposed", static_cast<OWeakObject *>(this)); + } + if ( !m_bConfigRead ) + { + m_bConfigRead = true; + m_pConfigAccess->readConfigurationData(); + } + aImplementationName = m_pConfigAccess->getFactorySpecifierFromTypeNameModule( aType, aName, aModuleId ); + } // SAFE + + if ( !aImplementationName.isEmpty() ) + { + uno::Reference< lang::XMultiServiceFactory > xServiceManager( Context->getServiceManager(), uno::UNO_QUERY ); + if ( xServiceManager.is() ) + { + uno::Reference< lang::XSingleComponentFactory > xFactory( + xServiceManager->createInstance( aImplementationName ), uno::UNO_QUERY ); + if ( xFactory.is() ) + { + // Be careful: We call external code. Therefore here we have to catch all exceptions + try + { + xWindow = xFactory->createInstanceWithArgumentsAndContext( Arguments, Context ); + } + catch ( uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("fwk"); + } + } + } + } + } + + // UNSAFE + if ( !xWindow.is()) + { + // Fallback: Use internal factory code to create a toolkit dialog as a content window + xWindow = createInstanceWithContext(Context); + } + + return xWindow; +} + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_framework_WindowContentFactoryManager_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new WindowContentFactoryManager(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/xml/acceleratorconfigurationreader.cxx b/framework/source/xml/acceleratorconfigurationreader.cxx new file mode 100644 index 000000000..ad6bc7259 --- /dev/null +++ b/framework/source/xml/acceleratorconfigurationreader.cxx @@ -0,0 +1,258 @@ +/* -*- 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 <sal/log.hxx> + +#include <accelerators/keymapping.hxx> +#include <xml/acceleratorconfigurationreader.hxx> + +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> +#include <com/sun/star/awt/KeyEvent.hpp> + +namespace framework{ + +/* Throws a SaxException in case a wrong formatted XML + structure was detected. + + This macro combined the given comment with a generic + way to find out the XML line (where the error occurred) + to format a suitable message. + + @param COMMENT + an ascii string, which describe the problem. + */ +#define THROW_PARSEEXCEPTION(COMMENT) \ + { \ + throw css::xml::sax::SAXException( \ + implts_getErrorLineString() + COMMENT, \ + static_cast< css::xml::sax::XDocumentHandler* >(this), \ + css::uno::Any()); \ + } + +AcceleratorConfigurationReader::AcceleratorConfigurationReader(AcceleratorCache& rContainer) + : m_rContainer (rContainer ) + , m_bInsideAcceleratorList(false ) + , m_bInsideAcceleratorItem(false ) +{ +} + +AcceleratorConfigurationReader::~AcceleratorConfigurationReader() +{ +} + +void SAL_CALL AcceleratorConfigurationReader::startDocument() +{ +} + +void SAL_CALL AcceleratorConfigurationReader::endDocument() +{ + // The xml file seems to be corrupted. + // Because we found no end-tags ... at least for + // one list or item. + if (m_bInsideAcceleratorList || m_bInsideAcceleratorItem) + { + THROW_PARSEEXCEPTION("No matching start or end element 'acceleratorlist' found!") + } +} + +void SAL_CALL AcceleratorConfigurationReader::startElement(const OUString& sElement , + const css::uno::Reference< css::xml::sax::XAttributeList >& xAttributeList) +{ + EXMLElement eElement = AcceleratorConfigurationReader::implst_classifyElement(sElement); + + // Note: We handle "accel:item" before "accel:acceleratorlist" to perform this operation. + // Because an item occurs very often ... a list should occur one times only! + if (eElement == E_ELEMENT_ITEM) + { + if (!m_bInsideAcceleratorList) + THROW_PARSEEXCEPTION("An element \"accel:item\" must be embedded into 'accel:acceleratorlist'.") + if (m_bInsideAcceleratorItem) + THROW_PARSEEXCEPTION("An element \"accel:item\" is not a container.") + m_bInsideAcceleratorItem = true; + + OUString sCommand; + css::awt::KeyEvent aEvent; + + sal_Int16 c = xAttributeList->getLength(); + sal_Int16 i = 0; + for (i=0; i<c; ++i) + { + OUString sAttribute = xAttributeList->getNameByIndex(i); + OUString sValue = xAttributeList->getValueByIndex(i); + EXMLAttribute eAttribute = AcceleratorConfigurationReader::implst_classifyAttribute(sAttribute); + switch(eAttribute) + { + case E_ATTRIBUTE_URL : + sCommand = sValue.intern(); + break; + + case E_ATTRIBUTE_KEYCODE : + aEvent.KeyCode = KeyMapping::get().mapIdentifierToCode(sValue); + break; + + case E_ATTRIBUTE_MOD_SHIFT : + aEvent.Modifiers |= css::awt::KeyModifier::SHIFT; + break; + + case E_ATTRIBUTE_MOD_MOD1 : + aEvent.Modifiers |= css::awt::KeyModifier::MOD1; + break; + + case E_ATTRIBUTE_MOD_MOD2 : + aEvent.Modifiers |= css::awt::KeyModifier::MOD2; + break; + + case E_ATTRIBUTE_MOD_MOD3 : + aEvent.Modifiers |= css::awt::KeyModifier::MOD3; + } + } + + // validate command and key event. + if ( + sCommand.isEmpty() || + (aEvent.KeyCode == 0 ) + ) + { + THROW_PARSEEXCEPTION("XML element does not describe a valid accelerator nor a valid command.") + } + + // register key event + command inside cache ... + // Check for already existing items there. + if (!m_rContainer.hasKey(aEvent)) + m_rContainer.setKeyCommandPair(aEvent, sCommand); + else + { + // Attention: It's not really a reason to throw an exception and kill the office, if the configuration contains + // multiple registrations for the same key :-) Show a warning ... and ignore the second item. + // THROW_PARSEEXCEPTION("Command is registered for the same key more than once.") + SAL_INFO("fwk", + "AcceleratorConfigurationReader::startElement(): Double registration detected. Command=\"" << + sCommand << + "\" KeyCode=" << + aEvent.KeyCode << + "Modifiers=" << + aEvent.Modifiers); + } + } + + if (eElement == E_ELEMENT_ACCELERATORLIST) + { + if (m_bInsideAcceleratorList) + THROW_PARSEEXCEPTION("An element \"accel:acceleratorlist\" cannot be used recursive.") + m_bInsideAcceleratorList = true; + return; + } +} + +void SAL_CALL AcceleratorConfigurationReader::endElement(const OUString& sElement) +{ + EXMLElement eElement = AcceleratorConfigurationReader::implst_classifyElement(sElement); + + // Note: We handle "accel:item" before "accel:acceleratorlist" to perform this operation. + // Because an item occurs very often ... a list should occur one times only! + if (eElement == E_ELEMENT_ITEM) + { + if (!m_bInsideAcceleratorItem) + THROW_PARSEEXCEPTION("Found end element 'accel:item', but no start element.") + m_bInsideAcceleratorItem = false; + } + + if (eElement == E_ELEMENT_ACCELERATORLIST) + { + if (!m_bInsideAcceleratorList) + THROW_PARSEEXCEPTION("Found end element 'accel:acceleratorlist', but no start element.") + m_bInsideAcceleratorList = false; + } +} + +void SAL_CALL AcceleratorConfigurationReader::characters(const OUString&) +{ +} + +void SAL_CALL AcceleratorConfigurationReader::ignorableWhitespace(const OUString&) +{ +} + +void SAL_CALL AcceleratorConfigurationReader::processingInstruction(const OUString& /*sTarget*/, + const OUString& /*sData*/ ) +{ +} + +void SAL_CALL AcceleratorConfigurationReader::setDocumentLocator(const css::uno::Reference< css::xml::sax::XLocator >& xLocator) +{ + m_xLocator = xLocator; +} + +AcceleratorConfigurationReader::EXMLElement AcceleratorConfigurationReader::implst_classifyElement(std::u16string_view sElement) +{ + AcceleratorConfigurationReader::EXMLElement eElement; + + if (sElement == u"http://openoffice.org/2001/accel^acceleratorlist") + eElement = E_ELEMENT_ACCELERATORLIST; + else if (sElement == u"http://openoffice.org/2001/accel^item") + eElement = E_ELEMENT_ITEM; + else + throw css::uno::RuntimeException( + "Unknown XML element detected!", + css::uno::Reference< css::xml::sax::XDocumentHandler >()); + + return eElement; +} + +AcceleratorConfigurationReader::EXMLAttribute AcceleratorConfigurationReader::implst_classifyAttribute(std::u16string_view sAttribute) +{ + AcceleratorConfigurationReader::EXMLAttribute eAttribute; + + if (sAttribute == u"http://openoffice.org/2001/accel^code") + eAttribute = E_ATTRIBUTE_KEYCODE; + else if (sAttribute == u"http://openoffice.org/2001/accel^shift") + eAttribute = E_ATTRIBUTE_MOD_SHIFT; + else if (sAttribute == u"http://openoffice.org/2001/accel^mod1") + eAttribute = E_ATTRIBUTE_MOD_MOD1; + else if (sAttribute == u"http://openoffice.org/2001/accel^mod2") + eAttribute = E_ATTRIBUTE_MOD_MOD2; + else if (sAttribute == u"http://openoffice.org/2001/accel^mod3") + eAttribute = E_ATTRIBUTE_MOD_MOD3; + else if (sAttribute == u"http://www.w3.org/1999/xlink^href") + eAttribute = E_ATTRIBUTE_URL; + else + throw css::uno::RuntimeException( + "Unknown XML attribute detected!", + css::uno::Reference< css::xml::sax::XDocumentHandler >()); + + return eAttribute; +} + +OUString AcceleratorConfigurationReader::implts_getErrorLineString() +{ + if (!m_xLocator.is()) + return "Error during parsing XML. (No further info available ...)"; + + return "Error during parsing XML in\nline = " + + OUString::number(m_xLocator->getLineNumber()) + + "\ncolumn = " + + OUString::number(m_xLocator->getColumnNumber()) + + "."; +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/xml/acceleratorconfigurationwriter.cxx b/framework/source/xml/acceleratorconfigurationwriter.cxx new file mode 100644 index 000000000..04e50f5ae --- /dev/null +++ b/framework/source/xml/acceleratorconfigurationwriter.cxx @@ -0,0 +1,123 @@ +/* -*- 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 <accelerators/keymapping.hxx> +#include <utility> +#include <xml/acceleratorconfigurationwriter.hxx> + +#include <acceleratorconst.h> + +#include <com/sun/star/xml/sax/XExtendedDocumentHandler.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> + +#include <comphelper/attributelist.hxx> +#include <rtl/ref.hxx> + +namespace framework{ + +AcceleratorConfigurationWriter::AcceleratorConfigurationWriter(const AcceleratorCache& rContainer, + css::uno::Reference< css::xml::sax::XDocumentHandler > xConfig ) + : m_xConfig (std::move(xConfig )) + , m_rContainer (rContainer ) +{ +} + +AcceleratorConfigurationWriter::~AcceleratorConfigurationWriter() +{ +} + +void AcceleratorConfigurationWriter::flush() +{ + css::uno::Reference< css::xml::sax::XExtendedDocumentHandler > xExtendedCFG(m_xConfig, css::uno::UNO_QUERY_THROW); + + // prepare attribute list + rtl::Reference<::comphelper::AttributeList> pAttribs = new ::comphelper::AttributeList; + + pAttribs->AddAttribute( + "xmlns:accel", ATTRIBUTE_TYPE_CDATA, + "http://openoffice.org/2001/accel"); + pAttribs->AddAttribute( + "xmlns:xlink", ATTRIBUTE_TYPE_CDATA, "http://www.w3.org/1999/xlink"); + + // generate xml + xExtendedCFG->startDocument(); + + xExtendedCFG->unknown( + "<!DOCTYPE accel:acceleratorlist PUBLIC \"-//OpenOffice.org//DTD" + " OfficeDocument 1.0//EN\" \"accelerator.dtd\">"); + xExtendedCFG->ignorableWhitespace(OUString()); + + xExtendedCFG->startElement(AL_ELEMENT_ACCELERATORLIST, pAttribs); + xExtendedCFG->ignorableWhitespace(OUString()); + + // TODO think about threadsafe using of cache + AcceleratorCache::TKeyList lKeys = m_rContainer.getAllKeys(); + for (auto const& lKey : lKeys) + { + const OUString& rCommand = m_rContainer.getCommandByKey(lKey); + impl_ts_writeKeyCommandPair(lKey, rCommand, xExtendedCFG); + } + + /* TODO write key-command list + for (auto const& writeAccelerator : m_aWriteAcceleratorList) + WriteAcceleratorItem(writeAccelerator); + */ + + xExtendedCFG->ignorableWhitespace(OUString()); + xExtendedCFG->endElement(AL_ELEMENT_ACCELERATORLIST); + xExtendedCFG->ignorableWhitespace(OUString()); + xExtendedCFG->endDocument(); +} + +void AcceleratorConfigurationWriter::impl_ts_writeKeyCommandPair(const css::awt::KeyEvent& aKey , + const OUString& sCommand, + const css::uno::Reference< css::xml::sax::XDocumentHandler >& xConfig ) +{ + rtl::Reference<::comphelper::AttributeList> pAttribs = new ::comphelper::AttributeList; + + OUString sKey = KeyMapping::get().mapCodeToIdentifier(aKey.KeyCode); + // TODO check if key is empty! + + pAttribs->AddAttribute("accel:code", ATTRIBUTE_TYPE_CDATA, sKey ); + pAttribs->AddAttribute("xlink:href", ATTRIBUTE_TYPE_CDATA, sCommand); + + if ((aKey.Modifiers & css::awt::KeyModifier::SHIFT) == css::awt::KeyModifier::SHIFT) + pAttribs->AddAttribute("accel:shift", ATTRIBUTE_TYPE_CDATA, "true"); + + if ((aKey.Modifiers & css::awt::KeyModifier::MOD1) == css::awt::KeyModifier::MOD1) + pAttribs->AddAttribute("accel:mod1", ATTRIBUTE_TYPE_CDATA, "true"); + + if ((aKey.Modifiers & css::awt::KeyModifier::MOD2) == css::awt::KeyModifier::MOD2) + pAttribs->AddAttribute("accel:mod2", ATTRIBUTE_TYPE_CDATA, "true"); + + if ((aKey.Modifiers & css::awt::KeyModifier::MOD3) == css::awt::KeyModifier::MOD3) + pAttribs->AddAttribute("accel:mod3", ATTRIBUTE_TYPE_CDATA, "true"); + + xConfig->ignorableWhitespace(OUString()); + xConfig->startElement(AL_ELEMENT_ITEM, pAttribs); + xConfig->ignorableWhitespace(OUString()); + xConfig->endElement(AL_ELEMENT_ITEM); + xConfig->ignorableWhitespace(OUString()); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/xml/imagesconfiguration.cxx b/framework/source/xml/imagesconfiguration.cxx new file mode 100644 index 000000000..6b4773c21 --- /dev/null +++ b/framework/source/xml/imagesconfiguration.cxx @@ -0,0 +1,104 @@ +/* -*- 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 <xml/imagesconfiguration.hxx> + +#include <xml/imagesdocumenthandler.hxx> +#include <xml/saxnamespacefilter.hxx> + +#include <com/sun/star/xml/sax/Parser.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/SAXException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XInputStream.hpp> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::io; + +namespace framework +{ +bool ImagesConfiguration::LoadImages( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::io::XInputStream>& rInputStream, ImageItemDescriptorList& rItems) +{ + Reference<XParser> xParser = Parser::create(rxContext); + + // connect stream to input stream to the parser + InputSource aInputSource; + + aInputSource.aInputStream = rInputStream; + + // create namespace filter and set document handler inside to support xml namespaces + Reference<XDocumentHandler> xDocHandler(new OReadImagesDocumentHandler(rItems)); + Reference<XDocumentHandler> xFilter(new SaxNamespaceFilter(xDocHandler)); + + // connect parser and filter + xParser->setDocumentHandler(xFilter); + + try + { + xParser->parseStream(aInputSource); + return true; + } + catch (const RuntimeException&) + { + return false; + } + catch (const SAXException&) + { + return false; + } + catch (const css::io::IOException&) + { + return false; + } +} + +bool ImagesConfiguration::StoreImages( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::io::XOutputStream>& rOutputStream, + const ImageItemDescriptorList& rItems) +{ + Reference<XWriter> xWriter = Writer::create(rxContext); + xWriter->setOutputStream(rOutputStream); + + try + { + OWriteImagesDocumentHandler aWriteImagesDocumentHandler(rItems, xWriter); + aWriteImagesDocumentHandler.WriteImagesDocument(); + return true; + } + catch (const RuntimeException&) + { + return false; + } + catch (const SAXException&) + { + return false; + } + catch (const css::io::IOException&) + { + return false; + } +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/framework/source/xml/imagesdocumenthandler.cxx b/framework/source/xml/imagesdocumenthandler.cxx new file mode 100644 index 000000000..ebdd5366f --- /dev/null +++ b/framework/source/xml/imagesdocumenthandler.cxx @@ -0,0 +1,372 @@ +/* -*- 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 <xml/imagesdocumenthandler.hxx> + +#include <com/sun/star/xml/sax/XExtendedDocumentHandler.hpp> +#include <com/sun/star/xml/sax/SAXException.hpp> + +#include <rtl/ref.hxx> +#include <rtl/ustrbuf.hxx> + +#include <comphelper/attributelist.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; + +#define ELEMENT_IMAGECONTAINER "imagescontainer" +#define ELEMENT_IMAGES "images" +#define ELEMENT_ENTRY "entry" +#define ELEMENT_EXTERNALIMAGES "externalimages" +#define ELEMENT_EXTERNALENTRY "externalentry" + +constexpr OUStringLiteral ELEMENT_NS_IMAGESCONTAINER = u"image:imagescontainer"; +constexpr OUStringLiteral ELEMENT_NS_IMAGES = u"image:images"; +constexpr OUStringLiteral ELEMENT_NS_ENTRY = u"image:entry"; + +#define ATTRIBUTE_HREF "href" +#define ATTRIBUTE_MASKCOLOR "maskcolor" +#define ATTRIBUTE_COMMAND "command" +#define ATTRIBUTE_BITMAPINDEX "bitmap-index" +#define ATTRIBUTE_MASKURL "maskurl" +#define ATTRIBUTE_MASKMODE "maskmode" +#define ATTRIBUTE_HIGHCONTRASTURL "highcontrasturl" +#define ATTRIBUTE_HIGHCONTRASTMASKURL "highcontrastmaskurl" +constexpr OUStringLiteral ATTRIBUTE_TYPE_CDATA = u"CDATA"; + +constexpr OUStringLiteral ATTRIBUTE_XMLNS_IMAGE = u"xmlns:image"; +constexpr OUStringLiteral ATTRIBUTE_XMLNS_XLINK = u"xmlns:xlink"; + +constexpr OUStringLiteral ATTRIBUTE_XLINK_TYPE = u"xlink:type"; +constexpr OUStringLiteral ATTRIBUTE_XLINK_TYPE_VALUE = u"simple"; + +constexpr OUStringLiteral XMLNS_IMAGE = u"http://openoffice.org/2001/image"; +constexpr OUStringLiteral XMLNS_XLINK = u"http://www.w3.org/1999/xlink"; +constexpr OUStringLiteral XMLNS_IMAGE_PREFIX = u"image:"; + +constexpr OUStringLiteral XMLNS_FILTER_SEPARATOR = u"^"; + +constexpr OUStringLiteral IMAGES_DOCTYPE = u"<!DOCTYPE image:imagecontainer PUBLIC \"-//OpenOffice.org//DTD OfficeDocument 1.0//EN\" \"image.dtd\">"; + +namespace framework +{ + +namespace { + +struct ImageXMLEntryProperty +{ + OReadImagesDocumentHandler::Image_XML_Namespace nNamespace; + char aEntryName[20]; +}; + +} + +ImageXMLEntryProperty const ImagesEntries[OReadImagesDocumentHandler::IMG_XML_ENTRY_COUNT] = +{ + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ELEMENT_IMAGECONTAINER }, + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ELEMENT_IMAGES }, + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ELEMENT_ENTRY }, + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ELEMENT_EXTERNALIMAGES }, + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ELEMENT_EXTERNALENTRY }, + { OReadImagesDocumentHandler::IMG_NS_XLINK, ATTRIBUTE_HREF }, + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ATTRIBUTE_MASKCOLOR }, + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ATTRIBUTE_COMMAND }, + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ATTRIBUTE_BITMAPINDEX }, + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ATTRIBUTE_MASKURL }, + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ATTRIBUTE_MASKMODE }, + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ATTRIBUTE_HIGHCONTRASTURL }, + { OReadImagesDocumentHandler::IMG_NS_IMAGE, ATTRIBUTE_HIGHCONTRASTMASKURL } +}; + +OReadImagesDocumentHandler::OReadImagesDocumentHandler( ImageItemDescriptorList& rItems ) : + m_rImageList( rItems ) +{ + // create hash map to speed up lookup + for ( int i = 0; i < IMG_XML_ENTRY_COUNT; i++ ) + { + OUStringBuffer temp( 20 ); + + if ( ImagesEntries[i].nNamespace == IMG_NS_IMAGE ) + temp.append( XMLNS_IMAGE ); + else + temp.append( XMLNS_XLINK ); + + temp.append( XMLNS_FILTER_SEPARATOR ); + temp.appendAscii( ImagesEntries[i].aEntryName ); + m_aImageMap.emplace( temp.makeStringAndClear(), static_cast<Image_XML_Entry>(i) ); + } + + // reset states + m_bImageContainerStartFound = false; + m_bImageContainerEndFound = false; + m_bImagesStartFound = false; +} + +OReadImagesDocumentHandler::~OReadImagesDocumentHandler() +{ +} + +// XDocumentHandler +void SAL_CALL OReadImagesDocumentHandler::startDocument() +{ +} + +void SAL_CALL OReadImagesDocumentHandler::endDocument() +{ + if (m_bImageContainerStartFound != m_bImageContainerEndFound) + { + OUString aErrorMessage = getErrorLineString() + "No matching start or end element 'image:imagecontainer' found!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } +} + +void SAL_CALL OReadImagesDocumentHandler::startElement( + const OUString& aName, const Reference< XAttributeList > &xAttribs ) +{ + ImageHashMap::const_iterator pImageEntry = m_aImageMap.find( aName ); + if ( pImageEntry == m_aImageMap.end() ) + return; + + switch ( pImageEntry->second ) + { + case IMG_ELEMENT_IMAGECONTAINER: + { + // image:imagecontainer element (container element for all further image elements) + if ( m_bImageContainerStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element 'image:imagecontainer' cannot be embedded into 'image:imagecontainer'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bImageContainerStartFound = true; + } + break; + + case IMG_ELEMENT_IMAGES: + { + if ( !m_bImageContainerStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element 'image:images' must be embedded into element 'image:imagecontainer'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + if ( m_bImagesStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element 'image:images' cannot be embedded into 'image:images'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_bImagesStartFound = true; + } + break; + + case IMG_ELEMENT_ENTRY: + { + // Check that image:entry is embedded into image:images! + if ( !m_bImagesStartFound ) + { + OUString aErrorMessage = getErrorLineString() + "Element 'image:entry' must be embedded into element 'image:images'!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + // Create new image item descriptor + ImageItemDescriptor aItem; + + // Read attributes for this image definition + for ( sal_Int16 n = 0; n < xAttribs->getLength(); n++ ) + { + pImageEntry = m_aImageMap.find( xAttribs->getNameByIndex( n ) ); + if ( pImageEntry != m_aImageMap.end() ) + { + switch ( pImageEntry->second ) + { + case IMG_ATTRIBUTE_COMMAND: + { + aItem.aCommandURL = xAttribs->getValueByIndex( n ); + } + break; + + default: + break; + } + } + } + + // Check required attribute "command" + if ( aItem.aCommandURL.isEmpty() ) + { + OUString aErrorMessage = getErrorLineString() + "Required attribute 'image:command' must have a value!"; + throw SAXException( aErrorMessage, Reference< XInterface >(), Any() ); + } + + m_rImageList.push_back( aItem ); + } + break; + + default: + break; + } +} + +void SAL_CALL OReadImagesDocumentHandler::endElement(const OUString& aName) +{ + ImageHashMap::const_iterator pImageEntry = m_aImageMap.find( aName ); + if ( pImageEntry == m_aImageMap.end() ) + return; + + switch ( pImageEntry->second ) + { + case IMG_ELEMENT_IMAGECONTAINER: + { + m_bImageContainerEndFound = true; + } + break; + + case IMG_ELEMENT_IMAGES: + { + m_bImagesStartFound = false; + } + break; + + default: break; + } +} + +void SAL_CALL OReadImagesDocumentHandler::characters(const OUString&) +{ +} + +void SAL_CALL OReadImagesDocumentHandler::ignorableWhitespace(const OUString&) +{ +} + +void SAL_CALL OReadImagesDocumentHandler::processingInstruction( + const OUString& /*aTarget*/, const OUString& /*aData*/ ) +{ +} + +void SAL_CALL OReadImagesDocumentHandler::setDocumentLocator( + const Reference< XLocator > &xLocator) +{ + m_xLocator = xLocator; +} + +OUString OReadImagesDocumentHandler::getErrorLineString() +{ + if ( m_xLocator.is() ) + { + return "Line: " + + OUString::number(m_xLocator->getLineNumber()) + + " - "; + } + else + return OUString(); +} + +// OWriteImagesDocumentHandler + +OWriteImagesDocumentHandler::OWriteImagesDocumentHandler( + const ImageItemDescriptorList& rItems, + Reference< XDocumentHandler > const & rWriteDocumentHandler ) : + m_rImageItemList( rItems ), + m_xWriteDocumentHandler( rWriteDocumentHandler ) +{ + m_xEmptyList = new ::comphelper::AttributeList; + m_aAttributeType = ATTRIBUTE_TYPE_CDATA; + m_aXMLImageNS = XMLNS_IMAGE_PREFIX; + m_aAttributeXlinkType = ATTRIBUTE_XLINK_TYPE; + m_aAttributeValueSimple = ATTRIBUTE_XLINK_TYPE_VALUE; +} + +OWriteImagesDocumentHandler::~OWriteImagesDocumentHandler() +{ +} + +void OWriteImagesDocumentHandler::WriteImagesDocument() +{ + m_xWriteDocumentHandler->startDocument(); + + // write DOCTYPE line! + Reference< XExtendedDocumentHandler > xExtendedDocHandler( m_xWriteDocumentHandler, UNO_QUERY ); + if ( xExtendedDocHandler.is() ) + { + xExtendedDocHandler->unknown( IMAGES_DOCTYPE ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + } + + rtl::Reference<::comphelper::AttributeList> pList = new ::comphelper::AttributeList; + + pList->AddAttribute( ATTRIBUTE_XMLNS_IMAGE, + m_aAttributeType, + XMLNS_IMAGE ); + + pList->AddAttribute( ATTRIBUTE_XMLNS_XLINK, + m_aAttributeType, + XMLNS_XLINK ); + + m_xWriteDocumentHandler->startElement( ELEMENT_NS_IMAGESCONTAINER, pList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + + WriteImageList( &m_rImageItemList ); + + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endElement( ELEMENT_NS_IMAGESCONTAINER ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + m_xWriteDocumentHandler->endDocument(); +} + +// protected member functions + +void OWriteImagesDocumentHandler::WriteImageList( const ImageItemDescriptorList* pImageList ) +{ + rtl::Reference<::comphelper::AttributeList> pList = new ::comphelper::AttributeList; + + // save required attributes + pList->AddAttribute( m_aAttributeXlinkType, + m_aAttributeType, + m_aAttributeValueSimple ); + + m_xWriteDocumentHandler->startElement( ELEMENT_NS_IMAGES, pList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + + for (const ImageItemDescriptor & i : *pImageList) + WriteImage( &i ); + + m_xWriteDocumentHandler->endElement( ELEMENT_NS_IMAGES ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); +} + +void OWriteImagesDocumentHandler::WriteImage( const ImageItemDescriptor* pImage ) +{ + rtl::Reference<::comphelper::AttributeList> pList = new ::comphelper::AttributeList; + + pList->AddAttribute( m_aXMLImageNS + ATTRIBUTE_COMMAND, + m_aAttributeType, + pImage->aCommandURL ); + + m_xWriteDocumentHandler->startElement( ELEMENT_NS_ENTRY, pList ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); + + m_xWriteDocumentHandler->endElement( ELEMENT_NS_ENTRY ); + m_xWriteDocumentHandler->ignorableWhitespace( OUString() ); +} + +} // namespace framework + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |